You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 09:06:59