Spring Security Oauth2:RedisTokenStore之JSON序列化


  前面一段时间做了通用的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();
    }


}
View Code

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);
    }
}
View Code
/**
 * @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;
    }
}
View Code
/**
 * @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);
        }
    }
}
View Code

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;
    }
}
View Code

 

5.如何覆盖jar包中的类,加载自己本地的类

1.在工程下创建同名包名package

2.添加同名的类,修改其中的代码

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM