Spring Security Advanced Usage Sample
来源:互联网 发布:dating付费软件靠谱吗 编辑:程序博客网 时间:2024/04/20 13:13
需求
- 分布式微服务架构,使用Oauth2协议,Token类型使用Jwt(不是必须);
- 用户会在多个平台(PC,Android,IOS,微信)登录,同一个账号,不同平台可以同时登录,同一个平台只能登录一个实例
- 后登陆的实例可以在验证身份验证通过之后,将前面登录的实例踢掉;
- 退出会话功能,也就是让token失效;
设计
- jwt token的生成因素包括client+user+platform,也就是说获取token的流程除了要传递oauth2协议规定的client和user信息给授权服务器之外,还要在合适的时间点传递platform;
- 如何将前面的实例踢掉,我们知道jwt token是不用持久化的,他的信息都存储在token本身,也就是说只要发了token,授权服务器就控制不了这个token的后续生命周期了,如果要控制,就只能持久化,持久化之后,如果想把前面的实例踢掉,只需要用新的token覆盖掉之前的token就可以了;
- 如果想实现覆盖掉之前token的功能,那我们在获取token的时候,还需要给授权服务器传递是否强制生成access_token的标识recreate_access_token;
实现思路
根据上边的要求,我们只需要继承JwtTokenStore,重载里面的关于OAuth2AccessToken、OAuth2Authentication和OAuth2RefreshToken持久化的相关实现,并增加platform和recreate_access_token的处理逻辑。
其实就是将Spring Security OAuth2中已经提供的RedisTokenStore和JwtTokenStore的功能整理起来;
如果想存储在关系型数据库里的话,就是将JdbcTokenStore和JwtTokenStore的功能整合起来
具体实现
[PanJwtTokenStore]
/** * * @author chenzhenyang * */public class PanJwtTokenStore extends JwtTokenStore { private JwtAccessTokenConverter jwtTokenEnhancer; private static final String CACHE_NAME = "JWT_TOKEN_STORE"; private static final String KEY_ACCESS_TOKEN_PREFIX = "JWT_ACCESS_TOKEN"; private static final String KEY_REFRESH_TOKEN_PREFIX = "JWT_REFRESH_TOKEN"; private static final String RECREATE_ACCESS_TOEKN_KEY = "recreate_access_token"; private static final String PLATFORM_KEY = "platform"; private Cache cache; public PanJwtTokenStore(JwtAccessTokenConverter jwtTokenEnhancer, CacheManager cacheManager) { super(jwtTokenEnhancer); this.jwtTokenEnhancer = jwtTokenEnhancer; this.cache = cacheManager.getCache(CACHE_NAME); } @Override public OAuth2AccessToken getAccessToken(OAuth2Authentication authentication) { String accessTokenCacheKey = getAccessTokenCacheKeyFromAuthentication(authentication); ValueWrapper value = cache.get(accessTokenCacheKey); @SuppressWarnings("unchecked") Map<String, Object> details = (Map<String, Object>) authentication.getUserAuthentication().getDetails(); String recreateAccessTokenStr = (String) details.getOrDefault(RECREATE_ACCESS_TOEKN_KEY, "true"); Boolean recreateAccessToken = Boolean.valueOf(recreateAccessTokenStr); if (recreateAccessToken) { return null; } if (StringUtils.isEmpty(value)) { return null; } String tokenValue = (String) value.get(); DefaultOAuth2AccessToken accessToken = (DefaultOAuth2AccessToken) convertAccessToken(tokenValue); String refreshTokenCacheKey = getRefreshTokenCacheKeyFromAuthentication(authentication); OAuth2RefreshToken refreshToken =(OAuth2RefreshToken)cache.get(refreshTokenCacheKey).get(); accessToken.setRefreshToken(refreshToken); return accessToken; } @Override public OAuth2Authentication readAuthentication(String token) { Map<String,Object> map = jwtTokenEnhancer.decode(token); OAuth2Authentication authentication = jwtTokenEnhancer.extractAuthentication(map); @SuppressWarnings("unchecked") Map<String,Object> details = (Map<String, Object>) authentication.getUserAuthentication().getDetails(); if(null == details){ details = new HashMap<>(); } details.put("recreate_access_token", map.get("recreate_access_token")); details.put("platform", map.get("platform")); AbstractAuthenticationToken authenticationToken = (AbstractAuthenticationToken) authentication.getUserAuthentication(); authenticationToken.setDetails(details); String key = getAccessTokenCacheKeyFromAuthentication(authentication); ValueWrapper vw = this.cache.get(key); if(ObjectUtils.isEmpty(vw)){ return null; } if(!token.equals(vw.get())){ return null; } //// String tokenValue = vw.get(); return authentication; } /** * * @param tokenValue * @return */ private OAuth2AccessToken convertAccessToken(String tokenValue) { Map<String, Object> map = jwtTokenEnhancer.decode(tokenValue); return jwtTokenEnhancer.extractAccessToken(tokenValue, map); } private String getAccessTokenCacheKeyFromAuthentication(OAuth2Authentication authentication) { String accessTokenCacheKey = KEY_ACCESS_TOKEN_PREFIX + getNoPrefixCacheKeyFromAuthentication(authentication); return accessTokenCacheKey; } private String getAccessTokenCacheKeyFromAccessToken(OAuth2AccessToken accessToken) { String accessTokenCacheKey = KEY_ACCESS_TOKEN_PREFIX + getNoPrefixCacheKeyFromAccessToken(accessToken); return accessTokenCacheKey; } private String getRefreshTokenCacheKeyFromAuthentication(OAuth2Authentication authentication) { String accessTokenCacheKey = KEY_REFRESH_TOKEN_PREFIX + getNoPrefixCacheKeyFromAuthentication(authentication); return accessTokenCacheKey; } /** * * @param authentication * @return */ private String getNoPrefixCacheKeyFromAuthentication(OAuth2Authentication authentication) { @SuppressWarnings("unchecked") Map<String, Object> details = (Map<String, Object>) authentication.getUserAuthentication().getDetails(); String platform = (String) details.getOrDefault(PLATFORM_KEY, "pc"); String user = null; Object principal = authentication.getPrincipal(); if (principal instanceof String) { user = principal.toString(); } else if (principal instanceof User) { user = ((User) principal).getUsername(); } return user + platform; } /** * * @param accessToken * @return */ private String getNoPrefixCacheKeyFromAccessToken(OAuth2AccessToken accessToken) { Map<String, Object> details = accessToken.getAdditionalInformation(); String platform = (String) details.get("platform"); if (StringUtils.isEmpty(platform)) { platform = "pc"; } Map<String, Object> additionalInformation = accessToken.getAdditionalInformation(); String user = (String) additionalInformation.get("user_name"); return user + platform; } @Override public void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) { String key = getAccessTokenCacheKeyFromAuthentication(authentication); if (StringUtils.isEmpty(key)) { key = UUID.randomUUID().toString(); } String tokenValue = token.getValue(); cache.put(key, tokenValue); } /** * 这个方法都是在判断accessToken过期的时候删除。 */ @Override public void removeAccessToken(OAuth2AccessToken token) { super.removeAccessToken(token); String key = getAccessTokenCacheKeyFromAccessToken(token); this.cache.evict(key); } /** * 一个accessToken,顶多有一个refreshToken。<br> * key = KEY_PREFIX + user + platform */ @Override public void storeRefreshToken(OAuth2RefreshToken refreshToken, OAuth2Authentication authentication) { super.storeRefreshToken(refreshToken, authentication); String key = getRefreshTokenCacheKeyFromAuthentication(authentication); this.cache.put(key, refreshToken); this.cache.put(refreshToken.getValue(), key); } /** * */ @Override public void removeRefreshToken(OAuth2RefreshToken token) { super.removeRefreshToken(token); ValueWrapper vw = this.cache.get(token.getValue()); if (ObjectUtils.isEmpty(vw)) { return; } String key = (String) vw.get(); this.cache.evict(token.getValue()); this.cache.evict(key); } @Override public void removeAccessTokenUsingRefreshToken(OAuth2RefreshToken refreshToken) { super.removeAccessTokenUsingRefreshToken(refreshToken); }}
[PanJwtAccessTokenConverter]
public class PanJwtAccessTokenConverter extends JwtAccessTokenConverter{ @Override public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) { accessToken = super.enhance(accessToken, authentication); Map<String,Object> additionalInformation = accessToken.getAdditionalInformation(); @SuppressWarnings("unchecked") Map<String,Object> details = (Map<String, Object>) authentication.getUserAuthentication().getDetails(); String platform = (String) details.getOrDefault("platform","pc"); additionalInformation.put("platform", platform); String recreateAccessTokenStr = (String)details.getOrDefault("recreate_access_token","false"); Boolean recreateAccessToken = Boolean.valueOf(recreateAccessTokenStr); additionalInformation.put("recreate_access_token", recreateAccessToken); ((DefaultOAuth2AccessToken)accessToken).setValue(encode(accessToken, authentication)); return accessToken; }}
在ResourceServerConfigurerAdapter中配置一下:
@Configuration@EnableResourceServer@EnableOAuth2Clientpublic class OAuth2ResourceServerConfig extends ResourceServerConfigurerAdapter{ private static final String DEMO_RESOURCE_ID = "resource1"; @Autowired private CacheManager cacheManager; @Autowired private JwtAccessTokenConverter jwtAccessTokenConverter; @Bean public TokenStore jwtTokenStore() { return new PanJwtTokenStore(jwtAccessTokenConverter,cacheManager); } @Override public void configure(ResourceServerSecurityConfigurer resources) { resources.resourceId(DEMO_RESOURCE_ID).stateless(true); resources.tokenStore(jwtTokenStore()); } @Override public void configure(HttpSecurity http) throws Exception { // @formatter:off http // Since we want the protected resources to be accessible in the UI as well we need // session creation to be allowed (it's disabled by default in 2.0.6) .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER) .and() .requestMatchers().anyRequest() .and().httpBasic().disable() .anonymous().disable() .authorizeRequests()// .antMatchers("/product/**").access("#oauth2.hasScope('select') and hasRole('ROLE_USER')") .antMatchers("/**").authenticated();//配置order访问控制,必须认证过后才可以访问 // @formatter:on }}
总结
如果我们不使用JWT Token而是普通的token的话,可以模仿JdbcTokenStore进行扩展,只需要在oauth_access_token表中添加,platform和recreate_access_token这两个字段,然后重写JdbcTokenStore,将所有关于此表的增删改查都加上platform和recreate_access_token就可以了。
阅读全文
0 0
- Spring Security Advanced Usage Sample
- Usage sample of SSL
- Wpf GridSplitter usage Sample
- WebDriver: Advanced Usage
- Advanced usage in vim
- PostgreSQL security usage guide
- Usage sample of unix semaphore
- Usage sample of unix pipe
- Oracle Data Security - Advanced Security
- Advanced SQLite Usage in Python
- Spring Sample
- Usage sample of unix mutex and conditional
- Usage sample of unix Message Queues
- A simple sample show generic interface usage
- Spring PropertyPlaceholderConfigurer Usage
- Spring Security
- Spring Security
- spring security
- Nginx安装教程
- cocos2dx 《忍着飞镖射幽灵》小结
- 解决无法看到eth0的简单情况
- 簇索引与非簇索引在查询中的应用与分析
- 颜色估值器
- Spring Security Advanced Usage Sample
- 单纯形法 simplex method 求解线性规划问题的通用方法.单纯形是美国数学家G.B.丹齐克于1947年首先提出来的.它的理论根据是:线性规划问题的可行域是 n维向量空间Rn中的多
- 搭建Git服务器的最简单方式
- 用tensorflow的slim模块快速实现mnist手写体识别分类
- Java中throw和throws的区别
- 某S2-052漏洞repetition的一些小白易卡点
- Vue2 构造器的生命周期
- BlockingQueue、ArrayBlockingQueue
- 蓝牙协议分析(4)_IPv6 Over BLE介绍