TokenServiceImpl.java 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. package com.gihon.sso.service.impl;
  2. import java.util.ArrayList;
  3. import java.util.List;
  4. import java.util.Optional;
  5. import java.util.concurrent.TimeUnit;
  6. import java.util.stream.Collectors;
  7. import org.springframework.beans.factory.annotation.Autowired;
  8. import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
  9. import org.springframework.boot.context.properties.EnableConfigurationProperties;
  10. import org.springframework.data.redis.core.StringRedisTemplate;
  11. import org.springframework.stereotype.Service;
  12. import org.springframework.util.Base64Utils;
  13. import org.springframework.util.StringUtils;
  14. import com.fasterxml.jackson.core.type.TypeReference;
  15. import com.gihon.common.auth.AuthConstans;
  16. import com.gihon.common.entity.GihonRole;
  17. import com.gihon.common.enums.ModuleType;
  18. import com.gihon.common.properties.GihonCommonProperties;
  19. import com.gihon.common.properties.RedisConstants;
  20. import com.gihon.common.util.JacksonJsonUtils;
  21. import com.gihon.common.util.UUIDGenerater;
  22. import com.gihon.sso.entity.vo.LoginUser;
  23. import com.gihon.sso.entity.vo.RefreshTokenVal;
  24. import com.gihon.sso.entity.vo.SsoToken;
  25. import com.gihon.sso.entity.vo.TokenVal;
  26. import com.gihon.sso.entity.vo.UserInfo;
  27. import com.gihon.sso.service.GihonRoleService;
  28. import com.gihon.sso.service.LoginUserService;
  29. import com.gihon.sso.service.TokenService;
  30. import lombok.extern.slf4j.Slf4j;
  31. /**
  32. * TODO 清理token和refreshToken 需要分开 最好TokenStore:有各种实现,类似Cas中的统一认证中心
  33. * 与SecurityTokenServiceImpl互斥不要同时出现
  34. * @author baihe
  35. *
  36. */
  37. @ConditionalOnMissingClass("org.springframework.security.core.userdetails.UserDetails")
  38. @Slf4j
  39. @Service("tokenService")
  40. @EnableConfigurationProperties(GihonCommonProperties.class)
  41. public class TokenServiceImpl implements TokenService {
  42. @Autowired
  43. protected LoginUserService loginUserService;
  44. @Autowired
  45. protected StringRedisTemplate stringRedisTemplate;
  46. @Autowired
  47. protected GihonCommonProperties gihonCommonProperties;
  48. @Autowired
  49. protected GihonRoleService gihonRoleService;
  50. protected String parseToken(String originToken) {
  51. return Base64Utils.encodeToUrlSafeString(originToken.getBytes());
  52. }
  53. @Override
  54. public String getUserName(String securityToken) {
  55. String token = new String(Base64Utils.decodeFromUrlSafeString(securityToken));
  56. return token.split(RedisConstants.SEP)[0];
  57. }
  58. @Override
  59. public SsoToken createToken(UserInfo userInfo, int moduleType) {
  60. if (userInfo == null || !StringUtils.hasText(userInfo.getUsername())) {
  61. return null;
  62. }
  63. ModuleType type = ModuleType.parse(moduleType);
  64. boolean mutiLogin = gihonCommonProperties.isMutiLogin();
  65. //暂时只有WEB端互踢
  66. if (type != ModuleType.WEB) {
  67. mutiLogin = false;
  68. }
  69. // 超级管理员只允许WEB登录并且互踢
  70. if (userInfo.getUsername().equals(AuthConstans.ADMIN)) {
  71. if (type != ModuleType.WEB) {
  72. return null;
  73. }
  74. mutiLogin = false;
  75. }
  76. SsoToken tokenEntity = new SsoToken();
  77. tokenEntity.setModuleType(moduleType);
  78. tokenEntity.setUserInfo(userInfo);
  79. tokenEntity.setToken(parseToken(userInfo.getUsername() + RedisConstants.SEP + UUIDGenerater.genUUID()));
  80. tokenEntity.setRefreshToken(parseToken(userInfo.getUsername() + RedisConstants.SEP + UUIDGenerater.genUUID()));
  81. long now = System.currentTimeMillis();
  82. tokenEntity.setRefreshTokenExpire(now + gihonCommonProperties.getRefreshTokenExpired());
  83. tokenEntity.setTokenExpire(now + gihonCommonProperties.getTokenExpired());
  84. saveToken(tokenEntity, mutiLogin);
  85. return tokenEntity;
  86. }
  87. @Override
  88. public SsoToken refreshToken(String refreshToken) {
  89. String rtvJsonStr = stringRedisTemplate.opsForValue().get(REFRESH_TOKEN_PRE + refreshToken);
  90. if (!StringUtils.hasText(rtvJsonStr)) {
  91. log.warn("refresh_token({})已经失效", refreshToken);
  92. return null;
  93. }
  94. RefreshTokenVal rtv = JacksonJsonUtils.readObject(rtvJsonStr, RefreshTokenVal.class);
  95. if (rtv == null) {
  96. log.warn("refresh_token({})解析失败({})", refreshToken, rtvJsonStr);
  97. return null;
  98. }
  99. // 获取tokenEntity,通过账号获取账号信息
  100. UserInfo userInfo = this.getUserInfo(rtv.getUserAccount());
  101. long now = System.currentTimeMillis();
  102. // 主动删除refresh 和 token
  103. TokenVal tv = TokenVal.builder().expireTime(rtv.getExpireTime()).userAccount(rtv.getUserAccount()).refreshToken(refreshToken).token(rtv.getToken())
  104. .moduleType(rtv.getModuleType()).build();
  105. this.removeToken(tv);
  106. if (rtv.getExpireTime() < now) {
  107. log.warn("refresh_token({})已经过期({})", refreshToken, rtv.getExpireTime());
  108. return null;
  109. }
  110. SsoToken tokenEntityNew = this.createToken(userInfo, rtv.getModuleType());
  111. // 发送通知更新Token
  112. notifyRefreshToken(rtv, tokenEntityNew);
  113. return tokenEntityNew;
  114. }
  115. @Override
  116. public TokenVal getTokenVal(String token) {
  117. String tvJsonStr = stringRedisTemplate.opsForValue().get(TOKEN_PRE + token);
  118. TokenVal tokenEntity = null;
  119. if (StringUtils.hasText(tvJsonStr)) {
  120. tokenEntity = JacksonJsonUtils.readObject(tvJsonStr, TokenVal.class);
  121. }
  122. return tokenEntity;
  123. }
  124. @Override
  125. public List<TokenVal> getTokenByAccount(String account) {
  126. String value = (String) stringRedisTemplate.opsForHash().get(USER_TOKEN_STORE, account);
  127. List<TokenVal> list = null;
  128. if (StringUtils.hasText(value)) {
  129. TypeReference<List<TokenVal>> type = new TypeReference<List<TokenVal>>() {
  130. };
  131. list = JacksonJsonUtils.readObject(value, type);
  132. }
  133. return list;
  134. }
  135. @Override
  136. public void clearToken(String token) {
  137. String tvJsonStr = stringRedisTemplate.opsForValue().get(TOKEN_PRE + token);
  138. TokenVal tokenEntity = null;
  139. if (StringUtils.hasText(tvJsonStr)) {
  140. tokenEntity = JacksonJsonUtils.readObject(tvJsonStr, TokenVal.class);
  141. } else {
  142. log.warn("{} token is missing", token);
  143. return;
  144. }
  145. if (tokenEntity != null) {
  146. this.removeToken(tokenEntity);
  147. }
  148. }
  149. @Override
  150. public void autoClearRefreshToken(String refreshToken, String account) {
  151. List<TokenVal> tl = getTokenByAccount(account);
  152. if (tl == null || tl.isEmpty()) {
  153. return;
  154. }
  155. Optional<TokenVal> a = tl.stream().filter(t -> t.getRefreshToken().equals(refreshToken)).findFirst();
  156. if (a.isPresent()) {
  157. this.removeTokenList(a.get(), tl);
  158. }
  159. }
  160. @Override
  161. public UserInfo checkToken(LoginUser loginUser) {
  162. return checkTokenInfo(loginUser.getToken());
  163. }
  164. @Override
  165. public UserInfo checkTokenInfo(String token) {
  166. String tokenValue = stringRedisTemplate.opsForValue().get(TOKEN_PRE + token);
  167. TokenVal tokenEntity = null;
  168. if (StringUtils.hasText(tokenValue)) {
  169. tokenEntity = JacksonJsonUtils.readObject(tokenValue, TokenVal.class);
  170. if (tokenEntity != null) {
  171. return this.getUserInfo(tokenEntity.getUserAccount());
  172. } else {
  173. stringRedisTemplate.delete(TOKEN_PRE + token);
  174. }
  175. }
  176. return null;
  177. }
  178. /**
  179. * 从UserStore中获取用户基本信息 TODO 定时刷新或者AOP通知
  180. *
  181. * @param account
  182. * @return
  183. */
  184. public UserInfo getUserInfo(String account) {
  185. String userAccount = (String) stringRedisTemplate.opsForHash().get(USER_STORE, account);
  186. UserInfo userInfo = null;
  187. long now = System.currentTimeMillis();
  188. if (StringUtils.hasText(userAccount)) {
  189. userInfo = JacksonJsonUtils.readObject(userAccount, UserInfo.class);
  190. if((userInfo.getCreatedTime() + gihonCommonProperties.getRefreshTokenExpired()) >= now) {//超时重新获取,有AOP同步账号信息
  191. userInfo = null;
  192. }
  193. }
  194. if(userInfo==null) {
  195. userInfo = loginUserService.queryUserByUserAccount(account);
  196. if (userInfo == null) {
  197. return null;
  198. }
  199. // add roleList;
  200. List<GihonRole> rl = gihonRoleService.getRoleList(userInfo.getId());
  201. userInfo.setRoleList(rl.stream().map(r -> r.getCompanyId()+RedisConstants.SEP+r.getRoleCode()).collect(Collectors.toList()));
  202. userInfo.setCreatedTime(now);
  203. stringRedisTemplate.opsForHash().put(USER_STORE, account, JacksonJsonUtils.writeObject(userInfo));
  204. }
  205. return userInfo;
  206. }
  207. private void saveToken(SsoToken tokenEntity, boolean mutiLogin) {
  208. String userAccount = tokenEntity.getUserInfo().getUsername();
  209. List<TokenVal> tokenList = getTokenByAccount(userAccount);
  210. if (tokenList == null) {
  211. tokenList = new ArrayList<TokenVal>();
  212. }
  213. if (!mutiLogin) {
  214. // 强制踢掉其他同类型的token
  215. List<TokenVal> removeList = tokenList.stream().filter(t -> t.getModuleType() == tokenEntity.getModuleType()).collect(Collectors.toList());
  216. //此处只删除token,不回写List
  217. removeList.forEach(t -> removeToken(t));
  218. tokenList.removeAll(removeList);
  219. }
  220. TokenVal tv = TokenVal.builder().expireTime(tokenEntity.getTokenExpire()).userAccount(userAccount).refreshToken(tokenEntity.getRefreshToken())
  221. .token(tokenEntity.getToken()).moduleType(tokenEntity.getModuleType()).build();
  222. RefreshTokenVal rtv = RefreshTokenVal.builder().expireTime(tokenEntity.getTokenExpire()).userAccount(userAccount)
  223. .refreshToken(tokenEntity.getRefreshToken()).token(tokenEntity.getToken()).moduleType(tokenEntity.getModuleType()).build();
  224. tokenList.remove(tv);
  225. tokenList.add(tv);
  226. boolean flag = stringRedisTemplate.opsForHash().hasKey(USER_STORE, userAccount);
  227. if (!flag) {
  228. stringRedisTemplate.opsForHash().putIfAbsent(USER_STORE, userAccount, JacksonJsonUtils.writeObject(tokenEntity.getUserInfo()));
  229. }
  230. stringRedisTemplate.opsForValue().set(TOKEN_PRE + tokenEntity.getToken(), JacksonJsonUtils.writeObject(tv),
  231. gihonCommonProperties.getTokenExpired() + gihonCommonProperties.getExipredMore(), TimeUnit.MILLISECONDS);
  232. stringRedisTemplate.opsForValue().set(REFRESH_TOKEN_PRE + tokenEntity.getRefreshToken(), JacksonJsonUtils.writeObject(rtv),
  233. gihonCommonProperties.getRefreshTokenExpired() + gihonCommonProperties.getExipredMore(), TimeUnit.MILLISECONDS);
  234. stringRedisTemplate.opsForHash().put(USER_TOKEN_STORE, userAccount, JacksonJsonUtils.writeObject(tokenList));
  235. }
  236. // 移除Redis中token
  237. private void removeTokenToStore(String userAccount, TokenVal tokenVal, List<TokenVal> tokenList) {
  238. if (tokenList != null) {
  239. tokenList.remove(tokenVal);
  240. }
  241. stringRedisTemplate.opsForHash().put(USER_TOKEN_STORE, userAccount, JacksonJsonUtils.writeObject(tokenList));
  242. }
  243. // 移除Redis中token
  244. private void removeToken(TokenVal tokenVal) {
  245. stringRedisTemplate.delete(REFRESH_TOKEN_PRE + tokenVal.getRefreshToken());
  246. stringRedisTemplate.delete(TOKEN_PRE + tokenVal.getToken());
  247. notifyToken(tokenVal);
  248. }
  249. // 移除Redis中token 多线程有问题
  250. private void removeTokenList(TokenVal tokenVal, List<TokenVal> tokenList) {
  251. removeToken(tokenVal);
  252. String userAccount = tokenVal.getUserAccount();
  253. removeTokenToStore(userAccount, tokenVal, tokenList);
  254. }
  255. /**
  256. * 通知其他系统Token失效,使用同一个Token的时候有用。需要其他系统重新登录
  257. *
  258. * @param tokenEntity
  259. */
  260. private void notifyToken(TokenVal tokenEntity) {
  261. // TODO
  262. return;
  263. }
  264. /**
  265. * 通知各个系统refreshToken,需要其他系统重新登录
  266. *
  267. * @param tokenEntity
  268. * @param tokenEntityNew
  269. */
  270. private void notifyRefreshToken(RefreshTokenVal rtv, SsoToken tokenEntityNew) {
  271. // TODO
  272. return;
  273. }
  274. }