我已经从Spring资源服务器依赖关系中实现了JWT令牌授权和身份验证。
@Configuration
@RequiredArgsConstructor
@EnableWebSecurity
public class WebSecurityConfig {
@Value("${app.chat.jwt.public.key}")
private RSAPublicKey publicKey;
@Value("${app.chat.jwt.private.key}")
private RSAPrivateKey privateKey;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.exceptionHandling(
exceptions ->
exceptions
.authenticationEntryPoint(new BearerTokenAuthenticationEntryPoint())
.accessDeniedHandler(new BearerTokenAccessDeniedHandler()));
http.authorizeHttpRequests()
.requestMatchers("/auth/sign-in").permitAll()
.requestMatchers("/auth/sign-up").permitAll()
.anyRequest().authenticated()
.and()
.httpBasic(Customizer.withDefaults())
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
return http.build();
}
@SneakyThrows
@Bean
public JwtEncoder jwtEncoder() {
var jwk = new RSAKey.Builder(publicKey).privateKey(privateKey).build();
var jwks = new ImmutableJWKSet<>(new JWKSet(jwk));
return new NimbusJwtEncoder(jwks);
}
@SneakyThrows
@Bean
public JwtDecoder jwtDecoder() {
return NimbusJwtDecoder.withPublicKey(publicKey).build();
}
@Bean
public JwtAuthenticationConverter jwtAuthenticationConverter() {
var jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName("roles");
jwtGrantedAuthoritiesConverter.setAuthorityPrefix("ROLE_");
var jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);
return jwtAuthenticationConverter;
}
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source =
new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(
AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
}
它运行良好。我有AuthController,在其中实现了用于登录、注册和刷新令牌的端点。在每个端点中,我返回一个带有访问令牌和刷新令牌的响应。下面是控制器:
@RestController
@RequestMapping("/auth")
@RequiredArgsConstructor
public class AuthController {
private final JwtTokenService tokenService;
private final AuthenticationManager authManager;
private final UserDetailsService usrDetailsService;
private final UserService userService;
record LoginRequest(String username, String password) {}
@PostMapping("/sign-in")
public TokensResponse login(@RequestBody LoginRequest request) {
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(request.username, request.password);
authManager.authenticate(authenticationToken);
var user = (User) usrDetailsService.loadUserByUsername(request.username);
String accessToken = tokenService.generateAccessToken(user);
String refreshToken = tokenService.generateRefreshToken(user);
return new TokensResponse(accessToken, refreshToken);
}
record SignUpRequest(String username, String password){}
@PostMapping("/sign-up")
public TokensResponse signUp(@RequestBody SignUpRequest signUpRequest) {
User registeredUser = userService.register(new AuthRequestDto(signUpRequest.username(), signUpRequest.password()));
String accessToken = tokenService.generateAccessToken(registeredUser);
String refreshToken = tokenService.generateRefreshToken(registeredUser);
return new TokensResponse(accessToken, refreshToken);
}
@PreAuthorize("hasRole('REFRESH_TOKEN')")
@GetMapping("/token/refresh")
public TokensResponse refreshToken(HttpServletRequest request) {
String headerAuth = request.getHeader("Authorization");
String previousRefreshToken = headerAuth.substring(7);
String username = tokenService.parseToken(previousRefreshToken);
var user = (User) usrDetailsService.loadUserByUsername(username);
String accessToken = tokenService.generateAccessToken(user);
String refreshToken = tokenService.generateRefreshToken(user);
return new TokensResponse(accessToken, refreshToken);
}
record TokensResponse(String accessToken, String refreshToken) {}
}
下面是TokenService类,我在其中生成这些令牌:
@Service
@RequiredArgsConstructor
public class JwtTokenServiceImpl implements JwtTokenService {
private final JwtEncoder jwtEncoder;
@Override
public String generateAccessToken(User user) {
Instant now = Instant.now();
String scope = user.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.joining(" "));
JwtClaimsSet claims = JwtClaimsSet.builder()
.issuer("self")
.issuedAt(now)
.expiresAt(now.plus(2, ChronoUnit.MINUTES))
.subject(user.getUsername())
.claim("scope", scope)
.build();
return this.jwtEncoder.encode(JwtEncoderParameters.from(claims)).getTokenValue();
}
@Override
public String generateRefreshToken(User user) {
Instant now = Instant.now();
String scope = "ROLE_REFRESH_TOKEN";
JwtClaimsSet claims = JwtClaimsSet.builder()
.issuer("self")
.issuedAt(now)
.expiresAt(now.plus(10, ChronoUnit.MINUTES))
.subject(user.getUsername())
.claim("scope", scope)
.build();
return this.jwtEncoder.encode(JwtEncoderParameters.from(claims)).getTokenValue();
}
@Override
public String parseToken(String token) {
try {
SignedJWT decodedJWT = SignedJWT.parse(token);
return decodedJWT.getJWTClaimsSet().getSubject();
} catch (ParseException e) {
e.printStackTrace();
}
return null;
}
}
我想做的是限制刷新令牌仅用于刷新端点。因为如果可以对所有端点使用刷新令牌,那么拥有短期实时访问令牌又有什么意义呢?我已经尝试为刷新令牌提供REFRESH_TOKEN范围,并为刷新令牌端点添加了@PreAuthorize("hasRole('REFRESH_TOKEN')")
注解。但它不起作用(我仍然可以发送访问令牌来刷新端点并获取新令牌),因为Spring不会从令牌中查找声明,他只是根据令牌中的用户名从数据库中加载用户,并在此处检查其角色。
请建议我如何才能使刷新令牌仅限于一个端点。也将是伟大的,使他一次性使用,但似乎我需要存储令牌的地方。
1条答案
按热度按时间wrrgggsh1#
来源https://www.rfc-editor.org/rfc/rfc6749#section-6
刷新令牌影响客户端范围(这意味着所有终结点)。因此,您的预期不可行。