Spring Security权限控制

时间:2020-12-21 08:12:35

Spring Security官网 : https://projects.spring.io/spring-security/

Spring Security简介:

Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。

Spring Security权限控制

spring security认证:

Basic

Digest

X.509

LDAP

Form

 

Spring Security权限控制

 

Spring Security的几个Filter

Spring Security权限控制

       Spring Security已经定义了一些Filter,不管实际应用中你用到了哪些,它们应当保持如下顺序。

       (1)ChannelProcessingFilter,如果你访问的channel错了,那首先就会在channel之间进行跳转,如http变为https。

       (2)SecurityContextPersistenceFilter,这样的话在一开始进行request的时候就可以在SecurityContextHolder中建立一个SecurityContext,然后在请求结束的时候,任何对SecurityContext的改变都可以被copy到HttpSession。

       (3)ConcurrentSessionFilter,因为它需要使用SecurityContextHolder的功能,而且更新对应session的最后更新时间,以及通过SessionRegistry获取当前的SessionInformation以检查当前的session是否已经过期,过期则会调用LogoutHandler。

       (4)认证处理机制,如UsernamePasswordAuthenticationFilter,CasAuthenticationFilter,BasicAuthenticationFilter等,以至于SecurityContextHolder可以被更新为包含一个有效的Authentication请求。

       (5)SecurityContextHolderAwareRequestFilter,它将会把HttpServletRequest封装成一个继承自HttpServletRequestWrapper的SecurityContextHolderAwareRequestWrapper,同时使用SecurityContext实现了HttpServletRequest中与安全相关的方法。

       (6)JaasApiIntegrationFilter,如果SecurityContextHolder中拥有的Authentication是一个JaasAuthenticationToken,那么该Filter将使用包含在JaasAuthenticationToken中的Subject继续执行FilterChain。

       (7)RememberMeAuthenticationFilter,如果之前的认证处理机制没有更新SecurityContextHolder,并且用户请求包含了一个Remember-Me对应的cookie,那么一个对应的Authentication将会设给SecurityContextHolder。

       (8)AnonymousAuthenticationFilter,如果之前的认证机制都没有更新SecurityContextHolder拥有的Authentication,那么一个AnonymousAuthenticationToken将会设给SecurityContextHolder。

       (9)ExceptionTransactionFilter,用于处理在FilterChain范围内抛出的AccessDeniedException和AuthenticationException,并把它们转换为对应的Http错误码返回或者对应的页面。

       (10)FilterSecurityInterceptor,保护Web URI,并且在访问被拒绝时抛出异常。

 

Spring Security权限控制

 与数据库管理不同的是,Spring Security提供了一个实现了可以缓存UserDetailService的实现类,这个类的名字是CachingUserDetailsService

Spring Security权限控制

该类的构造接收了一个用于真正加载UserDetails的UserDetailsService实现类,当需要加载UserDetails时,会首先从缓存中获取。如果缓存中没有对应的UserDetails,则使用UserDetailsService实现类进行加载,然后将加载后的结果存在缓存中。UserDetais与缓存的交互是通过UserCache实现的。CachingUserDetailsService默认有一个UserCache的空引用。

Spring Security权限控制

Spring的决策管理器,其接口为AccessDecisionManager,抽象类为AbstractAccessDecisionManager。而我们要自定义决策管理器的话一般是继承抽象类而不去直接实现接口。

在Spring中引入了投票器(AccessDecisionVoter)的概念,有无权限访问的最终觉得权是由投票器来决定的,最常见的投票器为RoleVoter,在RoleVoter中定义了权限的前缀,先看下Spring在RoleVoter中是怎么处理授权的。

public int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attributes) {  
    int result = ACCESS_ABSTAIN;  
    Collection<? extends GrantedAuthority> authorities = extractAuthorities(authentication);  
  
    for (ConfigAttribute attribute : attributes) {  
        if (this.supports(attribute)) {  
            result = ACCESS_DENIED;  
  
            // Attempt to find a matching granted authority  
            for (GrantedAuthority authority : authorities) {  
                if (attribute.getAttribute().equals(authority.getAuthority())) {  
                    return ACCESS_GRANTED;  
                }  
            }  
        }  
    }  
  
    return result;  
}  
  
Collection<? extends GrantedAuthority> extractAuthorities(Authentication authentication) {  
    return authentication.getAuthorities();  
}  

Authentication中是用户及用户权限信息,attributes是访问资源需要的权限,然后循环判断用户是否有访问资源需要的权限,如果有就返回ACCESS_GRANTED,通俗的说就是有权限。

Spring提供了3个决策管理器,至于这三个管理器是如何工作的请查看SpringSecurity源码

AffirmativeBased 一票通过,只要有一个投票器通过就允许访问

ConsensusBased 有一半以上投票器通过才允许访问资源

UnanimousBased 所有投票器都通过才允许访问

下面来实现一个简单的自定义决策管理器,这个决策管理器并没有使用投票器

public class DefaultAccessDecisionManager extends AbstractAccessDecisionManager {  
      
    public void decide( Authentication authentication, Object object,   
            Collection<ConfigAttribute> configAttributes)   
        throws AccessDeniedException, InsufficientAuthenticationException{  
          
        SysUser user = (SysUser)authentication.getPrincipal();  
        logger.info("访问资源的用户为"+user.getUsername());  
          
        //如果访问资源不需要任何权限则直接通过  
        if( configAttributes == null ) {  
            return ;  
        }  
          
        Iterator<ConfigAttribute> ite = configAttributes.iterator();  
        //遍历configAttributes看用户是否有访问资源的权限  
        while( ite.hasNext()){  
              
            ConfigAttribute ca = ite.next();  
            String needRole = ((SecurityConfig)ca).getAttribute();  
              
            //ga 为用户所被赋予的权限。 needRole 为访问相应的资源应该具有的权限。  
            for( GrantedAuthority ga: authentication.getAuthorities()){  
                  
                if(needRole.trim().equals(ga.getAuthority().trim())){  
  
                    return;  
                }  
            }  
        }  
          
        throw new AccessDeniedException("");  
          
    }  
}  

 

可以直接在Spring官网生成带Web和Security的基于Maven管理的Spring Boot项目,

下载项目,创建 SpringSecurityConfig 类继承 WebSecurityConfigurerAdapter 类,

SpringSecurityConfig类中设置放开静态资源,设置Http请求的拦截,

@Configuration
@EnableWebSecurity//打开web支持
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
/**
     * 设置http请求放开与拦截
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/").permitAll()//根目录可以直接访问
                .anyRequest().authenticated()//其他路径不能直接访问
                .and()
                .logout().permitAll()//注销任何权限都可以访问
                .and()
                .formLogin();//允许表单登录
        http.csrf().disable();//关闭默认的csrf的认证
    }

    /**
     * 设置静态资源放开
     * @param web
     * @throws Exception
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/js/**", "/css/**", "/images/**");
    }
}

 

简单的登录功能:

在  SpringSecurityConfig  类中

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//        设置可以登录的用户名和密码
        auth.inMemoryAuthentication().withUser("admin").password("123456").roles("ADMIN");
    }

这样在需要登录的,没有放行的功能中就需输入以上用户名和密码才可以进入。

这样可以设置多个用户和不同的权限。

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//        设置可以登录的用户名和密码
        auth.inMemoryAuthentication().withUser("admin").password("123456").roles("ADMIN");
        auth.inMemoryAuthentication().withUser("zhangsan").password("zhangsan").roles("ADMIN");
        auth.inMemoryAuthentication().withUser("demo").password("demo").roles("USER");

    }

给方法设置权限:

   @PreAuthorize("hasRole('ROLE_ADMIN')")
    @RequestMapping("/roleAuth")
    public String role() {
        return "admin auth";
    }

这样User权限的用户demo就不能访问该方法,只有admin角色的用户可以访问。

也可以从数据库中获取用户和权限信息:

定义MyUserService类,实现UserDetailsService接口,使用去提供的loadUserByUsername方法:

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return null;
    }
    @Autowired
    private MyUserService myUserService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//        设置可以登录的用户名和密码
//        auth.inMemoryAuthentication().withUser("admin").password("123456").roles("ADMIN");
//        auth.inMemoryAuthentication().withUser("zhangsan").password("zhangsan").roles("ADMIN");
//        auth.inMemoryAuthentication().withUser("demo").password("demo").roles("USER");
//
        auth.userDetailsService(myUserService)
                .passwordEncoder(new MyPasswordEncoder());//使用自己定义的验证器

        //默认的Security数据库验证,如果使用,需要使用给定的数据库表结构
        auth.jdbcAuthentication().usersByUsernameQuery("").authoritiesByUsernameQuery("").passwordEncoder(new MyPasswordEncoder());
    }

 

 

定义自己的密码验证规则:

public class MyPasswordEncoder implements PasswordEncoder {

    private final static String SALT = "123456";

    /**
     * 密码加密
     * @param rawPassword
     * @return
     */
    @Override
    public String encode(CharSequence rawPassword) {
        Md5PasswordEncoder encoder = new Md5PasswordEncoder();
        return encoder.encodePassword(rawPassword.toString(), SALT);
    }

    /**
     * 密码匹配
     * @param rawPassword
     * @param encodedPassword
     * @return
     */
    @Override
    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        Md5PasswordEncoder encoder = new Md5PasswordEncoder();
        return encoder.isPasswordValid(encodedPassword, rawPassword.toString(), SALT);
    }
}

 

基于表达式的权限控制

    @PreAuthorize("#id<10 and principal.username.equals(#username) and #user.username.equals('abc')")
    @PostAuthorize("returnObject%2==0")//返回的值是偶数,对2取余为0,此注解用于对返回值进行过滤,在方法完成后进行权限检查
    @RequestMapping("/test")
    public Integer test(Integer id, String username, User user) {
        // ...
        return id;
    }

    @PreFilter("filterObject%2==0")//传入的过滤
    @PostFilter("filterObject%4==0")//返回的过滤
    @RequestMapping("/test2")
    public List<Integer> test2(List<Integer> idList) {
        // ...
        return idList;
    }

 

总结:

优点:

1.提供了一套可用的安全框架

2.提供了很多用户认证功能,实现相关接口即可,节约了大量工作

3.基于Spring,易于集成到Spring项目中去,封装了许多方法

缺点:

1.配置文件过多,角色被“编码”到配置文件和源文件中,RBAC不明显

2.对于系统中的用户、角色、权限没有可操作的界面

3.大数据量的情况下几乎不可用