Spring Security之Java Configuration方式

java configuration方式

  • 上面是以xml文件的方式配置,接下来介绍以自定义java类的方式来配置spring security
  • 可先在719行:自定义过滤器一节了解认证流程。
1. 注册DelegatingFilterProxy
    package com.study.config;
    import org.springframework.security.web.context.*;

    public class SecurityWebApplicationInitializer
        extends AbstractSecurityWebApplicationInitializer {
        
        @Override
        protected Class<?>[] getRootConfigClasses() {
            return new Class[] { WebSecurityConfig.class };
        }
        
    }
  • 继承该类相当于在web.xml中:

      <filter>
          <filter-name>springSecurityFilterChain</filter-name>
          <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
      </filter>
    
  • 重写getRootConfigClasses()相当于把下一步Spring Security的配置加载到程序中。

2. Spring Security安全配置
    package com.study.config;

    import javax.annotation.Resource;
    
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.authentication.encoding.Md5PasswordEncoder;
    import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.builders.WebSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
    import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
    
    import com.study.security.MyFilterSecurityInterceptor;
    
    @Configuration
    @EnableWebSecurity //启用web安全功能
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
    //  @Resource
    //  private DataSource dataSource;
    
        //配置自定义的用户服务(即选择用户信息的查询方式是从用户库或数据库,怎么查询)
        @Resource(name="myUserDetailService")
        private UserDetailsService myUserDetailService;
        
        //第3点:自定义用户验证
        @Resource
        private AuthenticationProvider myAuthenticationProvider;
        
        //自定义认证成功处理器
        @Resource
        private AuthenticationSuccessHandler myAuthenticationSuccessHandler;
    
        //第4点:自定义拦截器(自定义权限验证,而不是拦截规则)
        @Resource
        private MyFilterSecurityInterceptor myFilterSecurityInterceptor;
        
        /**
         * 密码加密
         */
        @Bean
        public BCryptPasswordEncoder passwordEncoder(){
            return new BCryptPasswordEncoder();
        }
        
        /**
        * 自定义认证过程
        */
        @Override
        protected void configure(AuthenticationManagerBuilder auth)
                throws Exception {
    
            /* 不重写的话默认为
            auth
                .inMemoryAuthentication()
                    .withUser("user").password("password").roles("USER"); //默认准备了一个用户
            */
            
            //方式一:内存用户存储
            /*
            auth.inMemoryAuthentication()
            .withUser("user1").password("123456").roles("USER").and()
            .withUser("admin").password("admin").roles("USER","ADMIN");
            */
            //
            //方式二:数据库表认证
            /*
            auth.jdbcAuthentication().dataSource(dataSource)
            .usersByUsernameQuery("select username,password,enable from t_user where username=?")
            .authoritiesByUsernameQuery("select username,rolename from t_role where username=?");
            */
            //方式三:自定义数据库
            auth.userDetailsService(myUserDetailService)
                .passwordEncoder(new Md5PasswordEncoder()); 
                //加后MyAuthenticationProvider里比对密码时能把表单的明文和数据库的密文对应
            
            //自定义用户验证
            auth.authenticationProvider(myAuthenticationProvider);
        }
    
    //  暴露默认的authenticationManager
    //  @Override
    //    public AuthenticationManager authenticationManagerBean() throws Exception {  
    //      return super.authenticationManagerBean();  
    //    }  
    
        /**
        * 配置Spring Security的Filter链
        */
        @Override
        public void configure(WebSecurity web)throws Exception {
            // 设置不拦截规则
             web.ignoring().antMatchers("/css/**","/js/**","/img/**","/font-awesome/**");  
        }
    
        /**
        * 配置如何通过拦截器保护请求:配置了自定义的过滤器、自定义登录登出页面
        * 前端请求的时候先经过这里检测请求是否需要验证或权限,不需要的话直接把请求分发给controller去处理,需要就认证过了再分发
        */
        @Override
        protected void configure(HttpSecurity http) throws Exception {
              http
                .addFilterBefore(myFilterSecurityInterceptor,    
                    FilterSecurityInterceptor.class)//在正确的位置添加我们自定义的过滤器  
                .authorizeRequests() //需要权限,权限符合与否由上面过滤器检查
                .antMatchers("/admin/**").hasRole("ADMIN") //以admin开头的url需要admin权限
                .antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')")
                .anyRequest().authenticated() //不与以上匹配的所有请求只需要登录验证
    //         .and().formLogin().and() //and相当于节点结束
    //          .httpBasic();
            // 自定义登录页面:permitAll,允许all用户访问login页面
            //未登录前访问受保护资源被转回login
                .and()
                /*直接/jsp/login的话会分配给controller去处理,则需要写controller映射到登录页;请求login.jsp报404
                有后缀则直接请求资源,那么url=/login还是由controller处理;
                那么最好是加后缀,才不用再走controller*/
                .formLogin().loginPage("/jsp/login.jsp") 
                .failureUrl("/jsp/login.jsp?error=1")
                //表单提交地址,4.x默认为/login;无需认证,但是请求方式必须post
                .loginProcessingUrl("/spring_security_check") 
                .usernameParameter("username")
                .passwordParameter("password")
                .successHandler(myAuthenticationSuccessHandler) //自定义认证成功处理器
                //.failureHandler(myAuthenticationFailureHandler)
                .permitAll()
                //这里也是,要么配资源路径,要么配有controller处理的url
                .defaultSuccessUrl("/index.do")
                .and()
                //配置session管理
                .sessionManagement()
                /*指定了maximumSessions需配合web.xml中HttpSessionEventPublisher
                清理掉过期session*/
                .maximumSessions(1) 
                .expiredUrl("/login.jsp?expired=1");
    
              //如果(默认)开启了CSRF,退出则需要使用POST访问,可使用以下方式解决,但不推荐
              http.logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout"))  
              //登陆成功后跳转的地址,以及删除的cookie名称  
              .logoutSuccessUrl("/jsp/login.jsp?error=logout")  
             .invalidateHttpSession(true);  
        }
    
    }
  • 接收到请求,先在spring security中是否有符合的(/login、/logout、/spring_security_check、/**/favicon.ico、…),出现Checking match of request : ‘/login’; against '/logout’或Request ‘GET /login’ doesn’t match 'POST /spring_security_check都表示当前请求与已存在url不匹配,后者指明spring_security_check必须是post形式的,若不匹配,则检测用户是否已认证,未认证,后台报AccessDenyException,前端转回login页面,已认证,分发给controller处理。所以用户请求的资源是他不能访问的就会报AccessDenyException。
  • 未登录时用户为匿名用户

  • 登录后若未给用户设置权限,则用户为无权限

  • 所以给请求设置权限,如果没有指定权限,则至少不能是匿名用户

  • 大致相当于配置

      <!-- 获取访问url对应的所有权限 -->
      <beans:bean id="secureResourceFilterInvocationDefinitionSource" 
          class="com.ultrapower.me.util.security.interceptor.SecureResourceFilterInvocationDefinitionSource" />
      <!-- 校验用户的权限是否足够 -->
      <beans:bean id="mesecurityAccessDecisionManager" 
          class="com.ultrapower.me.util.security.interceptor.SecurityAccessDecisionManager" />
      
      <!-- 自定义权限认证 -->
      <beans:bean id="securityInterceptor" 
       class="com.ultrapower.me.util.security.interceptor.SecurityInterceptor">
      	<beans:property name="authenticationManager" ref="authenticationManager"/>
          <beans:property name="accessDecisionManager" ref="mesecurityAccessDecisionManager"/>
          <beans:property name="securityMetadataSource" 
              ref="secureResourceFilterInvocationDefinitionSource" />
      </beans:bean>
      
      <!-- 用户服务 -->
      <beans:bean id="myUserDetailsService" 
          class="com.ultrapower.me.util.security.support.myUserDetailsService"></beans:bean>
      
      <!-- 自定义用户验证-->
      <beans:bean id="myAuthenticationProvider"
          class="com.security.MyAuthenticationProvider">
          <beans:property name="userDetailsService" ref="myUserDetailsService" />
      </beans:bean>
         
      <!-- 1、自定义权限认证:依赖上面的bean -->
      <http auto-config="true"  use-expressions="false">      
          <intercept-url pattern="/login.jsp*" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
          <intercept-url pattern="/**" access="ROLE_USER"/>
          <form-login login-page="/login.jsp" default-target-url="/jsp/index/main.jsp" 
              authentication-failure-url="/login.jsp?error=true"/>
          <custom-filter before="FILTER_SECURITY_INTERCEPTOR" ref="securityInterceptor"/>
      </http>
      
      <!-- 2、自定义用户验证:依赖上面的bean-->
      <authentication-manager alias="authenticationManager"> //起别名,方便被下面自定义拦截器依赖
          <!--自定义用户验证-->
          <authentication-provider ref="myAuthenticationProvider" />
          <!--只自定义用户服务而不需要自定义用户验证的话
      	<authentication-provider user-service-ref="myUserDetailsService">-->
      		<password-encoder ref="myPasswordEncoder"/>
      	</authentication-provider>
      </authentication-manager>
    
一些自定义方式
  • 简单的自定义登录页面对比

      protected void configure(HttpSecurity http) throws Exception {
          http
              .authorizeRequests()
                  .anyRequest().authenticated()
                  .and()
              .formLogin()
                  .and()
              .httpBasic();
      }
    
  • 相当于

      <http>
          <intercept-url pattern="/**" access="authenticated"/>
          <form-login />
          <http-basic />
      </http>
    
  • 自定义权限

      protected void configure(HttpSecurity http) throws Exception {
          http
              .authorizeRequests() //需要权限                         
                  .antMatchers("/resources/**", "/signup", "/about").permitAll()                  
                  .antMatchers("/admin/**").hasRole("ADMIN") 
                  //使用hasRole()可以不写ROLE_前缀,但实际上我们指定的权限还是ROLE_ADMIN     
                  .antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')")
                  /*按照顺序匹配,下面是对的*/
                  //.antMatchers("/admin/**").hasRole("ADMIN")
                  //.antMatchers("/**").hasRole("USER")
                  /*下面是错的,因为满足第一个规则后将不会检查第二条,所以要从精确到广泛配置*/
                  //.antMatchers("/**").hasRole("USER")
                  //.antMatchers("/admin/**").hasRole("ADMIN")
                  .anyRequest().authenticated() //需要验证                                    
                  .and()
              // ...
              .formLogin();
      }
    
  • 自定义登出页面

      protected void configure(HttpSecurity http) throws Exception {
          http
              .logout()
                  .logoutUrl("/my/logout")
                  .logoutSuccessUrl("/my/index") 
                  .logoutSuccessHandler(logoutSuccessHandler) //自定义登出成功后的操作,上一行失效
                  .invalidateHttpSession(true)
                  //添加一个自定义登出处理器,SecurityContextLogoutHandler默认是最后一个处理器
                  .addLogoutHandler(logoutHandler) 
                  .deleteCookies(cookieNamesToClear)
                  .and()
              ...
      }
    
  • 一般前面两步都是直接用配置文件实现,然后注入自定义UserDetailServiceImpl,也就这个要用java写而已

自定义用户服务
  • 如果是要用数据库的话那肯定是要的,首先是用户实体类UserDetails必须改为符合数据库的字段,默认的也就用户名、密码加权限列表了,其次获取用户信息UserDetialsService的方式也要改成从数据库获取.

  • 首先是UserDetailsService,主要是根据用户名从数据库找出用户all信息,主要是用户名、密码和权限列表

      package com.study.security;
    
      import java.util.HashSet;
      import java.util.List;
      import java.util.Set;
      
      import javax.annotation.Resource;
      
      import org.springframework.security.core.GrantedAuthority;
      import org.springframework.security.core.authority.SimpleGrantedAuthority;
      import org.springframework.security.core.userdetails.UserDetails;
      import org.springframework.security.core.userdetails.UserDetailsService;
      import org.springframework.security.core.userdetails.UsernameNotFoundException;
      import org.springframework.stereotype.Component;
      
      import com.study.model.Resources;
      import com.study.model.User;
      import com.study.service.ResourcesService;
      import com.study.service.UserService;
      
      @Component("myUserDetailService")
      public class MyUserDetailServiceImpl implements UserDetailsService{
      
          //这里可以来个日志
          
          @Resource
          private UserService userService;
      
          @Resource
          private ResourcesService resourcesService;
          
          @Override
          public UserDetails loadUserByUsername(String username)
                  throws UsernameNotFoundException {
              //通过用户名查询用户信息
              User user = userService.findUserByName(username); 
              //系统实体类User implement UsersService,系统service类userService
              if(user ==null)
                  throw new UsernameNotFoundException(username+" not exist!");  
              //存放角色名的集合
              Set<GrantedAuthority> authSet = new HashSet<GrantedAuthority>();
              //系统实体类Resources,即各个url
              Resources resources = new Resources();
              resources.setUsername(username);
              //找到特定用户名能访问的url集合
              List<Resources> list = resourcesService.loadMenu(resources);
              for (Resources r : list) {
                  //根据资源找到角色名,添加到角色名集合
                  authSet.add(new SimpleGrantedAuthority("ROLE_" +r.getResKey()));
                  //resKey:资源名,拼成角色名
              }
              /*
              username():登录用户名
              password():从数据库查出来的密码
              enable: 用户状态,true为有效,false无效
              accoutNonExpired,credentialsNonExpired,accountNonLocked
              roles: Collections集合,当前登录用户的角色列表
              */
              return new org.springframework.security.core.userdetails.User(user.getUsername(), 
                      user.getPassword(), 
                      user.getEnable()==1?true:false, 
                      true, 
                      true,
                      true,
                      authSet); //返回的user不为空,则验证通过
          }
      
      }
    
  • 系统实体类User实现UserDetails,主要是查询数据库所有权限,返回权限列表

      public class User implements UserDetails {
    
      	private static final long serialVersionUID = 8026813053768023527L;
      
      	private String name;
      	private String password;
      	private Set<Role> roles; //可以不写
      	//其他属性,随便,自定义
       
          /**
          * 返回系统所有角色
          */
      	@Override
      	public Collection<? extends GrantedAuthority> getAuthorities() {
      		//根据自定义逻辑来返回用户权限,如果用户权限返回空或者和拦截路径对应权限不同,验证不通过
      		if(!roles.isEmpty()){
      			List<GrantedAuthority> list = new ArrayList<GrantedAuthority>();
      			GrantedAuthority au = new SimpleGrantedAuthority(role.getrName());
      			list.add(au);
      			return list;
      		}
      		return null;
      	}
      	
      	/**
          * 默认有一个方法返回当前用户的角色列表,后面介绍了它的重写;可选的
          */
       
          @Override
          public String getPassword() {
              return this.password;
          }
          @Override
          public String getUsername() {
              return this.username;
          }
          
      	/* 
      	 *帐号是否不过期,false则验证不通过
      	 */
      	 @Override
      	public boolean isAccountNonExpired() {
      		return true;
      	}
       
      	/* 
      	 * 帐号是否不锁定,false则验证不通过
      	 */
      	 @Override
      	public boolean isAccountNonLocked() {
      		return true;
      	}
       
      	/* 
      	 * 凭证是否不过期,false则验证不通过
      	 */
      	 @Override
      	public boolean isCredentialsNonExpired() {
      		return true;
      	}
       
      	/* 
      	 * 该帐号是否启用,false则验证不通过
      	 */
      	 @Override
      	public boolean isEnabled() {
      		return true;
      	}
      }
    
  • User类代码实例:llcweb/domain/models/

  • 数据库表介绍:五张表————用户表、角色表、资源表、用户角色表、角色资源表。给用户分配角色,给角色分配资源(即权限)。一个用户可以对应多个角色,一个角色可以对应多个资源。

  • 其中资源表t_resources包含了后台系统的所有url

  • resKey即角色名
自定义过滤器
  • 首先整理spring security的步骤:
  1. 在spring security中每一个url都是一个资源,系统启动时,spring security第一个拦截器FilterSecurityInterceptor调用SecurityMetadataSource将所有url与其权限对应。
  2. 当用户发起请求时,spring security检查该请求是否需要特定权限(需不需要都在WebSecurityConfigurerAdapter中指定了),不需要的话直接访问,连是否登录都不验证。
  3. 需要权限,AbstractAuthenticationProcessingFilter先验证当前用户是否已登录,否则跳转登录页面。
  4. 已登录,FilterSecurityInterceptor判断该用户是否拥有当前请求所需的权限,有则允许访问,没有则给出提示信息。
  • 我们这一节说的过滤器特指这个FilterSecurityInterceptor。基于数据库的权限验证必须要自定义过滤器,因为它主要就是做权限验证的,包括资源与权限对应关系的查询、当前用户是否持有请求资源所需权限的判断。

  • 前面不是用java注册spring security的话就需要以下配置

      <!--还要在http中添加<custom-filter before="FILTER_SECURITY_INTERCEPTOR" 
      ref="securityInterceptor"/>-->
      
      <!--ProviderManager类的实例注入authenticationManager-->
      <authentication-manager alias="authenticationManager">
      <!-- 获取访问url对应的所有权限 -->
      <beans:bean id="secureResourceFilterInvocationDefinitionSource" 
          class="com.ultrapower.me.util.security.interceptor.SecureResourceFilterInvocationDefinitionSource" />
      <!-- 校验用户的权限是否足够 -->
      <beans:bean id="mesecurityAccessDecisionManager" 
          class="com.ultrapower.me.util.security.interceptor.SecurityAccessDecisionManager" />
      
      <!-- 自定义拦截器:依赖上面的bean -->
      <beans:bean id="securityInterceptor" 
       class="com.ultrapower.me.util.security.interceptor.SecurityInterceptor">
      	<beans:property name="authenticationManager" ref="authenticationManager"/>
          <beans:property name="accessDecisionManager" ref="mesecurityAccessDecisionManager"/>
          <beans:property name="securityMetadataSource" 
              ref="secureResourceFilterInvocationDefinitionSource" />
      </beans:bean>
    
  • 开始自定义过滤器

      package com.study.security;
    
      import java.io.IOException;
      
      import javax.annotation.PostConstruct;
      import javax.annotation.Resource;
      import javax.servlet.Filter;
      import javax.servlet.FilterChain;
      import javax.servlet.FilterConfig;
      import javax.servlet.ServletException;
      import javax.servlet.ServletRequest;
      import javax.servlet.ServletResponse;
      
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.security.access.SecurityMetadataSource;
      import org.springframework.security.access.intercept.AbstractSecurityInterceptor;
      import org.springframework.security.access.intercept.InterceptorStatusToken;
      import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
      import org.springframework.security.web.FilterInvocation;
      import org.springframework.stereotype.Component;
      
      @Component
      public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor implements 
          Filter {
          //所有资源与权限的关系(需要在外面自定义):获取请求资源的权限
          @Autowired
          private MySecurityMetadataSource securityMetadataSource;
          
          //用户是否拥有所请求资源的权限(需要在外面自定义):判断是否拥有请求资源所需的权限
          @Autowired
          private MyAccessDecisionManager accessDecisionManager;
          
          /* 默认装配够用了
          @Resource(name="myAuthenticationManager")
          private AuthenticationManager authenticationManager;  
          */
          
          @Resource
          private AuthenticationConfiguration authenticationConfiguration;
      
      //  @Autowired
      //  public void setAuthenticationConfiguration(AuthenticationConfiguration authenticationConfiguration) {
      //       this.authenticationConfiguration = authenticationConfiguration;
      //  }
      
          @PostConstruct
          public void init() throws Exception{
              super.setAccessDecisionManager(accessDecisionManager);
              super.setAuthenticationManager(authenticationConfiguration.getAuthenticationManager());   
          }
      
          @Override
          public SecurityMetadataSource obtainSecurityMetadataSource() {
              return this.securityMetadataSource;
          }
      
          @Override
          public void doFilter(ServletRequest request, ServletResponse response,
                  FilterChain chain) throws IOException, ServletException {
              FilterInvocation fi = new FilterInvocation(request, response, chain);
          
              /*beforeInvocation做的事*/
              //super.beforeInvocation(fi);源码
              //获取请求资源的权限
              //执行Collection<ConfigAttribute> attributes =
                  SecurityMetadataSource.getAttributes(object); //object为FilterInvocation对象
              //是否拥有权限;authentication:封装了用户拥有的权限
              //this.accessDecisionManager.decide(authenticated, object, attributes);
              InterceptorStatusToken token = super.beforeInvocation(fi);
              
              try {
                  //执行下一个拦截器
                  fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
              } finally {
                  super.afterInvocation(token, null);
              }
          }
          
          @Override
          public void init(FilterConfig arg0) throws ServletException {
          }
      
          @Override
          public void destroy() {
      
          }
      
          @Override
          public Class<? extends Object> getSecureObjectClass() {
              //下面的MyAccessDecisionManager的supports方法必须返回true,否则会提醒类型错误
              return FilterInvocation.class;
          }
      }
    
  • 自定义过滤器需要用到的用户是否拥有所请求资源的权限:MyAccessDecisionManager。默认使用AbstractAccessDecisionManager,具体是继承了AbstractAccessDecisionManager的AffirmativeBased,还有其他其他的决策管理器,不过不知道怎么启用,只是投票方式不同而已,有的是只要有赞成票就通过权限验证,有的是只要有反对票就不通过。但是看不懂底层是根据什么投赞成票和反对票的,反正不通过就抛AccessDenyException,一层层向上抛出,由ExceptionTranslationFilter打印异常日志。

      package com.study.security;
      
      import java.util.Collection;
      import java.util.Iterator;
      
      import org.springframework.security.access.AccessDecisionManager;
      import org.springframework.security.access.AccessDeniedException;
      import org.springframework.security.access.ConfigAttribute;
      import org.springframework.security.authentication.InsufficientAuthenticationException;
      import org.springframework.security.core.Authentication;
      import org.springframework.security.core.GrantedAuthority;
      import org.springframework.stereotype.Component;
      
      @Component
      public class MyAccessDecisionManager implements AccessDecisionManager {
      
          /**
          * 决定authentication拥有的权限中有没有指定资源的权限
          * object:受保护对象,其可以是一个MethodInvocation、JoinPoint或FilterInvocation
          * authentication:封装了用户拥有的权限
          * configAttributes:受保护对象的相关配置属性,主要是访问资源需要的权限
          */
          public void decide(Authentication authentication, Object object,
              Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
              
              if(configAttributes == null) {
                  return;
              }
              
              //所请求的资源需要的权限(一个资源对多个权限)
              Iterator<ConfigAttribute> iterator = configAttributes.iterator();
              while(iterator.hasNext()) {
                  ConfigAttribute configAttribute = iterator.next();
                  //访问所请求资源所需要的权限
                  String Permission = configAttribute.getAttribute();
                  System.out.println("needPermission is " + Permission);
                  //用户所拥有的权限authentication
                  for(GrantedAuthority ga : authentication.getAuthorities()) {
                      if(Permission.equals(ga.getAuthority())) {
                          return;
                      }
                  }
              }
              
              //没有权限,后台抛异常而前端显示access-denied-page页面
              throw new AccessDeniedException(" 没有权限访问或未重新登录! ");
          }
      
           /**
           * 判断AccessDecisionManager是否能够处理对应的ConfigAttribute
           */
          public boolean supports(ConfigAttribute attribute) {
              // TODO Auto-generated method stub
              return true;
          }
      
          /**
           * 判断AccessDecisionManager是否支持对应的受保护对象类型
           */
          public boolean supports(Class<?> clazz) {
              // TODO Auto-generated method stub
              return true;
          }
      
      }
    
  • 自定义过滤器需要用到的获取资源权限(即查询t_resources表中所有resKey与resUrl的关系):MySecurityMetadataSource

      package com.study.security;
    
      import java.util.ArrayList;
      import java.util.Collection;
      import java.util.HashMap;
      import java.util.List;
      import java.util.Map;
      
      import javax.annotation.PostConstruct;
      import javax.annotation.Resource;
      
      import org.springframework.security.access.ConfigAttribute;
      import org.springframework.security.access.SecurityConfig;
      import org.springframework.security.web.FilterInvocation;
      import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
      import org.springframework.stereotype.Component;
      
      import com.study.dao.ResourcesDao;
      import com.study.model.Resources;
      
      @Component
      public class MySecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
      
          @Resource
          private ResourcesDao resourcesDao;
      
          private static Map<String, Collection<ConfigAttribute>> resourceMap = null;
      
          public Collection<ConfigAttribute> getAllConfigAttributes() {
              return null;
          }
      
          public boolean supports(Class<?> clazz) {
              return true;
          }
          
          /**
           * @PostConstruct是Java EE 5引入的注解,
           * Spring允许开发者在受管Bean中使用它。当DI容器实例化当前受管Bean时,
           * @PostConstruct注解的方法会被自动触发,从而完成一些初始化工作
           */
          @PostConstruct
          private void loadResourceDefine() { //加载所有资源与权限的关系
              if (resourceMap == null) {
                  resourceMap = new HashMap<String, Collection<ConfigAttribute>>();
                  //数据库查询所有资源
                  List<Resources> list = resourcesDao.queryAll(new Resources());
                  //Resources:本项目自定义的实体
                  for (Resources resources : list) {
                      Collection<ConfigAttribute> configAttributes = new ArrayList<ConfigAttribute>();
                      // 通过资源名称来表示具体的权限 注意:必须"ROLE_"开头
                      /*SecurityConfig:ConfigAttribute实现类;
                      只有一个成员,是个常量,构造函数传入string赋予此常量,
                      configAttribute.getAttribute()返回此常量*/
                      ConfigAttribute configAttribute = new SecurityConfig("ROLE_" + resources.getResKey());
                      configAttributes.add(configAttribute);
                      resourceMap.put(resources.getResUrl(), configAttributes);
                  }
              }
          }
          
          //返回所请求资源所需要的权限
          public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
              //从过滤链中获取当前请求
              String requestUrl = ((FilterInvocation) object).getRequestUrl();
              //如果资源角色库为空的话说明还未初始化
              if(resourceMap == null) {
                  loadResourceDefine(); //强制初始化
              }
              //System.err.println("resourceMap.get(requestUrl); "+resourceMap.get(requestUrl));
              //如果是get请求的话去掉后面参数
              if(requestUrl.indexOf("?")>-1){
                  requestUrl=requestUrl.substring(0,requestUrl.indexOf("?"));
              }
              //获取该请求对应权限,url作为key
              Collection<ConfigAttribute> configAttributes = resourceMap.get(requestUrl);
              return configAttributes;
          }
      }
    
  • ExceptionTranslationFilter处理AuthenticationException和AccessDeniedException两种异常。

      private void handleSpringSecurityException(HttpServletRequest request,     
          HttpServletResponse response, FilterChain chain, RuntimeException exception) throws IOException, ServletException {
          
          //如果异常是AuthenticationException(未登录即访问受保护资源)
          if (exception instanceof AuthenticationException) { 
              //打印异常
              this.logger.debug("Authentication exception occurred; 
                  redirecting to authentication entry point", exception);
              /*传入此方法后主要是调用AuthenticationEntryPoint的默认实现类
              LoginUrlAuthenticationEntryPoint中的commence方法重定向到登录页*/
              this.sendStartAuthentication(request, response, chain, 
                  (AuthenticationException)exception);
              /* sendStartAuthentication源码
              protected void sendStartAuthentication(HttpServletRequest request, 
                      HttpServletResponse response, FilterChain chain, 
                      AuthenticationException reason) throws ServletException, IOException {
                  SecurityContextHolder.getContext().setAuthentication((Authentication)null);
                  this.requestCache.saveRequest(request, response);
                  this.logger.debug("Calling Authentication entry point.");
                  this.authenticationEntryPoint.commence(request, response, reason);
              }
              */
          } 
          
          //如果是AccessDeniedException
          else if (exception instanceof AccessDeniedException) {
          
              //且是匿名用户(未登录即访问受保护资源;一般是该异常,少为AuthenticationException)
              if (this.authenticationTrustResolver.isAnonymous(SecurityContextHolder
                      .getContext().getAuthentication())) {
                  this.logger.debug("Access is denied (user is anonymous); 
                      redirecting to authentication entry point", exception);
                  //重定向到登录页,且把AccessDeniedException包装成AuthenticationException
                  this.sendStartAuthentication(request, response, chain, 
                      new InsufficientAuthenticationException("Full authentication is required 
                          to access this resource"));
              } 
              //非匿名用户(已登录但权限不足)
              else {
                  this.logger.debug("Access is denied (user is not anonymous); 
                      delegating to AccessDeniedHandler", exception);
                  //重定向到403页面;AccessDeniedHandler默认实现类:AccessDeniedHandlerImpl
                  this.accessDeniedHandler.handle(request, response
                      ,(AccessDeniedException)exception);
              }
          }
    
      }
    
参考文章

代码实例

  • thz-manager-web/config
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值