|
@@ -0,0 +1,90 @@
|
|
|
+package com.chelvc.framework.oauth.token;
|
|
|
+
|
|
|
+import java.util.Arrays;
|
|
|
+import java.util.Collection;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Objects;
|
|
|
+
|
|
|
+import com.chelvc.framework.base.context.ApplicationContextHolder;
|
|
|
+import com.chelvc.framework.base.context.SessionContextHolder;
|
|
|
+import com.chelvc.framework.common.util.ObjectUtils;
|
|
|
+import com.chelvc.framework.common.util.StringUtils;
|
|
|
+import com.chelvc.framework.oauth.context.OAuthContextHolder;
|
|
|
+import com.chelvc.framework.redis.context.RedisContextHolder;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
|
|
+import org.springframework.security.oauth2.core.OAuth2Error;
|
|
|
+import org.springframework.security.oauth2.core.OAuth2TokenValidator;
|
|
|
+import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult;
|
|
|
+import org.springframework.security.oauth2.jwt.Jwt;
|
|
|
+import org.springframework.security.oauth2.jwt.JwtTimestampValidator;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 基于Redis的令牌验证器实现
|
|
|
+ *
|
|
|
+ * @author Woody
|
|
|
+ * @date 2024/1/30
|
|
|
+ */
|
|
|
+@Slf4j
|
|
|
+public class RedisTokenValidator implements OAuth2TokenValidator<Jwt> {
|
|
|
+ private final JwtTimestampValidator timestampValidator = new JwtTimestampValidator();
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public OAuth2TokenValidatorResult validate(Jwt jwt) {
|
|
|
+ // 尝试批量从Redis获取令牌相关信息,如果Redis读取失败则降级为只校验令牌过期时间
|
|
|
+ String key = OAuthContextHolder.key(OAuthContextHolder.getId(jwt));
|
|
|
+ Collection<Object> fields = Arrays.asList(
|
|
|
+ SessionContextHolder.HEADER_SCOPE,
|
|
|
+ String.valueOf(SessionContextHolder.getTerminal())
|
|
|
+ );
|
|
|
+ List<?> values;
|
|
|
+ try {
|
|
|
+ values = RedisContextHolder.getDefaultTemplate().opsForHash().multiGet(key, fields);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("Redis token validate failed: {}", e.getMessage());
|
|
|
+
|
|
|
+ // 校验令牌是否过期
|
|
|
+ if (this.timestampValidator.validate(jwt).hasErrors()) {
|
|
|
+ throw new OAuth2AuthenticationException(new OAuth2Error(
|
|
|
+ "TOKEN_EXPIRED", ApplicationContextHolder.getMessage("Token.Expired"), null
|
|
|
+ ));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 初始化会话信息
|
|
|
+ SessionContextHolder.initializeSession(
|
|
|
+ OAuthContextHolder.getId(jwt),
|
|
|
+ OAuthContextHolder.getScope(jwt),
|
|
|
+ OAuthContextHolder.isAnonymous(jwt)
|
|
|
+ );
|
|
|
+ return OAuth2TokenValidatorResult.success();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 基于Redis令牌有效性校验
|
|
|
+ String scope = String.valueOf(ObjectUtils.size(values) > 0 ? values.get(0) : null);
|
|
|
+ String token = String.valueOf(ObjectUtils.size(values) > 1 ? values.get(1) : null);
|
|
|
+ if (StringUtils.isEmpty(token)) {
|
|
|
+ throw new OAuth2AuthenticationException(new OAuth2Error(
|
|
|
+ "TOKEN_EXPIRED", ApplicationContextHolder.getMessage("Token.Expired"), null
|
|
|
+ ));
|
|
|
+ } else if (!Objects.equals(token, jwt.getTokenValue())) {
|
|
|
+ // 判断令牌是否相同,如果不相同则表示令牌已经被重置(账号已在其他地方登录)
|
|
|
+ throw new OAuth2AuthenticationException(new OAuth2Error(
|
|
|
+ "TOKEN_CHANGED", ApplicationContextHolder.getMessage("Token.Changed"), null
|
|
|
+ ));
|
|
|
+ } else if (!Objects.equals(scope, String.valueOf(OAuthContextHolder.getScope(jwt)))) {
|
|
|
+ // 判断应用范围是否相同,如果不同则表示应用范围已被重置,需要刷新令牌
|
|
|
+ String message = ApplicationContextHolder.getMessage(
|
|
|
+ "Scope.Changed", ApplicationContextHolder.getMessage(scope)
|
|
|
+ );
|
|
|
+ throw new OAuth2AuthenticationException(new OAuth2Error("SCOPE_CHANGED", message, null));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 初始化会话信息
|
|
|
+ SessionContextHolder.initializeSession(
|
|
|
+ OAuthContextHolder.getId(jwt),
|
|
|
+ OAuthContextHolder.getScope(jwt),
|
|
|
+ OAuthContextHolder.isAnonymous(jwt)
|
|
|
+ );
|
|
|
+ return OAuth2TokenValidatorResult.success();
|
|
|
+ }
|
|
|
+}
|