博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Spring Security OAuth2 缓存使用jackson序列化的处理
阅读量:5739 次
发布时间:2019-06-18

本文共 10452 字,大约阅读时间需要 34 分钟。

不知道这个问题有没有人遇到或者处理过,Spring Security OAuth2的tokenStore的redis缓存默认的序列化策略是jdk序列化,这意味着redis里面的值是无法阅读的状态,而且这个缓存也无法被其他语言的web应用所使用,于是就打算使用最常见的json序列化策略来存储。

这个问题想处理很久了,虽然现在也能正常使用,但是之前一直没有时间仔细的去研究解决方案,所以今天花了些时间搞定并分享给大家。

RedisTokenStore中序列化策略的声明代码如下:

private RedisTokenStoreSerializationStrategy serializationStrategy = new JdkSerializationStrategy();复制代码

改为json序列化需要实现接口 RedisTokenStoreSerializationStrategy,该接口在Spring的源码中并没有提供json序列化策略的实现,可见Spring官方并没有对OAuth2默认支持json序列化。

由于项目需要,并没有在RedisTokenStore中注入新的SerializationStrategy,而是重写了TokenStore,本质是没有区别的。 在TokenStore中创建一个GenericJackson2JsonRedisSerializer对象,并不是RedisTokenStoreSerializationStrategy的实现,反正只要能对对象进行序列化和反序列化就行了,相关代码如下:

private val jacksonSerializer = buildSerializer()    private fun buildMapper(): ObjectMapper {    val mapper = createObjectMapper()    mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY)    mapper.disable(MapperFeature.AUTO_DETECT_SETTERS)    mapper.registerModule(CoreJackson2Module())    mapper.registerModule(WebJackson2Module())    return mapper  }  private fun buildSerializer(): GenericJackson2JsonRedisSerializer {    return GenericJackson2JsonRedisSerializer(buildMapper())  }复制代码

以为这样就OK了吗,too young!

来看一下对 OAuth2AccessToken 进行序列化的时候发生了什么

org.springframework.data.redis.serializer.SerializationException: Could not write JSON: Type id handling not implemented for type org.springframework.security.oauth2.common.OAuth2AccessToken (by serializer of type org.springframework.security.oauth2.common.OAuth2AccessTokenJackson2Serializer); nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Type id handling not implemented for type org.springframework.security.oauth2.common.OAuth2AccessToken (by serializer of type org.springframework.security.oauth2.common.OAuth2AccessTokenJackson2Serializer)复制代码

我们再来看看 OAuth2AccessToken 的源码

@org.codehaus.jackson.map.annotate.JsonSerialize(using = OAuth2AccessTokenJackson1Serializer.class)@org.codehaus.jackson.map.annotate.JsonDeserialize(using = OAuth2AccessTokenJackson1Deserializer.class)@com.fasterxml.jackson.databind.annotation.JsonSerialize(using = OAuth2AccessTokenJackson2Serializer.class)@com.fasterxml.jackson.databind.annotation.JsonDeserialize(using = OAuth2AccessTokenJackson2Deserializer.class)public interface OAuth2AccessToken {……复制代码

没错,Spring提供了对jackson序列化的支持,而且1.x和2.x都有。But,为什么还是会报错呢,我们来看一下 OAuth2AccessTokenJackson1Serializer 做了什么

public OAuth2AccessTokenJackson1Serializer() {    super(OAuth2AccessToken.class);}@Overridepublic void serialize(OAuth2AccessToken token, JsonGenerator jgen, SerializerProvider provider) throws IOException,	JsonGenerationException {...复制代码

这个Serializer的代码在刚才的报错中并没有执行,也就是说在序列化之前就报错了,这是为什么呢?因为它缺了点东西:

override fun serializeWithType(token: OAuth2AccessToken, jgen: JsonGenerator, serializers: SerializerProvider,                                 typeSer: TypeSerializer?) {    ser(token, jgen, serializers, typeSer)}复制代码

如果要在序列化时写入类型信息,必须要重载 serializeWithType 方法

所以我们需要自己写OAuth2AccessToken的Serializer:

/** * * @author 吴昊 * @since 2.2.1 */class AccessTokenJackson2Serializer : StdSerializer
(OAuth2AccessToken::class.java) { @Throws(IOException::class) override fun serialize(token: OAuth2AccessToken, jgen: JsonGenerator, provider: SerializerProvider) { ser(token, jgen, provider, null) } override fun serializeWithType(token: OAuth2AccessToken, jgen: JsonGenerator, serializers: SerializerProvider, typeSer: TypeSerializer?) { ser(token, jgen, serializers, typeSer) } private fun ser(token: OAuth2AccessToken, jgen: JsonGenerator, provider: SerializerProvider, typeSer: TypeSerializer?) { jgen.writeStartObject() if (typeSer != null) { jgen.writeStringField(typeSer.propertyName, token::class.java.name) } jgen.writeStringField(OAuth2AccessToken.ACCESS_TOKEN, token.value) jgen.writeStringField(OAuth2AccessToken.TOKEN_TYPE, token.tokenType) val refreshToken = token.refreshToken if (refreshToken != null) { jgen.writeStringField(OAuth2AccessToken.REFRESH_TOKEN, refreshToken.value) } val expiration = token.expiration if (expiration != null) { val now = System.currentTimeMillis() jgen.writeNumberField(OAuth2AccessToken.EXPIRES_IN, (expiration.time - now) / 1000) } val scope = token.scope if (scope != null && !scope.isEmpty()) { val scopes = StringBuffer() for (s in scope) { Assert.hasLength(s, "Scopes cannot be null or empty. Got $scope") scopes.append(s) scopes.append(" ") } jgen.writeStringField(OAuth2AccessToken.SCOPE, scopes.substring(0, scopes.length - 1)) } val additionalInformation = token.additionalInformation for (key in additionalInformation.keys) { jgen.writeObjectField(key, additionalInformation[key]) } jgen.writeEndObject() }}复制代码

反序列化的Deserializer也要重写:

fun JsonNode.readJsonNode(field: String): JsonNode? {  return if (this.has(field)) {    this.get(field)  } else {    null  }}/** * * @author 吴昊 * @since 2.2.1 */class AccessTokenJackson2Deserializer : StdDeserializer
(OAuth2AccessToken::class.java) { @Throws(IOException::class, JsonProcessingException::class) override fun deserialize(jp: JsonParser, ctxt: DeserializationContext): OAuth2AccessToken { val additionalInformation = LinkedHashMap
() val mapper = jp.codec as ObjectMapper val jsonNode = mapper.readTree
(jp) val tokenValue: String? = jsonNode.readJsonNode(ACCESS_TOKEN)?.asText() val tokenType: String? = jsonNode.readJsonNode(TOKEN_TYPE)?.asText() val refreshToken: String? = jsonNode.readJsonNode(REFRESH_TOKEN)?.asText() val expiresIn: Long? = jsonNode.readJsonNode(EXPIRES_IN)?.asLong() val scopeNode = jsonNode.readJsonNode(SCOPE) val scope: Set
? = if (scopeNode != null) { if (scopeNode.isArray) { scopeNode.map { it.asText() }.toSet() } else { OAuth2Utils.parseParameterList(scopeNode.asText()) } } else { null } jsonNode.fieldNames().asSequence().filter { it !in listOf( ACCESS_TOKEN, TOKEN_TYPE, REFRESH_TOKEN, EXPIRES_IN, SCOPE ) }.forEach { name -> additionalInformation[name] = mapper.readValue(jsonNode.get(name).traverse(mapper), Any::class.java) } // TODO What should occur if a required parameter (tokenValue or tokenType) is missing? val accessToken = DefaultOAuth2AccessToken(tokenValue) accessToken.tokenType = tokenType if (expiresIn != null) { accessToken.expiration = Date(System.currentTimeMillis() + expiresIn * 1000) } if (refreshToken != null) { accessToken.refreshToken = DefaultOAuth2RefreshToken(refreshToken) } accessToken.scope = scope accessToken.additionalInformation = additionalInformation return accessToken } override fun deserializeWithType(jp: JsonParser, ctxt: DeserializationContext, typeDeserializer: TypeDeserializer?): Any { return des(jp, ctxt, typeDeserializer) } private fun des(jp: JsonParser, ctxt: DeserializationContext, typeDeserializer: TypeDeserializer?): DefaultOAuth2AccessToken { return des(jp, ctxt, typeDeserializer) } @Throws(JsonParseException::class, IOException::class) private fun parseScope(jp: JsonParser): Set
{ val scope: MutableSet
if (jp.currentToken == JsonToken.START_ARRAY) { scope = TreeSet() while (jp.nextToken() != JsonToken.END_ARRAY) { scope.add(jp.valueAsString) } } else { val text = jp.text scope = OAuth2Utils.parseParameterList(text) } return scope }}复制代码

但是,如何覆盖OAuth2AccessToken接口上的注解呢?使用jackson的注解混入,创建混入类:

/** * * @author 吴昊 * @since 2.2.1 */@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, property = "@class")@com.fasterxml.jackson.databind.annotation.JsonSerialize(using = AccessTokenJackson2Serializer::class)@com.fasterxml.jackson.databind.annotation.JsonDeserialize(using = AccessTokenJackson2Deserializer::class)abstract class AccessTokenMixIn复制代码

这个类是abstract抑或不是并没有什么关系,jackson只会读取类上的注解

mapper中注册混入类

mapper.addMixIn(OAuth2AccessToken::class.java, AccessTokenMixIn::class.java)复制代码

可以正确序列化和反序列化了吗,是的,可以了。但是,还没有结束,因为TokenStore中不仅要序列化OAuth2AccessToken,还要序列化OAuth2Authentication: 看一下错误:

org.springframework.data.redis.serializer.SerializationException: Could not read JSON: Cannot construct instance of `org.springframework.security.oauth2.provider.OAuth2Authentication` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)复制代码

OAuth2Authentication 因为没有默认构造函数,不能反序列化(序列化是可以的)

实现OAuth2Authentication的deserializer

/** * * @author 吴昊 * @since 2.2.1 */class OAuth2AuthenticationDeserializer : JsonDeserializer
() { @Throws(IOException::class, JsonProcessingException::class) override fun deserialize(jp: JsonParser, ctxt: DeserializationContext): OAuth2Authentication { var token: OAuth2Authentication? = null val mapper = jp.codec as ObjectMapper val jsonNode = mapper.readTree
(jp) val requestNode = jsonNode.readJsonNode("storedRequest") val userAuthenticationNode = jsonNode.readJsonNode("userAuthentication") val request = mapper.readValue(requestNode!!.traverse(mapper), OAuth2Request::class.java) var auth: Authentication? = null if (userAuthenticationNode != null && userAuthenticationNode !is MissingNode) { auth = mapper.readValue(userAuthenticationNode.traverse(mapper), UsernamePasswordAuthenticationToken::class.java) } token = OAuth2Authentication(request, auth) val detailsNode = jsonNode.readJsonNode("details") if (detailsNode != null && detailsNode !is MissingNode) { token.details = mapper.readValue(detailsNode.traverse(mapper), OAuth2AuthenticationDetails::class.java) } return token }}复制代码

混入类

/** * * @author 吴昊 * @since 2.2.1 */@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, property = "@class")@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE)@JsonDeserialize(using = OAuth2AuthenticationDeserializer::class)internal abstract class OAuth2AuthenticationMixin复制代码

限于篇幅,不再过多的讲述其他问题,需要注意的是,mapper还是需要注册两个module,是Spring源码中提供的

mapper.registerModule(CoreJackson2Module())mapper.registerModule(WebJackson2Module())复制代码

这样jackson才能完全正确的序列化 OAuth2AccessToken 和 OAuth2Authentication

转载地址:http://xtfzx.baihongyu.com/

你可能感兴趣的文章
程序员该懂一点儿KPI
查看>>
R语言的三种聚类方法
查看>>
55%受访企业将物联网战略视为有效竞争手段
查看>>
深入理解Python中的ThreadLocal变量(上)
查看>>
如果一切即服务,为什么需要数据中心?
查看>>
《游戏开发物理学(第2版)》一导读
查看>>
Erlang简史(翻译)
查看>>
深入实践Spring Boot2.4.2 节点和关系实体建模
查看>>
信息可视化的经典案例:伦敦地铁线路图
查看>>
10个巨大的科学难题需要大数据解决方案
查看>>
Setting Up a Kerberos server (with Debian/Ubuntu)
查看>>
Node.js Undocumented(1)
查看>>
用 ThreadLocal 管理用户session
查看>>
setprecision后是要四舍五入吗?
查看>>
shiro初步 shiro授权
查看>>
上云就是这么简单——阿里云10分钟快速入门
查看>>
MFC多线程的创建,包括工作线程和用户界面线程
查看>>
我的友情链接
查看>>
FreeNAS8 ISCSI target & initiator for linux/windows
查看>>
cvs文件提交冲突解决方案
查看>>