代码版本信息
Spring Boot 2.7.10
spring-security-oauth2-resource-server 5.7.7
大致过程
BearerTokenAuthenticationFilter
过滤器会从 HTTP 请求的Authorization
头中提取 Bearer Token,将提取到的 Token 包装成BearerTokenAuthenticationToken
认证对象交给 Spring Security 的认证管理器 (AuthenticationManager
) 进行处理。- 如果配置了 JWT 认证,
BearerTokenAuthenticationFilter
会使用JwtAuthenticationProvider
作为认证提供者,来验证和解析 JWT Token。而JwtAuthenticationProvider
会使用一个JwtDecoder
来解析和验证 Token。 - 在
application.yml
中,如果配置了Spring Security 资源服务Resource Server
的issuer-uri
,那么 Spring Security 会使用这个 URI 自动发现授权服务器的 JWKS 端点。并通过JwtDecoder
的实现类NimbusJwtDecoder
构建一个 JWK Set URL,通过这个 URL 向授权服务器请求 JWKS(JSON Web Key Set),获取公钥用于验证 JWT 的签名。 NimbusJwtDecoder
会使用获取的密钥验证 JWT 的签名,检查 JWT 的有效性(如过期时间、受众等)。如果 JWT 验证成功,认证通过,客户端即可访问受保护的资源。
配置issuer-uri作用
目的和作用
-
确保JWT的可信来源:
issuer-uri
表示JWT签发者的URI。配置此URI可以确保资源服务器仅接受由该特定签发者签发的JWT。这有助于防止来自不可信来源的令牌被接受和使用。- 在验证过程中,资源服务器会检查JWT的
iss
(issuer)声明,确保其与配置的issuer-uri
匹配。如果不匹配,验证将失败。
-
自动发现和获取JWK集:
- 配置
issuer-uri
后,资源服务器可以自动从签发者的OIDC(OpenID Connect)发现端点或OAuth 2.0授权服务器元数据端点获取JWK集。 - JWK集包含用于验证JWT签名的公钥信息。资源服务器会定期从该端点获取最新的JWK集,以确保验证过程中使用的是最新的密钥。
- 配置
配置示例
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: http://127.0.0.1:9000
在上述配置中:
issuer-uri
设置为https://example.com/issuer
,表示资源服务器将从该URI获取JWT签发者信息和JWK集。- 当资源服务器接收到一个JWT时,它会检查该JWT的签发者声明(
iss
)是否与配置的issuer-uri
匹配,并从该URI获取用于验证的JWK集。
工作机制
-
JWT签发者验证:
- 当资源服务器接收到JWT时,它会检查JWT中的
iss
声明是否匹配issuer-uri
。 - 例如,如果
issuer-uri
配置为https://example.com/issuer
,JWT的iss
声明也必须是https://example.com/issuer
。
- 当资源服务器接收到JWT时,它会检查JWT中的
-
JWK集获取和使用:
- 资源服务器从
issuer-uri
的OIDC发现端点获取JWK集(例如,https://example.com/issuer/.well-known/openid-configuration
)。 - 获取的JWK集包含用于验证JWT签名的公钥。资源服务器会使用这些公钥来验证JWT的签名,确保JWT未被篡改且来自可信的签发者。
- 资源服务器从
总结
配置issuer-uri
可以确保JWT的来源可信,并自动获取和更新验证所需的公钥信息,从而增强了安全性和可靠性。这是确保资源服务器只接受合法、未篡改的JWT的重要步骤。
过滤器源码解析
BearerTokenAuthenticationFilter
是 Spring Security 用于处理 Token 认证的过滤器。它扩展了 OncePerRequestFilter
,确保在一次请求过程中只执行一次。这个过滤器从请求中提取 Bearer Token,然后尝试对令牌进行认证。
主要职责
- 提取 Bearer Token:从请求中解析出 Bearer Token。
- 创建认证请求:用提取的 Token 创建一个认证请求。
- 尝试认证:使用
AuthenticationManager
尝试认证请求。 - 处理认证结果:根据认证结果设置安全上下文或处理认证失败。
简单来说,该过滤器会获取客户端资源请求中的token,并验证其有效性,如果有效就允许客户端访问资源。
详细解析源码
构造函数
public BearerTokenAuthenticationFilter(AuthenticationManagerResolver<HttpServletRequest> authenticationManagerResolver) {
Assert.notNull(authenticationManagerResolver, "authenticationManagerResolver cannot be null");
this.authenticationManagerResolver = authenticationManagerResolver;
}
public BearerTokenAuthenticationFilter(AuthenticationManager authenticationManager) {
Assert.notNull(authenticationManager, "authenticationManager cannot be null");
this.authenticationManagerResolver = (request) -> authenticationManager;
}
-
BearerTokenAuthenticationFilter
接受一个AuthenticationManagerResolver
或AuthenticationManager
。这两个构造函数允许使用不同的方式来解析AuthenticationManager
。
doFilterInternal
方法
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String token;
try {
//解析 Token
//使用DefaultBearerTokenResolver从请求中获取token,分别从请求头及请求域中获取,只能在其中一个包含
token = this.bearerTokenResolver.resolve(request);
} catch (OAuth2AuthenticationException invalid) {
this.logger.trace("Sending to authentication entry point since failed to resolve bearer token", invalid);
//解析失败处理
this.authenticationEntryPoint.commence(request, response, invalid);
return;
}
//如果没有找到 Token,直接调用过滤器链的下一个过滤器
if (token == null) {
this.logger.trace("Did not process request since did not find bearer token");
filterChain.doFilter(request, response);
return;
}
//根据token创建认证对象,并设置认证详情,即设置详细信息
BearerTokenAuthenticationToken authenticationRequest = new BearerTokenAuthenticationToken(token);
authenticationRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
try {
//获取Manager用于验证认证对象。如果没有单独配置,默认情况下返回`ProviderManager`
AuthenticationManager authenticationManager = this.authenticationManagerResolver.resolve(request);
Authentication authenticationResult = authenticationManager.authenticate(authenticationRequest);
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(authenticationResult);
SecurityContextHolder.setContext(context);
this.securityContextRepository.saveContext(context, request, response);
if (this.logger.isDebugEnabled()) {
this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", authenticationResult));
}
filterChain.doFilter(request, response);
} catch (AuthenticationException failed) {
SecurityContextHolder.clearContext();
this.logger.trace("Failed to process authentication request", failed);
this.authenticationFailureHandler.onAuthenticationFailure(request, response, failed);
}
}
解析 Token
token = this.bearerTokenResolver.resolve(request);
默认使用 DefaultBearerTokenResolver
从请求中提取 Bearer Token。如果解析失败,调用 AuthenticationEntryPoint
处理异常。
Token 不存在
if (token == null) {
this.logger.trace("Did not process request since did not find bearer token");
filterChain.doFilter(request, response);
return;
}
如果没有找到 Token,直接调用过滤器链的下一个过滤器。
创建认证对象
BearerTokenAuthenticationToken authenticationRequest = new BearerTokenAuthenticationToken(token);
authenticationRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
使用 Token 创建一个 BearerTokenAuthenticationToken
认证对象,并设置认证详情。
尝试认证
AuthenticationManager authenticationManager = this.authenticationManagerResolver.resolve(request);
Authentication authenticationResult = authenticationManager.authenticate(authenticationRequest);
resolve方法获取AuthenticationManager
,用于认证上面的认证对象 BearerTokenAuthenticationToken
,如果配置类没有指定默认返回ProviderManager
,使用委托模式调用authenticate
方法进行认证
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
BearerTokenAuthenticationToken bearer = (BearerTokenAuthenticationToken) authentication;
//通过jwt解析器解析token
Jwt jwt = getJwt(bearer);
AbstractAuthenticationToken token = this.jwtAuthenticationConverter.convert(jwt);
token.setDetails(bearer.getDetails());
this.logger.debug("Authenticated token");
return token;
}
getJwt
方法中解析Token
private Jwt getJwt(BearerTokenAuthenticationToken bearer) {
try {
//使用NimbusJwtDecoder实现类
return this.jwtDecoder.decode(bearer.getToken());
}
catch (BadJwtException failed) {
this.logger.debug("Failed to authenticate since the JWT was invalid");
throw new InvalidBearerTokenException(failed.getMessage(), failed);
}
catch (JwtException failed) {
throw new AuthenticationServiceException(failed.getMessage(), failed);
}
}
解析器装配
OAuth2ResourceServerJwtConfiguration
通过内部类JwtDecoderConfiguration
的jwtDecoderByIssuerUri
方法进行配置:
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(JwtDecoder.class)
static class JwtDecoderConfiguration {
//对应配置文件spring.security.oauth2.resourceserver配置
private final OAuth2ResourceServerProperties.Jwt properties;
//...........省略源码
@Bean
@Conditional(IssuerUriCondition.class)
SupplierJwtDecoder jwtDecoderByIssuerUri() {
return new SupplierJwtDecoder(() -> {
String issuerUri = this.properties.getIssuerUri();
//调用JwtDecoderProviderConfigurationUtils的getConfiguration(String issuer, URI... uris)方法,获取授权服务元数据端点信息,包含jwks_uri等,依靠端点信息创建出NimbusJwtDecoder
NimbusJwtDecoder jwtDecoder = JwtDecoders.fromIssuerLocation(issuerUri);
jwtDecoder.setJwtValidator(getValidators(() -> JwtValidators.createDefaultWithIssuer(issuerUri)));
return jwtDecoder;
});
}
}
会读取如下yaml
配置信息
spring:
security:
oauth2:
resourceserver:
jwt:
#项目启动初始化时,在JwtDecoderProviderConfigurationUtils的getConfiguration方法处,发起http://127.0.0.1:9000/.well-known/openid-configuration请求,获取元数据断点信息,后续用于token认证
#在JwtDecoders的withProviderConfiguration方法中,为NimbusJwtDecoder赋予jwks_uri
issuer-uri: http://127.0.0.1:9000
可配置项包括:
spring.security.oauth2.resourceserver.jwt.jwk-set-uri
:用于验证 JWT 令牌的 JSON Web Key (JWK) URI。spring.security.oauth2.resourceserver.jwt.issuer-uri
:值为OAuth 2.0 授权服务器ip加端口,在OAuth2ResourceServerJwtConfiguration配置类的jwtDecoderByIssuerUri方法处,根据issuer-uri配置了NimbusJwtDecoder解析器spring.security.oauth2.resourceserver.jwt.public-key-location
:包含用于验证 JWT 的公钥的文件的位置。spring.security.oauth2.resourceserver.jwt.jws-algorithms
:用于验证数字签名的 JSON Web 算法列表,默认为RS256
。spring.security.oauth2.resourceserver.jwt.audiences
:标识 JWT 预期接收者的受众列表。
通过内部的如下代码进行:
@Bean
@Conditional(IssuerUriCondition.class)
SupplierJwtDecoder jwtDecoderByIssuerUri() {
return new SupplierJwtDecoder(() -> {
String issuerUri = this.properties.getIssuerUri();
//调用JwtDecoderProviderConfigurationUtils的getConfiguration(String issuer, URI... uris)方法,获取授权服务元数据端点信息,包含jwks_uri等,依靠端点信息创建出NimbusJwtDecoder
NimbusJwtDecoder jwtDecoder = JwtDecoders.fromIssuerLocation(issuerUri);
jwtDecoder.setJwtValidator(getValidators(() -> JwtValidators.createDefaultWithIssuer(issuerUri)));
return jwtDecoder;
});
}
调用的JwtDecoders.fromIssuerLocation
方法代码:
public static <T extends JwtDecoder> T fromIssuerLocation(String issuer) {
Assert.hasText(issuer, "issuer cannot be empty");
//1.通过issuer配置的地址发起请求,获得元数据端点数据
Map<String, Object> configuration = JwtDecoderProviderConfigurationUtils
.getConfigurationForIssuerLocation(issuer);
//2.配置解析器NimbusJwtDecoder,为其指定验证类,用于JWTtoken验证
return (T) withProviderConfiguration(configuration, issuer);
}
-
请求元数据端点,
JwtDecoderProviderConfigurationUtils
的getConfigurationForIssuerLocation
方法会再通过getConfiguration
方法发起请求获取元数据信息(截取源码片段):static Map<String, Object> getConfigurationForIssuerLocation(String issuer) { URI uri = URI.create(issuer); //调用下面的getConfiguration return getConfiguration(issuer, oidc(uri), oidcRfc8414(uri), oauth(uri)); } private static Map<String, Object> getConfiguration(String issuer, URI... uris) { String errorMessage = "Unable to resolve the Configuration with the provided Issuer of " + "\"" + issuer + "\""; for (URI uri : uris) { try { //向授权服务发起请求 RequestEntity<Void> request = RequestEntity.get(uri).build(); ResponseEntity<Map<String, Object>> response = rest.exchange(request, STRING_OBJECT_MAP); Map<String, Object> configuration = response.getBody(); Assert.isTrue(configuration.get("jwks_uri") != null, "The public JWK set URI must not be null"); return configuration; } catch (IllegalArgumentException ex) { throw ex; } catch (RuntimeException ex) { if (!(ex instanceof HttpClientErrorException && ((HttpClientErrorException) ex).getStatusCode().is4xxClientError())) { throw new IllegalArgumentException(errorMessage, ex); } // else try another endpoint } } throw new IllegalArgumentException(errorMessage); }
-
根据上一步获取的信息,配置解析器
NimbusJwtDecoder
。JwtDecoders.withProviderConfiguration
内部:private static JwtDecoder withProviderConfiguration(Map<String, Object> configuration, String issuer) { JwtDecoderProviderConfigurationUtils.validateIssuer(configuration, issuer); //创建JwtTimestampValidator与JwtIssuerValidator作为NimbusJwtDecoder的验证器 OAuth2TokenValidator<Jwt> jwtValidator = JwtValidators.createDefaultWithIssuer(issuer); String jwkSetUri = configuration.get("jwks_uri").toString(); //创建NimbusJwtDecoder NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder //设置验证 JWT 令牌的url .withJwkSetUri(jwkSetUri) //配置支持的 JWS 算法,用来验证 JWT 的签名 .jwtProcessorCustomizer(JwtDecoderProviderConfigurationUtils::addJWSAlgorithms) .build(); jwtDecoder.setJwtValidator(jwtValidator); return jwtDecoder; }
public static OAuth2TokenValidator<Jwt> createDefaultWithIssuer(String issuer) { List<OAuth2TokenValidator<Jwt>> validators = new ArrayList<>(); validators.add(new JwtTimestampValidator()); validators.add(new JwtIssuerValidator(issuer)); return new DelegatingOAuth2TokenValidator<>(validators); }
验证器添加目的是:
NimbusJwtDecoder
的decode
方法调用validateJwt
方法,解析token为JWT,并使用JwtIssuerValidator
对token进行验证:@Override public Jwt decode(String token) throws JwtException { //将token字符串转为JWT,通过token字符串头部解析出不同的JWT: // Algorithm.NONE:无签名的 JWT,返回 PlainJWT。 // JWSAlgorithm:签名的 JWT,返回 SignedJWT。 // JWEAlgorithm:加密的 JWT,返回 EncryptedJWT。 // 其他算法类型则抛出异常 JWT jwt = parse(token); //无签名的 JWT不支持 if (jwt instanceof PlainJWT) { this.logger.trace("Failed to decode unsigned token"); throw new BadJwtException("Unsupported algorithm of " + jwt.getHeader().getAlgorithm()); } Jwt createdJwt = createJwt(token, jwt); // 将token转化的JWT进行验证 return validateJwt(createdJwt); }
JwtIssuerValidator
内部又使用JwtClaimValidator
验证,由其构造方法决定:public JwtIssuerValidator(String issuer) { Assert.notNull(issuer, "issuer cannot be null"); Predicate<Object> testClaimValue = (claimValue) -> (claimValue != null) && issuer.equals(claimValue.toString()); this.validator = new JwtClaimValidator<>(JwtClaimNames.ISS, testClaimValue); }
调用
JwtClaimValidator
的验证方法validate
时,其使用的this.test.test
方法,实际为JwtIssuerValidator
构造方法的函数式接口testClaimValue,对比的就是JWT中的claimValue
是否与配置的issuer
一致,一致则认证成功@Override public OAuth2TokenValidatorResult validate(Jwt token) { Assert.notNull(token, "token cannot be null"); T claimValue = token.getClaim(this.claim); //对比JWT中的`claimValue`是否与配置的`issuer`一致 if (this.test.test(claimValue)) { return OAuth2TokenValidatorResult.success(); } this.logger.debug(this.error.getDescription()); return OAuth2TokenValidatorResult.failure(this.error); }
调用
JwtDecoderProviderConfigurationUtils::addJWSAlgorithms
方法,方法参数ConfigurableJWTProcessor
为其实现类DefaultJWTProcessor
:static <C extends SecurityContext> void addJWSAlgorithms(ConfigurableJWTProcessor<C> jwtProcessor) { JWSKeySelector<C> selector = jwtProcessor.getJWSKeySelector(); if (selector instanceof JWSVerificationKeySelector) { //使用RemoteJWKSet实现类,用于从授权服务远程获取JWK(密钥)来验证JWT签名 JWKSource<C> jwkSource = ((JWSVerificationKeySelector<C>) selector).getJWKSource(); Set<JWSAlgorithm> algorithms = getJWSAlgorithms(jwkSource); selector = new JWSVerificationKeySelector<>(algorithms, jwkSource); jwtProcessor.setJWSKeySelector(selector); } }
此代码的作用是在
NimbusJwtDecoder
进行token解析时,使用RemoteJWKSet
从远程授权服务中获取最新的、有效的JWK,以便用于JWT的签名验证。
JWT认证
NimbusJwtDecoder
的decode
方法
@Override
public Jwt decode(String token) throws JwtException {
//将token字符串转为JWT,通过token字符串头部解析出不同的JWT:
// Algorithm.NONE:无签名的 JWT,返回 PlainJWT。
// JWSAlgorithm:签名的 JWT,返回 SignedJWT。
// JWEAlgorithm:加密的 JWT,返回 EncryptedJWT。
// 其他算法类型则抛出异常
JWT jwt = parse(token);
//无签名的 JWT 不支持
if (jwt instanceof PlainJWT) {
this.logger.trace("Failed to decode unsigned token");
throw new BadJwtException("Unsupported algorithm of " + jwt.getHeader().getAlgorithm());
}
//验证JWT签名、提取头部信息和声明,并创建一个`Jwt`对象
Jwt createdJwt = createJwt(token, jwt);
// 将token转化的Jwt进行验证
return validateJwt(createdJwt);
}
- parse方法的JWT:
com.nimbusds.jwt.JWT
用于底层JWT操作,通常与低级别的JWT操作(如验证签名、加密/解密)有关。 - createJwt方法的Jwt:
org.springframework.security.oauth2.jwt.Jwt
属于Spring Security,集成在Spring Security框架中,处理Spring Security中的JWT认证和授权。
核心功能是通过验证JWT签名、提取头部信息和声明,最终创建一个Jwt
对象,并处理在此过程中可能出现的各种异常。
private Jwt createJwt(String token, JWT parsedJwt) {
try {
// 验证签名, jwtProcessor使用的实现类为DefaultJWTProcessor
// DefaultJWTProcessor使用了RemoteJWKSet实现类,用于从授权服务远程获取JWK(密钥)来验证JWT签名
JWTClaimsSet jwtClaimsSet = this.jwtProcessor.process(parsedJwt, null);
// 提取头部和声明
Map<String, Object> headers = new LinkedHashMap<>(parsedJwt.getHeader().toJSONObject());
Map<String, Object> claims = this.claimSetConverter.convert(jwtClaimsSet.getClaims());
// 创建Jwt对象
return Jwt.withTokenValue(token)
.headers((h) -> h.putAll(headers))
.claims((c) -> c.putAll(claims))
.build();
} catch (RemoteKeySourceException ex) {
this.logger.trace("Failed to retrieve JWK set", ex);
if (ex.getCause() instanceof ParseException) {
throw new JwtException(String.format(DECODING_ERROR_MESSAGE_TEMPLATE, "Malformed Jwk set"), ex);
}
throw new JwtException(String.format(DECODING_ERROR_MESSAGE_TEMPLATE, ex.getMessage()), ex);
} catch (JOSEException ex) {
this.logger.trace("Failed to process JWT", ex);
throw new JwtException(String.format(DECODING_ERROR_MESSAGE_TEMPLATE, ex.getMessage()), ex);
} catch (Exception ex) {
this.logger.trace("Failed to process JWT", ex);
if (ex.getCause() instanceof ParseException) {
throw new BadJwtException(String.format(DECODING_ERROR_MESSAGE_TEMPLATE, "Malformed payload"), ex);
}
throw new BadJwtException(String.format(DECODING_ERROR_MESSAGE_TEMPLATE, ex.getMessage()), ex);
}
}
这段代码是用来从给定的JWT字符串创建一个Jwt
对象。它通过以下步骤来实现这个过程:
- 验证签名:
JWTClaimsSet jwtClaimsSet = this.jwtProcessor.process(parsedJwt, null);
- 通过
jwtProcessor
处理解析后的JWT对象(parsedJwt
)来验证签名并提取JWT声明集(jwtClaimsSet
)。 jwtProcessor
使用已配置的JWK(JSON Web Key)源来验证JWT的签名。
- 提取头部和声明:
Map<String, Object> headers = new LinkedHashMap<>(parsedJwt.getHeader().toJSONObject());
- 从解析后的JWT对象中提取头部信息并转换为一个
Map
。 Map<String, Object> claims = this.claimSetConverter.convert(jwtClaimsSet.getClaims());
- 使用
claimSetConverter
将JWT声明集中的声明转换为一个Map
。
- 创建Jwt对象:
return Jwt.withTokenValue(token).headers((h) -> h.putAll(headers)).claims((c) -> c.putAll(claims)).build();
- 使用提取的头部信息和声明创建一个
Jwt
对象。这个Jwt
对象包含了原始的JWT字符串、头部信息和声明。
- 处理异常:
- 如果在验证或处理JWT的过程中发生异常,会捕获并处理这些异常。
- 异常类型包括
RemoteKeySourceException
、JOSEException
和通用的Exception
。 - 根据具体的异常类型,记录调试信息并抛出相应的
JwtException
或BadJwtException
。
关键点
- jwtProcessor:用于处理JWT并验证其签名。
- claimSetConverter:用于将JWT声明集转换为一个Map。
- 异常处理:通过不同的catch块处理不同类型的异常,并记录相关的调试信息。
validateJwt
方法代码:
private Jwt validateJwt(Jwt jwt) {
OAuth2TokenValidatorResult result = this.jwtValidator.validate(jwt);
if (result.hasErrors()) {
Collection<OAuth2Error> errors = result.getErrors();
String validationErrorString = getJwtValidationExceptionMessage(errors);
throw new JwtValidationException(validationErrorString, errors);
}
return jwt;
}
this.jwtValidator.validate
使用JwtIssuerValidator
对token进行验证,JwtIssuerValidator
内部又使用JwtClaimValidator
验证,由其构造方法决定:
public JwtIssuerValidator(String issuer) {
Assert.notNull(issuer, "issuer cannot be null");
Predicate<Object> testClaimValue = (claimValue) -> (claimValue != null) && issuer.equals(claimValue.toString());
this.validator = new JwtClaimValidator<>(JwtClaimNames.ISS, testClaimValue);
}
调用JwtClaimValidator
的验证方法validate
时,其使用的this.test.test
方法,实际为JwtIssuerValidator
构造方法的函数式接口testClaimValue,对比的就是JWT中的claimValue
是否与配置的issuer
一致,一致则认证成功
@Override
public OAuth2TokenValidatorResult validate(Jwt token) {
Assert.notNull(token, "token cannot be null");
T claimValue = token.getClaim(this.claim);
//对比JWT中的`claimValue`是否与配置的`issuer`一致
if (this.test.test(claimValue)) {
return OAuth2TokenValidatorResult.success();
}
this.logger.debug(this.error.getDescription());
return OAuth2TokenValidatorResult.failure(this.error);
}
JWT转换
NimbusJwtDecoder
的decode
方法解析成功后,将Jwt返回到JwtAuthenticationProvider
,并使用convert
将jwt转为AbstractAuthenticationToken
:
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
BearerTokenAuthenticationToken bearer = (BearerTokenAuthenticationToken) authentication;
Jwt jwt = getJwt(bearer);
//转换方法
AbstractAuthenticationToken token = this.jwtAuthenticationConverter.convert(jwt);
token.setDetails(bearer.getDetails());
this.logger.debug("Authenticated token");
return token;
}
//解析成功返回Jwt到上面的Jwt jwt = getJwt(bearer);
private Jwt getJwt(BearerTokenAuthenticationToken bearer) {
try {
return this.jwtDecoder.decode(bearer.getToken());
}
catch (BadJwtException failed) {
this.logger.debug("Failed to authenticate since the JWT was invalid");
throw new InvalidBearerTokenException(failed.getMessage(), failed);
}
catch (JwtException failed) {
throw new AuthenticationServiceException(failed.getMessage(), failed);
}
}
@Override
public final AbstractAuthenticationToken convert(Jwt jwt) {
//从 JWT 中提取权限。获取scope权限,将其转换为 Spring Security 的 GrantedAuthority 集合。
Collection<GrantedAuthority> authorities = extractAuthorities(jwt);
//获取 JWT 中特定声明(如 sub 或 user_name)的值,作为主体(principal)
String principalClaimValue = jwt.getClaimAsString(this.principalClaimName);
//创建并返回一个 JwtAuthenticationToken 对象。该对象包含原始 JWT、提取的权限和主体声明值。
return new JwtAuthenticationToken(jwt, authorities, principalClaimValue);
}
保存上下文
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
//............
try {
AuthenticationManager authenticationManager = this.authenticationManagerResolver.resolve(request);
//`JwtAuthenticationProvider`验证完成后将token对象返回
Authentication authenticationResult = authenticationManager.authenticate(authenticationRequest);
//保存authenticationResult到上下文
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(authenticationResult);
SecurityContextHolder.setContext(context);
this.securityContextRepository.saveContext(context, request, response);
if (this.logger.isDebugEnabled()) {
this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", authenticationResult));
}
filterChain.doFilter(request, response);
} catch (AuthenticationException failed) {
SecurityContextHolder.clearContext();
this.logger.trace("Failed to process authentication request", failed);
this.authenticationFailureHandler.onAuthenticationFailure(request, response, failed);
}
}
认证失败
catch (AuthenticationException failed) {
SecurityContextHolder.clearContext();
this.logger.trace("Failed to process authentication request", failed);
this.authenticationFailureHandler.onAuthenticationFailure(request, response, failed);
}
如果认证失败,清除安全上下文并调用 AuthenticationFailureHandler
处理失败。
配置项
- authenticationEntryPoint:处理认证异常的入口点。
- authenticationFailureHandler:处理认证失败的处理器。
- bearerTokenResolver:解析 Bearer Token 的策略。
- authenticationDetailsSource:提供认证详情的来源。
- securityContextRepository:保存安全上下文的仓库。
总结
BearerTokenAuthenticationFilter
会从 HTTP 请求的Authorization
头中提取 Bearer Token,将提取到的 Token 包装成BearerTokenAuthenticationToken
认证对象交给 Spring Security 的认证管理器 (AuthenticationManager
) 进行处理。- 如果配置了 JWT 认证,
BearerTokenAuthenticationFilter
会使用JwtAuthenticationProvider
作为认证提供者,来验证和解析 JWT Token。而JwtAuthenticationProvider
会使用一个JwtDecoder
来解析和验证 Token。 - 在
application.yml
中,如果配置了Spring Security 资源服务Resource Server
的issuer-uri
,那么 Spring Security 会使用这个 URI 自动发现授权服务器的 JWKS 端点。并通过JwtDecoder
的实现类NimbusJwtDecoder
构建一个 JWK Set URL,通过这个 URL 向授权服务器请求 JWKS(JSON Web Key Set),获取公钥用于验证 JWT 的签名。 NimbusJwtDecoder
会使用获取的密钥验证 JWT 的签名,检查 JWT 的有效性(如过期时间、受众等)。如果 JWT 验证成功,认证通过,客户端即可访问受保护的资源。
BearerTokenAuthenticationFilter
是处理 OAuth2 Bearer Token 认证的核心过滤器。它从请求中提取 Token,尝试认证,并根据认证结果设置安全上下文或处理认证失败。通过灵活的配置项和处理器,开发者可以自定义过滤器的行为以适应不同的需求。
JwtAuthenticationProvider
主要成员变量
jwtDecoder
:用于解码 JWT 令牌的组件。jwtAuthenticationConverter
:用于将 JWT 转换为AbstractAuthenticationToken
的组件,默认是JwtAuthenticationConverter
。
构造方法
public JwtAuthenticationProvider(JwtDecoder jwtDecoder) {
Assert.notNull(jwtDecoder, "jwtDecoder cannot be null");
this.jwtDecoder = jwtDecoder;
}
- 作用:初始化
jwtDecoder
,确保其不为 null。
authenticate
方法
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
BearerTokenAuthenticationToken bearer = (BearerTokenAuthenticationToken) authentication;
Jwt jwt = getJwt(bearer);
AbstractAuthenticationToken token = this.jwtAuthenticationConverter.convert(jwt);
token.setDetails(bearer.getDetails());
this.logger.debug("Authenticated token");
return token;
}
- 作用:
- 将传入的
authentication
对象转换为BearerTokenAuthenticationToken
。 - 调用
getJwt
方法解码并验证 JWT 令牌。 - 使用
jwtAuthenticationConverter
将 JWT 转换为AbstractAuthenticationToken
。 - 设置认证对象的细节信息。
- 返回生成的认证对象。
- 将传入的
getJwt
方法
private Jwt getJwt(BearerTokenAuthenticationToken bearer) {
try {
return this.jwtDecoder.decode(bearer.getToken());
}
catch (BadJwtException failed) {
this.logger.debug("Failed to authenticate since the JWT was invalid");
throw new InvalidBearerTokenException(failed.getMessage(), failed);
}
catch (JwtException failed) {
throw new AuthenticationServiceException(failed.getMessage(), failed);
}
}
- 作用:
- 调用
jwtDecoder.decode
方法解码 JWT 令牌。 - 如果解码失败,捕获
BadJwtException
异常并记录调试信息,抛出InvalidBearerTokenException
。 - 如果捕获到其他
JwtException
异常,抛出AuthenticationServiceException
。
- 调用
supports
方法
@Override
public boolean supports(Class<?> authentication) {
return BearerTokenAuthenticationToken.class.isAssignableFrom(authentication);
}
- 作用:确定当前
AuthenticationProvider
是否支持指定的认证类型,这里是BearerTokenAuthenticationToken
。
setJwtAuthenticationConverter
方法
public void setJwtAuthenticationConverter(Converter<Jwt, ? extends AbstractAuthenticationToken> jwtAuthenticationConverter) {
Assert.notNull(jwtAuthenticationConverter, "jwtAuthenticationConverter cannot be null");
this.jwtAuthenticationConverter = jwtAuthenticationConverter;
}
- 作用:允许设置自定义的
jwtAuthenticationConverter
。
配置 YAML 示例
为了使 JWT 验证正常工作,你需要在应用的配置文件中指定一些属性。以下是一个 YAML 配置示例:
spring:
security:
oauth2:
resourceserver:
jwt:
jwk-set-uri: "https://example.com/.well-known/jwks.json"
issuer-uri: "https://example.com/issuer"
spring.security.oauth2.resourceserver.jwt.jwk-set-uri
:用于验证 JWT 的 JWK 集 URI。spring.security.oauth2.resourceserver.jwt.issuer-uri
:授权服务器的发布者 URI。
总结
JwtAuthenticationProvider
通过 JwtDecoder
解码和验证 JWT 令牌,并使用 jwtAuthenticationConverter
将解码后的 JWT 转换为认证对象。配置项如 jwk-set-uri
和 issuer-uri
用于指示从何处获取验证 JWT 的必要信息。这个机制确保了 JWT 令牌的安全性和有效性,从而保障了应用的安全访问。
NimbusJwtDecoder如何使用jwk-set-uri进行jwt验证的
验证过程
-
初始化:
NimbusJwtDecoder
使用提供的jwk-set-uri
初始化一个JWKSource<SecurityContext>
。该 JWK 源负责从指定的 URI 获取 JWK 集。初始化代码如下:public static NimbusJwtDecoder withJwkSetUri(String jwkSetUri) { Assert.hasText(jwkSetUri, "jwkSetUri 不能为空"); RemoteJWKSet<SecurityContext> jwkSet = new RemoteJWKSet<>(new URL(jwkSetUri)); return new NimbusJwtDecoder(jwkSet); }
-
解析 JWT:
decode
方法首先使用parse
方法将 JWT 字符串解析为JWT
对象:JWT jwt = parse(token);
-
检查是否为 PlainJWT:
解码器检查 JWT 是否为PlainJWT
(未签名的 JWT)。如果是,记录跟踪消息并抛出BadJwtException
异常:if (jwt instanceof PlainJWT) { this.logger.trace("Failed to decode unsigned token"); throw new BadJwtException("Unsupported algorithm of " + jwt.getHeader().getAlgorithm()); }
-
创建 Jwt 对象:
调用createJwt
方法从解析的 JWT 和原始 token 字符串创建一个Jwt
对象。这涉及从 JWT 中提取声明和头部信息:Jwt createdJwt = createJwt(token, jwt);
-
验证 Jwt:
最后,validateJwt
方法用于验证创建的 Jwt 对象:return validateJwt(createdJwt);
具体到代码,decode
方法如下:
@Override
public Jwt decode(String token) throws JwtException {
JWT jwt = parse(token);
if (jwt instanceof PlainJWT) {
this.logger.trace("Failed to decode unsigned token");
throw new BadJwtException("Unsupported algorithm of " + jwt.getHeader().getAlgorithm());
}
Jwt createdJwt = createJwt(token, jwt);
return validateJwt(createdJwt);
}
这个过程确保从 jwk-set-uri
获取的 JWK 被用于验证传入的 JWT,以确保它的有效性和真实性。
核心方法简介
-
parse(token):
该方法将传入的 JWT 字符串解析为JWT
对象,但不涉及验证。这仅是对 JWT 字符串的语法解析,例如:private JWT parse(String token) throws JwtException { try { return JWTParser.parse(token); } catch (ParseException ex) { throw new JwtException("JWT 解析失败", ex); } }
解析后的
JWT
对象可以是SignedJWT
、EncryptedJWT
或PlainJWT
。 -
createJwt(token, jwt):
该方法根据传入的JWT
对象和原始 token 字符串创建一个Jwt
对象。在这个过程中,会使用到 JWK 集进行签名验证。关键步骤如下:- 提取
SignedJWT
中的签名信息。 - 使用 JWK 集中的公钥来验证签名的有效性。
例如:
private Jwt createJwt(String token, JWT parsedJwt) { SignedJWT signedJwt = (SignedJWT) parsedJwt; JWSVerifier verifier = new DefaultJWSVerifierFactory().createJWSVerifier(signedJwt.getHeader(), getJWKSource()); if (!signedJwt.verify(verifier)) { throw new BadJwtException("JWT 签名验证失败"); } // 其他处理 }
这里的
getJWKSource()
方法会从 JWK 集中获取公钥信息来验证签名。 - 提取
-
validateJwt(createdJwt):
该方法用于对创建的Jwt
对象进行进一步的验证,例如检查过期时间、受众等,但不会直接使用 JWK 集。示例代码:private Jwt validateJwt(Jwt jwt) { // 检查过期时间、受众等 if (jwt.getExpiresAt().isBefore(Instant.now())) { throw new JwtException("JWT 已过期"); } // 其他验证 return jwt; }
总结:在 NimbusJwtDecoder
中,createJwt(token, jwt)
方法中使用了从 jwk-set-uri
获取的 JWK 集来进行 JWT 的签名验证。
createJwt方法
private Jwt createJwt(String token, JWT parsedJwt) {
try {
// Verify the signature
JWTClaimsSet jwtClaimsSet = this.jwtProcessor.process(parsedJwt, null);
Map<String, Object> headers = new LinkedHashMap<>(parsedJwt.getHeader().toJSONObject());
Map<String, Object> claims = this.claimSetConverter.convert(jwtClaimsSet.getClaims());
// @formatter:off
return Jwt.withTokenValue(token)
.headers((h) -> h.putAll(headers))
.claims((c) -> c.putAll(claims))
.build();
// @formatter:on
}
catch (RemoteKeySourceException ex) {
this.logger.trace("Failed to retrieve JWK set", ex);
if (ex.getCause() instanceof ParseException) {
throw new JwtException(String.format(DECODING_ERROR_MESSAGE_TEMPLATE, "Malformed Jwk set"), ex);
}
throw new JwtException(String.format(DECODING_ERROR_MESSAGE_TEMPLATE, ex.getMessage()), ex);
}
catch (JOSEException ex) {
this.logger.trace("Failed to process JWT", ex);
throw new JwtException(String.format(DECODING_ERROR_MESSAGE_TEMPLATE, ex.getMessage()), ex);
}
catch (Exception ex) {
this.logger.trace("Failed to process JWT", ex);
if (ex.getCause() instanceof ParseException) {
throw new BadJwtException(String.format(DECODING_ERROR_MESSAGE_TEMPLATE, "Malformed payload"), ex);
}
throw new BadJwtException(String.format(DECODING_ERROR_MESSAGE_TEMPLATE, ex.getMessage()), ex);
}
}
代码分析
- JWTClaimsSet jwtClaimsSet = this.jwtProcessor.process(parsedJwt, null);
- 这一行代码使用
jwtProcessor
对parsedJwt
进行处理和验证。jwtProcessor
会根据 JWK 集中的公钥来验证 JWT 的签名。 jwtProcessor
通常是一个ConfigurableJWTProcessor
实例,它使用配置的JWKSource
来获取验证 JWT 签名所需的公钥。
- 这一行代码使用
- Map<String, Object> headers = new LinkedHashMap<>(parsedJwt.getHeader().toJSONObject());
- 这行代码提取 JWT 的头部信息,并将其转换为一个
Map
对象。
- 这行代码提取 JWT 的头部信息,并将其转换为一个
- Map<String, Object> claims = this.claimSetConverter.convert(jwtClaimsSet.getClaims());
- 这行代码提取 JWT 的声明信息,并使用
claimSetConverter
进行转换。转换后的声明信息也被存储在一个Map
对象中。
- 这行代码提取 JWT 的声明信息,并使用
- return Jwt.withTokenValue(token) .headers((h) -> h.putAll(headers)) .claims(© -> c.putAll(claims)) .build();
- 这段代码使用提取的头部信息和声明信息来构建一个新的
Jwt
对象。
- 这段代码使用提取的头部信息和声明信息来构建一个新的
异常处理
- RemoteKeySourceException:
- 如果在获取 JWK 集时发生异常,代码会捕获
RemoteKeySourceException
并记录错误信息。如果异常的原因是ParseException
,则抛出一个JwtException
,指明 JWK 集格式错误。否则,抛出一个带有详细错误信息的JwtException
。
- 如果在获取 JWK 集时发生异常,代码会捕获
- JOSEException:
- 如果在处理 JWT 时发生
JOSEException
,代码会捕获异常并记录错误信息,然后抛出一个JwtException
。
- 如果在处理 JWT 时发生
- 其他异常:
- 如果发生其他类型的异常,代码会捕获并记录错误信息。如果异常的原因是
ParseException
,则抛出一个BadJwtException
,指明 JWT 负载格式错误。否则,抛出一个带有详细错误信息的BadJwtException
。
- 如果发生其他类型的异常,代码会捕获并记录错误信息。如果异常的原因是
使用 JWK 集进行 JWT 验证
createJwt
方法的核心部分是 this.jwtProcessor.process(parsedJwt, null);
。这里的 jwtProcessor
使用 JWK 集中的公钥对 parsedJwt
进行签名验证。jwtProcessor
通常配置了一个 JWKSource
,它会从指定的 jwk-set-uri
获取 JWK 集,确保 JWT 的签名是可信的。
通过这种方式,NimbusJwtDecoder
使用 JWK 集对 JWT 进行验证和处理,以确保 JWT 的有效性和完整性。
资源服务配置类
方法详细解释
-
accessDeniedHandler(AccessDeniedHandler accessDeniedHandler)
public OAuth2ResourceServerConfigurer<H> accessDeniedHandler(AccessDeniedHandler accessDeniedHandler) { Assert.notNull(accessDeniedHandler, "accessDeniedHandler cannot be null"); this.accessDeniedHandler = accessDeniedHandler; return this; }
这个方法用于设置自定义的
AccessDeniedHandler
。当客户端尝试访问受保护的资源但权限不足时,将会调用这个处理器来处理AccessDeniedException
。 -
authenticationEntryPoint(AuthenticationEntryPoint entryPoint)
public OAuth2ResourceServerConfigurer<H> authenticationEntryPoint(AuthenticationEntryPoint entryPoint) { Assert.notNull(entryPoint, "entryPoint cannot be null"); this.authenticationEntryPoint = entryPoint; return this; }
这个方法用于设置自定义的
AuthenticationEntryPoint
。当认证失败或客户端尝试访问受保护的资源但未进行身份验证时,将会调用这个入口点。 -
authenticationManagerResolver(AuthenticationManagerResolver<HttpServletRequest> authenticationManagerResolver)
public OAuth2ResourceServerConfigurer<H> authenticationManagerResolver( AuthenticationManagerResolver<HttpServletRequest> authenticationManagerResolver) { Assert.notNull(authenticationManagerResolver, "authenticationManagerResolver cannot be null"); this.authenticationManagerResolver = authenticationManagerResolver; return this; }
这个方法用于设置自定义的
AuthenticationManagerResolver
。它根据传入的HttpServletRequest
决定使用哪个AuthenticationManager
进行认证。 -
bearerTokenResolver(BearerTokenResolver bearerTokenResolver)
public OAuth2ResourceServerConfigurer<H> bearerTokenResolver(BearerTokenResolver bearerTokenResolver) { Assert.notNull(bearerTokenResolver, "bearerTokenResolver cannot be null"); this.bearerTokenResolver = bearerTokenResolver; return this; }
这个方法用于设置自定义的
BearerTokenResolver
,它用于从 HTTP 请求中解析 Bearer Token(通常是从请求头中获取)。 -
jwt()
public JwtConfigurer jwt() { if (this.jwtConfigurer == null) { this.jwtConfigurer = new JwtConfigurer(this.context); } return this.jwtConfigurer; }
这个方法启用对 JWT 编码的 Bearer Token 的支持,并返回一个
JwtConfigurer
对象,以便进一步配置 JWT 的相关设置。 -
jwt(Customizer<JwtConfigurer> jwtCustomizer)
public OAuth2ResourceServerConfigurer<H> jwt(Customizer<JwtConfigurer> jwtCustomizer) { if (this.jwtConfigurer == null) { this.jwtConfigurer = new JwtConfigurer(this.context); } jwtCustomizer.customize(this.jwtConfigurer); return this; }
这个方法启用对 JWT 编码的 Bearer Token 的支持,并允许通过
Customizer
对JwtConfigurer
进行进一步配置。 -
opaqueToken()
public OpaqueTokenConfigurer opaqueToken() { if (this.opaqueTokenConfigurer == null) { this.opaqueTokenConfigurer = new OpaqueTokenConfigurer(this.context); } return this.opaqueTokenConfigurer; }
这个方法启用对不透明 Bearer Token 的支持,并返回一个
OpaqueTokenConfigurer
对象,以便进一步配置不透明令牌的相关设置。 -
opaqueToken(Customizer<OpaqueTokenConfigurer> opaqueTokenCustomizer)
public OAuth2ResourceServerConfigurer<H> opaqueToken(Customizer<OpaqueTokenConfigurer> opaqueTokenCustomizer) { if (this.opaqueTokenConfigurer == null) { this.opaqueTokenConfigurer = new OpaqueTokenConfigurer(this.context); } opaqueTokenCustomizer.customize(this.opaqueTokenConfigurer); return this; }
这个方法启用对不透明 Bearer Token 的支持,并允许通过
Customizer
对OpaqueTokenConfigurer
进行进一步配置。
总结
OAuth2ResourceServerConfigurer
类提供了一系列方法,用于配置 OAuth2 资源服务器的各种安全设置,包括处理访问拒绝、身份验证入口点、自定义令牌解析器、以及支持 JWT 和不透明令牌的配置。这些方法允许开发者根据具体需求自定义资源服务器的行为,以确保其安全性和灵活性。
资源服务YAML配置
OAuth2ResourceServerProperties
这个类包含两个内部类:Jwt
和 OpaqueToken
,分别用于配置 JWT 和不透明令牌的属性。
YAML 配置示例
spring:
security:
oauth2:
resourceserver:
jwt:
jwk-set-uri: "https://example.com/.well-known/jwks.json"
issuer-uri: "https://example.com/issuer"
public-key-location: "classpath:public-key.pem"
jws-algorithms:
- "RS256"
audiences:
- "your-audience"
opaqueToken:
client-id: "your-client-id"
client-secret: "your-client-secret"
introspection-uri: "https://example.com/introspect"
具体配置项及作用
JWT 配置项
spring:
security:
oauth2:
resourceserver:
jwt:
jwk-set-uri: "https://example.com/.well-known/jwks.json"
issuer-uri: "https://example.com/issuer"
public-key-location: "classpath:public-key.pem"
jws-algorithms:
- "RS256"
audiences:
- "your-audience"
spring.security.oauth2.resourceserver.jwt.jwk-set-uri
:用于验证 JWT 令牌的 JSON Web Key (JWK) URI。spring.security.oauth2.resourceserver.jwt.issuer-uri
:可以是 OpenID Connect 发现端点或 RFC 8414 定义的 OAuth 2.0 授权服务器元数据端点的 URI。spring.security.oauth2.resourceserver.jwt.public-key-location
:包含用于验证 JWT 的公钥的文件的位置。spring.security.oauth2.resourceserver.jwt.jws-algorithms
:用于验证数字签名的 JSON Web 算法列表,默认为RS256
。spring.security.oauth2.resourceserver.jwt.audiences
:标识 JWT 预期接收者的受众列表。
不透明令牌配置项
spring:
security:
oauth2:
resourceserver:
opaqueToken:
client-id: "your-client-id"
client-secret: "your-client-secret"
introspection-uri: "https://example.com/introspect"
spring.security.oauth2.resourceserver.opaqueToken.client-id
:用于与令牌检查端点进行认证的客户端 ID。spring.security.oauth2.resourceserver.opaqueToken.client-secret
:用于与令牌检查端点进行认证的客户端密钥。spring.security.oauth2.resourceserver.opaqueToken.introspection-uri
:用于执行令牌检查的 OAuth 2.0 端点。
总结
OAuth2ResourceServerProperties
类和相应的 YAML 配置项为 Spring Security OAuth2 资源服务器提供了丰富的配置选项。这些配置项允许开发者灵活地设置 JWT 和不透明令牌的验证细节,从而确保应用程序的安全性和可靠性。