Oauth2校验jwt的过期时间源码

4/11/2023 安全框架

# 前述

  • 基于代码:security-cloud-oauth2 (opens new window)

  • security-cloud-oauth2代码中,并没有看见校验Token过期的地方,经过查阅资料以及Debug,发现其实oauth2已经帮我们做了。

# JwtReactiveAuthenticationManager

  • oauth2JwtReactiveAuthenticationManager中就已经帮我们写好了校验token的逻辑,具体方法为:authenticate,在该方法中进行了客户端token的解析校验
public final class JwtReactiveAuthenticationManager implements ReactiveAuthenticationManager {

	private final ReactiveJwtDecoder jwtDecoder;

	private Converter<Jwt, ? extends Mono<? extends AbstractAuthenticationToken>> jwtAuthenticationConverter = new ReactiveJwtAuthenticationConverterAdapter(
			new JwtAuthenticationConverter());

	public JwtReactiveAuthenticationManager(ReactiveJwtDecoder jwtDecoder) {
		Assert.notNull(jwtDecoder, "jwtDecoder cannot be null");
		this.jwtDecoder = jwtDecoder;
	}

    // 在JwtReactiveAuthenticationManager#authenticate方法里进行了客户端token的解析校验。
	@Override
	public Mono<Authentication> authenticate(Authentication authentication) {
		// @formatter:off
		return Mono.justOrEmpty(authentication)
				.filter((a) -> a instanceof BearerTokenAuthenticationToken)
				.cast(BearerTokenAuthenticationToken.class)
				.map(BearerTokenAuthenticationToken::getToken)
				.flatMap(this.jwtDecoder::decode)
				.flatMap(this.jwtAuthenticationConverter::convert)
				.cast(Authentication.class)
				.onErrorMap(JwtException.class, this::onError);
		// @formatter:on
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
  • JwtReactiveAuthenticationManagerauthenticate方法,先是从authentication获取了加密的token,继而使用ReactiveJwtDecoderdecode去解析它.
@FunctionalInterface
public interface ReactiveJwtDecoder {

	/**
	 * Decodes the JWT from it's compact claims representation format and returns a
	 * {@link Jwt}.
	 * @param token the JWT value
	 * @return a {@link Jwt}
	 * @throws JwtException if an error occurs while attempting to decode the JWT
	 */
	Mono<Jwt> decode(String token) throws JwtException;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
  • ReactiveJwtDecoder有两个实现类:SupplierReactiveJwtDecoderNimbusReactiveJwtDecoder,经Debug发现NimbusReactiveJwtDecoder才是我们需要去了解的。

# NimbusReactiveJwtDecoder

  • NimbusReactiveJwtDecoder中,decode方法首先调用JWTParser.parse(token)解出明文成JWT,然后调用validateJwt方法验证解析出来的JWT
public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder {

  private final Converter<JWT, Mono<JWTClaimsSet>> jwtProcessor;

	private OAuth2TokenValidator<Jwt> jwtValidator = JwtValidators.createDefault();

	private Converter<Map<String, Object>, Map<String, Object>> claimSetConverter = MappedJwtClaimSetConverter
			.withDefaults(Collections.emptyMap());

	@Override
	public Mono<Jwt> decode(String token) throws JwtException {
        // 调用JWTParser.parse(token)解出明文成JWT
		JWT jwt = parse(token);
		if (jwt instanceof PlainJWT) {
			throw new BadJwtException("Unsupported algorithm of " + jwt.getHeader().getAlgorithm());
		}
		return this.decode(jwt);
	}
    
	private JWT parse(String token) {
		try {
			return JWTParser.parse(token);
		}
		catch (Exception ex) {
			throw new BadJwtException("An error occurred while attempting to decode the Jwt: " + ex.getMessage(), ex);
		}
	}

    // decode调用validateJwt方法验证解析出来的JWT
	private Mono<Jwt> decode(JWT parsedToken) {
		try {
			// @formatter:off
			return this.jwtProcessor.convert(parsedToken)
					.map((set) -> createJwt(parsedToken, set))
					.map(this::validateJwt)
					.onErrorMap((ex) -> !(ex instanceof IllegalStateException) && !(ex instanceof JwtException),
							(ex) -> new JwtException("An error occurred while attempting to decode the Jwt: ", ex));
			// @formatter:on
		}
		catch (JwtException ex) {
			throw ex;
		}
		catch (RuntimeException ex) {
			throw new JwtException("An error occurred while attempting to decode the Jwt: " + ex.getMessage(), ex);
		}
	}

    // 此处为校验token
	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;
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58

oauth1.png

# OAuth2TokenValidator

oauth1.png

如上图OAuth2TokenValidator校验JWT有五个实现类,这意味着有多个维度的校验,这里以时间戳的校验进行查看(即过期时间的校验)

# JwtTimestampValidator

public final class JwtTimestampValidator implements OAuth2TokenValidator<Jwt> {

	private final Log logger = LogFactory.getLog(getClass());

	private static final Duration DEFAULT_MAX_CLOCK_SKEW = Duration.of(60, ChronoUnit.SECONDS);

	private final Duration clockSkew;

	private Clock clock = Clock.systemUTC();

	/**
	 * A basic instance with no custom verification and the default max clock skew
	 */
	public JwtTimestampValidator() {
		this(DEFAULT_MAX_CLOCK_SKEW);
	}

	public JwtTimestampValidator(Duration clockSkew) {
		Assert.notNull(clockSkew, "clockSkew cannot be null");
		this.clockSkew = clockSkew;
	}

	@Override
	public OAuth2TokenValidatorResult validate(Jwt jwt) {
		Assert.notNull(jwt, "jwt cannot be null");
        // 过期时间点
		Instant expiry = jwt.getExpiresAt();
		if (expiry != null) {
          // isAfter 表示在过期时间点之后
          // this.clockSkew:springsecurity在校验过期时间的时候会在当前时间上减去这个时间偏移,
          // 默认是60秒,再和你设定的应该过期的时间点比较。计算后的时间节点如果在你设定的时间之后才会进入if,返回token失效。
          // (例如,当前时间8:00,你设定一分钟过期,实际判定需要等到8:02才会返回token失效)。
			if (Instant.now(this.clock).minus(this.clockSkew).isAfter(expiry)) {
				OAuth2Error oAuth2Error = createOAuth2Error(String.format("Jwt expired at %s", jwt.getExpiresAt()));
				return OAuth2TokenValidatorResult.failure(oAuth2Error);
			}
		}
		Instant notBefore = jwt.getNotBefore();
		if (notBefore != null) {
			if (Instant.now(this.clock).plus(this.clockSkew).isBefore(notBefore)) {
				OAuth2Error oAuth2Error = createOAuth2Error(String.format("Jwt used before %s", jwt.getNotBefore()));
				return OAuth2TokenValidatorResult.failure(oAuth2Error);
			}
		}
		return OAuth2TokenValidatorResult.success();
	}

	private OAuth2Error createOAuth2Error(String reason) {
		this.logger.debug(reason);
		return new OAuth2Error(OAuth2ErrorCodes.INVALID_TOKEN, reason,
				"https://tools.ietf.org/html/rfc6750#section-3.1");
	}

	/**
	 * Use this {@link Clock} with {@link Instant#now()} for assessing timestamp validity
	 * @param clock
	 */
	public void setClock(Clock clock) {
		Assert.notNull(clock, "clock cannot be null");
		this.clock = clock;
	}

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64

oauth3.png