|
|
@@ -1,417 +0,0 @@
|
|
|
-package com.fs.framework.web.service;
|
|
|
-
|
|
|
-import com.fs.framework.service.TokenService;
|
|
|
-import javax.annotation.Resource;
|
|
|
-
|
|
|
-import cn.hutool.core.bean.BeanUtil;
|
|
|
-import cn.hutool.core.util.ObjectUtil;
|
|
|
-import com.alibaba.fastjson.JSON;
|
|
|
-import com.alibaba.fastjson.JSONObject;
|
|
|
-import com.fs.common.enums.DataSourceType;
|
|
|
-import com.fs.common.service.WechatLoginService;
|
|
|
-import com.fs.common.utils.StringUtils;
|
|
|
-import com.fs.framework.datasource.DynamicDataSource;
|
|
|
-import com.fs.framework.datasource.DynamicDataSourceContextHolder;
|
|
|
-import com.fs.framework.datasource.TenantDataSourceManager;
|
|
|
-import com.fs.tenant.domain.TenantInfo;
|
|
|
-import org.springframework.beans.BeanUtils;
|
|
|
-import org.springframework.beans.factory.annotation.Autowired;
|
|
|
-import org.springframework.beans.factory.annotation.Value;
|
|
|
-import org.springframework.security.authentication.AuthenticationManager;
|
|
|
-import org.springframework.security.authentication.BadCredentialsException;
|
|
|
-import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
|
|
-import org.springframework.security.core.Authentication;
|
|
|
-import org.springframework.stereotype.Component;
|
|
|
-import com.fs.common.constant.Constants;
|
|
|
-import com.fs.common.core.domain.entity.SysUser;
|
|
|
-import com.fs.common.core.domain.model.LoginUser;
|
|
|
-import com.fs.common.core.redis.RedisCache;
|
|
|
-import com.fs.common.exception.ServiceException;
|
|
|
-import com.fs.common.exception.user.CaptchaException;
|
|
|
-import com.fs.common.exception.user.CaptchaExpireException;
|
|
|
-import com.fs.common.exception.user.UserPasswordNotMatchException;
|
|
|
-import com.fs.common.utils.DateUtils;
|
|
|
-import com.fs.common.utils.MessageUtils;
|
|
|
-import com.fs.common.utils.ServletUtils;
|
|
|
-import com.fs.common.utils.ip.IpUtils;
|
|
|
-import com.fs.framework.manager.AsyncManager;
|
|
|
-import com.fs.framework.manager.factory.AsyncFactory;
|
|
|
-import com.fs.system.service.ISysConfigService;
|
|
|
-import com.fs.system.service.ISysUserService;
|
|
|
-
|
|
|
-import java.util.*;
|
|
|
-import java.util.concurrent.TimeUnit;
|
|
|
-
|
|
|
-/**
|
|
|
- * 登录校验方法
|
|
|
- *
|
|
|
-
|
|
|
- */
|
|
|
-@Component
|
|
|
-public class SysLoginService
|
|
|
-{
|
|
|
- @Autowired
|
|
|
- private TokenService tokenService;
|
|
|
-
|
|
|
- @Resource
|
|
|
- private AuthenticationManager authenticationManager;
|
|
|
-
|
|
|
- @Autowired
|
|
|
- private RedisCache redisCache;
|
|
|
-
|
|
|
- @Autowired
|
|
|
- private ISysUserService userService;
|
|
|
-
|
|
|
- @Autowired
|
|
|
- private ISysConfigService configService;
|
|
|
- @Autowired
|
|
|
- private WechatLoginService wechatLoginService;
|
|
|
-
|
|
|
- @Value("${wechat.admin.appid:#{null}}")
|
|
|
- private String appId;
|
|
|
- @Value("${wechat.admin.secret:#{null}}")
|
|
|
- private String secret;
|
|
|
- @Value("${wechat.admin.redirectUri:#{null}}")
|
|
|
- private String redirectUri;
|
|
|
- @Value("${wechat.isNeedScan:false}")
|
|
|
- private Boolean isNeedScan;
|
|
|
-
|
|
|
- /**
|
|
|
- * 登录验证
|
|
|
- *
|
|
|
- * @param username 用户名
|
|
|
- * @param password 密码
|
|
|
- * @param code 验证码
|
|
|
- * @param uuid 唯一标识
|
|
|
- * @return 结果
|
|
|
- */
|
|
|
- public String login(String username, String password, String code, String uuid, String tenantCode)
|
|
|
- {
|
|
|
- boolean captchaOnOff = configService.selectCaptchaOnOff();
|
|
|
- // 验证码开关
|
|
|
- if (captchaOnOff)
|
|
|
- {
|
|
|
- validateCaptcha(username, code, uuid);
|
|
|
- }
|
|
|
-
|
|
|
- TenantInfo tenantInfo = null;
|
|
|
-
|
|
|
- // 默认使用主库
|
|
|
- DynamicDataSourceContextHolder.setDataSourceType(DataSourceType.MASTER.name());
|
|
|
-
|
|
|
- // ===== 只有传了 tenantCode 才查询租户并切库 =====
|
|
|
- if (StringUtils.isNotBlank(tenantCode))
|
|
|
- {
|
|
|
- // 查询租户(主库)
|
|
|
- tenantInfo = userService.getTenantInfo(tenantCode);
|
|
|
- if (BeanUtil.isEmpty(tenantInfo)) {
|
|
|
- throw new ServiceException("企业不存在");
|
|
|
- }
|
|
|
-
|
|
|
- if (!tenantInfo.getStatus().equals(1)) {
|
|
|
- throw new ServiceException("企业已禁用");
|
|
|
- }
|
|
|
-
|
|
|
- // 切租户库
|
|
|
- tenantDataSourceManager.switchTenant(tenantInfo);
|
|
|
- }
|
|
|
-
|
|
|
- try {
|
|
|
- // 用户验证
|
|
|
- Authentication authentication = null;
|
|
|
- try {
|
|
|
- // 该方法会去调用 UserDetailsServiceImpl.loadUserByUsername
|
|
|
- authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
|
|
|
- } catch (Exception e) {
|
|
|
- if (e instanceof BadCredentialsException) {
|
|
|
- AsyncManager.me().execute(AsyncFactory.recordLogininfor(0L, username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
|
|
|
- throw new UserPasswordNotMatchException();
|
|
|
- } else {
|
|
|
- AsyncManager.me().execute(AsyncFactory.recordLogininfor(0L, username, Constants.LOGIN_FAIL, e.getMessage()));
|
|
|
- throw new ServiceException(e.getMessage());
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- AsyncManager.me().execute(AsyncFactory.recordLogininfor(0L, username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
|
|
|
-
|
|
|
- LoginUser loginUser = (LoginUser) authentication.getPrincipal();
|
|
|
-
|
|
|
- // 只有多租户登录才设置 tenantId
|
|
|
- if (tenantInfo != null) {
|
|
|
- loginUser.setTenantId(tenantInfo.getId());
|
|
|
- }
|
|
|
-
|
|
|
- recordLoginInfo(loginUser.getUser());
|
|
|
-
|
|
|
- // 生成 token
|
|
|
- return tokenService.createToken(loginUser);
|
|
|
- } finally {
|
|
|
- // 防止线程串库(必须)
|
|
|
- tenantDataSourceManager.clear();
|
|
|
- DynamicDataSourceContextHolder.setDataSourceType(DataSourceType.MASTER.name());
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- /**
|
|
|
- * 校验验证码
|
|
|
- *
|
|
|
- * @param username 用户名
|
|
|
- * @param code 验证码
|
|
|
- * @param uuid 唯一标识
|
|
|
- * @return 结果
|
|
|
- */
|
|
|
- public void validateCaptcha(String username, String code, String uuid)
|
|
|
- {
|
|
|
- String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
|
|
|
- String captcha = redisCache.getCacheObject(verifyKey);
|
|
|
- redisCache.deleteObject(verifyKey);
|
|
|
- if (captcha == null)
|
|
|
- {
|
|
|
- AsyncManager.me().execute(AsyncFactory.recordLogininfor(0L, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")));
|
|
|
- throw new CaptchaExpireException();
|
|
|
- }
|
|
|
- if (!code.equalsIgnoreCase(captcha))
|
|
|
- {
|
|
|
- AsyncManager.me().execute(AsyncFactory.recordLogininfor(0L, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")));
|
|
|
- throw new CaptchaException();
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 记录登录信息
|
|
|
- */
|
|
|
- public void recordLoginInfo(SysUser user)
|
|
|
- {
|
|
|
- String ipAddr = IpUtils.getIpAddr(ServletUtils.getRequest());
|
|
|
- String loginIp = user.getLoginIp();
|
|
|
- if (com.fs.common.utils.StringUtils.isEmpty(loginIp)) {
|
|
|
- user.setLoginIp(ipAddr);
|
|
|
- } else {
|
|
|
- List<String> ipList = new ArrayList<>(Arrays.asList(loginIp.split(",")));
|
|
|
- if (!ipList.contains(ipAddr)) {
|
|
|
- ipList.add(ipAddr);
|
|
|
- user.setLoginIp(String.join(",", ipList));
|
|
|
- }
|
|
|
- }
|
|
|
- user.setLoginDate(DateUtils.getNowDate());
|
|
|
- userService.updateUserProfile(user);
|
|
|
- }
|
|
|
-
|
|
|
- @Resource
|
|
|
- private TenantDataSourceManager tenantDataSourceManager;
|
|
|
-
|
|
|
- public boolean checkIsNeedCheck(String username, String password, String code, String uuid, String tenantCode)
|
|
|
- {
|
|
|
- // 验证码开关
|
|
|
- boolean captchaOnOff = configService.selectCaptchaOnOff();
|
|
|
- if (captchaOnOff)
|
|
|
- {
|
|
|
- String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
|
|
|
- String captcha = redisCache.getCacheObject(verifyKey);
|
|
|
- redisCache.deleteObject(verifyKey);
|
|
|
-
|
|
|
- if (captcha == null)
|
|
|
- {
|
|
|
- AsyncManager.me().execute(AsyncFactory.recordLogininfor(0L, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")));
|
|
|
- throw new CaptchaExpireException();
|
|
|
- }
|
|
|
-
|
|
|
- if (!code.equalsIgnoreCase(captcha))
|
|
|
- {
|
|
|
- AsyncManager.me().execute(AsyncFactory.recordLogininfor(0L, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")));
|
|
|
- throw new CaptchaException();
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- TenantInfo tenantInfo = null;
|
|
|
-
|
|
|
- // 默认使用主库
|
|
|
- DynamicDataSourceContextHolder.setDataSourceType(DataSourceType.MASTER.name());
|
|
|
-
|
|
|
- // ===== 只有传了 tenantCode 才走租户逻辑 =====
|
|
|
- if (StringUtils.isNotBlank(tenantCode))
|
|
|
- {
|
|
|
- // 查询租户(主库)
|
|
|
- tenantInfo = userService.getTenantInfo(tenantCode);
|
|
|
- if (BeanUtil.isEmpty(tenantInfo))
|
|
|
- {
|
|
|
- throw new ServiceException("企业不存在");
|
|
|
- }
|
|
|
- if (!tenantInfo.getStatus().equals(1))
|
|
|
- {
|
|
|
- throw new ServiceException("企业已禁用");
|
|
|
- }
|
|
|
-
|
|
|
- // 切到租户库
|
|
|
- tenantDataSourceManager.switchTenant(tenantInfo);
|
|
|
- }
|
|
|
-
|
|
|
- try
|
|
|
- {
|
|
|
- // 用户验证
|
|
|
- Authentication authentication = null;
|
|
|
- try {
|
|
|
- authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
|
|
|
- } catch (Exception e)
|
|
|
- {
|
|
|
- if (e instanceof BadCredentialsException) {
|
|
|
- AsyncManager.me().execute(AsyncFactory.recordLogininfor(0L, username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
|
|
|
- throw new UserPasswordNotMatchException();
|
|
|
- } else {
|
|
|
- AsyncManager.me().execute(AsyncFactory.recordLogininfor(0L, username, Constants.LOGIN_FAIL, e.getMessage()));
|
|
|
- throw new ServiceException(e.getMessage());
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- LoginUser loginUser = (LoginUser) authentication.getPrincipal();
|
|
|
-
|
|
|
- // 查询当前登录用户信息(在当前数据源下)
|
|
|
- SysUser sysUser = userService.selectUserById(loginUser.getUserId());
|
|
|
-
|
|
|
- Long[] userIds = new Long[]{236L, 246L, 247L, 253L, 119L};
|
|
|
- for (Long userId : userIds)
|
|
|
- {
|
|
|
- if (userId.equals(sysUser.getUserId()))
|
|
|
- {
|
|
|
- return false;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // 判断是否开启了扫码配置
|
|
|
- if (ObjectUtil.isEmpty(isNeedScan) || !isNeedScan)
|
|
|
- {
|
|
|
- return false;
|
|
|
- }
|
|
|
-
|
|
|
- // true → 需要短信验证码
|
|
|
- // false → 直接登录
|
|
|
- return needCheck(sysUser);
|
|
|
- }
|
|
|
- finally
|
|
|
- {
|
|
|
- // 防止线程串库
|
|
|
- tenantDataSourceManager.clear();
|
|
|
- DynamicDataSourceContextHolder.setDataSourceType(DataSourceType.MASTER.name());
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- public boolean needCheck(SysUser sysUser) {
|
|
|
-
|
|
|
-
|
|
|
- // 1. 校验 IP
|
|
|
- if (!checkIp(sysUser)) {
|
|
|
- // IP 不一致
|
|
|
- return true;
|
|
|
- }
|
|
|
-
|
|
|
- // 2. 校验是否首次登录
|
|
|
- if (checkIsFirstLogin(sysUser)) {
|
|
|
- return true;
|
|
|
- }
|
|
|
-
|
|
|
- // 3. 校验上次登录时间是否在五天前
|
|
|
- if (checkIsLoginTime(sysUser)) {
|
|
|
- return true;
|
|
|
- }
|
|
|
-
|
|
|
- // 4. 检查是否在设置的某一天
|
|
|
-// if (checkIsSpecialDay(new Date())) {
|
|
|
-// return true;
|
|
|
-// }
|
|
|
- if (haveUnionId(sysUser)){
|
|
|
- return true;
|
|
|
- }
|
|
|
-
|
|
|
- return false;
|
|
|
- }
|
|
|
- public boolean haveUnionId( SysUser sysUser){
|
|
|
- if (StringUtils.isEmpty(sysUser.getUnionId())){
|
|
|
- return true;
|
|
|
- }
|
|
|
- return false;
|
|
|
- }
|
|
|
- public boolean checkIp(SysUser sysUser){
|
|
|
- // 获取当前 IP
|
|
|
- String ipAddr = IpUtils.getIpAddr(ServletUtils.getRequest());
|
|
|
- // 获取已记录的登录 IP
|
|
|
- String lastLoginIp = sysUser.getLoginIp();
|
|
|
-
|
|
|
- if (StringUtils.isNotEmpty(lastLoginIp)) {
|
|
|
- List<String> ipList = Arrays.asList(lastLoginIp.split(","));
|
|
|
- return ipList.contains(ipAddr);
|
|
|
- }
|
|
|
- return false;
|
|
|
- }
|
|
|
- //检查是否第一次登录
|
|
|
- public boolean checkIsFirstLogin(SysUser sysUser){
|
|
|
- // 获取上次登录 IP
|
|
|
- String lastLoginIp = sysUser.getLoginIp();
|
|
|
- if (StringUtils.isEmpty(lastLoginIp)||sysUser.getLoginDate()==null){
|
|
|
- return true;
|
|
|
- }
|
|
|
- return false;
|
|
|
- }
|
|
|
- public boolean checkIsLoginTime(SysUser sysUser) {
|
|
|
- // 获取上次登录时间
|
|
|
- Date loginDate = sysUser.getLoginDate();
|
|
|
- if (loginDate == null) {
|
|
|
- // 没有登录记录,直接返回 true(需要处理)
|
|
|
- return true;
|
|
|
- }
|
|
|
-
|
|
|
- // 当前时间
|
|
|
- Date now = new Date();
|
|
|
-
|
|
|
- // 计算两个时间的毫秒差
|
|
|
- long diff = now.getTime() - loginDate.getTime();
|
|
|
-
|
|
|
- // 5天 = 5 * 24 * 60 * 60 * 1000 毫秒
|
|
|
- long fiveDays = 5L * 24 * 60 * 60 * 1000;
|
|
|
-
|
|
|
- return diff >= fiveDays;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 获取微信登录二维码参数
|
|
|
- * @param account 当前登录用户名
|
|
|
- * @return 二维码参数
|
|
|
- */
|
|
|
- public Map<String, String> getWechatQrCode(String account) throws Exception {
|
|
|
- // 生成 loginTicket
|
|
|
- String ticket = UUID.randomUUID().toString();
|
|
|
- redisCache.setCacheObject("login:ticket:" + ticket, account, 60, TimeUnit.SECONDS);
|
|
|
-
|
|
|
- return wechatLoginService.getQrCode(ticket,appId,secret,redirectUri); // 返回二维码参数
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 微信扫码回调
|
|
|
- */
|
|
|
- public void handleCallback(String code, String ticket) {
|
|
|
- String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=" + appId
|
|
|
- + "&secret=" + secret
|
|
|
- + "&code=" + code
|
|
|
- + "&grant_type=authorization_code";
|
|
|
-
|
|
|
- JSONObject json = JSON.parseObject(cn.hutool.http.HttpUtil.get(url));
|
|
|
- String unionid = json.getString("unionid");
|
|
|
- if (unionid == null) throw new ServiceException("微信授权失败");
|
|
|
-
|
|
|
- String username = redisCache.getCacheObject("login:ticket:" + ticket);
|
|
|
- if (username == null) throw new ServiceException("ticket无效或过期");
|
|
|
- SysUser sysUser = userService.selectUserByUserName(username);
|
|
|
- if (sysUser == null) throw new ServiceException("用户不存在");
|
|
|
- if (sysUser.getUnionId() == null || sysUser.getUnionId().isEmpty()) {
|
|
|
- // 如果用户没有绑定 unionid,则绑定当前扫码用户的 unionid
|
|
|
- sysUser.setUnionId(unionid);
|
|
|
- userService.updateUserProfile(sysUser);
|
|
|
- } else if (!sysUser.getUnionId().equals(unionid)) {
|
|
|
- // 如果用户已绑定 unionid,但与扫码用户不一致,则拒绝登录
|
|
|
- redisCache.setCacheObject("wechat:scan:" + ticket, "error:账号与绑定用户不匹配", 30, TimeUnit.SECONDS);
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- redisCache.setCacheObject("wechat:scan:" + ticket, "ok", 30, TimeUnit.SECONDS);
|
|
|
- }
|
|
|
-}
|