Spring Security OAuth2.0 多点登录与单点登录【SpringCloud系列12】 您所在的位置:网站首页 单点登录 Spring Security OAuth2.0 多点登录与单点登录【SpringCloud系列12】

Spring Security OAuth2.0 多点登录与单点登录【SpringCloud系列12】

#Spring Security OAuth2.0 多点登录与单点登录【SpringCloud系列12】| 来源: 网络整理| 查看: 265

SpringCloud 大型系列课程正在制作中,欢迎大家关注与提意见。 程序员每天的CV 与 板砖,也要知其所以然,本系列课程可以帮助初学者学习 SpringBooot 项目开发 与 SpringCloud 微服务系列项目开发

本文章是系列文章中的一篇

SpringCloud 整合Gateway服务网关 【SpringCloud系列5】 SpringCloud 业务管理后台 通过FeignClient来调用oauth/token接口【SpringCloud系列11】

本文章实现的是单点登录功能 在这里插入图片描述

1 oauth/token 接口

管理后台业务登录多次调用 oauth/token 登录接口,会发现每次反回的token都是一样的,即时在不同的电脑上,使用相同的账号进行登录,反回的token还是一至的。 原因是我们在构造 AuthorizationServerTokenServices时使用的 DefaultTokenServices 中创建token时,会校验token是否存在,如果存在,就直接取出来返回,DefaultTokenServices 中创建 toke 的核心源码如下:

public class DefaultTokenServices implements AuthorizationServerTokenServices, ResourceServerTokenServices, ConsumerTokenServices, InitializingBean { ... @Transactional public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException { // 获取用户对应的token OAuth2AccessToken existingAccessToken = this.tokenStore.getAccessToken(authentication); OAuth2RefreshToken refreshToken = null; if (existingAccessToken != null) { //如果token存在并在有效期 直接返回 if (!existingAccessToken.isExpired()) { this.tokenStore.storeAccessToken(existingAccessToken, authentication); return existingAccessToken; } //如果token 过期了,移除 refresh-token if (existingAccessToken.getRefreshToken() != null) { refreshToken = existingAccessToken.getRefreshToken(); this.tokenStore.removeRefreshToken(refreshToken); } //移除已存在的token this.tokenStore.removeAccessToken(existingAccessToken); } if (refreshToken == null) { refreshToken = this.createRefreshToken(authentication); } else if (refreshToken instanceof ExpiringOAuth2RefreshToken) { ExpiringOAuth2RefreshToken expiring = (ExpiringOAuth2RefreshToken)refreshToken; if (System.currentTimeMillis() > expiring.getExpiration().getTime()) { refreshToken = this.createRefreshToken(authentication); } } OAuth2AccessToken accessToken = this.createAccessToken(authentication, refreshToken); this.tokenStore.storeAccessToken(accessToken, authentication); refreshToken = accessToken.getRefreshToken(); if (refreshToken != null) { this.tokenStore.storeRefreshToken(refreshToken, authentication); } return accessToken; } 复制代码 2 自定义 AuthenticationKey 自定义 token生成策略

下面是本项目中使用到的配置,在创建 AuthorizationServerTokenServices 时使用的是 DefaultTokenServices 。

@Configuration public class TokenStoreConfig { private String SIGNING_KEY = "qwert.123456"; @Autowired TokenStore tokenStore; @Autowired private JwtAccessTokenConverter accessTokenConverter; @Bean public JwtAccessTokenConverter accessTokenConverter() { JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); converter.setSigningKey(SIGNING_KEY); return converter; } //令牌管理服务 @Bean(name = "authorizationServerTokenServicesCustom") public AuthorizationServerTokenServices tokenService() { //默认的 DefaultTokenServices service = new DefaultTokenServices(); //自定义的 // TokenService service = new TokenService(); service.setSupportRefreshToken(true);//支持刷新令牌 service.setTokenStore(tokenStore);//令牌存储策略 TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain(); tokenEnhancerChain.setTokenEnhancers(Arrays.asList(accessTokenConverter)); service.setTokenEnhancer(tokenEnhancerChain); service.setAccessTokenValiditySeconds(7200); // 令牌默认有效期2小时 service.setRefreshTokenValiditySeconds(259200); // 刷新令牌默认有效期3天 return service; } } 复制代码

token 的缓存策略是使用的 Redis ,而我的需求是每次登录都要生成不一样的token所以可以自定义生成 token的策略

import org.springframework.security.oauth2.common.util.OAuth2Utils; import org.springframework.security.oauth2.provider.OAuth2Authentication; import org.springframework.security.oauth2.provider.OAuth2Request; import org.springframework.security.oauth2.provider.token.DefaultAuthenticationKeyGenerator; import java.util.LinkedHashMap; import java.util.Map; import java.util.TreeSet; public class CustomAuthenticationKeyGenerator extends DefaultAuthenticationKeyGenerator { private static final String CLIENT_ID = "client_id"; private static final String SCOPE = "scope"; private static final String USERNAME = "username"; @Override public String extractKey(OAuth2Authentication authentication) { Map values = new LinkedHashMap(); OAuth2Request authorizationRequest = authentication.getOAuth2Request(); if (!authentication.isClientOnly()) { //在用户名后面添加时间戳,使每次的key都不一样 values.put(USERNAME, authentication.getName() + System.currentTimeMillis()); } values.put(CLIENT_ID, authorizationRequest.getClientId()); if (authorizationRequest.getScope() != null) { values.put(SCOPE, OAuth2Utils.formatParameterList(new TreeSet(authorizationRequest.getScope()))); } return generateKey(values); } } 复制代码

然后在定义 TokenStore 时,将上述的 AuthenticationKey 进行配置

import com.biglead.auth.key.CustomAuthenticationKeyGenerator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.security.oauth2.provider.token.TokenStore; import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore; @Configuration public class RedisConfig { @Autowired private RedisConnectionFactory redisConnectionFactory; @Bean public TokenStore redisTokenStore(){ RedisTokenStore redisTokenStore = new RedisTokenStore(redisConnectionFactory); redisTokenStore.setAuthenticationKeyGenerator(new CustomAuthenticationKeyGenerator()); return redisTokenStore; } } 复制代码

然后重启项目后,多次使用同一个账户,每次登录都是生成不一样的 token ,互不影响 。

3 单点登录

我要实现的功能是 电脑A浏览器中用户登录管理后台后,可以正常访问资源接口,当在电脑B登录同一个账号后,电脑A中的token失效。

也就是所谓的单点登录功能,同一个用户生成的token 互斥。

单点登录实现的方案很多,这里不去概述,本项目实现的是 自定义 DefaultTokenServices,重写 createAccessToken(创建生成token)

import org.springframework.security.core.AuthenticationException; import org.springframework.security.oauth2.common.*; import org.springframework.security.oauth2.provider.OAuth2Authentication; import org.springframework.security.oauth2.provider.token.DefaultTokenServices; import org.springframework.security.oauth2.provider.token.TokenEnhancer; import org.springframework.security.oauth2.provider.token.TokenStore; import org.springframework.transaction.annotation.Transactional; import java.util.Date; import java.util.UUID; public class TokenService extends DefaultTokenServices { private TokenEnhancer accessTokenEnhancer; private TokenStore tokenStore; @Transactional @Override public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException { //获取当前用户的token OAuth2AccessToken existingAccessToken = tokenStore.getAccessToken(authentication); OAuth2RefreshToken refreshToken = null; if (existingAccessToken != null) { //如果刷新token不为null if (existingAccessToken.getRefreshToken() != null) { //移除刷新token refreshToken = existingAccessToken.getRefreshToken(); tokenStore.removeRefreshToken(refreshToken); } //再移除已存在的token tokenStore.removeAccessToken(existingAccessToken); } if (refreshToken == null) { refreshToken = createRefreshToken(authentication); } else if (refreshToken instanceof ExpiringOAuth2RefreshToken) { ExpiringOAuth2RefreshToken expiring = (ExpiringOAuth2RefreshToken) refreshToken; if (System.currentTimeMillis() > expiring.getExpiration().getTime()) { refreshToken = createRefreshToken(authentication); } } OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken); tokenStore.storeAccessToken(accessToken, authentication); // In case it was modified refreshToken = accessToken.getRefreshToken(); if (refreshToken != null) { tokenStore.storeRefreshToken(refreshToken, authentication); } return accessToken; } private OAuth2RefreshToken createRefreshToken(OAuth2Authentication authentication) { if (!isSupportRefreshToken(authentication.getOAuth2Request())) { return null; } int validitySeconds = getRefreshTokenValiditySeconds(authentication.getOAuth2Request()); String value = UUID.randomUUID().toString(); if (validitySeconds > 0) { return new DefaultExpiringOAuth2RefreshToken(value, new Date(System.currentTimeMillis() + (validitySeconds * 1000L))); } return new DefaultOAuth2RefreshToken(value); } private OAuth2AccessToken createAccessToken(OAuth2Authentication authentication, OAuth2RefreshToken refreshToken) { DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(UUID.randomUUID().toString()); int validitySeconds = getAccessTokenValiditySeconds(authentication.getOAuth2Request()); if (validitySeconds > 0) { token.setExpiration(new Date(System.currentTimeMillis() + (validitySeconds * 1000L))); } token.setRefreshToken(refreshToken); token.setScope(authentication.getOAuth2Request().getScope()); return accessTokenEnhancer != null ? accessTokenEnhancer.enhance(token, authentication) : token; } @Override public void setTokenStore(TokenStore tokenStore) { super.setTokenStore(tokenStore); this.tokenStore = tokenStore; } @Override public void setTokenEnhancer(TokenEnhancer accessTokenEnhancer) { super.setTokenEnhancer(accessTokenEnhancer); this.accessTokenEnhancer = accessTokenEnhancer; } } 复制代码

然后在定义 OAuth2.0 令牌管理服务时,使用自定义的 Service

//令牌管理服务 @Bean(name = "authorizationServerTokenServicesCustom") public AuthorizationServerTokenServices tokenService() { //默认的 //DefaultTokenServices service = new DefaultTokenServices(); //自定义的 TokenService service = new TokenService(); service.setSupportRefreshToken(true);//支持刷新令牌 service.setTokenStore(tokenStore);//令牌存储策略 TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain(); tokenEnhancerChain.setTokenEnhancers(Arrays.asList(accessTokenConverter)); service.setTokenEnhancer(tokenEnhancerChain); service.setAccessTokenValiditySeconds(7200); // 令牌默认有效期2小时 service.setRefreshTokenValiditySeconds(259200); // 刷新令牌默认有效期3天 return service; } 复制代码

本项目 SpringCloud 源码 gitee.com/android.lon… 本项目 管理后台web 源码gitee.com/android.lon… 如果有兴趣可以关注一下公众号 biglead ,每周都会有 java、Flutter、小程序、js 、英语相关的内容分享.

本文正在参加「金石计划」



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有