mirror of
https://gitee.com/dromara/RuoYi-Cloud-Plus.git
synced 2025-09-09 13:49:14 +00:00
!15 合并 新功能/satoken 分支
This commit is contained in:
@@ -1,24 +1,23 @@
|
||||
package com.ruoyi.auth.controller;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.dev33.satoken.exception.NotLoginException;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import com.ruoyi.auth.form.LoginBody;
|
||||
import com.ruoyi.auth.form.RegisterBody;
|
||||
import com.ruoyi.auth.service.SysLoginService;
|
||||
import com.ruoyi.common.core.domain.R;
|
||||
import com.ruoyi.common.core.utils.JwtUtils;
|
||||
import com.ruoyi.common.core.utils.StringUtils;
|
||||
import com.ruoyi.common.security.auth.AuthUtil;
|
||||
import com.ruoyi.common.security.service.TokenService;
|
||||
import com.ruoyi.common.security.utils.SecurityUtils;
|
||||
import com.ruoyi.common.core.enums.DeviceType;
|
||||
import com.ruoyi.common.satoken.utils.LoginHelper;
|
||||
import com.ruoyi.system.api.model.LoginUser;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* token 控制
|
||||
@@ -29,7 +28,6 @@ import javax.servlet.http.HttpServletRequest;
|
||||
@RestController
|
||||
public class TokenController {
|
||||
|
||||
private final TokenService tokenService;
|
||||
private final SysLoginService sysLoginService;
|
||||
|
||||
@PostMapping("login")
|
||||
@@ -37,29 +35,18 @@ public class TokenController {
|
||||
// 用户登录
|
||||
LoginUser userInfo = sysLoginService.login(form.getUsername(), form.getPassword());
|
||||
// 获取登录token
|
||||
return R.ok(tokenService.createToken(userInfo));
|
||||
LoginHelper.loginByDevice(userInfo, DeviceType.PC);
|
||||
// 接口返回信息
|
||||
Map<String, Object> rspMap = new HashMap<String, Object>();
|
||||
rspMap.put("access_token", StpUtil.getTokenValue());
|
||||
return R.ok(rspMap);
|
||||
}
|
||||
|
||||
@DeleteMapping("logout")
|
||||
public R<?> logout(HttpServletRequest request) {
|
||||
String token = SecurityUtils.getToken(request);
|
||||
if (StringUtils.isNotEmpty(token)) {
|
||||
String username = JwtUtils.getUserName(token);
|
||||
// 删除用户缓存记录
|
||||
AuthUtil.logoutByToken(token);
|
||||
// 记录用户退出日志
|
||||
sysLoginService.logout(username);
|
||||
}
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
@PostMapping("refresh")
|
||||
public R<?> refresh(HttpServletRequest request) {
|
||||
LoginUser loginUser = tokenService.getLoginUser(request);
|
||||
if (ObjectUtil.isNotNull(loginUser)) {
|
||||
// 刷新令牌有效期
|
||||
tokenService.refreshToken(loginUser);
|
||||
return R.ok();
|
||||
try {
|
||||
StpUtil.logout();
|
||||
} catch (NotLoginException e) {
|
||||
}
|
||||
return R.ok();
|
||||
}
|
||||
|
@@ -0,0 +1,121 @@
|
||||
package com.ruoyi.auth.listener;
|
||||
|
||||
import cn.dev33.satoken.config.SaTokenConfig;
|
||||
import cn.dev33.satoken.listener.SaTokenListener;
|
||||
import cn.dev33.satoken.stp.SaLoginModel;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.http.useragent.UserAgent;
|
||||
import cn.hutool.http.useragent.UserAgentUtil;
|
||||
import com.ruoyi.common.core.constant.CacheConstants;
|
||||
import com.ruoyi.common.core.enums.UserType;
|
||||
import com.ruoyi.common.core.utils.ServletUtils;
|
||||
import com.ruoyi.common.core.utils.ip.AddressUtils;
|
||||
import com.ruoyi.common.redis.utils.RedisUtils;
|
||||
import com.ruoyi.common.satoken.utils.LoginHelper;
|
||||
import com.ruoyi.system.api.domain.SysUserOnline;
|
||||
import com.ruoyi.system.api.model.LoginUser;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 用户行为 侦听器的实现
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Component
|
||||
@Slf4j
|
||||
public class UserActionListener implements SaTokenListener {
|
||||
|
||||
private final SaTokenConfig tokenConfig;
|
||||
|
||||
/**
|
||||
* 每次登录时触发
|
||||
*/
|
||||
@Override
|
||||
public void doLogin(String loginType, Object loginId, SaLoginModel loginModel) {
|
||||
UserType userType = UserType.getUserType(loginId.toString());
|
||||
if (userType == UserType.SYS_USER) {
|
||||
UserAgent userAgent = UserAgentUtil.parse(ServletUtils.getRequest().getHeader("User-Agent"));
|
||||
String ip = ServletUtils.getClientIP();
|
||||
LoginUser user = LoginHelper.getLoginUser();
|
||||
String tokenValue = StpUtil.getTokenValue();
|
||||
SysUserOnline userOnline = new SysUserOnline();
|
||||
userOnline.setIpaddr(ip);
|
||||
userOnline.setLoginLocation(AddressUtils.getRealAddressByIP(ip));
|
||||
userOnline.setBrowser(userAgent.getBrowser().getName());
|
||||
userOnline.setOs(userAgent.getOs().getName());
|
||||
userOnline.setLoginTime(System.currentTimeMillis());
|
||||
userOnline.setTokenId(tokenValue);
|
||||
userOnline.setUserName(user.getUsername());
|
||||
if (ObjectUtil.isNotNull(user.getDeptName())) {
|
||||
userOnline.setDeptName(user.getDeptName());
|
||||
}
|
||||
RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, userOnline, tokenConfig.getTimeout(), TimeUnit.SECONDS);
|
||||
log.info("user doLogin, useId:{}, token:{}", loginId, tokenValue);
|
||||
} else if (userType == UserType.APP_USER) {
|
||||
// app端 自行根据业务编写
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 每次注销时触发
|
||||
*/
|
||||
@Override
|
||||
public void doLogout(String loginType, Object loginId, String tokenValue) {
|
||||
RedisUtils.deleteObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue);
|
||||
log.info("user doLogout, useId:{}, token:{}", loginId, tokenValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* 每次被踢下线时触发
|
||||
*/
|
||||
@Override
|
||||
public void doKickout(String loginType, Object loginId, String tokenValue) {
|
||||
RedisUtils.deleteObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue);
|
||||
log.info("user doLogoutByLoginId, useId:{}, token:{}", loginId, tokenValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* 每次被顶下线时触发
|
||||
*/
|
||||
@Override
|
||||
public void doReplaced(String loginType, Object loginId, String tokenValue) {
|
||||
RedisUtils.deleteObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue);
|
||||
log.info("user doReplaced, useId:{}, token:{}", loginId, tokenValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* 每次被封禁时触发
|
||||
*/
|
||||
@Override
|
||||
public void doDisable(String loginType, Object loginId, long disableTime) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 每次被解封时触发
|
||||
*/
|
||||
@Override
|
||||
public void doUntieDisable(String loginType, Object loginId) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 每次创建Session时触发
|
||||
*/
|
||||
@Override
|
||||
public void doCreateSession(String id) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 每次注销Session时触发
|
||||
*/
|
||||
@Override
|
||||
public void doLogoutSession(String id) {
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -1,12 +1,14 @@
|
||||
package com.ruoyi.auth.service;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.ruoyi.common.core.constant.CacheConstants;
|
||||
import com.ruoyi.common.core.constant.Constants;
|
||||
import com.ruoyi.common.core.constant.UserConstants;
|
||||
import com.ruoyi.common.core.enums.UserStatus;
|
||||
import com.ruoyi.common.core.exception.ServiceException;
|
||||
import com.ruoyi.common.core.utils.ServletUtils;
|
||||
import com.ruoyi.common.core.utils.StringUtils;
|
||||
import com.ruoyi.common.redis.utils.RedisUtils;
|
||||
import com.ruoyi.common.security.utils.SecurityUtils;
|
||||
import com.ruoyi.system.api.RemoteLogService;
|
||||
import com.ruoyi.system.api.RemoteUserService;
|
||||
@@ -16,6 +18,8 @@ import com.ruoyi.system.api.model.LoginUser;
|
||||
import org.apache.dubbo.config.annotation.DubboReference;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 登录校验方法
|
||||
*
|
||||
@@ -40,36 +44,58 @@ public class SysLoginService {
|
||||
}
|
||||
// 密码如果不在指定范围内 错误
|
||||
if (password.length() < UserConstants.PASSWORD_MIN_LENGTH
|
||||
|| password.length() > UserConstants.PASSWORD_MAX_LENGTH) {
|
||||
|| password.length() > UserConstants.PASSWORD_MAX_LENGTH) {
|
||||
recordLogininfor(username, Constants.LOGIN_FAIL, "用户密码不在指定范围");
|
||||
throw new ServiceException("用户密码不在指定范围");
|
||||
}
|
||||
// 用户名不在指定范围内 错误
|
||||
if (username.length() < UserConstants.USERNAME_MIN_LENGTH
|
||||
|| username.length() > UserConstants.USERNAME_MAX_LENGTH) {
|
||||
|| username.length() > UserConstants.USERNAME_MAX_LENGTH) {
|
||||
recordLogininfor(username, Constants.LOGIN_FAIL, "用户名不在指定范围");
|
||||
throw new ServiceException("用户名不在指定范围");
|
||||
}
|
||||
// 查询用户信息
|
||||
LoginUser userInfo = remoteUserService.getUserInfo(username);
|
||||
LoginUser userInfo;
|
||||
try {
|
||||
// 查询用户信息
|
||||
userInfo = remoteUserService.getUserInfo(username);
|
||||
|
||||
if (ObjectUtil.isNull(userInfo)) {
|
||||
recordLogininfor(username, Constants.LOGIN_FAIL, "登录用户不存在");
|
||||
throw new ServiceException("登录用户:" + username + " 不存在");
|
||||
if (ObjectUtil.isNull(userInfo)) {
|
||||
recordLogininfor(username, Constants.LOGIN_FAIL, "登录用户不存在");
|
||||
throw new ServiceException("登录用户:" + username + " 不存在");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage());
|
||||
throw new ServiceException(e.getMessage());
|
||||
}
|
||||
SysUser user = userInfo.getSysUser();
|
||||
if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) {
|
||||
recordLogininfor(username, Constants.LOGIN_FAIL, "对不起,您的账号已被删除");
|
||||
throw new ServiceException("对不起,您的账号:" + username + " 已被删除");
|
||||
|
||||
// 获取用户登录错误次数(可自定义限制策略 例如: key + username + ip)
|
||||
Integer errorNumber = RedisUtils.getCacheObject(CacheConstants.LOGIN_ERROR + username);
|
||||
// 锁定时间内登录 则踢出
|
||||
if (ObjectUtil.isNotNull(errorNumber) && errorNumber.equals(CacheConstants.LOGIN_ERROR_NUMBER)) {
|
||||
String msg = "密码错误次数过多,帐户锁定" + CacheConstants.LOGIN_ERROR_LIMIT_TIME + "分钟";
|
||||
recordLogininfor(username, Constants.LOGIN_FAIL, msg);
|
||||
throw new ServiceException(msg, null);
|
||||
}
|
||||
if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
|
||||
recordLogininfor(username, Constants.LOGIN_FAIL, "用户已停用,请联系管理员");
|
||||
throw new ServiceException("对不起,您的账号:" + username + " 已停用");
|
||||
}
|
||||
if (!SecurityUtils.matchesPassword(password, user.getPassword())) {
|
||||
recordLogininfor(username, Constants.LOGIN_FAIL, "用户密码错误");
|
||||
throw new ServiceException("用户不存在/密码错误");
|
||||
|
||||
if (!SecurityUtils.matchesPassword(password, userInfo.getPassword())) {
|
||||
// 是否第一次
|
||||
errorNumber = ObjectUtil.isNull(errorNumber) ? 1 : errorNumber + 1;
|
||||
// 达到规定错误次数 则锁定登录
|
||||
if (errorNumber.equals(CacheConstants.LOGIN_ERROR_NUMBER)) {
|
||||
String msg = "密码错误次数过多,帐户锁定" + CacheConstants.LOGIN_ERROR_LIMIT_TIME + "分钟";
|
||||
RedisUtils.setCacheObject(CacheConstants.LOGIN_ERROR + username, errorNumber, CacheConstants.LOGIN_ERROR_LIMIT_TIME, TimeUnit.MINUTES);
|
||||
recordLogininfor(username, Constants.LOGIN_FAIL, msg);
|
||||
throw new ServiceException(msg, null);
|
||||
} else {
|
||||
// 未达到规定错误次数 则递增
|
||||
String msg = "密码输入错误" + errorNumber + "次";
|
||||
RedisUtils.setCacheObject(CacheConstants.LOGIN_ERROR + username, errorNumber);
|
||||
recordLogininfor(username, Constants.LOGIN_FAIL, msg);
|
||||
throw new ServiceException(msg, null);
|
||||
}
|
||||
}
|
||||
// 登录成功 清空错误次数
|
||||
RedisUtils.deleteObject(CacheConstants.LOGIN_ERROR + username);
|
||||
recordLogininfor(username, Constants.LOGIN_SUCCESS, "登录成功");
|
||||
return userInfo;
|
||||
}
|
||||
@@ -87,11 +113,11 @@ public class SysLoginService {
|
||||
throw new ServiceException("用户/密码必须填写");
|
||||
}
|
||||
if (username.length() < UserConstants.USERNAME_MIN_LENGTH
|
||||
|| username.length() > UserConstants.USERNAME_MAX_LENGTH) {
|
||||
|| username.length() > UserConstants.USERNAME_MAX_LENGTH) {
|
||||
throw new ServiceException("账户长度必须在2到20个字符之间");
|
||||
}
|
||||
if (password.length() < UserConstants.PASSWORD_MIN_LENGTH
|
||||
|| password.length() > UserConstants.PASSWORD_MAX_LENGTH) {
|
||||
|| password.length() > UserConstants.PASSWORD_MAX_LENGTH) {
|
||||
throw new ServiceException("密码长度必须在5到20个字符之间");
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user