素材巴巴 > 程序开发 >

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

程序开发 2023-09-03 22:54:08

本文章实现的是单点登录功能 

1 oauth/token 接口

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

public class DefaultTokenServices implements AuthorizationServerTokenServices, ResourceServerTokenServices, ConsumerTokenServices, InitializingBean {...@Transactionalpublic 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-tokenif (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";@AutowiredTokenStore tokenStore;@Autowiredprivate JwtAccessTokenConverter accessTokenConverter;@Beanpublic 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";@Overridepublic 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 {@Autowiredprivate RedisConnectionFactory redisConnectionFactory;@Beanpublic 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@Overridepublic OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException {//获取当前用户的token OAuth2AccessToken existingAccessToken = tokenStore.getAccessToken(authentication);OAuth2RefreshToken refreshToken = null;if (existingAccessToken != null) {//如果刷新token不为nullif (existingAccessToken.getRefreshToken() != null) {//移除刷新tokenrefreshToken = existingAccessToken.getRefreshToken();tokenStore.removeRefreshToken(refreshToken);}//再移除已存在的tokentokenStore.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 modifiedrefreshToken = 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;}@Overridepublic void setTokenStore(TokenStore tokenStore) {super.setTokenStore(tokenStore);this.tokenStore = tokenStore;}@Overridepublic 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…


标签:

素材巴巴 Copyright © 2013-2021 http://www.sucaibaba.com/. Some Rights Reserved. 备案号:备案中。