• 使用Json改写基于对象序列化的RedisTokenStore


    之前使用的RedisTokenStroe存储token信息,由于它使用的是java二进制序列化的方式,将token信息存入Redis,导致我们在开发中就遇到了以下问题:

    1. 如果UserDetails定义的字段发生增删,已存在的token,访问校验的时候,就会发生序列化错误;

    2. 如果去redis中查看某个token的内容的时候,会发现全是乱码,完全看不懂;

    于是某个晚上,我狠心就加班搞了4个小时,使用json改写了。

    同时考虑到,如果每次token校验都去redis中读取数据,会导致redis压力增大,毕竟token信息都是些长json字符串,于是就使用了Caffeine做了本地缓存。

    代码:

    JsonRedisTokenStore

      1 public class JsonRedisTokenStore implements TokenStore {
      2     private static final String ACCESS = "access:";
      3     private static final String AUTH_TO_ACCESS = "auth_to_access:";
      4     private static final String AUTH = "auth:";
      5     private static final String REFRESH_AUTH = "refresh_auth:";
      6     private static final String ACCESS_TO_REFRESH = "access_to_refresh:";
      7     private static final String REFRESH = "refresh:";
      8     private static final String REFRESH_TO_ACCESS = "refresh_to_access:";
      9     private static final String CLIENT_ID_TO_ACCESS = "client_id_to_access:";
     10     private static final String UNAME_TO_ACCESS = "uname_to_access:";
     11 
     12     private final static Cache<Object, Object> CACHE;
     13 
     14     static {
     15         CACHE = Caffeine.newBuilder()
     16                 .expireAfterWrite(60, TimeUnit.SECONDS)
     17                 .maximumSize(1000).build();
     18     }
     19 
     20 
     21     private final StringRedisTemplate stringRedisTemplate;
     22     private final AuthenticationKeyGenerator authenticationKeyGenerator = new CustomAuthenticationKeyGenerator();
     23 
     24 
     25     public JsonRedisTokenStore(StringRedisTemplate stringRedisTemplate) {
     26         this.stringRedisTemplate = stringRedisTemplate;
     27     }
     28 
     29 
     30     @Override
     31     public OAuth2Authentication readAuthentication(OAuth2AccessToken token) {
     32         return this.readAuthentication(token.getValue());
     33     }
     34 
     35     @Override
     36     public OAuth2Authentication readAuthentication(String token) {
     37         String key = AUTH + token;
     38 
     39         return (OAuth2Authentication) loadCache(key, (k) -> {
     40             String json = stringRedisTemplate.opsForValue().get(key);
     41             if (StringUtils.isBlank(json)) {
     42                 return null;
     43             }
     44             return fullParseJSON(json);
     45         });
     46     }
     47 
     48     /**
     49      * 完整的OAuth2Authentication 对象转换
     50      *
     51      * @param json 完整OAuth2Authentication json字符串
     52      * @return OAuth2Authentication对象
     53      */
     54     private OAuth2Authentication fullParseJSON(String json) {
     55         //String json = "{"authorities":[{"authority":"ROLE_system_role"}],"details":null,"authenticated":true,"userAuthentication":{"authorities":[{"authority":"ROLE_system_role"}],"details":{"client_secret":"secret","grant_type":"password","client_id":"system","username":"test"},"authenticated":true,"principal":{"id":2715353679532032,"companyId":null,"password":"71b596cb42ee254f7416043d184fc970","username":"运营","phone":"test","accountNonExpired":true,"accountNonLocked":true,"credentialsNonExpired":true,"enabled":true,"authorities":[{"authority":"ROLE_system_role"}]},"credentials":null,"name":"运营"},"credentials":"","clientOnly":false,"oauth2Request":{"clientId":"system","scope":["all"],"requestParameters":{"grant_type":"password","client_id":"system","username":"test"},"resourceIds":["base-server","accident-system","common-server","auth-server"],"authorities":[{"authority":"client"}],"approved":true,"refresh":false,"redirectUri":null,"responseTypes":[],"extensions":{},"refreshTokenRequest":null,"grantType":"password"},"principal":{"id":2715353679532032,"companyId":null,"password":"71b596cb42ee254f7416043d184fc970","username":"运营","phone":"test","accountNonExpired":true,"accountNonLocked":true,"credentialsNonExpired":true,"enabled":true,"authorities":[{"authority":"ROLE_system_role"}]},"name":"运营"}";
     56         JSONObject jsonObject = JSONObject.parseObject(json);
     57 
     58         JSONObject userAuthenticationObject = jsonObject.getJSONObject("userAuthentication");
     59 
     60         TeacherUserDetails userInfoDetails = userAuthenticationObject.getObject("principal", TeacherUserDetails.class);
     61 
     62         String credentials = userAuthenticationObject.getString("credentials");
     63 
     64         JSONObject detailsJSONObject = userAuthenticationObject.getJSONObject("details");
     65         LinkedHashMap<String, Object> details = new LinkedHashMap<>();
     66         for (String key : detailsJSONObject.keySet()) {
     67             details.put(key, detailsJSONObject.get(key));
     68         }
     69 
     70         UsernamePasswordAuthenticationToken userAuthentication = new UsernamePasswordAuthenticationToken(userInfoDetails
     71                 , credentials, new ArrayList<>(0));
     72         userAuthentication.setDetails(details);
     73 
     74         JSONObject storedRequest = jsonObject.getJSONObject("oAuth2Request");
     75         String clientId = storedRequest.getString("clientId");
     76 
     77         JSONObject requestParametersJSON = storedRequest.getJSONObject("requestParameters");
     78         Map<String, String> requestParameters = new HashMap<>();
     79         for (String key : requestParametersJSON.keySet()) {
     80             requestParameters.put(key, requestParametersJSON.getString(key));
     81         }
     82 
     83         Set<String> scope = convertSetString(storedRequest, "scope");
     84         Set<String> resourceIds = convertSetString(storedRequest, "resourceIds");
     85         Set<String> responseTypes = convertSetString(storedRequest, "responseTypes");
     86 
     87         OAuth2Request oAuth2Request = new OAuth2Request(requestParameters
     88                 , clientId
                //由于这个项目不需要处理权限角色,所以就没有对权限角色集合做处理
    89 , new ArrayList<>(0) 90 , storedRequest.getBoolean("approved") 91 , scope 92 , resourceIds 93 , storedRequest.getString("redirectUri") 94 , responseTypes 95 , null //extensionProperties 96 ); 97 98 return new OAuth2Authentication(oAuth2Request, userAuthentication); 99 } 100 101 private static Set<String> convertSetString(JSONObject data, String key) { 102 List<String> list = data.getJSONArray(key).toJavaList(String.class); 103 104 return new HashSet<>(list); 105 } 106 107 @Override 108 public void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) { 109 String serializedAccessToken = JSONObject.toJSONString(token); 110 String serializedAuth = JSONObject.toJSONString(authentication); 111 String accessKey = ACCESS + token.getValue(); 112 String authKey = AUTH + token.getValue(); 113 String authToAccessKey = AUTH_TO_ACCESS + authenticationKeyGenerator.extractKey(authentication); 114 String approvalKey = UNAME_TO_ACCESS + getApprovalKey(authentication); 115 String clientId = CLIENT_ID_TO_ACCESS + authentication.getOAuth2Request().getClientId(); 116 117 int seconds = 30 * 24 * 60 * 60; 118 if (token.getExpiration() != null) { 119 seconds = token.getExpiresIn(); 120 } 121 122 try { 123 stringRedisTemplate.opsForValue().set(accessKey, serializedAccessToken); 124 stringRedisTemplate.opsForValue().set(authKey, serializedAuth); 125 stringRedisTemplate.opsForValue().set(authToAccessKey, serializedAccessToken); 126 127 if (!authentication.isClientOnly()) { 128 stringRedisTemplate.opsForHash().putIfAbsent(approvalKey, token.getValue(), serializedAccessToken); 129 } 130 } finally { 131 //如果中途失败,则还可以补偿过期时间 132 stringRedisTemplate.expire(accessKey, seconds, TimeUnit.SECONDS); 133 stringRedisTemplate.expire(authKey, seconds, TimeUnit.SECONDS); 134 stringRedisTemplate.expire(authToAccessKey, seconds, TimeUnit.SECONDS); 135 stringRedisTemplate.expire(clientId, seconds, TimeUnit.SECONDS); 136 stringRedisTemplate.expire(approvalKey, seconds, TimeUnit.SECONDS); 137 } 138 139 OAuth2RefreshToken refreshToken = token.getRefreshToken(); 140 if (refreshToken != null && refreshToken.getValue() != null) { 141 String refreshValue = token.getRefreshToken().getValue(); 142 String refreshToAccessKey = REFRESH_TO_ACCESS + refreshValue; 143 String accessToRefreshKey = ACCESS_TO_REFRESH + token.getValue(); 144 145 try { 146 stringRedisTemplate.opsForValue().set(refreshToAccessKey, token.getValue()); 147 stringRedisTemplate.opsForValue().set(accessToRefreshKey, refreshValue); 148 } finally { 149 //如果中途失败,则还可以补偿过期时间 150 refreshTokenProcess(refreshToken, refreshToAccessKey, accessToRefreshKey); 151 } 152 153 CACHE.put(refreshToAccessKey, token.getValue()); 154 CACHE.put(accessToRefreshKey, refreshValue); 155 } 156 157 CACHE.put(accessKey, token); 158 CACHE.put(authKey, authentication); 159 CACHE.put(authToAccessKey, token); 160 } 161 162 private void refreshTokenProcess(OAuth2RefreshToken refreshToken, String refreshKey, String refreshAuthKey) { 163 int seconds = 30 * 24 * 60 * 60; 164 if (refreshToken instanceof ExpiringOAuth2RefreshToken) { 165 ExpiringOAuth2RefreshToken expiringRefreshToken = (ExpiringOAuth2RefreshToken) refreshToken; 166 Date expiration = expiringRefreshToken.getExpiration(); 167 168 int temp; 169 if (expiration != null) { 170 temp = Long.valueOf((expiration.getTime() - System.currentTimeMillis()) / 1000L) 171 .intValue(); 172 173 } else { 174 temp = seconds; 175 } 176 stringRedisTemplate.expire(refreshKey, temp, TimeUnit.SECONDS); 177 stringRedisTemplate.expire(refreshAuthKey, temp, TimeUnit.SECONDS); 178 } 179 } 180 181 182 private String getApprovalKey(OAuth2Authentication authentication) { 183 String userName = ""; 184 if (authentication.getUserAuthentication() != null) { 185 TeacherUserDetails userInfoDetails = (TeacherUserDetails) authentication.getUserAuthentication().getPrincipal(); 186 userName = userInfoDetails.getPhone() + "_" + userInfoDetails.getUsername(); 187 } 188 189 return getApprovalKey(authentication.getOAuth2Request().getClientId(), userName); 190 } 191 192 private String getApprovalKey(String clientId, String userName) { 193 return clientId + (userName == null ? "" : ":" + userName); 194 } 195 196 @Override 197 public OAuth2AccessToken readAccessToken(String tokenValue) { 198 String key = ACCESS + tokenValue; 199 200 return (OAuth2AccessToken) loadCache(key, (k) -> { 201 String json = stringRedisTemplate.opsForValue().get(key); 202 if (StringUtils.isNotBlank(json)) { 203 return JSONObject.parseObject(json, DefaultOAuth2AccessTokenEx.class); 204 } 205 return null; 206 }); 207 } 208 209 @Override 210 public void removeAccessToken(OAuth2AccessToken accessToken) { 211 removeAccessToken(accessToken.getValue()); 212 } 213 214 public void removeAccessToken(String tokenValue) { 215 String accessKey = ACCESS + tokenValue; 216 String authKey = AUTH + tokenValue; 217 String accessToRefreshKey = ACCESS_TO_REFRESH + tokenValue; 218 219 OAuth2Authentication authentication = readAuthentication(tokenValue); 220 String access = stringRedisTemplate.opsForValue().get(accessKey); 221 222 List<String> keys = new ArrayList<>(6); 223 keys.add(accessKey); 224 keys.add(authKey); 225 keys.add(accessToRefreshKey); 226 227 stringRedisTemplate.delete(keys); 228 229 if (authentication != null) { 230 String key = authenticationKeyGenerator.extractKey(authentication); 231 String authToAccessKey = AUTH_TO_ACCESS + key; 232 String unameKey = UNAME_TO_ACCESS + getApprovalKey(authentication); 233 String clientId = CLIENT_ID_TO_ACCESS + authentication.getOAuth2Request().getClientId(); 234 235 stringRedisTemplate.delete(authToAccessKey); 236 stringRedisTemplate.opsForHash().delete(unameKey, tokenValue); 237 stringRedisTemplate.opsForList().remove(clientId, 1, access); 238 stringRedisTemplate.delete(ACCESS + key); 239 240 CACHE.invalidate(authToAccessKey); 241 CACHE.invalidate(ACCESS + key); 242 } 243 244 CACHE.invalidateAll(keys); 245 } 246 247 @Override 248 public void storeRefreshToken(OAuth2RefreshToken refreshToken, OAuth2Authentication authentication) { 249 String refreshKey = REFRESH + refreshToken.getValue(); 250 String refreshAuthKey = REFRESH_AUTH + refreshToken.getValue(); 251 String serializedRefreshToken = JSONObject.toJSONString(refreshToken); 252 253 stringRedisTemplate.opsForValue().set(refreshKey, serializedRefreshToken); 254 stringRedisTemplate.opsForValue().set(refreshAuthKey, JSONObject.toJSONString(authentication)); 255 256 refreshTokenProcess(refreshToken, refreshKey, refreshAuthKey); 257 258 CACHE.put(refreshKey, refreshToken); 259 CACHE.put(refreshAuthKey, authentication); 260 } 261 262 263 @Override 264 public OAuth2RefreshToken readRefreshToken(String tokenValue) { 265 String key = REFRESH + tokenValue; 266 return (OAuth2RefreshToken) loadCache(key, (k) -> { 267 String json = stringRedisTemplate.opsForValue().get(key); 268 if (StringUtils.isNotBlank(json)) { 269 return JSONObject.parseObject(json, DefaultOAuth2RefreshTokenEx.class); 270 } 271 272 return null; 273 }); 274 } 275 276 @Override 277 public OAuth2Authentication readAuthenticationForRefreshToken(OAuth2RefreshToken token) { 278 return this.readAuthenticationForRefreshToken(token.getValue()); 279 } 280 281 public OAuth2Authentication readAuthenticationForRefreshToken(String token) { 282 String key = REFRESH_AUTH + token; 283 284 return (OAuth2Authentication) loadCache(key, (k) -> { 285 String json = stringRedisTemplate.opsForValue().get(key); 286 if (StringUtils.isBlank(json)) { 287 return null; 288 } 289 290 return fullParseJSON(json); 291 }); 292 } 293 294 @Override 295 public void removeRefreshToken(OAuth2RefreshToken refreshToken) { 296 this.removeRefreshToken(refreshToken.getValue()); 297 } 298 299 public void removeRefreshToken(String refreshToken) { 300 String refreshKey = REFRESH + refreshToken; 301 String refreshAuthKey = REFRESH_AUTH + refreshToken; 302 String refresh2AccessKey = REFRESH_TO_ACCESS + refreshToken; 303 String access2RefreshKey = ACCESS_TO_REFRESH + refreshToken; 304 305 List<String> keys = new ArrayList<>(7); 306 keys.add(refreshKey); 307 keys.add(refreshAuthKey); 308 keys.add(refresh2AccessKey); 309 keys.add(access2RefreshKey); 310 311 stringRedisTemplate.delete(keys); 312 313 CACHE.invalidateAll(keys); 314 } 315 316 @Override 317 public void removeAccessTokenUsingRefreshToken(OAuth2RefreshToken refreshToken) { 318 this.removeAccessTokenUsingRefreshToken(refreshToken.getValue()); 319 } 320 321 private void removeAccessTokenUsingRefreshToken(String refreshToken) { 322 String key = REFRESH_TO_ACCESS + refreshToken; 323 324 String accessToken = stringRedisTemplate.opsForValue().get(key); 325 stringRedisTemplate.delete(key); 326 327 if (accessToken != null) { 328 removeAccessToken(accessToken); 329 } 330 331 CACHE.invalidate(key); 332 } 333 334 private <T> Object loadCache(String key, Function<Object, ? extends T> loadData) { 335 try { 336 Object value = CACHE.getIfPresent(key); 337 if (value == null) { 338 value = loadData.apply(key); 339 if (value != null) { 340 CACHE.put(key, value); 341 } 342 } 343 344 return value; 345 } catch (Exception e) { 346 throw new RuntimeException("JsonRedisTokenStore.loadCache从缓存中加载数据发生错误", e); 347 } 348 } 349 350 @Override 351 public OAuth2AccessToken getAccessToken(OAuth2Authentication authentication) { 352 String key = AUTH_TO_ACCESS + authenticationKeyGenerator.extractKey(authentication); 353 354 return (OAuth2AccessToken) loadCache(key, (k) -> { 355 String json = stringRedisTemplate.opsForValue().get(key); 356 357 if (StringUtils.isNotBlank(json)) { 358 DefaultOAuth2AccessToken accessToken = JSONObject.parseObject(json, DefaultOAuth2AccessTokenEx.class); 359 360 361 OAuth2Authentication storedAuthentication = readAuthentication(accessToken.getValue()); 362 363 if (storedAuthentication == null 364 || !key.equals(authenticationKeyGenerator.extractKey(storedAuthentication))) { 365 this.storeAccessToken(accessToken, authentication); 366 } 367 368 return accessToken; 369 } 370 371 return null; 372 }); 373 } 374 375 376 377 @Override 378 public Collection<OAuth2AccessToken> findTokensByClientIdAndUserName(String clientId, String userName) { 379 String approvalKey = UNAME_TO_ACCESS + getApprovalKey(clientId, userName); 380 381 return getOAuth2AccessTokens(approvalKey); 382 } 383 384 private Collection<OAuth2AccessToken> getOAuth2AccessTokens(String approvalKey) { 385 final Collection<OAuth2AccessToken> oAuth2AccessTokens = (Collection<OAuth2AccessToken>) loadCache(approvalKey, (k) -> { 386 Map<Object, Object> accessTokens = stringRedisTemplate.opsForHash().entries(approvalKey); 387 388 if (accessTokens.size() == 0) { 389 return Collections.emptySet(); 390 } 391 392 List<OAuth2AccessToken> result = new ArrayList<>(); 393 394 for (Object json : accessTokens.values()) { 395 String strJSON = json.toString(); 396 OAuth2AccessToken accessToken = JSONObject.parseObject(strJSON, DefaultOAuth2AccessTokenEx.class); 397 398 result.add(accessToken); 399 } 400 return Collections.unmodifiableCollection(result); 401 }); 402 return oAuth2AccessTokens; 403 } 404 405 @Override 406 public Collection<OAuth2AccessToken> findTokensByClientId(String clientId) { 407 String key = CLIENT_ID_TO_ACCESS + clientId; 408 409 return getOAuth2AccessTokens(key); 410 } 411 }
    DefaultOAuth2AccessTokenEx:
     1 public class DefaultOAuth2AccessTokenEx extends DefaultOAuth2AccessToken {
     2     private DefaultOAuth2RefreshTokenEx refreshToken;
     3 
     4     public DefaultOAuth2AccessTokenEx() {
     5         super((String) null);
     6     }
     7 
     8     @Override
     9     public void setValue(String value) {
    10         super.setValue(value);
    11     }
    12 
    13     @Override
    14     public DefaultOAuth2RefreshTokenEx getRefreshToken() {
    15         return refreshToken;
    16     }
    17 
    18     public void setRefreshToken(DefaultOAuth2RefreshTokenEx refreshToken) {
    19         this.refreshToken = refreshToken;
    20     }
    21 }
    DefaultOAuth2RefreshTokenEx
    1 @Data
    2 public class DefaultOAuth2RefreshTokenEx extends DefaultOAuth2RefreshToken implements ExpiringOAuth2RefreshToken {
    3     private Date expiration;
    4     private String value;
    5 
    6     public DefaultOAuth2RefreshTokenEx() {
    7         super(null);
    8     }
    9 }
    由于Oauth自己定义的实体中,用了jackson对字段做了注解,使用jackson对token对象做json序列化,有字段名称变化,可能会导致debug费时间等,就改用了fastjson,如果有IT同仁不喜欢用fastjson,就根据自己需要改用自己用的顺手的json库就行了。
     
     
  • 相关阅读:
    自动重连脚本
    自动输入用户名密码脚本
    idea 安装及配置
    manjaro安装anaconda
    hive 常用命令
    python built-in zip()
    isinstance(),issubclass()
    python built-in delattr()
    字符串匹配算法
    贪心,分治,回溯,动态规划 4大核心算法思想
  • 原文地址:https://www.cnblogs.com/chongsha/p/14558011.html
Copyright © 2020-2023  润新知