SysLoginService.java 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429
  1. package com.ruoyi.web.service;
  2. import javax.annotation.Resource;
  3. import com.google.common.collect.Lists;
  4. import com.google.common.collect.Maps;
  5. import com.ruoyi.common.core.domain.AjaxResult;
  6. import com.ruoyi.common.core.domain.entity.SysUser;
  7. import com.ruoyi.common.enums.AccessFromType;
  8. import com.ruoyi.common.enums.ErrorCodes;
  9. import com.ruoyi.common.exception.ErrorException;
  10. import com.ruoyi.common.utils.PhoneUtils;
  11. import com.ruoyi.dz.domain.DzCards;
  12. import com.ruoyi.dz.service.IDzCardsService;
  13. import com.ruoyi.dz.service.impl.DzCardsServiceImpl;
  14. import com.ruoyi.framework.web.service.TokenService;
  15. import com.ruoyi.system.domain.ZuserToken;
  16. import com.ruoyi.system.service.IZuserTokenService;
  17. import com.ruoyi.system.service.ShortMessageService;
  18. import com.ruoyi.web.controller.dz.DzCardsController;
  19. import org.apache.commons.collections4.CollectionUtils;
  20. import org.apache.commons.lang3.tuple.Pair;
  21. import org.springframework.beans.factory.annotation.Autowired;
  22. import org.springframework.security.authentication.AuthenticationManager;
  23. import org.springframework.security.authentication.BadCredentialsException;
  24. import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
  25. import org.springframework.security.core.Authentication;
  26. import org.springframework.stereotype.Component;
  27. import com.ruoyi.common.constant.CacheConstants;
  28. import com.ruoyi.common.constant.Constants;
  29. import com.ruoyi.common.constant.UserConstants;
  30. import com.ruoyi.common.core.domain.model.LoginUser;
  31. import com.ruoyi.common.core.redis.RedisCache;
  32. import com.ruoyi.common.exception.ServiceException;
  33. import com.ruoyi.common.exception.user.BlackListException;
  34. import com.ruoyi.common.exception.user.CaptchaException;
  35. import com.ruoyi.common.exception.user.CaptchaExpireException;
  36. import com.ruoyi.common.exception.user.UserNotExistsException;
  37. import com.ruoyi.common.exception.user.UserPasswordNotMatchException;
  38. import com.ruoyi.common.utils.DateUtils;
  39. import com.ruoyi.common.utils.MessageUtils;
  40. import com.ruoyi.common.utils.StringUtils;
  41. import com.ruoyi.common.utils.ip.IpUtils;
  42. import com.ruoyi.framework.manager.AsyncManager;
  43. import com.ruoyi.framework.manager.factory.AsyncFactory;
  44. import com.ruoyi.framework.security.context.AuthenticationContextHolder;
  45. import com.ruoyi.system.service.ISysConfigService;
  46. import com.ruoyi.system.service.ISysUserService;
  47. import org.springframework.transaction.annotation.Transactional;
  48. import java.util.*;
  49. import java.util.stream.Collectors;
  50. /**
  51. * 登录校验方法
  52. *
  53. * @author ruoyi
  54. */
  55. @Component
  56. public class SysLoginService
  57. {
  58. @Autowired
  59. private TokenService tokenService;
  60. @Resource
  61. private AuthenticationManager authenticationManager;
  62. @Autowired
  63. private RedisCache redisCache;
  64. @Autowired
  65. private ISysUserService userService;
  66. @Autowired
  67. private ISysConfigService configService;
  68. @Autowired
  69. private ShortMessageService shortMessageService;
  70. @Autowired
  71. private IDzCardsService cardsService;
  72. @Autowired
  73. private IZuserTokenService zuserTokenService;
  74. /**
  75. * 登录验证
  76. * @param mobile
  77. * @param username
  78. * @param password
  79. * @param code
  80. * @param uuid
  81. * @return
  82. */
  83. public AjaxResult login(String mobile, String username, String password, String code, String uuid,String type) {
  84. if (StringUtils.isNotBlank(mobile)) {
  85. //手机验证码登录:{code: "9", uuid: "cc94320c3fce4db5898213b727ac1dc0", mobile: "18774924158", password: "1234"}
  86. if (!shortMessageService.checkCode(mobile, password)) {
  87. AsyncManager.me().execute(AsyncFactory.recordLogininfor(mobile, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")));
  88. throw new CaptchaException();
  89. }
  90. loginMobilePreCheck(mobile, password);
  91. username = mobile;
  92. password = UserConstants.LOGIN_SMS_PASS;
  93. } else {
  94. // 验证码校验
  95. // 卡密登录json:{code: "0", uuid: "a9eee0b6729f42c4862a57acef8a4bdd", username: "20000025", password: "123456"}
  96. validateCaptcha(username, code, uuid);
  97. // 登录前置校验
  98. loginPreCheck(username, password);
  99. if(AccessFromType.isFrontApp(type)||AccessFromType.isH5(type)||AccessFromType.isWechat(type)){
  100. //卡密登录时,要将卡的密码转换为用户的密码。手机号的密码与卡的密码分开
  101. DzCards card = cardsService.selectDzCardsByCardNo(username);
  102. if (null==card){
  103. if(username.length()>6&&username.length()<8){
  104. //老师账号,直接跳过
  105. }else if(username.length()>=8){
  106. //卡用户
  107. return AjaxResult.error("卡不存在");
  108. }
  109. }
  110. if(null!=card&&username.length()>=8){
  111. //卡用户
  112. if (!password.trim().equalsIgnoreCase(card.getPassword())){
  113. return AjaxResult.error("密码错误");
  114. }
  115. String dbPwd = userService.selectPasswordByCardId(card.getCardId());
  116. if (StringUtils.isNotEmpty(dbPwd)){
  117. password = dbPwd;
  118. }
  119. }
  120. }
  121. }
  122. // 用户验证
  123. Authentication authentication = null;
  124. try
  125. {
  126. UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
  127. AuthenticationContextHolder.setContext(authenticationToken);
  128. // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
  129. authentication = authenticationManager.authenticate(authenticationToken);
  130. }
  131. catch (Exception e)
  132. {
  133. if (e instanceof BadCredentialsException)
  134. {
  135. AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
  136. throw new UserPasswordNotMatchException();
  137. }
  138. else
  139. {
  140. AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage()));
  141. if(e.getCause() instanceof ErrorException) {
  142. ErrorException errorException = (ErrorException) e.getCause();
  143. Map map = Maps.newHashMap();
  144. ErrorCodes errorCode = errorException.getErrorCode();
  145. map.put("code", errorCode.getCode());
  146. map.put("message", StringUtils.isNotEmpty(errorException.getMessage()) ? errorException.getMessage() : errorCode.getTitle());
  147. return AjaxResult.success(map);
  148. }
  149. throw new ServiceException(e.getMessage());
  150. }
  151. }
  152. finally
  153. {
  154. AuthenticationContextHolder.clearContext();
  155. }
  156. AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
  157. LoginUser loginUser = (LoginUser) authentication.getPrincipal();
  158. Long userId = loginUser.getUserId();
  159. recordLoginInfo(userId);
  160. // 生成token
  161. AjaxResult ajax = AjaxResult.success();
  162. //单点登录
  163. resetTokensByUserIds(Arrays.asList(userId),true);
  164. // 生成token
  165. String token = tokenService.createToken(loginUser);
  166. ZuserToken userToken = new ZuserToken();
  167. userToken.setUserName(StringUtils.isEmpty(type)?username:(type+":"+username));
  168. userToken.setUserId(userId);
  169. userToken.setToken(token);
  170. userToken.setUpdateTime(new Date());
  171. zuserTokenService.insertZuserToken(userToken);
  172. ajax.put(Constants.TOKEN, token);
  173. return ajax;
  174. }
  175. /**
  176. * 此处不使用resetTokens是为了与志愿保持一致
  177. * 重置指定用户的登陆信息,强制重新登陆系统
  178. * @param userIds
  179. * @param isSso:适配志愿中锁分时也要清空用户token信息,此时设置为false,直接清空用户的token,设置为true就表示走单点登录的逻辑
  180. * @param isSetSso:后台配置的单点登录配置,设置为true时启用单点登录,为false时不启用单点登录
  181. * @return
  182. */
  183. public AjaxResult resetTokensByUserIds(List<Long> userIds,Boolean isSso) {
  184. if (isSso){
  185. String isSetSso = configService.selectConfigByKey( "is.set.sso");
  186. if (StringUtils.isBlank(isSetSso)||!Boolean.valueOf(isSetSso)){
  187. //参数配置的单点登录为false,此时不设置单点登录(将userids设置为空就不走清空流程),不需要清空用户已登录的token
  188. userIds = new ArrayList<>();
  189. }
  190. Set<String> excludeUserIdSet=new HashSet<>();
  191. String ssoWhiteListUserName = configService.selectConfigByKey("sso.white.list");
  192. if(StringUtils.isNotBlank(ssoWhiteListUserName)){
  193. Arrays.asList(ssoWhiteListUserName.split(",")).forEach(username->{
  194. excludeUserIdSet.add(username);
  195. });
  196. }
  197. if (CollectionUtils.isNotEmpty(excludeUserIdSet)){
  198. List<Long> excludeUserIds = userService.selectUsers(null,null,excludeUserIdSet).stream().map(SysUser::getUserId).collect(Collectors.toList());
  199. //userIds减去excludeUserIds
  200. // 将排除的用户ID转化为Set,以便高效查找
  201. Set<Long> excludeSet = new HashSet<>(excludeUserIds);
  202. // 使用Stream API过滤掉排除的用户ID
  203. userIds = userIds.stream().filter(userId -> !excludeSet.contains(userId)).collect(Collectors.toList());
  204. }
  205. }
  206. return resetTokensByUserIds(userIds);
  207. }
  208. /**
  209. * 重置指定用户的登陆信息,强制重新登陆系统
  210. * @param startUserList
  211. * @return
  212. */
  213. public AjaxResult resetTokensByUserIds(List<Long> userIds) {
  214. if(CollectionUtils.isEmpty(userIds)){
  215. return AjaxResult.success();
  216. }
  217. ZuserToken zuserToken = new ZuserToken();
  218. zuserToken.setUserIds(userIds);
  219. List<ZuserToken> zuserTokenList = zuserTokenService.selectTopZuserTokenList(zuserToken);
  220. for (ZuserToken zt : zuserTokenList) {
  221. if(StringUtils.isNotNull(zt)){
  222. tokenService.removeUserToken(zt.getToken());
  223. // zuserTokenService.deleteZuserTokenById(zt.getId());
  224. }
  225. }
  226. List<Long> tokenIdList = zuserTokenList.stream().map(ZuserToken::getId).collect(Collectors.toList()).stream().distinct().collect(Collectors.toList());
  227. Long[] tokenIds = tokenIdList.toArray(new Long[tokenIdList.size()]);
  228. if(tokenIds.length>0){
  229. zuserTokenService.deleteZuserTokenByIds(tokenIds);
  230. }
  231. return AjaxResult.success();
  232. }
  233. public void updateBindStatus(Long userId, Integer bindStatus) {
  234. ZuserToken zuserToken = new ZuserToken();
  235. zuserToken.setUserIds(Lists.newArrayList(userId));
  236. List<ZuserToken> zuserTokenList = zuserTokenService.selectTopZuserTokenList(zuserToken);
  237. for (ZuserToken zt : zuserTokenList) {
  238. if(StringUtils.isNotNull(zt)){
  239. tokenService.updateBindStatus(zt.getToken(), bindStatus);
  240. }
  241. }
  242. }
  243. public AjaxResult resetTokens(List<SysUser> startUserList) {
  244. List<Long> userIds = startUserList.stream().map(SysUser::getUserId).collect(Collectors.toList());
  245. return resetTokensByUserIds(userIds);
  246. }
  247. /**
  248. * 重置指定用户的登陆信息,强制重新登陆系统
  249. * @param userName
  250. * @return
  251. */
  252. public AjaxResult resetTokens(String userName) {
  253. ZuserToken zuserToken = new ZuserToken();
  254. zuserToken.setUserName(userName);
  255. List<ZuserToken> zuserTokenList = zuserTokenService.selectTopZuserTokenList(zuserToken);
  256. for (ZuserToken zt : zuserTokenList) {
  257. if(StringUtils.isNotNull(zt)){
  258. tokenService.removeUserToken(zt.getToken());
  259. zuserTokenService.deleteZuserTokenById(zt.getId());
  260. }
  261. }
  262. return AjaxResult.success();
  263. }
  264. /**
  265. * 内部自动登陆,不记录日志
  266. * @param username
  267. * @param password
  268. * @return
  269. */
  270. public String loginInner(String username, String password) {
  271. // 用户验证
  272. Authentication authentication = null;
  273. try
  274. {
  275. UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
  276. AuthenticationContextHolder.setContext(authenticationToken);
  277. // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
  278. authentication = authenticationManager.authenticate(authenticationToken);
  279. }
  280. catch (Exception e)
  281. {
  282. if (e instanceof BadCredentialsException)
  283. {
  284. throw new UserPasswordNotMatchException();
  285. }
  286. else
  287. {
  288. throw new ServiceException(e.getMessage());
  289. }
  290. }
  291. finally
  292. {
  293. AuthenticationContextHolder.clearContext();
  294. }
  295. LoginUser loginUser = (LoginUser) authentication.getPrincipal();
  296. return tokenService.createToken(loginUser);
  297. }
  298. /**
  299. * 校验验证码
  300. *
  301. * @param username 用户名
  302. * @param code 验证码
  303. * @param uuid 唯一标识
  304. * @return 结果
  305. */
  306. public void validateCaptcha(String username, String code, String uuid)
  307. {
  308. boolean captchaEnabled = configService.selectCaptchaEnabled();
  309. if (captchaEnabled)
  310. {
  311. String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.nvl(uuid, "");
  312. String captcha = redisCache.getCacheObject(verifyKey);
  313. if (captcha == null)
  314. {
  315. AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")));
  316. throw new CaptchaExpireException();
  317. }
  318. redisCache.deleteObject(verifyKey);
  319. if (!code.equalsIgnoreCase(captcha))
  320. {
  321. AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")));
  322. throw new CaptchaException();
  323. }
  324. }
  325. }
  326. /**
  327. * 登录前置校验
  328. * @param username 用户名
  329. * @param password 用户密码
  330. */
  331. public void loginPreCheck(String username, String password)
  332. {
  333. // 用户名或密码为空 错误
  334. if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password))
  335. {
  336. AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("not.null")));
  337. throw new UserNotExistsException();
  338. }
  339. // 密码如果不在指定范围内 错误
  340. if (password.length() < UserConstants.PASSWORD_MIN_LENGTH
  341. || password.length() > UserConstants.PASSWORD_MAX_LENGTH)
  342. {
  343. AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
  344. throw new UserPasswordNotMatchException();
  345. }
  346. // 用户名不在指定范围内 错误
  347. if (username.length() < UserConstants.USERNAME_MIN_LENGTH
  348. || username.length() > UserConstants.USERNAME_MAX_LENGTH)
  349. {
  350. AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
  351. throw new UserPasswordNotMatchException();
  352. }
  353. // IP黑名单校验
  354. String blackStr = configService.selectConfigByKey("sys.login.blackIPList");
  355. if (IpUtils.isMatchedIp(blackStr, IpUtils.getIpAddr()))
  356. {
  357. AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("login.blocked")));
  358. throw new BlackListException();
  359. }
  360. }
  361. public void loginMobilePreCheck(String phoneNumber, String code)
  362. {
  363. // 用户名或密码为空 错误
  364. if (StringUtils.isEmpty(phoneNumber) || StringUtils.isEmpty(code))
  365. {
  366. AsyncManager.me().execute(AsyncFactory.recordLogininfor(phoneNumber, Constants.LOGIN_FAIL, MessageUtils.message("not.null")));
  367. throw new UserNotExistsException();
  368. }
  369. // 密码如果不在指定范围内 错误
  370. if (code.length() != 4)
  371. {
  372. AsyncManager.me().execute(AsyncFactory.recordLogininfor(phoneNumber, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
  373. throw new UserPasswordNotMatchException();
  374. }
  375. // 用户名不在指定范围内 错误
  376. if (!PhoneUtils.isPhoneNumber(phoneNumber))
  377. {
  378. AsyncManager.me().execute(AsyncFactory.recordLogininfor(phoneNumber, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
  379. throw new UserPasswordNotMatchException();
  380. }
  381. // IP黑名单校验
  382. String blackStr = configService.selectConfigByKey("sys.login.blackIPList");
  383. if (IpUtils.isMatchedIp(blackStr, IpUtils.getIpAddr()))
  384. {
  385. AsyncManager.me().execute(AsyncFactory.recordLogininfor(phoneNumber, Constants.LOGIN_FAIL, MessageUtils.message("login.blocked")));
  386. throw new BlackListException();
  387. }
  388. }
  389. /**
  390. * 记录登录信息
  391. *
  392. * @param userId 用户ID
  393. */
  394. public void recordLoginInfo(Long userId)
  395. {
  396. userService.updateLoginInfo(userId, IpUtils.getIpAddr(), DateUtils.getNowDate());
  397. }
  398. }