Spring Security 从入门到精通之获取认证用户信息(四)

1. 前言

当用户认证通过后,SpringSecurity会将当前用户信息保存起来,以供后续业务流程使用,下面将简单介绍两种获取认证用户的信息。

2 通过SecurityContextHolder获取用户信息

当用户认证通过后,在后续的业务逻辑中还会获取到当前认证的用户信息,在未使用安全框架前,一般是将认证成功的用户存储在HttpSession中,在后续业务逻辑中再从HttpSession中获取用户即可。实际上Spring Security依然是将用户存储在HttpSession中。不过Spring Security在此基础上还对HttpSession中的用户信息作了进一步的封装,Spring Security是将认证的用户信息保存在SecurityContextHolder 对象中。下面我们演示如何从这个类中获取用户信息:

 @GetMapping("/user")
    public ResponseEntity<String> getUser() throws JsonProcessingException {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        Map<String,Object> map = new HashMap<>();
        map.put("authentication",authentication);
        String value = objectMapper.writeValueAsString(map);
        return ResponseEntity.ok(value);
    }

当认证成功后,在浏览器中输入http://127.0.0.1:8001/user,并返回如下信息
在这里插入图片描述

2.1. SecurityContextHolder获取用户信息源码分析

首先需要说明的是SecurityContextHolder 中存储的是SecurityContext对象,然后在这个对象中存储了认证用户Authentication对象,下面是三者的关系图:
在这里插入图片描述
首先在SecurityContext在初始化是依靠 SecurityContextHolderStrategy 接口,根据不同的策略生成相应的SecurityContext,在SecurityContextHolder类加载的时候就需要初始化当前策略,我们可以在源码中找到答案:
在这里插入图片描述
SecurityContextHolder 中通过实例化 SecurityContextHolderStrategy 接口不同子类以满足不同的策略需要,
SecurityContextHolderStrategy 接口定义如下:
在这里插入图片描述
SecurityContextHolderStrategy 接口是用来保存SecurityContext对象里,而这个SecurityContext对象里则保存了用户认证Authentication对象,SecurityContext接口定义如下:
在这里插入图片描述
SecurityContextHolderStrategy接口实现类有如下四个:
在这里插入图片描述
SecurityContextHolderinitializeStrategy方法中默认设置了MODE_THREADLOCAL 策略即 ThreadLocalSecurityContextHolderStrategy 类,这个类源码如下所示:
在这里插入图片描述
可以看到这个类是通过ThreadLocal存储、获取、清空操作的,我们都知道ThreadLocal都是在单个线程中进行读写的,所以在多线程环境下ThreadLocal是线程安全的。这也意味着如果在一个线程中开启了子线程,那么在子线程中是无法获取认证用户信息的。为了解决这个问题SecurityContextHolder 提供了 MODE_INHERITABLETHREADLOCAL策略对应InheritableThreadLocalSecurityContextHolderStrategy 实现,此类定义如下:
在这里插入图片描述
InheritableThreadLocalThreadLocal子类,与 ThreadLocal 不同的是在子线程创建的一瞬间,会自动将父线程中的数据复制到子线程中。所以此策略适用于多线程环境下业务场景。我们可以用以下代码检验一下是否是这样?

    @GetMapping("/user")
    public ResponseEntity<String> getUser() throws JsonProcessingException {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        Map<String,Object> map = new HashMap<>();
        map.put("authentication",authentication);
        String value = objectMapper.writeValueAsString(map);
        // 开启子线程获取认证用户信息
        new Thread(()-> {
            Authentication authentication1 = SecurityContextHolder.getContext().getAuthentication();
            if(authentication1 ==null) {
                System.out.println("获取用户信息失败!");
                return;
            }
            System.out.println("子线程获取当前登陆用户名称:"+authentication1.getName());
        }).start();
        return ResponseEntity.ok(value);
    }

项目重启用户认证成功后,浏览器输入:http://127.0.0.1:8001/user 接口控制台输出如下结果:
在这里插入图片描述
可以看到在默认策略下,是无法在子线程中获取用户信息的,如果想要在子线程中获取用户信息,可修改SecurityContextHolder类默认策略,默认的策略是通过System.getProperty进行加载的,在源码中我们不难发现这一点如下所示:
在这里插入图片描述
我们可以在项目启动类上加上VM参数:-Dspring.security.strategy=MODE_INHERITABLETHREADLOCAL
在这里插入图片描述
再次重启重新认证就发现子线程可以获取到当前用户认证的信息了如下所示:
在这里插入图片描述
如果对当前使用场景要求简单的情况下,还可以使用GlobalSecurityContextHolderStrategy,此类使用静态变量来保存SecurityContext对象,不过在web开发中这种策略使用场景不多,此类定义如下所示:
在这里插入图片描述

3. 从HttpServletRequest获取用户信息

  /**
     * 登陆成功后可以直接从HttpServletRequest获取当前已经认证成功的对象
     * @param httpServletRequest HttpServletRequest
     * @return ResponseEntity
     * @throws JsonProcessingException JsonProcessingException
     */
    @GetMapping("/getUserFromServlet")
    public ResponseEntity<String> getUserFromServlet(HttpServletRequest httpServletRequest) throws JsonProcessingException {
        String user = httpServletRequest.getRemoteUser();
        Authentication userPrincipal = (Authentication) httpServletRequest.getUserPrincipal();
        Map<String, Object> map = new HashMap<>();
        map.put("authentication", userPrincipal);
        map.put("user", user);
        String value = objectMapper.writeValueAsString(map);
        return ResponseEntity.ok(value);
    }

3.1 HttpServletRequest获取用户信息源码分析

首先我们看一下HttpServletRequest 类层级关系如下图所示:
在这里插入图片描述
下面我们重点查看一下SecurityContextHolderAwareRequestWrapper#getUserPrincipal 方法
在这里插入图片描述
可以很清晰的看到HttpServletRequest获取的用户认证信息,底层还是通过SecurityContextHolder类进行获取的。

4. SecurityContextHolder 保存认证用户信息流程

Spring Security 将用户登陆成功的用户信息保存在session中,为了能够更好的获取到用户信息,Spring Security 提供了一个SecurityContextHolder类,用来保存用户登陆信息。
具体原理是:当用户登陆成功后,Spring Security会将用户信息保存在SecurityContextHolder中,SecurityContextHolder中的数据保存默认是通过ThreadLocal来实现的,ThreadLocal这个类想必大家都不陌生,我们经常会使用到它来保存一些线程隔离的、全局的变量信息。使用ThreadLocal维护变量时,每个线程都会获得该线程独享一份变量副本。这里通过ThreadLocal 实现了请求线程与用户数据绑定(每一个请求线程都有自己的用户数据),当登陆请求处理完毕后,Spring Security会将SecurityContextHolder中保存用户信息取出并保存在Session中,同时删除SecurityContextHolder保存的用户信息,待下一次请求时,再从session中取出用户信息并保存在SecurityContextHolder中,然后在请求结束时,又会将用户信息从SecurityContextHolder取出并保存在session中并清除SecurityContextHolder中的用户信息。这样做的好处就是可以很方便在Controller或者service中随时获取到用户信息(通过SecurityContextHolder),但是在子线程中获取父线程用户数据就异常麻烦。

5 总结

这里我们学习了如何在Spring Security中获取认证成功的用户信息,下一章我们将继续深入学习Spring Security的整个认证流程,敬请期待

### Spring Security 入门精通教程 Spring SecuritySpring 生态系统中用于安全管理的核心框架,提供了身份认证、授权、会话管理等功能。以下将从入门到高级全面讲解 Spring Security 的核心知识点。 --- #### 一、Spring Security 简介 Spring Boot/Spring Cloud 技术栈在 Java 开发领域占据主流地位[^1],而 Spring Security 是其安全模块的首选解决方案。它能够为应用程序提供强大的安全保障,支持多种认证和授权机制。 --- #### 二、Spring Security 入门:自动配置与默认设置 在 Spring Boot 项目中引入 Spring Security 后,框架会自动应用一系列默认的安全配置[^2]。例如: - 默认情况下,所有 HTTP 请求都需要经过身份验证。 - 提供了一个内置的登录页面,账号和密码由 Spring Security 自动生成[^5]。 如果未进行任何自定义配置,Spring Security 会生成一个随机密码并打印到控制台[^5]。 --- #### 三、Spring Security 配置类 为了满足实际项目需求,通常需要自定义 Spring Security 的行为。可以通过创建一个继承 `WebSecurityConfigurerAdapter` 的配置类来实现[^3]: ```java @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); // 使用 BCrypt 加密算法 } @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() // 禁用 CSRF 保护(仅适用于某些场景) .authorizeRequests() .antMatchers("/public/**").permitAll() // 允许匿名访问 /public 路径 .anyRequest().authenticated(); // 其他请求均需认证 } } ``` 上述代码展示了如何通过 `HttpSecurity` 配置 HTTP 请求的安全策略[^3]。 --- #### 、用户认证逻辑 默认情况下,Spring Security 使用内存中的用户信息进行认证。但在实际项目中,用户数据通常存储在数据库中。可以通过实现 `UserDetailsService` 接口来自定义认证逻辑[^5]: ```java @Component public class UserSecurityService implements UserDetailsService { @Autowired private UserService userService; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userService.findUserByUsername(username); if (user == null) { throw new UsernameNotFoundException("用户名不存在"); } return new org.springframework.security.core.userdetails.User( user.getUsername(), user.getPassword(), AuthorityUtils.createAuthorityList("ROLE_USER") ); } } ``` 上述代码实现了从数据库中加载用户信息,并返回符合 Spring Security 规范的 `UserDetails` 对象[^5]。 --- #### 五、跨域支持与 JWT 集成 在现代 Web 应用中,跨域资源共享(CORS)是一个常见需求。可以通过以下方式配置 Spring Security 支持跨域访问[^4]: ```java @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .cors() // 启用 CORS 支持 .and() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() .antMatchers("/user/login").anonymous() // 登录接口允许匿名访问 .anyRequest().authenticated(); } ``` 此外,Spring Security 还可以与 JSON Web Token (JWT) 结合使用,以实现无状态的身份认证[^4]。 --- #### 六、OAuth2 集成 Spring Security 提供了对 OAuth2 的全面支持,可以轻松集成第三方认证服务(如 Google、Facebook 或自定义 OAuth2 提供商)。以下是基本配置示例[^2]: ```java @Configuration public class OAuth2Config extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.oauth2Login() .and() .authorizeRequests() .antMatchers("/public/**").permitAll() .anyRequest().authenticated(); } } ``` --- #### 七、高级主题 1. **异常处理**:可以通过自定义 `AuthenticationEntryPoint` 和 `AccessDeniedHandler` 来处理认证失败或权限不足的情况。 2. **密码加密**:`BCryptPasswordEncoder` 是推荐的密码加密工具,确保用户密码的安全性。 3. **过滤器链**:Spring Security 的核心是基于过滤器链的设计,开发者可以根据需求添加自定义过滤器。 --- #### 八、参考资料 快速排序的基本思想是选择一个基准元素[^1]。 ```python def quick_sort(arr): if len(arr) <= 1: return arr else: pivot = arr[len(arr) // 2] left = [x for x in arr if x < pivot] middle = [x for x in arr if x == pivot] right = [x for x in arr if x > pivot] return quick_sort(left) + middle + quick_sort(right) ``` --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值