前面一段时间做了通用的sso认证服务auth,使用的框架spring security oauth2,刚开始弄不熟悉,很想看看redis中关于token和会话中存储了哪些信息,
由于默认采用的是jdk序列化,就很苦恼查询了一堆资料,国内的没有一个能正常使用fastjson序列化的就先搁置了。念念不忘,必有回响。在外网终于找到了
一份能够正常序列化和反序列化的文章,虽然也有问题,但问题不大,我也填完坑了,会把填坑的地方贴上来
http://programmersought.com/article/82796400667
update: 此链接已不能访问2021-08-27
下面是我自己的序列化和反序列化的代码,仅供参考
1.spring security版本 2.3.2
2.序列化配置
@Bean public TokenStore tokenStore() { RedisTokenStore tokenStore = new RedisTokenStore(redisConnectionFactory); tokenStore.setSerializationStrategy(new FastjsonRedisTokenStoreSerializationStrategy()); return tokenStore; }
3.序列化类
redis序列化配置

import com.alibaba.fastjson.support.spring.GenericFastJsonRedisSerializer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.StringRedisSerializer; /** * @author wangzhihui * @date 2020/10/19 */ @Configuration public class RedisConfig { /** * 配置RedisTemplate参数 */ @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) { GenericFastJsonRedisSerializer genericFastJsonRedisSerializer = serializer(); RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); // key采用String的序列化方式 redisTemplate.setKeySerializer(StringRedisSerializer.UTF_8); // value序列化方式采用fastjson redisTemplate.setValueSerializer(genericFastJsonRedisSerializer); // hash的key也采用String的序列化方式 redisTemplate.setHashKeySerializer(StringRedisSerializer.UTF_8); //hash的value序列化方式采用 redisTemplate.setHashValueSerializer(genericFastJsonRedisSerializer); redisTemplate.setConnectionFactory(redisConnectionFactory); return redisTemplate; } /** * 此方法不能用@Ben注解,避免替换Spring容器中的同类型对象 */ public GenericFastJsonRedisSerializer serializer() { return new GenericFastJsonRedisSerializer(); } }
security相关序列化类

import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.parser.ParserConfig; import com.alibaba.fastjson.serializer.SerializerFeature; import com.alibaba.fastjson.util.IOUtils; import com.alibaba.fastjson.util.TypeUtils; import com.google.common.base.Preconditions; import org.springframework.security.authentication.RememberMeAuthenticationToken; import org.springframework.security.oauth2.common.DefaultExpiringOAuth2RefreshToken; import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; import org.springframework.security.oauth2.common.DefaultOAuth2RefreshToken; import org.springframework.security.oauth2.common.exceptions.SerializationException; import org.springframework.security.oauth2.provider.OAuth2Authentication; import org.springframework.security.oauth2.provider.client.BaseClientDetails; import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStoreSerializationStrategy; import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; /** * @author suidd * @name FastjsonRedisTokenStoreSerializationStrategy * @description fastjson redis storage json format serialization deserialization tool class * @date 2020/4/15 9:36 * Version 1.0 **/ public class FastjsonRedisTokenStoreSerializationStrategy implements RedisTokenStoreSerializationStrategy { private static ParserConfig config = new ParserConfig(); static { init(); } protected static void init() { //Custom oauth2 serialization: DefaultOAuth2RefreshToken has no setValue method, which will cause JSON serialization to null config.setAutoTypeSupport(true);//Open AutoType //Custom DefaultOauth2RefreshTokenSerializer deserialization config.putDeserializer(DefaultOAuth2RefreshToken.class, new DefaultOauth2RefreshTokenSerializer()); //Custom OAuth2Authentication deserialization config.putDeserializer(OAuth2Authentication.class, new OAuth2AuthenticationSerializer()); //Add autotype whitelist config.addAccept("org.springframework.security.oauth2.provider."); config.addAccept("org.springframework.security.oauth2.provider.client"); config.addAccept("org.springframework.security.authentication"); TypeUtils.addMapping("org.springframework.security.oauth2.provider.OAuth2Authentication", OAuth2Authentication.class); TypeUtils.addMapping("org.springframework.security.oauth2.provider.client.BaseClientDetails", BaseClientDetails.class); TypeUtils.addMapping("org.springframework.security.authentication.RememberMeAuthenticationToken", RememberMeAuthenticationToken.class); config.addAccept("org.springframework.security.oauth2.common."); TypeUtils.addMapping("org.springframework.security.oauth2.common.DefaultOAuth2AccessToken", DefaultOAuth2AccessToken.class); TypeUtils.addMapping("org.springframework.security.oauth2.common.DefaultExpiringOAuth2RefreshToken", DefaultExpiringOAuth2RefreshToken.class); config.addAccept("com.aicloud.common.core.entity"); TypeUtils.addMapping("com.aicloud.common.core.entity.BaseUser", BaseUser.class); config.addAccept("org.springframework.security.web.authentication.preauth"); TypeUtils.addMapping("org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken", PreAuthenticatedAuthenticationToken.class); } @Override public <T> T deserialize(byte[] bytes, Class<T> aClass) { Preconditions.checkArgument(aClass != null, "clazz can't be null"); if (bytes == null || bytes.length == 0) { return null; } try { return JSON.parseObject(new String(bytes, IOUtils.UTF8), aClass, config); } catch (Exception ex) { throw new SerializationException("Could not serialize: " + ex.getMessage(), ex); } } @Override public String deserializeString(byte[] bytes) { if (bytes == null || bytes.length == 0) { return null; } return new String(bytes, IOUtils.UTF8); } @Override public byte[] serialize(Object o) { if (o == null) { return new byte[0]; } try { return JSON.toJSONBytes(o, SerializerFeature.WriteClassName, SerializerFeature.DisableCircularReferenceDetect); } catch (Exception ex) { throw new SerializationException("Could not serialize: " + ex.getMessage(), ex); } } @Override public byte[] serialize(String data) { if (data == null || data.length() == 0) { return new byte[0]; } return data.getBytes(StandardCharsets.UTF_8); } }

/** * @author suidd * @name DefaultOauth2RefreshTokenSerializer * @description Customize the default refresh token serialization tool class * @date 2020/4/15 9:45 * Version 1.0 **/ public class DefaultOauth2RefreshTokenSerializer implements ObjectDeserializer { @Override public <T> T deserialze(DefaultJSONParser parser, Type type, Object fieldName) { if (type == DefaultOAuth2RefreshToken.class) { JSONObject jsonObject = parser.parseObject(); String tokenId = jsonObject.getString("value"); DefaultOAuth2RefreshToken refreshToken = new DefaultOAuth2RefreshToken(tokenId); return (T) refreshToken; } return null; } @Override public int getFastMatchToken() { return 0; } }

/** * @author suidd * @name OAuth2AuthenticationSerializer * @description custom OAuth2 authentication serialization tool class * @date 2020/4/15 9:43 * Version 1.0 **/ public class OAuth2AuthenticationSerializer implements ObjectDeserializer { @Override public <T> T deserialze(DefaultJSONParser parser, Type type, Object fieldName) { if (type == OAuth2Authentication.class) { try { Object o = parse(parser); if (o == null) { return null; } else if (o instanceof OAuth2Authentication) { return (T) o; } JSONObject jsonObject = (JSONObject) o; OAuth2Request request = parseOAuth2Request(jsonObject); //Determine the type of userAuthentication of the json node, and dynamically obtain the value according to the type //UsernamePasswordAuthenticationToken password mode/authorization code mode, the storage type is UsernamePasswordAuthenticationToken //PreAuthenticatedAuthenticationToken refresh token mode, the storage type is PreAuthenticatedAuthenticationToken Object autoType = jsonObject.get("userAuthentication"); return (T) new OAuth2Authentication(request, jsonObject.getObject("userAuthentication", (Type) autoType.getClass())); } catch (Exception e) { e.printStackTrace(); } return null; } return null; } private OAuth2Request parseOAuth2Request(JSONObject jsonObject) { JSONObject json = jsonObject.getObject("oAuth2Request", JSONObject.class); Map<String, String> requestParameters = json.getObject("requestParameters", Map.class); String clientId = json.getString("clientId"); String grantType = json.getString("grantType"); String redirectUri = json.getString("redirectUri"); Boolean approved = json.getBoolean("approved"); Set<String> responseTypes = json.getObject("responseTypes", new TypeReference<HashSet<String>>() { }); Set<String> scope = json.getObject("scope", new TypeReference<HashSet<String>>() { }); Set<String> authorities = json.getObject("authorities", new TypeReference<HashSet<String>>() { }); Set<GrantedAuthority> grantedAuthorities = new HashSet<>(0); if (authorities != null && !authorities.isEmpty()) { authorities.forEach(s -> grantedAuthorities.add(new SimpleGrantedAuthority(s))); } Set<String> resourceIds = json.getObject("resourceIds", new TypeReference<HashSet<String>>() { }); Map<String, Serializable> extensions = json.getObject("extensions", new TypeReference<HashMap<String, Serializable>>() { }); OAuth2Request request = new OAuth2Request(requestParameters, clientId, grantedAuthorities, approved, scope, resourceIds, redirectUri, responseTypes, extensions); TokenRequest tokenRequest = new TokenRequest(requestParameters, clientId, scope, grantType); request.refresh(tokenRequest); return request; } @Override public int getFastMatchToken() { return 0; } private Object parse(DefaultJSONParser parse) { JSONObject object = new JSONObject(parse.lexer.isEnabled(Feature.OrderedField)); Object parsedObject = parse.parseObject((Map) object); if (parsedObject instanceof JSONObject) { return (JSONObject) parsedObject; } else if (parsedObject instanceof OAuth2Authentication) { return parsedObject; } else { return parsedObject == null ? null : new JSONObject((Map) parsedObject); } } }
4.遇到的坑
remeber-me相关的类RememberMeAuthenticationToken构造方法和fastjson序列化构造方法有冲突
需要覆盖掉类RememberMeAuthenticationToken,修改默认构造方法位置。

/** * 为什么覆盖本类: RememberMeAuthenticationToken 构造方法{@linkplain RememberMeAuthenticationToken#RememberMeAuthenticationToken(Integer keyHash, Object, Collection)} * 是私有方法在使用fastjson反序列化的时候,会默认使用非私有的构造方法,就导致创建对象失败,因此把私有方法改为 public * <p> * Represents a remembered <code>Authentication</code>. * <p> * A remembered <code>Authentication</code> must provide a fully valid * <code>Authentication</code>, including the <code>GrantedAuthority</code>s that apply. * * @author Ben Alex * @author Luke Taylor */ public class RememberMeAuthenticationToken extends AbstractAuthenticationToken { private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID; // ~ Instance fields // ================================================================================================ private final Object principal; private final int keyHash; // ~ Constructors // =================================================================================================== /** * Private Constructor to help in Jackson deserialization. * * @param keyHash hashCode of above given key. * @param principal the principal (typically a <code>UserDetails</code>) * @param authorities the authorities granted to the principal * @since 4.2 */ public RememberMeAuthenticationToken(Integer keyHash, Object principal, Collection<? extends GrantedAuthority> authorities) { super(authorities); this.keyHash = keyHash; this.principal = principal; setAuthenticated(true); } /** * Constructor. * * @param key to identify if this object made by an authorised client * @param principal the principal (typically a <code>UserDetails</code>) * @param authorities the authorities granted to the principal * @throws IllegalArgumentException if a <code>null</code> was passed */ public RememberMeAuthenticationToken(String key, Object principal, Collection<? extends GrantedAuthority> authorities) { super(authorities); if ((key == null) || ("".equals(key)) || (principal == null) || "".equals(principal)) { throw new IllegalArgumentException( "Cannot pass null or empty values to constructor"); } this.keyHash = key.hashCode(); this.principal = principal; setAuthenticated(true); } // ~ Methods // ======================================================================================================== /** * Always returns an empty <code>String</code> * * @return an empty String */ @Override public Object getCredentials() { return ""; } public int getKeyHash() { return this.keyHash; } @Override public Object getPrincipal() { return this.principal; } @Override public boolean equals(Object obj) { if (!super.equals(obj)) { return false; } if (obj instanceof RememberMeAuthenticationToken) { RememberMeAuthenticationToken test = (RememberMeAuthenticationToken) obj; if (this.getKeyHash() != test.getKeyHash()) { return false; } return true; } return false; } @Override public int hashCode() { int result = super.hashCode(); result = 31 * result + this.keyHash; return result; } }
5.如何覆盖jar包中的类,加载自己本地的类
1.在工程下创建同名包名package
2.添加同名的类,修改其中的代码