123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316 |
- package com.gihon.sso.service.impl;
- import java.util.ArrayList;
- import java.util.List;
- import java.util.Optional;
- import java.util.concurrent.TimeUnit;
- import java.util.stream.Collectors;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
- import org.springframework.boot.context.properties.EnableConfigurationProperties;
- import org.springframework.data.redis.core.StringRedisTemplate;
- import org.springframework.stereotype.Service;
- import org.springframework.util.Base64Utils;
- import org.springframework.util.StringUtils;
- import com.fasterxml.jackson.core.type.TypeReference;
- import com.gihon.common.auth.AuthConstans;
- import com.gihon.common.entity.GihonRole;
- import com.gihon.common.enums.ModuleType;
- import com.gihon.common.properties.GihonCommonProperties;
- import com.gihon.common.properties.RedisConstants;
- import com.gihon.common.util.JacksonJsonUtils;
- import com.gihon.common.util.UUIDGenerater;
- import com.gihon.sso.entity.vo.LoginUser;
- import com.gihon.sso.entity.vo.RefreshTokenVal;
- import com.gihon.sso.entity.vo.SsoToken;
- import com.gihon.sso.entity.vo.TokenVal;
- import com.gihon.sso.entity.vo.UserInfo;
- import com.gihon.sso.service.GihonRoleService;
- import com.gihon.sso.service.LoginUserService;
- import com.gihon.sso.service.TokenService;
- import lombok.extern.slf4j.Slf4j;
- /**
- * TODO 清理token和refreshToken 需要分开 最好TokenStore:有各种实现,类似Cas中的统一认证中心
- * 与SecurityTokenServiceImpl互斥不要同时出现
- * @author baihe
- *
- */
- @ConditionalOnMissingClass("org.springframework.security.core.userdetails.UserDetails")
- @Slf4j
- @Service("tokenService")
- @EnableConfigurationProperties(GihonCommonProperties.class)
- public class TokenServiceImpl implements TokenService {
- @Autowired
- protected LoginUserService loginUserService;
- @Autowired
- protected StringRedisTemplate stringRedisTemplate;
- @Autowired
- protected GihonCommonProperties gihonCommonProperties;
- @Autowired
- protected GihonRoleService gihonRoleService;
- protected String parseToken(String originToken) {
- return Base64Utils.encodeToUrlSafeString(originToken.getBytes());
- }
- @Override
- public String getUserName(String securityToken) {
- String token = new String(Base64Utils.decodeFromUrlSafeString(securityToken));
- return token.split(RedisConstants.SEP)[0];
- }
- @Override
- public SsoToken createToken(UserInfo userInfo, int moduleType) {
- if (userInfo == null || !StringUtils.hasText(userInfo.getUsername())) {
- return null;
- }
- ModuleType type = ModuleType.parse(moduleType);
- boolean mutiLogin = gihonCommonProperties.isMutiLogin();
- //暂时只有WEB端互踢
- if (type != ModuleType.WEB) {
- mutiLogin = false;
- }
- // 超级管理员只允许WEB登录并且互踢
- if (userInfo.getUsername().equals(AuthConstans.ADMIN)) {
- if (type != ModuleType.WEB) {
- return null;
- }
- mutiLogin = false;
- }
- SsoToken tokenEntity = new SsoToken();
- tokenEntity.setModuleType(moduleType);
- tokenEntity.setUserInfo(userInfo);
- tokenEntity.setToken(parseToken(userInfo.getUsername() + RedisConstants.SEP + UUIDGenerater.genUUID()));
- tokenEntity.setRefreshToken(parseToken(userInfo.getUsername() + RedisConstants.SEP + UUIDGenerater.genUUID()));
-
- long now = System.currentTimeMillis();
- tokenEntity.setRefreshTokenExpire(now + gihonCommonProperties.getRefreshTokenExpired());
- tokenEntity.setTokenExpire(now + gihonCommonProperties.getTokenExpired());
-
- saveToken(tokenEntity, mutiLogin);
-
- return tokenEntity;
- }
- @Override
- public SsoToken refreshToken(String refreshToken) {
- String rtvJsonStr = stringRedisTemplate.opsForValue().get(REFRESH_TOKEN_PRE + refreshToken);
- if (!StringUtils.hasText(rtvJsonStr)) {
- log.warn("refresh_token({})已经失效", refreshToken);
- return null;
- }
- RefreshTokenVal rtv = JacksonJsonUtils.readObject(rtvJsonStr, RefreshTokenVal.class);
- if (rtv == null) {
- log.warn("refresh_token({})解析失败({})", refreshToken, rtvJsonStr);
- return null;
- }
- // 获取tokenEntity,通过账号获取账号信息
- UserInfo userInfo = this.getUserInfo(rtv.getUserAccount());
- long now = System.currentTimeMillis();
- // 主动删除refresh 和 token
- TokenVal tv = TokenVal.builder().expireTime(rtv.getExpireTime()).userAccount(rtv.getUserAccount()).refreshToken(refreshToken).token(rtv.getToken())
- .moduleType(rtv.getModuleType()).build();
- this.removeToken(tv);
- if (rtv.getExpireTime() < now) {
- log.warn("refresh_token({})已经过期({})", refreshToken, rtv.getExpireTime());
- return null;
- }
- SsoToken tokenEntityNew = this.createToken(userInfo, rtv.getModuleType());
- // 发送通知更新Token
- notifyRefreshToken(rtv, tokenEntityNew);
- return tokenEntityNew;
- }
- @Override
- public TokenVal getTokenVal(String token) {
- String tvJsonStr = stringRedisTemplate.opsForValue().get(TOKEN_PRE + token);
- TokenVal tokenEntity = null;
- if (StringUtils.hasText(tvJsonStr)) {
- tokenEntity = JacksonJsonUtils.readObject(tvJsonStr, TokenVal.class);
- }
- return tokenEntity;
- }
- @Override
- public List<TokenVal> getTokenByAccount(String account) {
- String value = (String) stringRedisTemplate.opsForHash().get(USER_TOKEN_STORE, account);
- List<TokenVal> list = null;
- if (StringUtils.hasText(value)) {
- TypeReference<List<TokenVal>> type = new TypeReference<List<TokenVal>>() {
- };
- list = JacksonJsonUtils.readObject(value, type);
- }
- return list;
- }
- @Override
- public void clearToken(String token) {
- String tvJsonStr = stringRedisTemplate.opsForValue().get(TOKEN_PRE + token);
- TokenVal tokenEntity = null;
- if (StringUtils.hasText(tvJsonStr)) {
- tokenEntity = JacksonJsonUtils.readObject(tvJsonStr, TokenVal.class);
- } else {
- log.warn("{} token is missing", token);
- return;
- }
- if (tokenEntity != null) {
- this.removeToken(tokenEntity);
- }
- }
- @Override
- public void autoClearRefreshToken(String refreshToken, String account) {
- List<TokenVal> tl = getTokenByAccount(account);
- if (tl == null || tl.isEmpty()) {
- return;
- }
- Optional<TokenVal> a = tl.stream().filter(t -> t.getRefreshToken().equals(refreshToken)).findFirst();
- if (a.isPresent()) {
- this.removeTokenList(a.get(), tl);
- }
- }
- @Override
- public UserInfo checkToken(LoginUser loginUser) {
- return checkTokenInfo(loginUser.getToken());
- }
- @Override
- public UserInfo checkTokenInfo(String token) {
- String tokenValue = stringRedisTemplate.opsForValue().get(TOKEN_PRE + token);
- TokenVal tokenEntity = null;
- if (StringUtils.hasText(tokenValue)) {
- tokenEntity = JacksonJsonUtils.readObject(tokenValue, TokenVal.class);
- if (tokenEntity != null) {
- return this.getUserInfo(tokenEntity.getUserAccount());
- } else {
- stringRedisTemplate.delete(TOKEN_PRE + token);
- }
- }
- return null;
- }
- /**
- * 从UserStore中获取用户基本信息 TODO 定时刷新或者AOP通知
- *
- * @param account
- * @return
- */
- public UserInfo getUserInfo(String account) {
- String userAccount = (String) stringRedisTemplate.opsForHash().get(USER_STORE, account);
- UserInfo userInfo = null;
- long now = System.currentTimeMillis();
- if (StringUtils.hasText(userAccount)) {
- userInfo = JacksonJsonUtils.readObject(userAccount, UserInfo.class);
- if((userInfo.getCreatedTime() + gihonCommonProperties.getRefreshTokenExpired()) >= now) {//超时重新获取,有AOP同步账号信息
- userInfo = null;
- }
- }
- if(userInfo==null) {
- userInfo = loginUserService.queryUserByUserAccount(account);
- if (userInfo == null) {
- return null;
- }
- // add roleList;
- List<GihonRole> rl = gihonRoleService.getRoleList(userInfo.getId());
- userInfo.setRoleList(rl.stream().map(r -> r.getCompanyId()+RedisConstants.SEP+r.getRoleCode()).collect(Collectors.toList()));
- userInfo.setCreatedTime(now);
- stringRedisTemplate.opsForHash().put(USER_STORE, account, JacksonJsonUtils.writeObject(userInfo));
- }
- return userInfo;
- }
- private void saveToken(SsoToken tokenEntity, boolean mutiLogin) {
- String userAccount = tokenEntity.getUserInfo().getUsername();
- List<TokenVal> tokenList = getTokenByAccount(userAccount);
- if (tokenList == null) {
- tokenList = new ArrayList<TokenVal>();
- }
- if (!mutiLogin) {
- // 强制踢掉其他同类型的token
- List<TokenVal> removeList = tokenList.stream().filter(t -> t.getModuleType() == tokenEntity.getModuleType()).collect(Collectors.toList());
- //此处只删除token,不回写List
- removeList.forEach(t -> removeToken(t));
- tokenList.removeAll(removeList);
- }
- TokenVal tv = TokenVal.builder().expireTime(tokenEntity.getTokenExpire()).userAccount(userAccount).refreshToken(tokenEntity.getRefreshToken())
- .token(tokenEntity.getToken()).moduleType(tokenEntity.getModuleType()).build();
- RefreshTokenVal rtv = RefreshTokenVal.builder().expireTime(tokenEntity.getTokenExpire()).userAccount(userAccount)
- .refreshToken(tokenEntity.getRefreshToken()).token(tokenEntity.getToken()).moduleType(tokenEntity.getModuleType()).build();
- tokenList.remove(tv);
- tokenList.add(tv);
- boolean flag = stringRedisTemplate.opsForHash().hasKey(USER_STORE, userAccount);
- if (!flag) {
- stringRedisTemplate.opsForHash().putIfAbsent(USER_STORE, userAccount, JacksonJsonUtils.writeObject(tokenEntity.getUserInfo()));
- }
- stringRedisTemplate.opsForValue().set(TOKEN_PRE + tokenEntity.getToken(), JacksonJsonUtils.writeObject(tv),
- gihonCommonProperties.getTokenExpired() + gihonCommonProperties.getExipredMore(), TimeUnit.MILLISECONDS);
- stringRedisTemplate.opsForValue().set(REFRESH_TOKEN_PRE + tokenEntity.getRefreshToken(), JacksonJsonUtils.writeObject(rtv),
- gihonCommonProperties.getRefreshTokenExpired() + gihonCommonProperties.getExipredMore(), TimeUnit.MILLISECONDS);
- stringRedisTemplate.opsForHash().put(USER_TOKEN_STORE, userAccount, JacksonJsonUtils.writeObject(tokenList));
- }
- // 移除Redis中token
- private void removeTokenToStore(String userAccount, TokenVal tokenVal, List<TokenVal> tokenList) {
- if (tokenList != null) {
- tokenList.remove(tokenVal);
- }
- stringRedisTemplate.opsForHash().put(USER_TOKEN_STORE, userAccount, JacksonJsonUtils.writeObject(tokenList));
- }
- // 移除Redis中token
- private void removeToken(TokenVal tokenVal) {
- stringRedisTemplate.delete(REFRESH_TOKEN_PRE + tokenVal.getRefreshToken());
- stringRedisTemplate.delete(TOKEN_PRE + tokenVal.getToken());
- notifyToken(tokenVal);
- }
- // 移除Redis中token 多线程有问题
- private void removeTokenList(TokenVal tokenVal, List<TokenVal> tokenList) {
- removeToken(tokenVal);
-
- String userAccount = tokenVal.getUserAccount();
- removeTokenToStore(userAccount, tokenVal, tokenList);
- }
- /**
- * 通知其他系统Token失效,使用同一个Token的时候有用。需要其他系统重新登录
- *
- * @param tokenEntity
- */
- private void notifyToken(TokenVal tokenEntity) {
- // TODO
- return;
- }
- /**
- * 通知各个系统refreshToken,需要其他系统重新登录
- *
- * @param tokenEntity
- * @param tokenEntityNew
- */
- private void notifyRefreshToken(RefreshTokenVal rtv, SsoToken tokenEntityNew) {
- // TODO
- return;
- }
- }
|