使用JWT令牌访问Spring Boot API时出现401 Unauthorized错误(Postman)
按照Dan Vega的JWT令牌授权教程开发,Postman输入有效用户名密码能正常生成Token,但使用该Token访问任意API时返回401 Unauthorized错误。
安全配置文件
import com.nimbusds.jose.jwk.JWK; import com.nimbusds.jose.jwk.JWKSet; import com.nimbusds.jose.jwk.RSAKey; import com.nimbusds.jose.jwk.source.ImmutableJWKSet; import com.nimbusds.jose.jwk.source.JWKSource; import com.nimbusds.jose.proc.SecurityContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; 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.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.core.userdetails.User; import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.jwt.JwtEncoder; import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; import org.springframework.security.oauth2.jwt.NimbusJwtEncoder; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.SecurityFilterChain; import static org.springframework.security.config.Customizer.*; @Configuration @EnableWebSecurity public class SecurityConfig { private final RsaKeyProperties rsaKeys; public SecurityConfig(RsaKeyProperties rsaKeys) { this.rsaKeys = rsaKeys; } @Bean public InMemoryUserDetailsManager bedrijf() { return new InMemoryUserDetailsManager( User.withUsername("cezar") .password("{noop}password") .authorities("read") .build() ); } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { return http .csrf(csrf -> csrf.disable()) .authorizeRequests(auth -> auth .anyRequest().authenticated() ) .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .httpBasic(withDefaults()) .build(); } @Bean JwtDecoder jwtDecoder() { return NimbusJwtDecoder.withPublicKey(rsaKeys.publicKey()).build(); } @Bean JwtEncoder jwtEncoder() { JWK jwk = new RSAKey.Builder(rsaKeys.publicKey()).privateKey(rsaKeys.privateKey()).build(); JWKSource<SecurityContext> jwks = new ImmutableJWKSet<>(new JWKSet(jwk)); return new NimbusJwtEncoder(jwks); } }
Token服务类
import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.oauth2.jwt.JwtClaimsSet; import org.springframework.security.oauth2.jwt.JwtEncoder; import org.springframework.security.oauth2.jwt.JwtEncoderParameters; import org.springframework.stereotype.Service; import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.stream.Collectors; @Service public class TokenserviceImplimentation { private final JwtEncoder encoder; public TokenserviceImplimentation(JwtEncoder encoder){ this.encoder = encoder; } //In deze methode kan fouten zitten public String generateToken(Authentication authentication){ Instant now = Instant.now(); String scope = authentication.getAuthorities().stream() .map(GrantedAuthority::getAuthority) .collect(Collectors.joining(" ")); JwtClaimsSet claims = JwtClaimsSet.builder() .issuer("self") .issuedAt(now) .expiresAt(now.plus(1, ChronoUnit.HOURS)) .subject(authentication.getName()) .claim("scope", scope) .build(); return this.encoder.encode(JwtEncoderParameters.from(claims)).getTokenValue(); } }
认证控制器
import com.Code.Pakket.management.service.TokenserviceImplimentation; import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/account") @CrossOrigin public class AcountController { private final TokenserviceImplimentation tokenserviceImplimentation; public AcountController(TokenserviceImplimentation tokenserviceImplimentation){ this.tokenserviceImplimentation = tokenserviceImplimentation; } @PostMapping("/token") public String token(Authentication authentication) { String token = tokenserviceImplimentation.generateToken(authentication); return token; } }
Maven依赖
<?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.7.4</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.Code</groupId> <artifactId>Pakket-management</artifactId> <version>0.0.1-SNAPSHOT</version> <name>Pakket-management</name> <description>Full stack application - Springboot & React</description> <properties> <java.version>19</java.version> <sonar.organization>cezarpop12</sonar.organization> <sonar.host.url>https://sonarcloud.io</sonar.host.url> <junit.jupiter.version>5.9.1</junit.jupiter.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-websocket --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-websocket</artifactId> <version>6.0.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> <version>2.7.5</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> <version>3.0.0</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-oauth2-jose</artifactId> <version>6.0.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-oauth2-resource-server</artifactId> <version>3.0.0</version> </dependency> </dependencies> </project>
目标API(首页控制器)
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import java.security.Principal; @RestController public class HomeController { @GetMapping("/") public String home(Principal principal) { return "Hello, " + principal.getName(); } }
问题修复步骤
1. 统一依赖版本
Spring Boot 2.7.4与Spring Security 6.x版本不兼容,需移除手动指定的高版本依赖,让父依赖管理版本:
<!-- 替换原有的OAuth2相关依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-oauth2-resource-server</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-oauth2-jose</artifactId> </dependency>
同时将spring-boot-configuration-processor版本改为2.7.4,保持和父版本一致。
2. 配置JWT权限转换器
Spring Security默认不会自动解析Token中的scope声明为权限,需添加转换器配置:
在SecurityConfig中新增Bean:
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter; // ... 其他导入 @Bean public JwtAuthenticationConverter jwtAuthenticationConverter() { JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter(); // 移除权限前缀,匹配你的Token中权限格式 grantedAuthoritiesConverter.setAuthorityPrefix(""); // 指定从scope claim获取权限 grantedAuthoritiesConverter.setAuthoritiesClaimName("scope"); JwtAuthenticationConverter converter = new JwtAuthenticationConverter(); converter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter); return converter; }
然后修改securityFilterChain中的oauth2ResourceServer配置:
.oauth2ResourceServer(oauth2 -> oauth2 .jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter())) )
3. 验证Token传递方式
确保Postman中通过Authorization请求头传递Token,格式为:
Bearer <生成的JWT Token>
4. 检查RSA密钥配置
确认RsaKeyProperties正确加载公钥和私钥,application.properties配置示例:
rsa.public-key=classpath:public.pem rsa.private-key=classpath:private.pem
确保公钥、私钥文件存在于resources目录,且为合法PEM格式。
内容的提问来源于stack exchange,提问作者Cezar P
相关产品推荐
相关产品推荐

