• SpringSecurity基础:SecurityContext对象


    SecurityContext

    用户登录之后,怎么获取当前已经登录用户的信息呢?通过使用SecurityContextHolder就可以很方便地得到SecurityContext对象了,我们可以直接使用SecurityContext对象来获取当前的认证信息:

    1. @RequestMapping("/index")
    2. public String index(){
    3. SecurityContext context = SecurityContextHolder.getContext();
    4. Authentication authentication = context.getAuthentication();
    5. User user = (User) authentication.getPrincipal();
    6. System.out.println(user.getUsername());
    7. System.out.println(user.getAuthorities());
    8. return "index";
    9. }

    通过SecurityContext我们就可以快速获取当前用户的名称和授权信息等。

    除了这种方式以外,我们还可以直接从Session中获取:

    1. @RequestMapping("/index")
    2. public String index(@SessionAttribute("SPRING_SECURITY_CONTEXT") SecurityContext context){
    3. Authentication authentication = context.getAuthentication();
    4. User user = (User) authentication.getPrincipal();
    5. System.out.println(user.getUsername());
    6. System.out.println(user.getAuthorities());
    7. return "index";
    8. }

    注意SecurityContextHolder是有一定的存储策略的,SecurityContextHolder中的SecurityContext对象会在一开始请求到来时被设定,至于存储方式其实是由存储策略决定的,如果我们这样编写,那么在默认情况下是无法获取到认证信息的:

    1. @RequestMapping("/index")
    2. public String index(){
    3. new Thread(() -> { //创建一个子线程去获取
    4. SecurityContext context = SecurityContextHolder.getContext();
    5. Authentication authentication = context.getAuthentication();
    6. User user = (User) authentication.getPrincipal(); //NPE
    7. System.out.println(user.getUsername());
    8. System.out.println(user.getAuthorities());
    9. });
    10. return "index";
    11. }

    这是因为SecurityContextHolder的存储策略默认是MODE_THREADLOCAL,它是基于ThreadLocal实现的,getContext()方法本质上调用的是对应的存储策略实现的方法:

    1. public static SecurityContext getContext() {
    2. return strategy.getContext();
    3. }

    SecurityContextHolderStrategy有三个实现类:

    ●GlobalSecurityContextHolderStrategy:全局模式,不常用
    ●ThreadLocalSecurityContextHolderStrategy:基于ThreadLocal实现,线程内可见
    ●InheritableThreadLocalSecurityContextHolderStrategy:基于InheritableThreadLocal实现,线程和子线程可见

    因此,如果上述情况需要在子线程中获取,那么需要修改SecurityContextHolder的存储策略,在初始化的时候设置:

     

    1. @PostConstruct
    2. public void init(){
    3. SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
    4. }

    这样在子线程中也可以获取认证信息了。

    因为用户的验证信息是基于SecurityContext进行判断的,我们可以直接修改SecurityContext的内容,来手动为用户进行登陆:

    1. @RequestMapping("/auth")
    2. @ResponseBody
    3. public String auth(){
    4. SecurityContext context = SecurityContextHolder.getContext(); //获取SecurityContext对象(当前会话肯定是没有登陆的)
    5. UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("Test", null,
    6. AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_user")); //手动创建一个UsernamePasswordAuthenticationToken对象,也就是用户的认证信息,角色需要添加ROLE_前缀,权限直接写
    7. context.setAuthentication(token); //手动为SecurityContext设定认证信息
    8. return "Login success!";
    9. }

    在未登陆的情况下,访问此地址将直接进行手动登陆,再次访问/index页面,可以直接访问,说明手动设置认证信息成功。

    疑惑:SecurityContext这玩意不是默认线程独占吗,那每次请求都是一个新的线程,按理说上一次的SecurityContext对象应该没了才对啊,为什么再次请求依然能够继续使用上一次SecurityContext中的认证信息呢?

    SecurityContext的生命周期:请求到来时从Session中取出,放入SecurityContextHolder中,请求结束时从SecurityContextHolder取出,并放到Session中,实际上就是依靠Session来存储的,一旦会话过期验证信息也跟着消失。

  • 相关阅读:
    fastjson 1.2.47 远程命令执行漏洞
    Delphi记录
    线上linux,服务突然不可用(引出Docker文件查找与删除)
    观成科技:证券行业加密业务安全风险监测与防御技术研究
    springboot+avue框架开发的医院绩效考核系统全套源码
    请问各位程序员,是我的思维方式有错误吗?
    为什么EDR需要深度防御来打击勒索软件?
    CentOS 8最小安装,VM使用这个内存占用小很多
    选择SSL证书的理由以及优势
    计算机毕业设计Java本地助农产品销售系统(源码+系统+mysql数据库+lw文档)
  • 原文地址:https://blog.csdn.net/Leon_Jinhai_Sun/article/details/126907154