Spring Security Oauth2:RedisTokenStore修改默认序列化方式,由JDK改为JSON

RedisTokenStore:Json序列化
一. 前言
Spring Security Oauth2 存储Token的方式有多种, 比如JWT、Jdbc(数据库)、Redis等,但是对于一个大型的分布式服务应用,Redis存储方式应该是最佳选择。

二. 问题
我们使用默认的Redis存储方式,序列化到到Redis的数据是采用JDK序列化策略写入到redis的。这样对于程序的功能毫无影响,但是对于开发者却很不直观,出现问题,也不容易排查,我们能不能把它们序列化成JSON格式呢?

默认序列化方式
Spring Security Oauth2 Redis序列化Token相关的数据是采用JdkSerializationStrategy,具体的代码如下:

//org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore

public class RedisTokenStore implements TokenStore {

    private static final String ACCESS = "access:";
    private static final String AUTH_TO_ACCESS = "auth_to_access:";
    private static final String AUTH = "auth:";
    private static final String REFRESH_AUTH = "refresh_auth:";
    private static final String ACCESS_TO_REFRESH = "access_to_refresh:";
    private static final String REFRESH = "refresh:";
    private static final String REFRESH_TO_ACCESS = "refresh_to_access:";
    private static final String CLIENT_ID_TO_ACCESS = "client_id_to_access:";
    private static final String UNAME_TO_ACCESS = "uname_to_access:";

    private static final boolean springDataRedis_2_0 = ClassUtils.isPresent(
            "org.springframework.data.redis.connection.RedisStandaloneConfiguration",
            RedisTokenStore.class.getClassLoader());

    private final RedisConnectionFactory connectionFactory;
    private AuthenticationKeyGenerator authenticationKeyGenerator = new DefaultAuthenticationKeyGenerator();
    //Jdk序列方式
    private RedisTokenStoreSerializationStrategy serializationStrategy = new JdkSerializationStrategy();
    ......省略无关代码


Fastjson序列化策略
使用Fastjson实现了一个序列化策略,并注入到Spring Bean容器中,代码如下:

FastjsonRedisTokenStoreSerializationStrategy 工具类,自定义反序列化设计器和反序列化时需要添加的白名单。
/**
 * @author suidd
 * @name FastjsonRedisTokenStoreSerializationStrategy
 * @description fastjson redis存储json格式序列化反序列化工具类
 * @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() {
        //自定义oauth2序列化:DefaultOAuth2RefreshToken 没有setValue方法,会导致JSON序列化为null
        config.setAutoTypeSupport(true);//开启AutoType
        自定义DefaultOauth2RefreshTokenSerializer反序列化
        config.putDeserializer(DefaultOAuth2RefreshToken.class, new DefaultOauth2RefreshTokenSerializer());
        自定义OAuth2Authentication反序列化
        config.putDeserializer(OAuth2Authentication.class, new OAuth2AuthenticationSerializer());
        //添加autotype白名单
        config.addAccept("org.springframework.security.oauth2.provider.");
        config.addAccept("org.springframework.security.oauth2.provider.client");
        TypeUtils.addMapping("org.springframework.security.oauth2.provider.OAuth2Authentication",
                OAuth2Authentication.class);
        TypeUtils.addMapping("org.springframework.security.oauth2.provider.client.BaseClientDetails",
                BaseClientDetails.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("cn.com.huak.securityoauth.entity");
        TypeUtils.addMapping("cn.com.huak.securityoauth.entity.UserDetailsEntity", UserDetailsEntity.class);

        config.addAccept("org.springframework.security.web.authentication.preauth");
        TypeUtils.addMapping("org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken", PreAuthenticatedAuthenticationToken.class);
    }

    @Override
    public  T deserialize(byte[] bytes, Class 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(Charset.forName("utf-8"));
    }
}

 自定义UserDetails实体,继承自org.springframework.security.core.userdetails下的UserDetails

/**
 * @author suidd
 * @name UserDetailsEntity
 * @description 用户详情实体类
 * @date 2020/4/7 17:39
 * Version 1.0
 **/
@Data
public class UserDetailsEntity implements UserDetails {
    private static final long serialVersionUID = 8081363717997957932L;
    /**
     * 帐号
     */
    private String id;
    /**
     * 用户名
     */
    private String username;
    /**
     * 密码
     */
    private String password;
    /**
     * 组织ID
     */
    private String orgId;
    /**
     * 组织名称
     */
    private String orgName;
    /**
     * 用户角色权限
     */
    private Collection authorities;

    /**
     * 帐号是否过期
     */
    private boolean accountNonExpired = true;
    /**
     * 认证是否过期
     */
    private boolean credentialsNonExpired = true;
    /**
     * 帐号是否锁定
     */
    private boolean accountNonLocked = true;
    /**
     * 帐号是否删除
     */
    private boolean enabled = true;

    /**
     * 构造
     *
     * @param id
     * @param username
     * @param password
     * @param orgId
     * @param orgName
     * @param authorities
     */
    public UserDetailsEntity(String id, String username, String password, String orgId, String orgName, Collection authorities) {
        this.id = id;
        this.username = username;
        this.password = password;
        this.orgId = orgId;
        this.orgName = orgName;
        this.authorities = authorities;
    }

    /**
     * @param
     * @return change notes
     * @author suidd
     * @description //获取用户角色权限
     * @date 2020/4/10 13:55
     **/
    @Override
    public Collection getAuthorities() {
        return authorities;
    }

    /**
     * @param
     * @return change notes
     * @author suidd
     * @description //获取密码
     * @date 2020/4/10 13:56
     **/
    @Override
    public String getPassword() {
        return password;
    }

    /**
     * @param
     * @return change notes
     * @author suidd
     * @description //获取用户名
     * @date 2020/4/10 13:56
     **/
    @Override
    public String getUsername() {
        return username;
    }

    /**
     * @param
     * @return change notes
     * @author suidd
     * @description //帐号是否过期
     * @date 2020/4/10 13:52
     **/
    @Override
    public boolean isAccountNonExpired() {
        return this.accountNonExpired;
    }

    /**
     * @param
     * @return change notes
     * @author suidd
     * @description //帐号是否被锁定
     * @date 2020/4/10 13:53
     **/
    @Override
    public boolean isAccountNonLocked() {
        return this.accountNonLocked;
    }

    /**
     * @param
     * @return change notes
     * @author suidd
     * @description //认证是否过期
     * @date 2020/4/10 13:53
     **/
    @Override
    public boolean isCredentialsNonExpired() {
        return this.credentialsNonExpired;
    }

    /**
     * @param
     * @return change notes
     * @author suidd
     * @description //帐号是否被删除
     * @date 2020/4/10 13:53
     **/
    @Override
    public boolean isEnabled() {
        return this.enabled;
    }
}

自定义默认的刷新token序列化工具类 

/**
 * @author suidd
 * @name DefaultOauth2RefreshTokenSerializer
 * @description 自定义默认的刷新token序列化工具类
 * @date 2020/4/15 9:45
 * Version 1.0
 **/
public class DefaultOauth2RefreshTokenSerializer implements ObjectDeserializer {

    @Override
    public  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;
    }
}

自定义OAuth2认证序列化工具类 ,反序列化时,需要特别注意deserialze方法中我写的注释。

//判断json节点userAuthentication的类型,根据类型动态取值
//UsernamePasswordAuthenticationToken 密码模式/授权码模式下,redis存储的json串类型为UsernamePasswordAuthenticationToken
//PreAuthenticatedAuthenticationToken 刷新token模式下,redis存储的json串类型为PreAuthenticatedAuthenticationToken


/**
 * @author suidd
 * @name OAuth2AuthenticationSerializer
 * @description 自定义OAuth2认证序列化工具类
 * @date 2020/4/15 9:43
 * Version 1.0
 **/
public class OAuth2AuthenticationSerializer implements ObjectDeserializer {

    @Override
    public  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);

                //判断json节点userAuthentication的类型,根据类型动态取值
                //UsernamePasswordAuthenticationToken 密码模式/授权码模式下,存储类型为UsernamePasswordAuthenticationToken
                //PreAuthenticatedAuthenticationToken 刷新token模式下,存储类型为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 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 responseTypes = json
                .getObject("responseTypes", new TypeReference>() {
                });
        Set scope = json.getObject("scope", new TypeReference>() {
        });
        Set authorities = json.getObject("authorities", new TypeReference>() {
        });
        Set grantedAuthorities = new HashSet<>(0);
        if (authorities != null && !authorities.isEmpty()) {
            authorities.forEach(s -> grantedAuthorities.add(new SimpleGrantedAuthority(s)));
        }
        Set resourceIds = json
                .getObject("resourceIds", new TypeReference>() {
                });
        Map extensions = json
                .getObject("extensions", new TypeReference>() {
                });

        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);
        }
    }
}

在认证核心配置类中,修改token存储方式为redis

     @Autowired
    private RedisConnectionFactory redisConnectionFactory;

    /**
     * redis存储token
     * @return
     */
    @Bean
    public RedisTokenStore redisTokenStore(){
        RedisTokenStore store = new RedisTokenStore(redisConnectionFactory);
        //自定义json进行序列化和反序列化
        store.setSerializationStrategy(new FastjsonRedisTokenStoreSerializationStrategy());

        return store;
    }


至此,RedisTokenStore存储在redis为json格式就大功告成了,如果反序列化时报错,那请参照错误信息,进行修改。

我这里只列举因为未添加白名单导致报错的错误代码:

com.alibaba.fastjson.JSONException: autoType is not support. org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken
	at com.alibaba.fastjson.parser.ParserConfig.checkAutoType(ParserConfig.java:1072)
	at com.alibaba.fastjson.parser.DefaultJSONParser.parseObject(DefaultJSONParser.java:327)
	at com.alibaba.fastjson.parser.DefaultJSONParser.parseObject(DefaultJSONParser.java:569)
	at com.alibaba.fastjson.parser.DefaultJSONParser.parseObject(DefaultJSONParser.java:1129)
	at cn.com.huak.securityoauth.common.OAuth2AuthenticationSerializer.parse(OAuth2AuthenticationSerializer.java:95)
	at cn.com.huak.securityoauth.common.OAuth2AuthenticationSerializer.deserialze(OAuth2AuthenticationSerializer.java:35)
......

 

你可能感兴趣的:(Spring,Security,Java)