【Spring Boot】SecurityContextHolder.getContext().getAuthentication()为null的情况

SecurityContextHolder.getContext().getAuthentication()为null的情况

问题描述

在登录的时候,用如下方法获取输入的用户名:

/**
 * 获得当前用户名称
 */
private String getUsername() {
    String username = SecurityContextHolder.getContext().getAuthentication().getName();
    System.out.println("username= " + username);
    return username;
}

但是实际却不能取到用户名,错误日志如下:
错误异常情况

Spring Security 的基本组件 SecurityContextHolder

Spring Security 中最基本的组件应该是SecurityContextHolder了。这是一个工具类,只提供一些静态方法。这个工具类的目的是用来保存应用程序中当前使用人的安全上下文。

SecurityContextHolder的工作原理

缺省工作模式 MODE_THREADLOCAL

我们知道,一个应用同时可能有多个使用者,每个使用者对应不同的安全上下文,那么SecurityContextHolder是怎么保存这些安全上下文的呢 ?缺省情况下,SecurityContextHolder使用了ThreadLocal机制来保存每个使用者的安全上下文。这意味着,只要针对某个使用者的逻辑执行都是在同一个线程中进行即使不在各个方法之间以参数的形式传递其安全上下文,各个方法也能通过SecurityContextHolder工具获取到该安全上下文。只要在处理完当前使用者的请求之后注意清除ThreadLocal中的安全上下文,这种使用ThreadLocal的方式是很安全的。当然在Spring Security中,这些工作已经被Spring Security自动处理,开发人员不用担心这一点。
这里提到的SecurityContextHolder基于ThreadLocal的工作方式天然很适合Servlet Web应用,因为缺省情况下根据Servlet规范,一个Servlet request的处理不管经历了多少个Filter,自始至终都由同一个线程来完成。

注意: 这里讲的是一个Servlet request的处理不管经历了多少个Filter,自始至终都由同一个线程来完成;而对于同一个使用者的不同Servlet request,它们在服务端被处理时,使用的可不一定是同一个线程(存在由同一个线程处理的可能性但不确保)。

其他工作模式

有一些应用并不适合使用ThreadLocal模式,那么还能不能使用SecurityContextHolder了呢?答案是可以的。SecurityContextHolder还提供了其他工作模式。
比如有些应用,像Java Swing客户端应用,它就可能希望JVM中所有的线程使用同一个安全上下文。此时我们可以在启动阶段将SecurityContextHolder配置成全局策略MODE_GLOBAL。
还有其他的一些应用会有自己的线程创建,并且希望这些新建线程也能使用创建者的安全上下文。这种效果,可以通过将SecurityContextHolder配置成MODE_INHERITABLETHREADLOCAL策略达到。

使用SecurityContextHolder

获取当前用户信息

在SecurityContextHolder中保存的是当前访问者的信息。Spring Security使用一个Authentication对象来表示这个信息。一般情况下,我们都不需要创建这个对象,在登录过程中,Spring Security已经创建了该对象并帮我们放到了SecurityContextHolder中。从SecurityContextHolder中获取这个对象也是很简单的。比如,获取当前登录用户的用户名,可以这样:

String username = "";
// 获取安全上下文对象,就是那个保存在 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) {
    username = ((UserDetails) principal).getUsername();
} else {
    username = principal.toString();
}
修改SecurityContextHolder的工作模式

综上所述,SecurityContextHolder可以工作在以下三种模式之一:

  • MODE_THREADLOCAL (缺省工作模式)
  • MODE_GLOBAL
  • MODE_INHERITABLETHREADLOCAL
    修改SecurityContextHolder的工作模式有两种方法 :
    • 设置一个系统属性(system.properties) : spring.security.strategy; SecurityContextHolder会自动从该系统属性中尝试获取被设定的工作模式
    • 调用SecurityContextHolder静态方法setStrategyName(): 程序化方式主动设置工作模式的方法

解决方案

暂无

### 关于 Spring Boot 和 Vue 实现 JWT 令牌的前后端代码示例 #### 后端部分 (Spring Boot) 为了创建基于JWT的身份验证服务,在`pom.xml`文件中引入必要的依赖项[^2]: ```xml <dependencies> <!-- Spring Security --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <!-- JWT Library --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>0.11.5</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-impl</artifactId> <version>0.11.5</version> <scope>runtime</scope> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-jackson</artifactId> <version>0.11.5</version> <scope>runtime</scope> </dependency> </dependencies> ``` 配置类用于设置安全策略并定义如何处理身份验证请求。 ```java @Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests() .antMatchers("/auth/**").permitAll() // Allow all users to access authentication endpoints. .anyRequest().authenticated(); // All other requests require authentication. JwtTokenFilterAuthentication filter = new JwtTokenFilterAuthentication(); filter.setAuthenticationManager(authenticationManagerBean()); http.addFilterBefore(filter, UsernamePasswordAuthenticationFilter.class); } } ``` 编写控制器来接收登录请求,并返回带有访问令牌的响应给前端应用。 ```java @RestController @RequestMapping("/auth") public class AuthController { private final AuthenticationManagerBuilder auth; public AuthController(AuthenticationManagerBuilder auth){ this.auth=auth; } @PostMapping("/login") public ResponseEntity<?> authenticateUser(@Valid @RequestBody LoginRequest loginRequest) { try{ String username = loginRequest.getUsername(); String password = loginRequest.getPassword(); Authentication authentication = auth.getObject().authenticate( new UsernamePasswordAuthenticationToken(username,password)); SecurityContextHolder.getContext().setAuthentication(authentication); String jwt = TokenProvider.generateToken(authentication); return ResponseEntity.ok(new JwtResponse(jwt)); }catch(BadCredentialsException ex){ throw new BadCredentialsException("Invalid credentials"); } } } ``` #### 前端部分 (Vue.js) 安装 `axios` 库以便发送HTTP请求至服务器。 ```bash npm install axios ``` 在 Vuex store 中管理用户认证状态以及存储获取到的token。 ```javascript import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export default new Vuex.Store({ state: { isAuthenticated: false, token: null }, mutations: { SET_AUTHENTICATED(state, status) { state.isAuthenticated = status }, SET_TOKEN(state, token) { localStorage.setItem('jwt', JSON.stringify(token)) state.token = token } }, actions: { async login({ commit }, {username, password}) { const response = await axios.post('/api/auth/login', {username, password}) let data=response.data if(data && data.accessToken){ commit('SET_AUTHENTICATED', true) commit('SET_TOKEN',data.accessToken ) return Promise.resolve(response) }else{ return Promise.reject('Failed to log in') } } } }) ``` 组件内调用store中的action方法完成登录操作。 ```html <template> <div id="app"> <form v-on:submit.prevent="handleLogin()"> 用户名:<input type="text" name="username" v-model="credentials.username"/><br/> 密码:<input type="password" name="password" v-model="credentials.password"/><br/> <button type="submit">登陆</button> </form> </div> </template> <script> export default { data(){ return { credentials:{ username:'', password:'' } }; }, methods:{ handleLogin:function () { this.$store.dispatch('login',this.credentials).then(()=>{ console.log('Logged In'); }).catch((err)=>{ alert(err.message || "Error logging in."); }); } } }; </script> ```
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

镰刀韭菜

看在我不断努力的份上,支持我吧

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值