Spring Security JWT认证返回401 Unauthorized问题求助
我是Spring Security JWT认证新手,尝试通过认证控制器实现登录,但用Postman测试/authenticate接口时一直收到401 Unauthorized响应。以下是相关代码文件:
WebSecurityConfig.java
package com.cognizant.auth.microservice.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; @Autowired private UserDetailsService jwtUserDetailsService; @Autowired private JwtRequestFilter jwtRequestFilter; @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { // configure AuthenticationManager so that it knows from where to load // user for matching credentials // Use BCryptPasswordEncoder auth.userDetailsService(jwtUserDetailsService).passwordEncoder(passwordEncoder()); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Override protected void configure(HttpSecurity httpSecurity) throws Exception { // We don't need CSRF for this example httpSecurity.csrf().disable() // dont authenticate this particular request .authorizeRequests().antMatchers("/authenticate").permitAll().antMatchers(HttpMethod.OPTIONS, "/**") .permitAll(). // all other requests need to be authenticated anyRequest().authenticated().and(). // make sure we use stateless session; session won't be used to // store user's state. exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and().sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS); // Add a filter to validate the tokens with every request httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class); } }
AuthController.java
package com.cognizant.auth.microservice.controller; import java.util.Objects; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.DisabledException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import com.cognizant.auth.microservice.config.JwtTokenUtil; import com.cognizant.auth.microservice.model.JwtResponse; import com.cognizant.auth.microservice.model.LoginRequest; @RestController @CrossOrigin public class AuthController { @Autowired private AuthenticationManager authenticationManager; @Autowired private JwtTokenUtil jwtTokenUtil; @Autowired private UserDetailsService jwtInMemoryUserDetailsService; @RequestMapping(value = "/authenticate", method = RequestMethod.POST) public ResponseEntity<?> createAuthenticationToken(@RequestBody LoginRequest authenticationRequest) throws Exception { System.out.println("Inside"); authenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword()); final UserDetails userDetails = jwtInMemoryUserDetailsService .loadUserByUsername(authenticationRequest.getUsername()); String token; token = jwtTokenUtil.generateToken(userDetails); return ResponseEntity.ok(new JwtResponse(token)); } private void authenticate(String username, String password) throws Exception { Objects.requireNonNull(username); Objects.requireNonNull(password); try { authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password)); } catch (DisabledException e) { throw new Exception("USER_DISABLED", e); } catch (BadCredentialsException e) { throw new Exception("INVALID_CREDENTIALS", e); } } }
pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.6.6</version> <relativePath /> <!-- lookup parent from repository --> </parent> <groupId>com.cognizant.authMicroservice</groupId> <artifactId>authMicroservice</artifactId> <version>0.0.1-SNAPSHOT</version> <name>AuthorizationMicroservice</name> <description>Authorization Microservice that taking care to login and session</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>3.0.0</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>3.0.0</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
可能的问题点及排查方案
UserDetailsService注入不一致:WebSecurityConfig中注入的是
jwtUserDetailsService,但AuthController中注入的是jwtInMemoryUserDetailsService,需确保两者指向同一个Bean。若自定义UserDetailsService的Bean名称为jwtUserDetailsService,则AuthController的注入需统一,否则会导致AuthenticationManager与控制器使用的用户数据源不一致,无法正确验证。密码加密匹配错误:确认数据库中存储的用户密码是通过
BCryptPasswordEncoder加密后的字符串。若存储明文密码,AuthenticationManager验证时会自动对输入密码做BCrypt加密对比,必然不匹配,抛出BadCredentialsException导致401。可先通过passwordEncoder().encode("你的密码")生成加密串替换数据库密码后测试。JwtRequestFilter错误拦截登录请求:检查
JwtRequestFilter的逻辑,若过滤器在Security链之前就尝试验证Token,而登录请求无Token,会直接返回401。需在过滤器中对/authenticate路径跳过验证:@Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { String requestURI = request.getRequestURI(); if ("/authenticate".equals(requestURI)) { chain.doFilter(request, response); return; } // 后续Token验证逻辑 }请求格式不匹配:Postman发送请求时需确保
Content-Type为application/json,且请求体为正确JSON格式:{ "username": "test", "password": "test123" }若格式错误,
@RequestBody LoginRequest无法正确绑定参数,导致username/password为null,触发Objects.requireNonNull异常,最终返回401。JwtAuthenticationEntryPoint异常触发时机错误:临时注释
exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint),测试是否能正常访问/authenticate。若能正常访问,说明该入口点在不该触发的场景下返回了401,需检查其commence方法逻辑。
内容的提问来源于stack exchange,提问作者vishwa_001

