Spring Boot /authenticates端点无法返回JWT,Postman报401未授权
Spring Boot JWT认证调用/authenticates返回401未授权问题
我参考2019年的教程视频开发Spring Boot JWT认证功能,程序可正常启动,但使用Postman调用/authenticates端点时,返回401未授权错误,无法获取JSON Web Token。相关代码如下:
JwtUtil.java
package com.stdbankjwtauth.authsystem.util; import java.util.Map; import java.util.function.Function; import java.util.Date; import java.util.HashMap; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Service; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; @Service public class JwtUtil { private String SECRET_KEY = "secret"; public String extractUsername(String token) { return extractClaim(token, Claims::getSubject); } public Date extractExpiration(String token) { return extractClaim(token,Claims::getExpiration); } public <T> T extractClaim(String token, Function<Claims,T> claimsResolver) { final Claims claims = extractAllClaims(token); return claimsResolver.apply(claims); } private Claims extractAllClaims(String token) { return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody(); } private boolean isTokenExpired(String token) { return extractExpiration(token).before(new Date()); } public String generateToken(UserDetails userDetails) { Map<String,Object> claims = new HashMap<>(); return createToken(claims,userDetails.getUsername()); } private String createToken(Map<String,Object> claims, String Subject) { return Jwts.builder().setClaims(claims).setSubject(Subject).setIssuedAt(new Date(System.currentTimeMillis())) .setExpiration(new Date(System.currentTimeMillis()+1000*60*60*10)) .signWith(SignatureAlgorithm.HS256,SECRET_KEY).compact(); } public boolean validateToken(String token, UserDetails userDetails){ final String username = extractUsername(token); return (username.equals(userDetails.getUsername()) && !isTokenExpired(token)); } }
AuthenticationRequest.java
package com.stdbankjwtauth.authsystem.models; public class AuthenticationRequest { private String username; private String password; public AuthenticationRequest(String Username,String Password) { this.username = Username; this.password = Password; } public String getUsername() { return username; } public void setUsername(String Username) { this.username = Username; } public String getPassword() { return password; } public void setPassword(String Password) { this.password = Password; } }
AuthenticationResponse.java
package com.stdbankjwtauth.authsystem.models; public class AuthenticationResponse { private final String jwt; public AuthenticationResponse(String Jwt) { this.jwt = Jwt; } public String getJwt() { return jwt; } }
AuthenticationEndpoint.java
package com.stdbankjwtauth.authsystem; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.BadCredentialsException; 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.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import com.stdbankjwtauth.authsystem.models.AuthenticationRequest; import com.stdbankjwtauth.authsystem.models.AuthenticationResponse; import com.stdbankjwtauth.authsystem.util.JwtUtil; @RestController public class AuthenticateEnpoint { @Autowired private AuthenticationManager authenticationManager; @Autowired private UserDetailsService userDetailsService; @Autowired private JwtUtil jwtTokenUtil; @PostMapping("/authenticates") public ResponseEntity <?> createAuthenticationToken(@RequestBody AuthenticationRequest authenticationRequest) throws Exception{ try { authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(authenticationRequest.getUsername(),authenticationRequest.getPassword()) ); }catch(BadCredentialsException e) { throw new Exception("Incorrect username or password",e); } final UserDetails userDetails = userDetailsService .loadUserByUsername(authenticationRequest.getUsername()); final String jwt = jwtTokenUtil.generateToken(userDetails); return ResponseEntity.ok(new AuthenticationResponse(jwt)); } }
SecurityConfigurer.java
package com.stdbankjwtauth.authsystem; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 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.WebSecurityConfiguration; import org.springframework.security.config.http.SessionCreationPolicy; @Configuration @EnableWebSecurity public class SecurirtyConfigurer extends WebSecurityConfiguration { protected void configure(AuthenticationManagerBuilder auth) throws Exception{ auth.getDefaultUserDetailsService(); } @Bean public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception { return http.getSharedObject(AuthenticationManagerBuilder.class) .build(); } protected void configure(HttpSecurity http) throws Exception{ http .csrf() .disable() .authorizeHttpRequests().requestMatchers("/authenticates").permitAll() .anyRequest().authenticated() .and().sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS); } }
FileBasedUserDetailsService.java
package com.stdbankjwtauth.authsystem.services; import java.nio.file.Files; import java.nio.file.Paths; import java.util.List; import java.util.ArrayList; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.factory.PasswordEncoderFactories; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.stereotype.Service; @Configuration @Service public class FileBasedUserDetailsService { private static final String FILE_PATH = "C:\\repos\\STDBank-JWT-Auth-System\\stdbank-jwt-auth\\credentials.txt"; @Bean public InMemoryUserDetailsManager userDetailsService() { List<String> lines = new ArrayList<>(); try { lines = Files.readAllLines(Paths.get(FILE_PATH)); }catch (Exception e) { e.printStackTrace(); } for (String line : lines) { String[] parts = line.split(":"); PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder(); UserDetails user = User.withUsername(parts[0]) .password(encoder.encode(parts[1])) .roles("USER") .build(); return new InMemoryUserDetailsManager(user); } throw new UsernameNotFoundException("Username not found: "+ lines); } }
Postman报错显示401未授权。
问题排查与修复
1. 安全配置类继承错误
SecurirtyConfigurer继承了WebSecurityConfiguration(Spring Security内部配置类),正确应继承WebSecurityConfigurerAdapter(适配2019年教程的Spring Security版本),否则HttpSecurity规则不会生效,导致/authenticates端点未被正确放行。同时调整AuthenticationManager的配置方式:
修复后的SecurityConfigurer.java:
package com.stdbankjwtauth.authsystem; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 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; @Configuration @EnableWebSecurity public class SecurirtyConfigurer extends WebSecurityConfigurerAdapter { @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { super.configure(auth); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() .authorizeRequests() .antMatchers("/authenticates").permitAll() .anyRequest().authenticated() .and() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS); } }
2. 用户详情服务逻辑错误
FileBasedUserDetailsService存在两个关键问题:
- 循环读取用户时,读取到第一个用户就直接返回,导致仅加载第一个用户
- 文件读取异常仅打印堆栈,未正确抛出;每次循环重复创建
PasswordEncoder
修复后的FileBasedUserDetailsService.java:
package com.stdbankjwtauth.authsystem.services; import java.nio.file.Files; import java.nio.file.Paths; import java.util.List; import org.springframework.context.annotation.Bean; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.factory.PasswordEncoderFactories; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.stereotype.Service; @Service public class FileBasedUserDetailsService { private static final String FILE_PATH = "C:\\repos\\STDBank-JWT-Auth-System\\stdbank-jwt-auth\\credentials.txt"; @Bean public UserDetailsService userDetailsService() { InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(); PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder(); List<String> lines; try { lines = Files.readAllLines(Paths.get(FILE_PATH)); } catch (Exception e) { throw new RuntimeException("Failed to read credentials file", e); } if (lines.isEmpty()) { throw new UsernameNotFoundException("No users found in credentials file"); } for (String line : lines) { String[] parts = line.split(":"); if (parts.length != 2) { continue; } UserDetails user = User.withUsername(parts[0]) .password(encoder.encode(parts[1])) .roles("USER") .build(); manager.createUser(user); } return manager; } }
3. 请求实体类缺少无参构造函数
AuthenticationRequest仅有带参构造函数,Spring使用@RequestBody反序列化JSON时需要无参构造函数,否则会导致参数绑定失败,进而引发认证错误。
修复后的AuthenticationRequest.java:
package com.stdbankjwtauth.authsystem.models; public class AuthenticationRequest { private String username; private String password; // 添加无参构造函数 public AuthenticationRequest() {} public AuthenticationRequest(String username, String password) { this.username = username; this.password = password; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } }
4. 其他注意事项
- 确保
credentials.txt文件格式正确,每行遵循用户名:密码格式,例如user1:123456 - Postman请求时使用
POST方法,请求体选择raw->JSON,传入正确的用户名和密码:
{ "username": "你的用户名", "password": "你的密码" }
- 检查Spring Security依赖版本与教程匹配,避免版本差异导致的API变化
内容的提问来源于stack exchange,提问作者Tinotenda Mutadza
相关产品推荐
相关产品推荐

