1. 增加前台用户的 token 刷新

2. 增加前台用户的 logout 退出
pull/2/head
YunaiV 2021-10-10 22:52:20 +08:00
parent 71b9104a13
commit 024d44cea1
13 changed files with 166 additions and 29 deletions

View File

@ -16,10 +16,7 @@ public interface SysErrorCodeConstants {
ErrorCode AUTH_LOGIN_CAPTCHA_NOT_FOUND = new ErrorCode(1002000003, "验证码不存在");
ErrorCode AUTH_LOGIN_CAPTCHA_CODE_ERROR = new ErrorCode(1002000004, "验证码不正确");
ErrorCode AUTH_THIRD_LOGIN_NOT_BIND = new ErrorCode(1002000005, "未绑定账号,需要进行绑定");
// ========== TOKEN 模块 1002001000 ==========
ErrorCode TOKEN_EXPIRED = new ErrorCode(1002001000, "Token 已经过期");
ErrorCode TOKEN_PARSE_FAIL = new ErrorCode(1002001001, "Token 解析失败");
ErrorCode AUTH_TOKEN_EXPIRED = new ErrorCode(1002000006, "Token 已经过期");
// ========== 菜单模块 1002002000 ==========
ErrorCode MENU_NAME_DUPLICATE = new ErrorCode(1002002000, "已经存在该名字的菜单");

View File

@ -255,15 +255,15 @@ public class SysAuthServiceImpl implements SysAuthService {
}
// 删除 session
userSessionCoreService.deleteUserSession(token);
// 记录登出日子和
this.createLogoutLog(loginUser.getUsername());
// 记录登出日
this.createLogoutLog(loginUser.getId(), loginUser.getUsername());
}
private void createLogoutLog(String username) {
// TODO 芋艿:这里未设置 userId
private void createLogoutLog(Long userId, String username) {
SysLoginLogCreateReqDTO reqDTO = new SysLoginLogCreateReqDTO();
reqDTO.setLogType(SysLoginLogTypeEnum.LOGOUT_SELF.getType());
reqDTO.setTraceId(TracerUtils.getTraceId());
reqDTO.setUserId(userId);
reqDTO.setUserType(UserTypeEnum.ADMIN.getValue());
reqDTO.setUsername(username);
reqDTO.setUserAgent(ServletUtils.getUserAgent());
@ -294,7 +294,7 @@ public class SysAuthServiceImpl implements SysAuthService {
// 重新加载 SysUserDO 信息
SysUserDO user = userService.getUser(loginUser.getId());
if (user == null || CommonStatusEnum.DISABLE.getStatus().equals(user.getStatus())) {
throw exception(TOKEN_EXPIRED); // 校验 token 时,用户被禁用的情况下,也认为 token 过期,方便前端跳转到登录界面
throw exception(AUTH_TOKEN_EXPIRED); // 校验 token 时,用户被禁用的情况下,也认为 token 过期,方便前端跳转到登录界面
}
// 刷新 LoginUser 缓存

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.userserver.modules.member.service.user;
import cn.iocoder.yudao.framework.common.validation.Mobile;
import cn.iocoder.yudao.userserver.modules.member.dal.dataobject.user.MbrUserDO;
/**
@ -17,6 +18,16 @@ public interface MbrUserService {
*/
MbrUserDO getUserByMobile(String mobile);
/**
*
*
*
* @param mobile
* @param registerIp IP
* @return
*/
MbrUserDO createUserIfAbsent(@Mobile String mobile, String registerIp);
/**
*
*

View File

@ -1,9 +1,14 @@
package cn.iocoder.yudao.userserver.modules.member.service.user.impl;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.userserver.modules.member.dal.dataobject.user.MbrUserDO;
import cn.iocoder.yudao.userserver.modules.member.dal.mysql.user.MbrUserMapper;
import cn.iocoder.yudao.userserver.modules.member.service.user.MbrUserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@ -23,11 +28,38 @@ public class MbrUserServiceImpl implements MbrUserService {
@Resource
private MbrUserMapper userMapper;
@Resource
private PasswordEncoder passwordEncoder;
@Override
public MbrUserDO getUserByMobile(String mobile) {
return userMapper.selectByMobile(mobile);
}
@Override
public MbrUserDO createUserIfAbsent(String mobile, String registerIp) {
// 用户已经存在
MbrUserDO user = userMapper.selectByMobile(mobile);
if (user != null) {
return user;
}
// 用户不存在,则进行创建
return this.createUser(mobile, registerIp);
}
private MbrUserDO createUser(String mobile, String registerIp) {
// 生成密码
String password = IdUtil.fastSimpleUUID();
// 插入用户
MbrUserDO user = new MbrUserDO();
user.setMobile(mobile);
user.setStatus(CommonStatusEnum.ENABLE.getStatus()); // 默认开启
user.setPassword(passwordEncoder.encode(password)); // 加密密码
user.setRegisterIp(registerIp);
userMapper.insert(user);
return user;
}
@Override
public void updateUserLogin(Long id, String loginIp) {
userMapper.updateById(new MbrUserDO().setId(id).setLoginIp(loginIp).setLoginDate(new Date()));

View File

@ -12,6 +12,20 @@ POST {{userServerUrl}}/send-sms-code
Content-Type: application/json
{
"mobile": "15601691300",
"mobile": "15601691301",
"scene": 1
}
### 请求 /sms-login 接口 => 成功
POST {{userServerUrl}}/sms-login
Content-Type: application/json
{
"mobile": "15601691301",
"code": 9999
}
### 请求 /logout 接口 => 成功
POST {{userServerUrl}}/logout
Content-Type: application/json
Authorization: Bearer c1b76bdaf2c146c581caa4d7fd81ee66

View File

@ -33,28 +33,30 @@ public class SysAuthController {
@PostMapping("/login")
@ApiOperation("使用手机 + 密码登录")
public CommonResult<MbrAuthLoginRespVO> login(@RequestBody @Valid SysAuthLoginReqVO reqVO) {
public CommonResult<SysAuthLoginRespVO> login(@RequestBody @Valid SysAuthLoginReqVO reqVO) {
String token = authService.login(reqVO, getClientIP(), getUserAgent());
// 返回结果
return success(MbrAuthLoginRespVO.builder().token(token).build());
return success(SysAuthLoginRespVO.builder().token(token).build());
}
@PostMapping("/sms-login")
@ApiOperation("使用手机 + 验证码登录")
public CommonResult<MbrAuthLoginRespVO> smsLogin(@RequestBody @Valid SysAuthLoginReqVO reqVO) {
return null;
public CommonResult<SysAuthLoginRespVO> smsLogin(@RequestBody @Valid SysAuthSmsLoginReqVO reqVO) {
String token = authService.smsLogin(reqVO, getClientIP(), getUserAgent());
// 返回结果
return success(SysAuthLoginRespVO.builder().token(token).build());
}
@PostMapping("/send-sms-code")
@ApiOperation("发送手机验证码")
public CommonResult<Boolean> sendSmsCode(@RequestBody @Valid MbrAuthSendSmsReqVO reqVO) {
public CommonResult<Boolean> sendSmsCode(@RequestBody @Valid SysAuthSendSmsReqVO reqVO) {
smsCodeService.sendSmsCode(reqVO.getMobile(), reqVO.getScene(), getClientIP());
return success(true);
}
@PostMapping("/reset-password")
@ApiOperation(value = "重置密码", notes = "用户忘记密码时使用")
public CommonResult<Boolean> resetPassword(@RequestBody @Valid SysAuthResetPasswordReqVO reqVO) {
public CommonResult<Boolean> resetPassword(@RequestBody @Valid MbrAuthResetPasswordReqVO reqVO) {
return null;
}
@ -74,7 +76,7 @@ public class SysAuthController {
@PostMapping("/social-login")
@ApiOperation("社交登录,使用 code 授权码")
public CommonResult<MbrAuthLoginRespVO> socialLogin(@RequestBody @Valid MbrAuthSocialLoginReqVO reqVO) {
public CommonResult<SysAuthLoginRespVO> socialLogin(@RequestBody @Valid MbrAuthSocialLoginReqVO reqVO) {
// String token = authService.socialLogin(reqVO, getClientIP(), getUserAgent());
// // 返回结果
// return success(MbrAuthLoginRespVO.builder().token(token).build());
@ -83,7 +85,7 @@ public class SysAuthController {
@PostMapping("/social-login2")
@ApiOperation("社交登录,使用 code 授权码 + 账号密码")
public CommonResult<MbrAuthLoginRespVO> socialLogin2(@RequestBody @Valid MbrAuthSocialLogin2ReqVO reqVO) {
public CommonResult<SysAuthLoginRespVO> socialLogin2(@RequestBody @Valid MbrAuthSocialLogin2ReqVO reqVO) {
// String token = authService.socialLogin2(reqVO, getClientIP(), getUserAgent());
// // 返回结果
// return success(MbrAuthLoginRespVO.builder().token(token).build());

View File

@ -16,7 +16,7 @@ import javax.validation.constraints.Pattern;
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class SysAuthResetPasswordReqVO {
public class MbrAuthResetPasswordReqVO {
@ApiModelProperty(value = "新密码", required = true, example = "buzhidao")
@NotEmpty(message = "新密码不能为空")

View File

@ -12,7 +12,7 @@ import lombok.NoArgsConstructor;
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class MbrAuthLoginRespVO {
public class SysAuthLoginRespVO {
@ApiModelProperty(value = "token", required = true, example = "yudaoyuanma")
private String token;

View File

@ -13,7 +13,7 @@ import javax.validation.constraints.NotNull;
@ApiModel("发送手机验证码 Response VO")
@Data
@Accessors(chain = true)
public class MbrAuthSendSmsReqVO {
public class SysAuthSendSmsReqVO {
@ApiModelProperty(value = "手机号", example = "15601691234")
@Mobile

View File

@ -17,18 +17,13 @@ import javax.validation.constraints.Pattern;
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class MbrAuthSmsLoginReqVO {
public class SysAuthSmsLoginReqVO {
@ApiModelProperty(value = "手机号", required = true, example = "15601691300")
@NotEmpty(message = "手机号不能为空")
@Mobile
private String mobile;
@ApiModelProperty(value = "密码", required = true, example = "buzhidao")
@NotEmpty(message = "密码不能为空")
@Length(min = 4, max = 16, message = "密码长度为 4-16 位")
private String password;
@ApiModelProperty(value = "手机验证码", required = true, example = "1024")
@NotEmpty(message = "手机验证码不能为空")
@Length(min = 4, max = 6, message = "手机验证码长度为 4-6 位")

View File

@ -13,6 +13,7 @@ public interface SysErrorCodeConstants {
ErrorCode AUTH_LOGIN_BAD_CREDENTIALS = new ErrorCode(1005000000, "登录失败,账号密码不正确");
ErrorCode AUTH_LOGIN_USER_DISABLED = new ErrorCode(1005000001, "登录失败,账号被禁用");
ErrorCode AUTH_LOGIN_FAIL_UNKNOWN = new ErrorCode(1005000002, "登录失败"); // 登录失败的兜底,未知原因
ErrorCode AUTH_TOKEN_EXPIRED = new ErrorCode(1005000003, "Token 已经过期");
// ========== SMS CODE 模块 1005001000 ==========
ErrorCode USER_SMS_CODE_NOT_FOUND = new ErrorCode(1005001000, "验证码不存在");

View File

@ -2,6 +2,7 @@ package cn.iocoder.yudao.userserver.modules.system.service.auth;
import cn.iocoder.yudao.framework.security.core.service.SecurityAuthFrameworkService;
import cn.iocoder.yudao.userserver.modules.system.controller.auth.vo.SysAuthLoginReqVO;
import cn.iocoder.yudao.userserver.modules.system.controller.auth.vo.SysAuthSmsLoginReqVO;
import javax.validation.Valid;
@ -24,4 +25,14 @@ public interface SysAuthService extends SecurityAuthFrameworkService {
*/
String login(@Valid SysAuthLoginReqVO reqVO, String userIp, String userAgent);
/**
* +
*
* @param reqVO
* @param userIp IP
* @param userAgent UA
* @return 使 JWT
*/
String smsLogin(@Valid SysAuthSmsLoginReqVO reqVO, String userIp, String userAgent);
}

View File

@ -1,18 +1,24 @@
package cn.iocoder.yudao.userserver.modules.system.service.auth.impl;
import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.coreservice.modules.system.service.auth.SysUserSessionCoreService;
import cn.iocoder.yudao.coreservice.modules.system.service.logger.SysLoginLogCoreService;
import cn.iocoder.yudao.coreservice.modules.system.service.logger.dto.SysLoginLogCreateReqDTO;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
import cn.iocoder.yudao.framework.security.core.LoginUser;
import cn.iocoder.yudao.userserver.modules.system.controller.auth.vo.SysAuthLoginReqVO;
import cn.iocoder.yudao.userserver.modules.system.controller.auth.vo.SysAuthSmsLoginReqVO;
import cn.iocoder.yudao.userserver.modules.system.convert.auth.SysAuthConvert;
import cn.iocoder.yudao.userserver.modules.member.dal.dataobject.user.MbrUserDO;
import cn.iocoder.yudao.userserver.modules.system.enums.sms.SysSmsSceneEnum;
import cn.iocoder.yudao.userserver.modules.system.service.auth.SysAuthService;
import cn.iocoder.yudao.userserver.modules.member.service.user.MbrUserService;
import cn.iocoder.yudao.coreservice.modules.system.enums.logger.SysLoginLogTypeEnum;
import cn.iocoder.yudao.coreservice.modules.system.enums.logger.SysLoginResultEnum;
import cn.iocoder.yudao.userserver.modules.system.service.sms.SysSmsCodeService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.authentication.AuthenticationManager;
@ -24,7 +30,7 @@ import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.Objects;
@ -48,6 +54,8 @@ public class SysAuthServiceImpl implements SysAuthService {
@Resource
private MbrUserService userService;
@Resource
private SysSmsCodeService smsCodeService;
@Resource
private SysLoginLogCoreService loginLogCoreService;
@Resource
private SysUserSessionCoreService userSessionCoreService;
@ -72,6 +80,25 @@ public class SysAuthServiceImpl implements SysAuthService {
return userSessionCoreService.createUserSession(loginUser, userIp, userAgent);
}
@Override
@Transactional
public String smsLogin(SysAuthSmsLoginReqVO reqVO, String userIp, String userAgent) {
// 校验验证码
smsCodeService.useSmsCode(reqVO.getMobile(), SysSmsSceneEnum.LOGIN_BY_SMS.getScene(),
reqVO.getCode(), userIp);
// 获得获得注册用户
MbrUserDO user = userService.createUserIfAbsent(reqVO.getMobile(), userIp);
Assert.notNull(user, "获取用户失败,结果为空");
// 执行登陆
this.createLoginLog(user.getMobile(), SysLoginLogTypeEnum.LOGIN_SMS, SysLoginResultEnum.SUCCESS);
LoginUser loginUser = SysAuthConvert.INSTANCE.convert(user);
// 缓存登录用户到 Redis 中,返回 sessionId 编号
return userSessionCoreService.createUserSession(loginUser, userIp, userAgent);
}
private LoginUser login0(String username, String password) {
final SysLoginLogTypeEnum logTypeEnum = SysLoginLogTypeEnum.LOGIN_USERNAME;
// 用户验证
@ -120,7 +147,31 @@ public class SysAuthServiceImpl implements SysAuthService {
@Override
public LoginUser verifyTokenAndRefresh(String token) {
return null;
// 获得 LoginUser
LoginUser loginUser = userSessionCoreService.getLoginUser(token);
if (loginUser == null) {
return null;
}
// 刷新 LoginUser 缓存
this.refreshLoginUserCache(token, loginUser);
return loginUser;
}
private void refreshLoginUserCache(String token, LoginUser loginUser) {
// 每 1/3 的 Session 超时时间,刷新 LoginUser 缓存
if (System.currentTimeMillis() - loginUser.getUpdateTime().getTime() <
userSessionCoreService.getSessionTimeoutMillis() / 3) {
return;
}
// 重新加载 MbrUserDO 信息
MbrUserDO user = userService.getUser(loginUser.getId());
if (user == null || CommonStatusEnum.DISABLE.getStatus().equals(user.getStatus())) {
throw exception(AUTH_TOKEN_EXPIRED); // 校验 token 时,用户被禁用的情况下,也认为 token 过期,方便前端跳转到登录界面
}
// 刷新 LoginUser 缓存
userSessionCoreService.refreshUserSession(token, loginUser);
}
@Override
@ -130,6 +181,8 @@ public class SysAuthServiceImpl implements SysAuthService {
if (user == null) {
throw new UsernameNotFoundException(String.valueOf(userId));
}
// 执行登陆
this.createLoginLog(user.getMobile(), SysLoginLogTypeEnum.LOGIN_MOCK, SysLoginResultEnum.SUCCESS);
// 创建 LoginUser 对象
@ -138,7 +191,28 @@ public class SysAuthServiceImpl implements SysAuthService {
@Override
public void logout(String token) {
// 查询用户信息
LoginUser loginUser = userSessionCoreService.getLoginUser(token);
if (loginUser == null) {
return;
}
// 删除 session
userSessionCoreService.deleteUserSession(token);
// 记录登出日志
this.createLogoutLog(loginUser.getId(), loginUser.getUsername());
}
private void createLogoutLog(Long userId, String username) {
SysLoginLogCreateReqDTO reqDTO = new SysLoginLogCreateReqDTO();
reqDTO.setLogType(SysLoginLogTypeEnum.LOGOUT_SELF.getType());
reqDTO.setTraceId(TracerUtils.getTraceId());
reqDTO.setUserId(userId);
reqDTO.setUserType(UserTypeEnum.MEMBER.getValue());
reqDTO.setUsername(username);
reqDTO.setUserAgent(ServletUtils.getUserAgent());
reqDTO.setUserIp(ServletUtils.getClientIP());
reqDTO.setResult(SysLoginResultEnum.SUCCESS.getResult());
loginLogCoreService.createLoginLog(reqDTO);
}
}