代码版本信息

Spring Boot 2.7.10
spring-security-oauth2-resource-server 5.7.7


大致过程

  1. BearerTokenAuthenticationFilter 过滤器会从 HTTP 请求的 Authorization 头中提取 Bearer Token,将提取到的 Token 包装成 BearerTokenAuthenticationToken 认证对象交给 Spring Security 的认证管理器 (AuthenticationManager) 进行处理。
  2. 如果配置了 JWT 认证,BearerTokenAuthenticationFilter 会使用JwtAuthenticationProvider 作为认证提供者,来验证和解析 JWT Token。而JwtAuthenticationProvider 会使用一个 JwtDecoder 来解析和验证 Token。
  3. application.yml 中,如果配置了Spring Security 资源服务Resource Serverissuer-uri,那么 Spring Security 会使用这个 URI 自动发现授权服务器的 JWKS 端点。并通过JwtDecoder 的实现类NimbusJwtDecoder 构建一个 JWK Set URL,通过这个 URL 向授权服务器请求 JWKS(JSON Web Key Set),获取公钥用于验证 JWT 的签名。
  4. NimbusJwtDecoder 会使用获取的密钥验证 JWT 的签名,检查 JWT 的有效性(如过期时间、受众等)。如果 JWT 验证成功,认证通过,客户端即可访问受保护的资源。



配置issuer-uri作用

目的和作用

  1. 确保JWT的可信来源

    • issuer-uri表示JWT签发者的URI。配置此URI可以确保资源服务器仅接受由该特定签发者签发的JWT。这有助于防止来自不可信来源的令牌被接受和使用。
    • 在验证过程中,资源服务器会检查JWT的iss(issuer)声明,确保其与配置的issuer-uri匹配。如果不匹配,验证将失败。
  2. 自动发现和获取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集。

工作机制

  1. JWT签发者验证

    • 当资源服务器接收到JWT时,它会检查JWT中的iss声明是否匹配issuer-uri
    • 例如,如果issuer-uri配置为https://example.com/issuer,JWT的iss声明也必须是https://example.com/issuer
  2. 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 接受一个 AuthenticationManagerResolverAuthenticationManager。这两个构造函数允许使用不同的方式来解析 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通过内部类JwtDecoderConfigurationjwtDecoderByIssuerUri方法进行配置:

@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);
    
}
  1. 请求元数据端点,JwtDecoderProviderConfigurationUtilsgetConfigurationForIssuerLocation方法会再通过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);
    }
    
  2. 根据上一步获取的信息,配置解析器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);
    }
    

    验证器添加目的是:NimbusJwtDecoderdecode方法调用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认证

NimbusJwtDecoderdecode方法

@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对象。它通过以下步骤来实现这个过程:

  1. 验证签名
    • JWTClaimsSet jwtClaimsSet = this.jwtProcessor.process(parsedJwt, null);
    • 通过jwtProcessor处理解析后的JWT对象(parsedJwt)来验证签名并提取JWT声明集(jwtClaimsSet)。
    • jwtProcessor使用已配置的JWK(JSON Web Key)源来验证JWT的签名。

  2. 提取头部和声明
    • Map<String, Object> headers = new LinkedHashMap<>(parsedJwt.getHeader().toJSONObject());
    • 从解析后的JWT对象中提取头部信息并转换为一个Map
    • Map<String, Object> claims = this.claimSetConverter.convert(jwtClaimsSet.getClaims());
    • 使用claimSetConverter将JWT声明集中的声明转换为一个Map

  3. 创建Jwt对象
    • return Jwt.withTokenValue(token).headers((h) -> h.putAll(headers)).claims((c) -> c.putAll(claims)).build();
    • 使用提取的头部信息和声明创建一个Jwt对象。这个Jwt对象包含了原始的JWT字符串、头部信息和声明。

  4. 处理异常
    • 如果在验证或处理JWT的过程中发生异常,会捕获并处理这些异常。
    • 异常类型包括RemoteKeySourceExceptionJOSEException和通用的Exception
    • 根据具体的异常类型,记录调试信息并抛出相应的JwtExceptionBadJwtException

关键点

  • 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转换

NimbusJwtDecoderdecode方法解析成功后,将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:保存安全上下文的仓库。

总结

  1. BearerTokenAuthenticationFilter 会从 HTTP 请求的 Authorization 头中提取 Bearer Token,将提取到的 Token 包装成 BearerTokenAuthenticationToken 认证对象交给 Spring Security 的认证管理器 (AuthenticationManager) 进行处理。
  2. 如果配置了 JWT 认证,BearerTokenAuthenticationFilter 会使用JwtAuthenticationProvider 作为认证提供者,来验证和解析 JWT Token。而JwtAuthenticationProvider 会使用一个 JwtDecoder 来解析和验证 Token。
  3. application.yml 中,如果配置了Spring Security 资源服务Resource Serverissuer-uri,那么 Spring Security 会使用这个 URI 自动发现授权服务器的 JWKS 端点。并通过JwtDecoder 的实现类NimbusJwtDecoder 构建一个 JWK Set URL,通过这个 URL 向授权服务器请求 JWKS(JSON Web Key Set),获取公钥用于验证 JWT 的签名。
  4. 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;
}
  • 作用
    1. 将传入的 authentication 对象转换为 BearerTokenAuthenticationToken
    2. 调用 getJwt 方法解码并验证 JWT 令牌。
    3. 使用 jwtAuthenticationConverter 将 JWT 转换为 AbstractAuthenticationToken
    4. 设置认证对象的细节信息。
    5. 返回生成的认证对象。

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);
    }
}
  • 作用
    1. 调用 jwtDecoder.decode 方法解码 JWT 令牌。
    2. 如果解码失败,捕获 BadJwtException 异常并记录调试信息,抛出 InvalidBearerTokenException
    3. 如果捕获到其他 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-uriissuer-uri 用于指示从何处获取验证 JWT 的必要信息。这个机制确保了 JWT 令牌的安全性和有效性,从而保障了应用的安全访问。




NimbusJwtDecoder如何使用jwk-set-uri进行jwt验证的

验证过程

  1. 初始化:
    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);
    }
    
  2. 解析 JWT:
    decode 方法首先使用 parse 方法将 JWT 字符串解析为 JWT 对象:

    JWT jwt = parse(token);
    
  3. 检查是否为 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());
    }
    
  4. 创建 Jwt 对象:
    调用 createJwt 方法从解析的 JWT 和原始 token 字符串创建一个 Jwt 对象。这涉及从 JWT 中提取声明和头部信息:

    Jwt createdJwt = createJwt(token, jwt);
    
  5. 验证 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,以确保它的有效性和真实性。


核心方法简介

  1. 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 对象可以是 SignedJWTEncryptedJWTPlainJWT

  2. 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 集中获取公钥信息来验证签名。

  3. 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);
    }
}

代码分析

  1. JWTClaimsSet jwtClaimsSet = this.jwtProcessor.process(parsedJwt, null);
    • 这一行代码使用 jwtProcessorparsedJwt 进行处理和验证。jwtProcessor 会根据 JWK 集中的公钥来验证 JWT 的签名。
    • jwtProcessor 通常是一个 ConfigurableJWTProcessor 实例,它使用配置的 JWKSource 来获取验证 JWT 签名所需的公钥。

  2. Map<String, Object> headers = new LinkedHashMap<>(parsedJwt.getHeader().toJSONObject());
    • 这行代码提取 JWT 的头部信息,并将其转换为一个 Map 对象。

  3. Map<String, Object> claims = this.claimSetConverter.convert(jwtClaimsSet.getClaims());
    • 这行代码提取 JWT 的声明信息,并使用 claimSetConverter 进行转换。转换后的声明信息也被存储在一个 Map 对象中。

  4. return Jwt.withTokenValue(token) .headers((h) -> h.putAll(headers)) .claims(© -> c.putAll(claims)) .build();
    • 这段代码使用提取的头部信息和声明信息来构建一个新的 Jwt 对象。

异常处理

  • RemoteKeySourceException:
    • 如果在获取 JWK 集时发生异常,代码会捕获 RemoteKeySourceException 并记录错误信息。如果异常的原因是 ParseException,则抛出一个 JwtException,指明 JWK 集格式错误。否则,抛出一个带有详细错误信息的 JwtException
  • JOSEException:
    • 如果在处理 JWT 时发生 JOSEException,代码会捕获异常并记录错误信息,然后抛出一个 JwtException
  • 其他异常:
    • 如果发生其他类型的异常,代码会捕获并记录错误信息。如果异常的原因是 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 的有效性和完整性。




资源服务配置类

方法详细解释

  1. accessDeniedHandler(AccessDeniedHandler accessDeniedHandler)

    public OAuth2ResourceServerConfigurer<H> accessDeniedHandler(AccessDeniedHandler accessDeniedHandler) {
        Assert.notNull(accessDeniedHandler, "accessDeniedHandler cannot be null");
        this.accessDeniedHandler = accessDeniedHandler;
        return this;
    }
    

    这个方法用于设置自定义的 AccessDeniedHandler。当客户端尝试访问受保护的资源但权限不足时,将会调用这个处理器来处理 AccessDeniedException

  2. authenticationEntryPoint(AuthenticationEntryPoint entryPoint)

    public OAuth2ResourceServerConfigurer<H> authenticationEntryPoint(AuthenticationEntryPoint entryPoint) {
        Assert.notNull(entryPoint, "entryPoint cannot be null");
        this.authenticationEntryPoint = entryPoint;
        return this;
    }
    

    这个方法用于设置自定义的 AuthenticationEntryPoint。当认证失败或客户端尝试访问受保护的资源但未进行身份验证时,将会调用这个入口点。

  3. 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 进行认证。

  4. 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(通常是从请求头中获取)。

  5. jwt()

    public JwtConfigurer jwt() {
        if (this.jwtConfigurer == null) {
            this.jwtConfigurer = new JwtConfigurer(this.context);
        }
        return this.jwtConfigurer;
    }
    

    这个方法启用对 JWT 编码的 Bearer Token 的支持,并返回一个 JwtConfigurer 对象,以便进一步配置 JWT 的相关设置。

  6. 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 的支持,并允许通过 CustomizerJwtConfigurer 进行进一步配置。

  7. opaqueToken()

    public OpaqueTokenConfigurer opaqueToken() {
        if (this.opaqueTokenConfigurer == null) {
            this.opaqueTokenConfigurer = new OpaqueTokenConfigurer(this.context);
        }
        return this.opaqueTokenConfigurer;
    }
    

    这个方法启用对不透明 Bearer Token 的支持,并返回一个 OpaqueTokenConfigurer 对象,以便进一步配置不透明令牌的相关设置。

  8. 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 的支持,并允许通过 CustomizerOpaqueTokenConfigurer 进行进一步配置。

总结

OAuth2ResourceServerConfigurer 类提供了一系列方法,用于配置 OAuth2 资源服务器的各种安全设置,包括处理访问拒绝、身份验证入口点、自定义令牌解析器、以及支持 JWT 和不透明令牌的配置。这些方法允许开发者根据具体需求自定义资源服务器的行为,以确保其安全性和灵活性。




资源服务YAML配置

OAuth2ResourceServerProperties

这个类包含两个内部类:JwtOpaqueToken,分别用于配置 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 和不透明令牌的验证细节,从而确保应用程序的安全性和可靠性。

08-22 18:09