禁用http.csrf()后控制器中Principal为null的问题排查
看起来你遇到的问题是:为了测试REST接口临时禁用CSRF后,原本能正常获取的Principal突然变成了null。这大概率是因为禁用CSRF的同时,你的安全配置没确保认证机制依然生效,或者测试请求没正确携带认证信息。下面给你一步步的解决方案:
1. 检查并修正Security配置
首先,确保你的WebSecurityConfigurerAdapter配置中,禁用CSRF后依然开启了认证规则和合适的认证方式(比如HTTP Basic,适合REST测试)。这里给你一个正确的配置示例:
@Configuration @EnableWebSecurity public class TestSecurityConfig extends WebSecurityConfigurerAdapter { // 配置测试用的内存用户 @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() .withUser("test_user") .password("{noop}test_pass") // {noop}表示不使用密码加密,仅用于测试 .roles("USER"); } @Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() // 禁用CSRF,适配REST接口 .authorizeRequests() .anyRequest().authenticated() // 强制所有请求都需要认证 .and() .httpBasic(); // 启用HTTP Basic认证,方便测试时传递用户名密码 } }
这里的关键是:禁用CSRF后,一定要保留authorizeRequests().anyRequest().authenticated()来强制认证,同时通过httpBasic()开启简单的认证方式——REST接口通常用无状态的HTTP Basic比Session认证更合适。
2. 确保测试请求携带认证信息
如果用MockMvc做单元测试,一定要在请求中添加HTTP Basic认证信息,否则Spring Security无法识别用户,Principal自然为null。示例代码如下:
@Autowired private MockMvc mockMvc; @Test public void testPostEndpoint() throws Exception { mockMvc.perform(post("/your-rest-endpoint") .contentType(MediaType.APPLICATION_JSON) .content("{\"field\":\"value\"}") // 携带测试用户的认证信息 .with(httpBasic("test_user", "test_pass"))) .andExpect(status().isOk()) // 可以额外验证返回内容中是否包含用户名 .andExpect(jsonPath("$.username").value("test_user")); }
如果用Postman、curl等工具测试,需要在请求头中添加Authorization字段,值为Basic [base64编码的用户名:密码]。比如test_user:test_pass编码后是dGVzdF91c2VyOnRlc3RfcGFzcw==,所以请求头就是:
Authorization: Basic dGVzdF91c2VyOnRlc3RfcGFzcw==
3. 确认Controller中Principal的获取方式
确保你在Controller中正确获取Principal,推荐使用@AuthenticationPrincipal注解直接拿到用户详情,比原生Principal更方便:
@PostMapping("/your-rest-endpoint") public ResponseEntity<YourResponse> handlePostRequest( @RequestBody YourPojo requestBody, @AuthenticationPrincipal UserDetails userDetails) { // 这里可以直接拿到用户名 String username = userDetails.getUsername(); // ... 业务逻辑 return ResponseEntity.ok(new YourResponse(username)); }
当然,如果你坚持用Principal参数,只要请求已认证,它也会被正确填充:
@PostMapping("/your-rest-endpoint") public ResponseEntity<YourResponse> handlePostRequest( @RequestBody YourPojo requestBody, Principal principal) { if (principal != null) { String username = principal.getName(); // ... } // ... }
问题根源解释
为什么禁用CSRF会导致Principal为null?其实两者本身没有直接关联,但很可能是你在修改配置时,不小心移除了认证规则(比如anyRequest().authenticated()),或者原本依赖Session的认证在CSRF禁用后出现了状态丢失。而REST接口本身适合无状态的认证方式(比如HTTP Basic),所以调整配置为HTTP Basic后,就能同时满足禁用CSRF和正常获取用户信息的需求。
内容的提问来源于stack exchange,提问作者garthoid

