安全认证之SecurityContextHolder

  1. 简介
     1) SecurityContextHolder是SpringSecurity最基本的组件了,是用来存放SecurityContext的对象,默认是使用ThreadLocal实现的,这样就保证了本线程内所有的方法都可以获得SecurityContext对象。
     2) SecurityContextHolder还有其他两种模式,分别为SecurityContextHolder.MODE_GLOBAL和SecurityContextHolder.MODE_INHERITABLETHREADLOCAL,前者表示SecurityContextHolder对象
         的全局的,应用中所有线程都可以访问;后者用于线程有父子关系的情境中,线程希望自己的子线程和自己有相同的安全性。
     3)大部分情况下我们不需要修改默认的配置,ThreadLocal是最常用也是最合适大部分应用的。
  2. 使用SecurityContextHolder获取当前登录的用户信息
    SecurityContextHolder中保存的是当前访问者的信息。Spring Security使用一个Authentication对象来表示这个信息。一般情况下,我们都不需要创建这个对象,在登录过程中,Spring Security已经创建了该对象并帮我们放到了SecurityContextHolder中。从SecurityContextHolder中获取这个对象也是很简单的。比如,获取当前登录用户的用户名,可以这样 :
    
    // 获取安全上下文对象,就是那个保存在 ThreadLocal 里面的安全上下文对象
    // 总是不为null(如果不存在,则创建一个authentication属性为null的empty安全上下文对象)
    SecurityContext securityContext = SecurityContextHolder.getContext();
    
    // 获取当前认证了的 principal(当事人),或者 request token (令牌)
    // 如果没有认证,会是 null,该例子是认证之后的情况
    Authentication authentication = securityContext.getAuthentication()
    
    // 获取当事人信息对象,返回结果是 Object 类型,但实际上可以是应用程序自定义的带有更多应用相关信息的某个类型。
    // 很多情况下,该对象是 Spring Security 核心接口 UserDetails 的一个实现类,你可以把 UserDetails 想像
    // 成我们数据库中保存的一个用户信息到 SecurityContextHolder 中 Spring Security 需要的用户信息格式的
    // 一个适配器。
    Object principal = authentication.getPrincipal();
    if (principal instanceof UserDetails) {
    	String username = ((UserDetails)principal).getUsername();
    } else {
    	String username = principal.toString();
    }
    
    

  3. 修改SecurityContextHolder的工作模式
     综上所述,SecurityContextHolder可以工作在以下三种模式之一:
     1)MODE_THREADLOCAL (缺省工作模式)
     2)MODE_GLOBAL
     3)  MODE_INHERITABLETHREADLOCAL
     4) 修改SecurityContextHolder的工作模式有两种方法 :
             a: 设置一个系统属性(system.properties) : spring.security.strategy;
                  SecurityContextHolder会自动从该系统属性中尝试获取被设定的工作模式
             b:  调用SecurityContextHolder静态方法setStrategyName()
         
    程序化方式主动设置工作模式的方法     

  4. SecurityContextHolder存储SecurityContext的方式(默认就是mode_threadlocal)
     
    1)单机系统,即应用从开启到关闭的整个生命周期只有一个用户在使用。由于整个应用只需要保存一个SecurityContext(安全上下文即可)
     2)多用户系统,比如典型的Web系统,整个生命周期可能同时有多个用户在使用。这时候应用需要保存多个SecurityContext(安全上下文),需要利用ThreadLocal进行保存,每个线程都可以利用ThreadLocal获取其自己的SecurityContext,及安全上下文。

  5. 比如我们使用jwt实现登录功能的时候,我们可以将用户信息保存在安全上下文中

     @Override
        public String login(String username, String password) {
            String token = null;
            //密码需要客户端加密后传递
            try {
                UserDetails userDetails = loadUserByUsername(username);
                if(!password.equals(BasicEncryptUtils.decryptByRSA(userDetails.getPassword()))){
                    throw new ServiceException("password.is.not.correct", ResultCode.VALIDATE_FAILED.getCode());
                }
                //根据userDetails构建新的Authentication
                UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                //存放authentication到SecurityContextHolder
                SecurityContextHolder.getContext().setAuthentication(authentication);
                //使用jwt生成token
                token = jwtTokenUtil.generateToken(userDetails, RsaUtils.getPrivateKey(propertiesUtils.getPrivateKeyUrl()));
                //到redis中查看该账号是否之前已经登录
                if (redisUtil.getValueByKey(CommonConstant.ormis_online_login_user_info+username)!=null){
                    //删掉对应的数据
                    redisUtil.deleteCache(CommonConstant.ormis_online_login_user_info+username);
                }
                //重新存储
                redisUtil.set(CommonConstant.ormis_online_login_user_info+username,token,propertiesUtils.getExpiration());
            } catch (Exception e) {
                log.error("login error is {}", e.getMessage());
                throw new ServiceException(e.getMessage(), ResultCode.VALIDATE_FAILED.getCode());
            }
            return token;
        }

      由上面代码可以发现我们获取用户的信息之后,通过SecurityContextHolder.getContext().setAuthentication(authentication);方式将用户相关的信息存放到系统的安全上下文中,并且由于 SecurityContextHolder默认是mode_threadlocal模式,那么会将所有登录的用户信息都保存,每个登录的用户都可以通过SecurityContextHolder.getContext().getAuthentication();方式获取
    当前自己保存的用户信息

你可能感兴趣的:(spring,security,spring,security)