写在前面的话
因为SpringBoot3.x是目前最新的版本,整合spring-security-oauth2-authorization-server的资料很少,所以产生了这篇文章,主要为想尝试SpringBoot高版本,想整合最新的spring-security-oauth2-authorization-server的初学者,旨在为大家提供一个简单上手的参考,如果哪里写得不对或可以优化的还请大家踊跃评论指正。
前面几篇文章主要结合官网的描述实现了基于JDBC获取用户信息登录并存储token在数据库中。但是我们实际中使用较多的是将token存储在Redis,官方只提供了InMemory和JDBC两种模式,所以本篇文章主要想实现一个自定义的RedisService来存储和获取token。此中会遇到很多问题,会一一列出。 整个项目的配置还是复用的上一篇。
一、自定义RedisOAuth2AuthorizationService
1. 整合Redis
在pom中添加如下坐标,如果开启lettuce连接池需要引入common-pools否则会报错
xml
代码解读
复制代码
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!-- 开启lettuce pool需要此依赖 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency>
yaml
代码解读
复制代码
spring: data: redis: lettuce: pool: max-active: 8 max-idle: 8 min-idle: 1 max-wait: 30000 # 开启连接池,需要引入common-pools enabled: true database: 15 host: 192.168.0.51 port: 6379 password: lx90150..
2. 创建RedisOAuth2AuthorizationService
我们上一篇实现了基于JDBC的JdbcOAuth2AuthorizationService,点进去会看到是实现了OAuth2AuthorizationService接口,那我们也实现此接口。里面有用到RedisUtils工具类,比较简单就不贴了。
整理了这份面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题
需要全套面试笔记【点击此处】即可免费获取
java
代码解读
复制代码
public class RedisOAuth2AuthorizationService implements OAuth2AuthorizationService { @Override public void save(OAuth2Authorization authorization) { } @Override public void remove(OAuth2Authorization authorization) { } @Override public OAuth2Authorization findById(String id) { return null; } @Override public OAuth2Authorization findByToken(String token, OAuth2TokenType tokenType) { return null; } }
然后我们一一去重写它。
2.1 save(OAuth2Authorization authorization)
之前我们认证服务器配置中,有配置RegisteredClientRepository Bean,里面我们设置了tokenSettings,里面包含token的一些信息,如:授权码过期时间,accessToken过期时间等,默认是5分钟,支持自定义。所以我们将RegisteredClientRepository引入进来供查询客户端信息。
java
代码解读
复制代码
private final RegisteredClientRepository clientRepository; @Setter private ObjectMapper objectMapper = new ObjectMapper(); public RedisOAuth2AuthorizationService(RegisteredClientRepository clientRepository) { this.clientRepository = clientRepository; } @Override public void save(OAuth2Authorization authorization) { // 获取客户端信息 final String clientId = authorization.getRegisteredClientId(); RegisteredClient registeredClient = clientRepository.findById(clientId); Assert.notNull(registeredClient, "客户端信息不可为空"); Assert.notNull(registeredClient.getTokenSettings(), "token配置信息不可为空"); // 获取授权码、AccessToken、RefreshToken过期时间,默认5分钟 Duration codeLiveTime = registeredClient.getTokenSettings().getAuthorizationCodeTimeToLive(); Duration accessTokenLiveTime = registeredClient.getTokenSettings().getAccessTokenTimeToLive(); Duration refreshTokenLiveTime = registeredClient.getTokenSettings().getRefreshTokenTimeToLive(); // 获取authorizationId final String authorizationId = authorization.getId(); final String idToAuthorizationKey = SecurityConstant.getIdToAuthorizationKey(authorizationId); // write()是用objectMapper序列化,想以原样存储原样获取方便查看 RedisUtils.setEx(idToAuthorizationKey, write(authorization), accessTokenLiveTime.getSeconds(), TimeUnit.SECONDS); //因为授权码、AccessToken、RefreshToken都是通过此service存储和获取,所以都需要存储 Optional.ofNullable(authorization.getToken(OAuth2AuthorizationCode.class)).ifPresent(token -> { final String codeToAuthorizationKey = SecurityConstant.getCodeToAuthorization(token.getToken().getTokenValue()); RedisUtils.setEx(codeToAuthorizationKey, authorizationId, codeLiveTime.getSeconds(), TimeUnit.SECONDS); }); Optional.ofNullable(authorization.getAccessToken()).ifPresent(token -> { final String accessToAuthorization = SecurityConstant.getAccessToAuthorization(token.getToken().getTokenValue()); RedisUtils.setEx(accessToAuthorization, authorizationId, accessTokenLiveTime.getSeconds(), TimeUnit.SECONDS); }); Optional.ofNullable(authorization.getRefreshToken()).ifPresent(token -> { final String refreshToAuthorization = SecurityConstant.getRefreshToAuthorization(token.getToken().getTokenValue()); RedisUtils.setEx(refreshToAuthorization, authorizationId, refreshTokenLiveTime.getSeconds(), TimeUnit.SECONDS); }); } /** * 将OAuth2Authorization对象序列化成字符串 * * @param data OAuth2Authorization对象 * @return OAuth2Authorization字符串 */ private String write(Object data) { try { return this.objectMapper.writeValueAsString(data); } catch (Exception ex) { throw new IllegalArgumentException(ex.getMessage(), ex); } }
附上SecurityConstant定义的常量
java
代码解读
复制代码
public class SecurityConstant { /** * redisToken前缀 */ public final static String CACHE_TOKEN_PREFIX = "ADP-SSO:"; /** * authorization_id key */ public static final String ID_TO_AUTHORIZATION = CACHE_TOKEN_PREFIX + "id_to_authorization:"; /** * authorization_code key */ public static final String CODE_TO_AUTHORIZATION = CACHE_TOKEN_PREFIX + "code_to_authorization:"; /** * access_token key */ public static final String ACCESS_TO_AUTHORIZATION = CACHE_TOKEN_PREFIX + "access_to_authorization:"; /** * refresh_token key */ public static final String REFRESH_TO_AUTHORIZATION = CACHE_TOKEN_PREFIX + "refresh_to_authorization:"; private static final MessageDigest DIGEST; static { try { DIGEST = MessageDigest.getInstance("SHA-256"); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } } protected static String generateKey(String rawKey) { byte[] bytes = DIGEST.digest(rawKey.getBytes(StandardCharsets.UTF_8)); return String.format("%032x", new BigInteger(1, bytes)); } public static String getIdToAuthorizationKey(String authorizationId) { return SecurityConstant.ID_TO_AUTHORIZATION + authorizationId; } public static String getCodeToAuthorization(String code) { return SecurityConstant.CODE_TO_AUTHORIZATION + generateKey(code); } public static String getAccessToAuthorization(String accessToken) { return SecurityConstant.ACCESS_TO_AUTHORIZATION + generateKey(accessToken); } public static String getRefreshToAuthorization(String refreshToken) { return SecurityConstant.REFRESH_TO_AUTHORIZATION + generateKey(refreshToken); } }
2.2 OAuth2Authorization findById(String id)
提供根据authorizationId查询OAuth2Authorization
java
代码解读
复制代码
@Override public OAuth2Authorization findById(String id) { return Optional.ofNullable(RedisUtils.get(SecurityConstant.getIdToAuthorizationKey(id))).map(this::parse).orElse(null); } /** * 将redis里的OAuth2Authorization反序列化成OAuth2Authorization对象 * * @param data OAuth2Authorization序列化串 * @return OAuth2Authorization */ private OAuth2Authorization parse(String data) { try { return this.objectMapper.readValue(data, new TypeReference<>() { }); } catch (Exception ex) { throw new IllegalArgumentException(ex.getMessage(), ex); } }
2.3 OAuth2Authorization findByToken(String token, OAuth2TokenType tokenType)
根据token查询OAuth2Authorization
java
代码解读
复制代码
@Override public OAuth2Authorization findByToken(String token, OAuth2TokenType tokenType) { Assert.notBlank(token, "token不能为空"); if (tokenType == null) { return Optional.ofNullable(RedisUtils.get(SecurityConstant.getCodeToAuthorization(token))) .or(() -> Optional.ofNullable(RedisUtils.get(SecurityConstant.getAccessToAuthorization(token)))) .or(() -> Optional.ofNullable(RedisUtils.get(SecurityConstant.getRefreshToAuthorization(token)))) .map(this::findById).orElse(null); } else if (OAuth2ParameterNames.CODE.equals(tokenType.getValue())) { return Optional.ofNullable(RedisUtils.get(SecurityConstant.getCodeToAuthorization(token))).map(this::findById).orElse(null); } else if (OAuth2TokenType.ACCESS_TOKEN.equals(tokenType)) { return Optional.ofNullable(RedisUtils.get(SecurityConstant.getAccessToAuthorization(token))).map(this::findById).orElse(null); } else if (OAuth2TokenType.REFRESH_TOKEN.equals(tokenType)) { return Optional.ofNullable(RedisUtils.get(SecurityConstant.getRefreshToAuthorization(token))).map(this::findById).orElse(null); } return null; }
2.4 remove(OAuth2Authorization authorization)
java
代码解读
复制代码
@Override public void remove(OAuth2Authorization authorization) { List<String> keysToRemove = new ArrayList<>(); keysToRemove.add(SecurityConstant.getIdToAuthorizationKey(authorization.getId())); RedisUtils.delete(keysToRemove); }
2.5 测试并处理一系列问题
2.5.1 反序列化时间错误
markdown
代码解读
复制代码
Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Java 8 date/time type `java.time.LocalDateTime` not supported by default: add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" to enable handling (through reference chain: org.springframework.security.oauth2.server.authorization.OAuth2Authorization["attributes"]->java.util.Collections$UnmodifiableMap["java.security.Principal"]->org.springframework.security.authentication.UsernamePasswordAuthenticationToken["principal"]->com.roshine.authorization.domain.AccAccountDO["gmtCreate"])
看着像是无法反序列化attributes下principal的gmtCreate时间字段,那我们把JavaTimeModule配好再试试。
java
代码解读
复制代码
public class ObjectMapperUtils { public static ObjectMapper objectMapper() { ObjectMapper objectMapper = new ObjectMapper(); objectMapper.setTimeZone(TimeZone.getTimeZone("GMT+8")); objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); objectMapper.disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE); JavaTimeModule javaTimeModule = new JavaTimeModule(); // 添加序列化 javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN))); javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATE_PATTERN))); javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DatePattern.NORM_TIME_PATTERN))); // 添加反序列化 javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN))); javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATE_PATTERN))); javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DatePattern.NORM_TIME_PATTERN))); objectMapper.registerModules(javaTimeModule); return objectMapper; } }
然后把RedisOAuth2AuthorizationService中的ObjectMapper改成ObjectMapperUtils.objectMapper()再测试试试
java
代码解读
复制代码
@Setter private ObjectMapper objectMapper = ObjectMapperUtils.objectMapper();
2.5.2 点击授权按钮重定向到/oauth2/authorize 400
成功来到授权页,继续下面的操作,发现又报错:
不知道什么原因,大概率是我们刚写的RedisOAuth2AuthorizationService有问题,debug看下情况,发现:点击授权按钮后来到了findByToken方法,找tokenType == state
的信息,我们没有定义返回了null。
那就把它补上再试试。常量类SecurityConstant补充:
java
代码解读
复制代码
/** * state key */ public static final String STATE_TO_AUTHORIZATION = CACHE_TOKEN_PREFIX + "state_to_authorization:"; public static String getStateToAuthorization(String state) { return SecurityConstant.STATE_TO_AUTHORIZATION + generateKey(state); }
save(OAuth2Authorization authorization)下追加state存储
java
代码解读
复制代码
// 点击授权按钮,会根据state查询authorization,所以state也要存起来 Optional.ofNullable(authorization.getAttribute(OAuth2ParameterNames.STATE)).ifPresent(token -> { final String stateToAuthorizationKey = SecurityConstant.getStateToAuthorization((String) token); RedisUtils.setEx(stateToAuthorizationKey, authorizationId, stateLiveTime.getSeconds(), TimeUnit.SECONDS); });
OAuth2Authorization findByToken(String token, OAuth2TokenType tokenType)下追加state查询OAuth2Authorization的方法
java
代码解读
复制代码
else if (OAuth2ParameterNames.STATE.equals(tokenType.getValue())) { return Optional.ofNullable(RedisUtils.get(SecurityConstant.getStateToAuthorization(token))).map(this::findById).orElse(null); }
2.5.3 反序列问题 (重要)
再测试成功通过授权,页面报错500了,有错误信息就接着往下看。错误信息是反序列化问题,这个很重要也很麻烦
markdown
代码解读
复制代码
Caused by: com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `org.springframework.security.oauth2.core.AuthorizationGrantType` (although at least one Creator exists): cannot deserialize from Object value (no delegate- or property-based Creator) at [Source: (String)"{"id":"70362044-b300-4845-a6ce-dc9a90e724d7","registeredClientId":"oauth2-client","principalName":"admin","authorizationGrantType":{"value":"authorization_code"},"authorizedScopes":[],"attributes":{"java.security.Principal":{"authorities":[{"authority":"user"}],"details":{"remoteAddress":"192.168.0.10","sessionId":"40EA09B3FBC4DB6BCC6A5D9EFC8ED54E"},"authenticated":true,"principal":{"id":1,"createBy":1,"createByName":"roshine","gmtCreate":"2022-08-20 17:42:08","modifyBy":1,"modifyByName":"roshin"[truncated 1088 chars]; line: 1, column: 133] (through reference chain: org.springframework.security.oauth2.server.authorization.OAuth2Authorization["authorizationGrantType"])
Jackson反序列化时需要对象的无参构造器,但是org.springframework.security.oauth2.core.AuthorizationGrantType又没有为我们提供,那就只能自定义反序列化器。
网上有这样一篇文章,介绍了两种方式自定义反序列器值得一看《Jackson 解决没有无参构造函数的反序列化问题》,官方也给我们定义了几个Module可以看看。
java
代码解读
复制代码
public class AuthorizationGrantTypeDeserializer extends StdDeserializer<AuthorizationGrantType> { @Serial private static final long serialVersionUID = 2884780317780523184L; public final ObjectMapper objectMapper = new ObjectMapper(); public AuthorizationGrantTypeDeserializer() { super(AuthorizationGrantType.class); } @Override public AuthorizationGrantType deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { Map<String, String> map = objectMapper.readValue(p, new TypeReference<>() {}); return new AuthorizationGrantType(map.values().stream().findFirst().orElse(null)); } }
2.5.4 AuthorizationGrantTypeMixin
java
代码解读
复制代码
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE) public abstract class AuthorizationGrantTypeMixin { @JsonCreator AuthorizationGrantTypeMixin(@JsonProperty("value") String value) { } }
2.5.5 OAuth2AccessTokenMixin
java
代码解读
复制代码
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE) public abstract class OAuth2AccessTokenMixin { @JsonCreator OAuth2AccessTokenMixin(@JsonProperty("tokenType") OAuth2AccessToken.TokenType tokenType, @JsonProperty("tokenValue") String tokenValue, @JsonProperty("issuedAt") Instant issuedAt, @JsonProperty("expiresAt") Instant expiresAt, @JsonProperty("scopes") Set<String> scopes) { } }
2.5.6 OAuth2AuthorizationCodeMixin
java
代码解读
复制代码
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE) public abstract class OAuth2AuthorizationCodeMixin { @JsonCreator OAuth2AuthorizationCodeMixin(@JsonProperty("tokenValue") String tokenValue, @JsonProperty("issuedAt") Instant issuedAt, @JsonProperty("expiresAt") Instant expiresAt) { } }
2.5.7 OAuth2RefreshTokenMixin
java
代码解读
复制代码
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE) public abstract class OAuth2RefreshTokenMixin { @JsonCreator OAuth2RefreshTokenMixin(@JsonProperty("tokenValue") String tokenValue, @JsonProperty("issuedAt") Instant issuedAt, @JsonProperty("expiresAt") Instant expiresAt) { } }
2.5.8 TokenMixin
java
代码解读
复制代码
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE) @JsonIgnoreProperties(ignoreUnknown = true) public abstract class TokenMixin { @JsonCreator TokenMixin(@JsonProperty("token") OAuth2Token token, @JsonProperty("metadata") Map<String, Object> metadata) { } }
2.5.9 TokenTypeMixin
java
代码解读
复制代码
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE) public abstract class TokenTypeMixin { @JsonCreator TokenTypeMixin(@JsonProperty("value") String value) { } }
2.5.10 OAuth2AuthorizationMixin
这里比较重要,OAuth2Authorization内部对象很复杂,通过简单的@JsonCreator已经无法实现,所以需要自定义OAuth2AuthorizationDeserializer
java
代码解读
复制代码
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) @JsonDeserialize(using = OAuth2AuthorizationDeserializer.class) @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE) @JsonIgnoreProperties(ignoreUnknown = true) public abstract class OAuth2AuthorizationMixin { }
java
代码解读
复制代码
public class OAuth2AuthorizationDeserializer extends JsonDeserializer<OAuth2Authorization> { private final RegisteredClientRepository registeredClientRepository; private static final TypeReference<Set<String>> SET_TYPE_REFERENCE = new TypeReference<>() { }; private static final TypeReference<Map<String, Object>> MAP_TYPE_REFERENCE = new TypeReference<>() { }; private static final TypeReference<AuthorizationGrantType> GRANT_TYPE_TYPE_REFERENCE = new TypeReference<>() { }; public OAuth2AuthorizationDeserializer(RegisteredClientRepository registeredClientRepository) { this.registeredClientRepository = registeredClientRepository; } @Override public OAuth2Authorization deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { ObjectMapper mapper = (ObjectMapper) jp.getCodec(); JsonNode jsonNode = mapper.readTree(jp); Set<String> authorizedScopes = mapper.convertValue(jsonNode.get("authorizedScopes"), SET_TYPE_REFERENCE); Map<String, Object> attributes = mapper.convertValue(jsonNode.get("attributes"), MAP_TYPE_REFERENCE); Map<String, Object> tokens = mapper.convertValue(jsonNode.get("tokens"), MAP_TYPE_REFERENCE); AuthorizationGrantType grantType = mapper.convertValue(jsonNode.get("authorizationGrantType"), GRANT_TYPE_TYPE_REFERENCE); String id = readJsonNode(jsonNode, "id").asText(); String registeredClientId = readJsonNode(jsonNode, "registeredClientId").asText(); String principalName = readJsonNode(jsonNode, "principalName").asText(); RegisteredClient registeredClient = registeredClientRepository.findById(registeredClientId); Assert.notNull(registeredClient, "Registered client must not be null"); Builder builder = withRegisteredClient(registeredClient) .id(id) .principalName(principalName) .authorizationGrantType(grantType) .authorizedScopes(authorizedScopes) .attributes(map -> map.putAll(attributes)); Optional.ofNullable(tokens.get(OAuth2AuthorizationCode.class.getName())).ifPresent( token -> addToken((Token) token, builder)); Optional.ofNullable(tokens.get(OAuth2AccessToken.class.getName())).ifPresent( token -> addToken((Token) token, builder)); Optional.ofNullable(tokens.get(OAuth2RefreshToken.class.getName())).ifPresent( token -> addToken((Token) token, builder)); Optional.ofNullable(tokens.get(OidcIdToken.class.getName())).ifPresent( token -> addToken((Token) token, builder)); return builder.build(); } public void addToken(Token<OAuth2Token> token, Builder builder) { builder.token(token.getToken(), map -> map.putAll(token.getMetadata())); } private JsonNode readJsonNode(JsonNode jsonNode, String field) { return jsonNode.has(field) ? jsonNode.get(field) : MissingNode.getInstance(); } }
然后加入到RedisOAuth2AuthorizationService构造器中。
java
代码解读
复制代码
public RedisOAuth2AuthorizationService(RegisteredClientRepository clientRepository) { this.clientRepository = clientRepository; objectMapper.registerModule(new OAuth2AuthorizationModule()); }
2.5.11 OAuth2AuthorizationDeserializer内部对象没有无参构造器错误
markdown
代码解读
复制代码
Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Class com.roshine.authorization.deserializer.OAuth2AuthorizationDeserializer has no default (no arg) constructor at [Source: (String)"{"@class":"org.springframework.security.oauth2.server.authorization.OAuth2Authorization","id":"56b7e0b2-0366-49a6-899f-17bc3f0fe79f","registeredClientId":"oauth2-client","principalName":"admin","authorizationGrantType":{"@class":"org.springframework.security.oauth2.core.AuthorizationGrantType","value":"authorization_code"},"authorizedScopes":["java.util.Collections$UnmodifiableSet",[]],"tokens":{"@class":"java.util.Collections$UnmodifiableMap"},"attributes":{"@class":"java.util.Collections$Unmod"[truncated 2130 chars]; line: 1, column: 1]
说我们自定义的OAuth2AuthorizationDeserializer没有无参构造器,那尝试提供一个无参构造,就变成这样
java
代码解读
复制代码
private RegisteredClientRepository registeredClientRepository; private static final TypeReference<Set<String>> SET_TYPE_REFERENCE = new TypeReference<>() { }; private static final TypeReference<Map<String, Object>> MAP_TYPE_REFERENCE = new TypeReference<>() { }; private static final TypeReference<AuthorizationGrantType> GRANT_TYPE_TYPE_REFERENCE = new TypeReference<>() { }; public OAuth2AuthorizationDeserializer() {} public OAuth2AuthorizationDeserializer(RegisteredClientRepository registeredClientRepository) { this.registeredClientRepository = registeredClientRepository; }
2.5.12 Collections$UnmodifiableSet is not in the allowlist.
此时没有再提示:OAuth2AuthorizationDeserializer has no default (no arg) constructor错误了,有了新的错误,接着往下。
markdown
代码解读
复制代码
Caused by: java.lang.IllegalArgumentException: The class with java.util.Collections$UnmodifiableSet and name of java.util.Collections$UnmodifiableSet is not in the allowlist. If you believe this class is safe to deserialize, please provide an explicit mapping using Jackson annotations or by providing a Mixin. If the serialization is only done by a trusted source, you can also enable default typing. See https://github.com/spring-projects/spring-security/issues/4370 for details
我们发现就是一些简单数据类型的反序列化失败,翻看github的issue,也翻看了一些文章说试着找找类加载器里有什么module,都加进来再试试。
java
代码解读
复制代码
public RedisOAuth2AuthorizationService(RegisteredClientRepository clientRepository) { this.clientRepository = clientRepository; objectMapper.registerModule(new OAuth2AuthorizationModule()); ClassLoader classLoader = this.getClass().getClassLoader(); List<Module> securityModules = SecurityJackson2Modules.getModules(classLoader); objectMapper.registerModules(securityModules); }
2.5.13 java.util.HashSet is not in the allowlist
发现可以了,没有再提示UnmodifiableSet is not in the allowlist,提示了以下错误,也是常见数据类型反序列化失败,翻看源码发现给我们提供了一个OAuth2AuthorizationServerJackson2Module,里面就包含了我们需要的类型。
markdown
代码解读
复制代码
Caused by: java.lang.IllegalArgumentException: The class with java.util.HashSet and name of java.util.HashSet is not in the allowlist. If you believe this class is safe to deserialize, please provide an explicit mapping using Jackson annotations or by providing a Mixin. If the serialization is only done by a trusted source, you can also enable default typing. See https://github.com/spring-projects/spring-security/issues/4370 for details
加进来就变成了这样
java
代码解读
复制代码
public RedisOAuth2AuthorizationService(RegisteredClientRepository clientRepository) { this.clientRepository = clientRepository; objectMapper.registerModule(new OAuth2AuthorizationModule()); ClassLoader classLoader = this.getClass().getClassLoader(); List<Module> securityModules = SecurityJackson2Modules.getModules(classLoader); objectMapper.registerModules(securityModules); objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module()); }
很好,没有反序列化的报错了,但是又出现了新的报错。
java
代码解读
复制代码
Caused by: java.lang.NullPointerException: Cannot invoke "org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository.findById(String)" because "this.registeredClientRepository" is null
这一看就是我们上面加的OAuth2AuthorizationDeserializer加的无参构造器引起的,实例化对象的时候默认调用了无参构造器,导致我们的registeredClientRepository为null,所以不能添加无参构造器,但是不添加又会报OAuth2AuthorizationDeserializer内部对象没有无参构造器错误。查阅一些文章得到了解决方法。 这种方式为什么能解决这个问题还不是很清楚,希望有大佬看到这里能解解惑。
先把OAuth2AuthorizationDeserializer无参构造去掉
,然后RedisOAuth2AuthorizationService构造器中添加AutowireCapableBeanFactory beanFactory。
java
代码解读
复制代码
public RedisOAuth2AuthorizationService(RegisteredClientRepository clientRepository, AutowireCapableBeanFactory beanFactory) { this.clientRepository = clientRepository; objectMapper.registerModule(new OAuth2AuthorizationModule()); ClassLoader classLoader = this.getClass().getClassLoader(); List<Module> securityModules = SecurityJackson2Modules.getModules(classLoader); objectMapper.registerModules(securityModules); objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module()); objectMapper.setHandlerInstantiator(new SpringHandlerInstantiator(beanFactory)); }
java
代码解读
复制代码
@Bean OAuth2AuthorizationService auth2AuthorizationService(RegisteredClientRepository registeredClientRepository, AutowireCapableBeanFactory beanFactory) { return new RedisOAuth2AuthorizationService(registeredClientRepository, beanFactory); }
2.6 最终达到目的
最终成功获取到授权码,然后就可以去拿我们的token了。 www.baidu.com/?code=DMtKb…
这篇文章写的很长,本次实现断断续续持续了很长时间看起来可能有点乱希望可以耐心看。但是这是整套开发的必经过程。 如果发现有哪里不对或者能优化的还望不吝赐教。