Spring Security(学习笔记)-SecurityContextHolder!

重点表示

登录用户数据,用户信息的获取,以及Srping Security为什么把用户信息放到SecurityContextHolder中的,又是如何做的。

多线程下,在Spring Security是如何获取到用户信息的?

匿名访问!

用户数据

获取登录成功后的数据,
用户数据存在Authenticatiion,这里就是我在配置中的Authentication。

存在threadLocal中,是线程级别。

Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

点进去看一下:SecurityContextHolder,简单解释一下,根据源码逻辑,不指定strategyName的情况下,就是空的,默认给一个MODE_THREADLOCAL。

  private static void initializeStrategy() {
        if ("MODE_PRE_INITIALIZED".equals(strategyName)) {
            Assert.state(strategy != null, "When using MODE_PRE_INITIALIZED, setContextHolderStrategy must be called with the fully constructed strategy");
        } else {
            if (!StringUtils.hasText(strategyName)) {
                strategyName = "MODE_THREADLOCAL";
            }

            if (strategyName.equals("MODE_THREADLOCAL")) {
                strategy = new ThreadLocalSecurityContextHolderStrategy();
            } else if (strategyName.equals("MODE_INHERITABLETHREADLOCAL")) {
                strategy = new InheritableThreadLocalSecurityContextHolderStrategy();
            } else if (strategyName.equals("MODE_GLOBAL")) {
                strategy = new GlobalSecurityContextHolderStrategy();
            } else {
                try {
                    Class<?> clazz = Class.forName(strategyName);
                    Constructor<?> customStrategy = clazz.getConstructor();
                    strategy = (SecurityContextHolderStrategy)customStrategy.newInstance();
                } catch (Exception var2) {
                    ReflectionUtils.handleReflectionException(var2);
                }

            }
        }
    }

这里有一点要注意,在我们的Web工程中,登陆之后,Tomcat从线程池中拿出一个线程处理,new 一个新线程的情况下,就拿不到用户信息了。

我们进入源码看一下,实际上,这块就是在SecurityContextHolderFilter里面,我们看它的doFilter方法。

private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
        if (request.getAttribute(FILTER_APPLIED) != null) {
            chain.doFilter(request, response);
        } else {
            request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
            Supplier<SecurityContext> deferredContext = this.securityContextRepository.loadDeferredContext(request);

            try {
                this.securityContextHolderStrategy.setDeferredContext(deferredContext);
                chain.doFilter(request, response);
            } finally {
                this.securityContextHolderStrategy.clearContext();
                request.removeAttribute(FILTER_APPLIED);
            }

        }
    }

在这里插入图片描述

这里简单解释一下:
使用Http Session,从session里面拿到值,然后存到ContextHolder中,最后请求结束的时候,又清理掉。

那为什么要这么弄呢,为什么不直接放在Http Session中呢,这是因为有些场景下,Http Session并不是那么容易获取的,甚至在一些场景中,根本就没用到Http Session,比如说
Service要获取到用户信息,那没有Http session,就要注入session,比较麻烦,最关键的是,在一些前后端分离登录中,可能服务端生成jwt,存在redis,不用session,所以,Spring Security一开始就替我们考虑到了这样的场景,直接存到SecurityContextHolder中,更灵活。

看一下从session中怎么拿:

 @GetMapping("/hello")
    public String hello(HttpSession httpSession){
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

        SecurityContext securityContext =(SecurityContext)httpSession.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY);
        Authentication authentication1 = securityContext.getAuthentication();
        System.out.println(authentication1 == authentication);
        return "hello";
    }

那如果一定要在子线程中获取到用户信息呢,Security也提供了相关的配置,如下


    public static void main(String[] args) {
        System.setProperty("spring.security.strategy", SecurityContextHolder.MODE_GLOBAL);
        SpringApplication.run(Security02DemoApplication.class, args);
    }



    @GetMapping("/hello")
    public String hello(HttpSession httpSession){
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

        SecurityContext securityContext =(SecurityContext)httpSession.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY);
        Authentication authentication1 = securityContext.getAuthentication();
        System.out.println(authentication1 == authentication);
        new Thread(new Runnable() {
            @Override
            public void run() {
                Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
                System.out.println(authentication.getPrincipal());
            }
        }).start();
        return "hello";
    }

就是在启动类前,加上这个全局配置,存到 private static SecurityContext contextHolder;这里面去,相当于静态变量保存,自然和线程就没啥关系了,不过这种方式是不推荐的。

还有一种获取Authentication和Principal的方法,直接放在参数中,可以直接拿到。

 /**
     * 这两个参数默认会被解析,类似于HttpSession,HttpServletRequest
     * @param authentication
     * @param principal
     */
    @GetMapping("/user")
    public void user(Authentication authentication, Principal principal){


    }

还可以通过HttpServletRequest,来获取,如下

    @GetMapping("/hello2")
    public void hello2(HttpServletRequest request){
        //本质上也是从ContextHolder里面拿的,所以子线程也是获取不到
        //这里是说,获取用户名
        String remoteUser = request.getRemoteUser();
       //是否具备admin角色
        boolean admin = request.isUserInRole("admin");

        //当前用户登陆对象
        Principal userPrincipal = request.getUserPrincipal();


    }

我们可以进去看一下,

在这里插入图片描述
进入到SecurityContextHolderAwareRequestWrapper ,这里我们可以看到 this.securityContextHolderStrategy = SecurityContextHolder.getContextHolderStrategy();,这样就拿到了,本质上还是从ContextHolder中获取的。

匿名访问

    //适用于静态页面放行,不会走security过滤器链,从contextHolder取不到值
  @Bean
    WebSecurityCustomizer webSecurityCustomizer(){
      return web -> web.ignoring().requestMatchers("/hello");
  }

    /**
     * 可以在这里修改过滤器
     * @param httpSecurity  所有过滤器都可以通过这个来配置。保存的默认的过滤器
     * @return
     */
    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
        //所有请求,都要登陆后才能访问
         httpSecurity.authorizeRequests(a -> a
                 //登陆或不登陆都可以访问
                         .requestMatchers("/hello").permitAll()
                 //只能未认证访问
                         .requestMatchers("/hello").anonymous()
                         
                         .anyRequest().authenticated())

这就是他们的区别,这一点一定要注意。

结语

攀登的过程,注定艰辛!

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值