mirror of
https://gitee.com/hhyykk/ipms-sjy.git
synced 2025-07-25 00:15:06 +08:00
Merge branch 'master' of https://gitee.com/zhijiantianya/ruoyi-vue-pro into feature/1.8.0-uniapp
Conflicts: yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/UserController.java yudao-ui-admin/src/components/ImageUpload/index.vue
This commit is contained in:
@ -17,7 +17,6 @@ public interface ErrorCodeConstants {
|
||||
// ========== AUTH 模块 1004003000 ==========
|
||||
ErrorCode AUTH_LOGIN_BAD_CREDENTIALS = new ErrorCode(1004003000, "登录失败,账号密码不正确");
|
||||
ErrorCode AUTH_LOGIN_USER_DISABLED = new ErrorCode(1004003001, "登录失败,账号被禁用");
|
||||
ErrorCode AUTH_LOGIN_FAIL_UNKNOWN = new ErrorCode(1004003002, "登录失败"); // 登录失败的兜底,未知原因
|
||||
ErrorCode AUTH_TOKEN_EXPIRED = new ErrorCode(1004003004, "Token 已经过期");
|
||||
ErrorCode AUTH_THIRD_LOGIN_NOT_BIND = new ErrorCode(1004003005, "未绑定账号,需要进行绑定");
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
### 请求 /login 接口 => 成功
|
||||
POST {{appApi}}/member/login
|
||||
POST {{appApi}}/member/auth/login
|
||||
Content-Type: application/json
|
||||
tenant-id: {{appTenentId}}
|
||||
|
||||
@ -19,7 +19,7 @@ tenant-id: {{appTenentId}}
|
||||
}
|
||||
|
||||
### 请求 /sms-login 接口 => 成功
|
||||
POST {{appApi}}/member/sms-login
|
||||
POST {{appApi}}/member/auth/sms-login
|
||||
Content-Type: application/json
|
||||
tenant-id: {{appTenentId}}
|
||||
|
||||
@ -29,7 +29,12 @@ tenant-id: {{appTenentId}}
|
||||
}
|
||||
|
||||
### 请求 /logout 接口 => 成功
|
||||
POST {{appApi}}/member/logout
|
||||
POST {{appApi}}/member/auth/logout
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer c1b76bdaf2c146c581caa4d7fd81ee66
|
||||
tenant-id: {{appTenentId}}
|
||||
|
||||
### 请求 /auth/refresh-token 接口 => 成功
|
||||
POST {{appApi}}/member/auth/refresh-token?refreshToken=bc43d929094849a28b3a69f6e6940d70
|
||||
Content-Type: application/json
|
||||
tenant-id: {{appTenentId}}
|
||||
|
@ -1,7 +1,11 @@
|
||||
package cn.iocoder.yudao.module.member.controller.app.auth;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
|
||||
import cn.iocoder.yudao.framework.security.config.SecurityProperties;
|
||||
import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
|
||||
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
|
||||
import cn.iocoder.yudao.module.member.controller.app.auth.vo.*;
|
||||
import cn.iocoder.yudao.module.member.service.auth.MemberAuthService;
|
||||
import io.swagger.annotations.Api;
|
||||
@ -13,11 +17,10 @@ import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.validation.Valid;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
|
||||
import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getUserAgent;
|
||||
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
|
||||
|
||||
@Api(tags = "用户 APP - 认证")
|
||||
@ -30,19 +33,39 @@ public class AppAuthController {
|
||||
@Resource
|
||||
private MemberAuthService authService;
|
||||
|
||||
@Resource
|
||||
private SecurityProperties securityProperties;
|
||||
|
||||
@PostMapping("/login")
|
||||
@ApiOperation("使用手机 + 密码登录")
|
||||
public CommonResult<AppAuthLoginRespVO> login(@RequestBody @Valid AppAuthLoginReqVO reqVO) {
|
||||
String token = authService.login(reqVO, getClientIP(), getUserAgent());
|
||||
return success(AppAuthLoginRespVO.builder().token(token).build());
|
||||
return success(authService.login(reqVO));
|
||||
}
|
||||
|
||||
@PostMapping("/logout")
|
||||
@ApiOperation("登出系统")
|
||||
public CommonResult<Boolean> logout(HttpServletRequest request) {
|
||||
String token = SecurityFrameworkUtils.obtainAuthorization(request, securityProperties.getTokenHeader());
|
||||
if (StrUtil.isNotBlank(token)) {
|
||||
authService.logout(token);
|
||||
}
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@PostMapping("/refresh-token")
|
||||
@ApiOperation("刷新令牌")
|
||||
@ApiImplicitParam(name = "refreshToken", value = "刷新令牌", required = true, dataTypeClass = String.class)
|
||||
@OperateLog(enable = false) // 避免 Post 请求被记录操作日志
|
||||
public CommonResult<AppAuthLoginRespVO> refreshToken(@RequestParam("refreshToken") String refreshToken) {
|
||||
return success(authService.refreshToken(refreshToken));
|
||||
}
|
||||
|
||||
// ========== 短信登录相关 ==========
|
||||
|
||||
@PostMapping("/sms-login")
|
||||
@ApiOperation("使用手机 + 验证码登录")
|
||||
public CommonResult<AppAuthLoginRespVO> smsLogin(@RequestBody @Valid AppAuthSmsLoginReqVO reqVO) {
|
||||
String token = authService.smsLogin(reqVO, getClientIP(), getUserAgent());
|
||||
// 返回结果
|
||||
return success(AppAuthLoginRespVO.builder().token(token).build());
|
||||
return success(authService.smsLogin(reqVO));
|
||||
}
|
||||
|
||||
@PostMapping("/send-sms-code")
|
||||
@ -83,16 +106,14 @@ public class AppAuthController {
|
||||
|
||||
@PostMapping("/social-quick-login")
|
||||
@ApiOperation(value = "社交快捷登录,使用 code 授权码", notes = "适合未登录的用户,但是社交账号已绑定用户")
|
||||
public CommonResult<AppAuthLoginRespVO> socialLogin(@RequestBody @Valid AppAuthSocialQuickLoginReqVO reqVO) {
|
||||
String token = authService.socialQuickLogin(reqVO, getClientIP(), getUserAgent());
|
||||
return success(AppAuthLoginRespVO.builder().token(token).build());
|
||||
public CommonResult<AppAuthLoginRespVO> socialQuickLogin(@RequestBody @Valid AppAuthSocialQuickLoginReqVO reqVO) {
|
||||
return success(authService.socialQuickLogin(reqVO));
|
||||
}
|
||||
|
||||
@PostMapping("/social-bind-login")
|
||||
@ApiOperation(value = "社交绑定登录,使用 手机号 + 手机验证码", notes = "适合未登录的用户,进行登录 + 绑定")
|
||||
public CommonResult<AppAuthLoginRespVO> socialLogin2(@RequestBody @Valid AppAuthSocialBindLoginReqVO reqVO) {
|
||||
String token = authService.socialBindLogin(reqVO, getClientIP(), getUserAgent());
|
||||
return success(AppAuthLoginRespVO.builder().token(token).build());
|
||||
public CommonResult<AppAuthLoginRespVO> socialBindLogin(@RequestBody @Valid AppAuthSocialBindLoginReqVO reqVO) {
|
||||
return success(authService.socialBindLogin(reqVO));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -7,14 +7,25 @@ import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@ApiModel("用户 APP - 手机密码登录 Response VO")
|
||||
import java.util.Date;
|
||||
|
||||
@ApiModel("用户 APP - 登录 Response VO")
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class AppAuthLoginRespVO {
|
||||
|
||||
@ApiModelProperty(value = "token", required = true, example = "yudaoyuanma")
|
||||
private String token;
|
||||
@ApiModelProperty(value = "用户编号", required = true, example = "1024")
|
||||
private Long userId;
|
||||
|
||||
@ApiModelProperty(value = "访问令牌", required = true, example = "happy")
|
||||
private String accessToken;
|
||||
|
||||
@ApiModelProperty(value = "刷新令牌", required = true, example = "nice")
|
||||
private String refreshToken;
|
||||
|
||||
@ApiModelProperty(value = "过期时间", required = true)
|
||||
private Date expiresTime;
|
||||
|
||||
}
|
||||
|
@ -1,17 +1,14 @@
|
||||
package cn.iocoder.yudao.module.member.convert.auth;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
|
||||
import cn.iocoder.yudao.framework.security.core.LoginUser;
|
||||
import cn.iocoder.yudao.module.member.controller.app.auth.vo.*;
|
||||
import cn.iocoder.yudao.module.member.controller.app.social.vo.AppSocialUserUnbindReqVO;
|
||||
import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
|
||||
import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenRespDTO;
|
||||
import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeSendReqDTO;
|
||||
import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeUseReqDTO;
|
||||
import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
|
||||
import cn.iocoder.yudao.module.system.api.social.dto.SocialUserUnbindReqDTO;
|
||||
import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
@Mapper
|
||||
@ -19,14 +16,6 @@ public interface AuthConvert {
|
||||
|
||||
AuthConvert INSTANCE = Mappers.getMapper(AuthConvert.class);
|
||||
|
||||
@Mapping(source = "mobile", target = "username")
|
||||
LoginUser convert0(MemberUserDO bean);
|
||||
|
||||
default LoginUser convert(MemberUserDO bean) {
|
||||
// 目的,为了设置 UserTypeEnum.MEMBER.getValue()
|
||||
return convert0(bean).setUserType(UserTypeEnum.MEMBER.getValue());
|
||||
}
|
||||
|
||||
SocialUserBindReqDTO convert(Long userId, Integer userType, AppAuthSocialBindLoginReqVO reqVO);
|
||||
SocialUserBindReqDTO convert(Long userId, Integer userType, AppAuthSocialQuickLoginReqVO reqVO);
|
||||
SocialUserUnbindReqDTO convert(Long userId, Integer userType, AppSocialUserUnbindReqVO reqVO);
|
||||
@ -35,4 +24,6 @@ public interface AuthConvert {
|
||||
SmsCodeUseReqDTO convert(AppAuthResetPasswordReqVO reqVO, SmsSceneEnum scene, String usedIp);
|
||||
SmsCodeUseReqDTO convert(AppAuthSmsLoginReqVO reqVO, Integer scene, String usedIp);
|
||||
|
||||
AppAuthLoginRespVO convert(OAuth2AccessTokenRespDTO bean);
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,6 @@
|
||||
/**
|
||||
* 属于 system 模块的 framework 封装
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
package cn.iocoder.yudao.module.member.framework;
|
@ -0,0 +1,28 @@
|
||||
package cn.iocoder.yudao.module.member.framework.security.config;
|
||||
|
||||
import cn.iocoder.yudao.framework.security.config.AuthorizeRequestsCustomizer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
|
||||
|
||||
/**
|
||||
* Member 模块的 Security 配置
|
||||
*/
|
||||
@Configuration("memberSecurityConfiguration")
|
||||
public class SecurityConfiguration {
|
||||
|
||||
@Bean("memberAuthorizeRequestsCustomizer")
|
||||
public AuthorizeRequestsCustomizer authorizeRequestsCustomizer() {
|
||||
return new AuthorizeRequestsCustomizer() {
|
||||
|
||||
@Override
|
||||
public void customize(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry) {
|
||||
// 登录的接口
|
||||
registry.antMatchers(buildAdminApi("/member/auth/logout")).permitAll();
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
/**
|
||||
* 占位
|
||||
*/
|
||||
package cn.iocoder.yudao.module.member.framework.security.core;
|
@ -1,6 +1,5 @@
|
||||
package cn.iocoder.yudao.module.member.service.auth;
|
||||
|
||||
import cn.iocoder.yudao.framework.security.core.service.SecurityAuthFrameworkService;
|
||||
import cn.iocoder.yudao.module.member.controller.app.auth.vo.*;
|
||||
|
||||
import javax.validation.Valid;
|
||||
@ -12,47 +11,46 @@ import javax.validation.Valid;
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface MemberAuthService extends SecurityAuthFrameworkService {
|
||||
public interface MemberAuthService {
|
||||
|
||||
/**
|
||||
* 手机 + 密码登录
|
||||
*
|
||||
* @param reqVO 登录信息
|
||||
* @param userIp 用户 IP
|
||||
* @param userAgent 用户 UA
|
||||
* @return 身份令牌,使用 JWT 方式
|
||||
* @return 登录结果
|
||||
*/
|
||||
String login(@Valid AppAuthLoginReqVO reqVO, String userIp, String userAgent);
|
||||
AppAuthLoginRespVO login(@Valid AppAuthLoginReqVO reqVO);
|
||||
|
||||
/**
|
||||
* 基于 token 退出登录
|
||||
*
|
||||
* @param token token
|
||||
*/
|
||||
void logout(String token);
|
||||
|
||||
/**
|
||||
* 手机 + 验证码登陆
|
||||
*
|
||||
* @param reqVO 登陆信息
|
||||
* @param userIp 用户 IP
|
||||
* @param userAgent 用户 UA
|
||||
* @return 身份令牌,使用 JWT 方式
|
||||
* @return 登录结果
|
||||
*/
|
||||
String smsLogin(@Valid AppAuthSmsLoginReqVO reqVO, String userIp, String userAgent);
|
||||
AppAuthLoginRespVO smsLogin(@Valid AppAuthSmsLoginReqVO reqVO);
|
||||
|
||||
/**
|
||||
* 社交登录,使用 code 授权码
|
||||
*
|
||||
* @param reqVO 登录信息
|
||||
* @param userIp 用户 IP
|
||||
* @param userAgent 用户 UA
|
||||
* @return 身份令牌,使用 JWT 方式
|
||||
* @return 登录结果
|
||||
*/
|
||||
String socialQuickLogin(@Valid AppAuthSocialQuickLoginReqVO reqVO, String userIp, String userAgent);
|
||||
AppAuthLoginRespVO socialQuickLogin(@Valid AppAuthSocialQuickLoginReqVO reqVO);
|
||||
|
||||
/**
|
||||
* 社交登录,使用 手机号 + 手机验证码
|
||||
*
|
||||
* @param reqVO 登录信息
|
||||
* @param userIp 用户 IP
|
||||
* @param userAgent 用户 UA
|
||||
* @return 身份令牌,使用 JWT 方式
|
||||
* @return 登录结果
|
||||
*/
|
||||
String socialBindLogin(@Valid AppAuthSocialBindLoginReqVO reqVO, String userIp, String userAgent);
|
||||
AppAuthLoginRespVO socialBindLogin(@Valid AppAuthSocialBindLoginReqVO reqVO);
|
||||
|
||||
/**
|
||||
* 获得社交认证 URL
|
||||
@ -84,4 +82,11 @@ public interface MemberAuthService extends SecurityAuthFrameworkService {
|
||||
*/
|
||||
void sendSmsCode(Long userId, AppAuthSmsSendReqVO reqVO);
|
||||
|
||||
/**
|
||||
* 刷新访问令牌
|
||||
*
|
||||
* @param refreshToken 刷新令牌
|
||||
* @return 登录结果
|
||||
*/
|
||||
AppAuthLoginRespVO refreshToken(String refreshToken);
|
||||
}
|
||||
|
@ -1,35 +1,29 @@
|
||||
package cn.iocoder.yudao.module.member.service.auth;
|
||||
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
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.framework.security.core.authentication.MultiUsernamePasswordAuthenticationToken;
|
||||
import cn.iocoder.yudao.module.member.controller.app.auth.vo.*;
|
||||
import cn.iocoder.yudao.module.member.convert.auth.AuthConvert;
|
||||
import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
|
||||
import cn.iocoder.yudao.module.member.dal.mysql.user.MemberUserMapper;
|
||||
import cn.iocoder.yudao.module.member.service.user.MemberUserService;
|
||||
import cn.iocoder.yudao.module.system.api.auth.UserSessionApi;
|
||||
import cn.iocoder.yudao.module.system.api.auth.OAuth2TokenApi;
|
||||
import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenCreateReqDTO;
|
||||
import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenRespDTO;
|
||||
import cn.iocoder.yudao.module.system.api.logger.LoginLogApi;
|
||||
import cn.iocoder.yudao.module.system.api.logger.dto.LoginLogCreateReqDTO;
|
||||
import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi;
|
||||
import cn.iocoder.yudao.module.system.api.social.SocialUserApi;
|
||||
import cn.iocoder.yudao.module.system.enums.auth.OAuth2ClientConstants;
|
||||
import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum;
|
||||
import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum;
|
||||
import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.authentication.DisabledException;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@ -50,10 +44,6 @@ import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.*;
|
||||
@Slf4j
|
||||
public class MemberAuthServiceImpl implements MemberAuthService {
|
||||
|
||||
@Resource
|
||||
@Lazy // 延迟加载,因为存在相互依赖的问题
|
||||
private AuthenticationManager authenticationManager;
|
||||
|
||||
@Resource
|
||||
private MemberUserService userService;
|
||||
@Resource
|
||||
@ -61,9 +51,9 @@ public class MemberAuthServiceImpl implements MemberAuthService {
|
||||
@Resource
|
||||
private LoginLogApi loginLogApi;
|
||||
@Resource
|
||||
private UserSessionApi userSessionApi;
|
||||
@Resource
|
||||
private SocialUserApi socialUserApi;
|
||||
@Resource
|
||||
private OAuth2TokenApi oauth2TokenApi;
|
||||
|
||||
@Resource
|
||||
private PasswordEncoder passwordEncoder;
|
||||
@ -71,44 +61,31 @@ public class MemberAuthServiceImpl implements MemberAuthService {
|
||||
private MemberUserMapper userMapper;
|
||||
|
||||
@Override
|
||||
public UserDetails loadUserByUsername(String mobile) throws UsernameNotFoundException {
|
||||
// 获取 username 对应的 SysUserDO
|
||||
MemberUserDO user = userService.getUserByMobile(mobile);
|
||||
if (user == null) {
|
||||
throw new UsernameNotFoundException(mobile);
|
||||
}
|
||||
// 创建 LoginUser 对象
|
||||
return AuthConvert.INSTANCE.convert(user);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String login(AppAuthLoginReqVO reqVO, String userIp, String userAgent) {
|
||||
public AppAuthLoginRespVO login(AppAuthLoginReqVO reqVO) {
|
||||
// 使用手机 + 密码,进行登录。
|
||||
LoginUser loginUser = this.login0(reqVO.getMobile(), reqVO.getPassword());
|
||||
MemberUserDO user = login0(reqVO.getMobile(), reqVO.getPassword());
|
||||
|
||||
// 缓存登录用户到 Redis 中,返回 Token 令牌
|
||||
return createUserSessionAfterLoginSuccess(loginUser, LoginLogTypeEnum.LOGIN_USERNAME, userIp, userAgent);
|
||||
// 创建 Token 令牌,记录登录日志
|
||||
return createTokenAfterLoginSuccess(user, reqVO.getMobile(), LoginLogTypeEnum.LOGIN_MOBILE);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public String smsLogin(AppAuthSmsLoginReqVO reqVO, String userIp, String userAgent) {
|
||||
public AppAuthLoginRespVO smsLogin(AppAuthSmsLoginReqVO reqVO) {
|
||||
// 校验验证码
|
||||
String userIp = getClientIP();
|
||||
smsCodeApi.useSmsCode(AuthConvert.INSTANCE.convert(reqVO, SmsSceneEnum.MEMBER_LOGIN.getScene(), userIp));
|
||||
|
||||
// 获得获得注册用户
|
||||
MemberUserDO user = userService.createUserIfAbsent(reqVO.getMobile(), userIp);
|
||||
Assert.notNull(user, "获取用户失败,结果为空");
|
||||
|
||||
// 执行登陆
|
||||
LoginUser loginUser = AuthConvert.INSTANCE.convert(user);
|
||||
|
||||
// 缓存登录用户到 Redis 中,返回 Token 令牌
|
||||
return createUserSessionAfterLoginSuccess(loginUser, LoginLogTypeEnum.LOGIN_SMS, userIp, userAgent);
|
||||
// 创建 Token 令牌,记录登录日志
|
||||
return createTokenAfterLoginSuccess(user, reqVO.getMobile(), LoginLogTypeEnum.LOGIN_SMS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String socialQuickLogin(AppAuthSocialQuickLoginReqVO reqVO, String userIp, String userAgent) {
|
||||
public AppAuthLoginRespVO socialQuickLogin(AppAuthSocialQuickLoginReqVO reqVO) {
|
||||
// 使用 code 授权码,进行登录。然后,获得到绑定的用户编号
|
||||
Long userId = socialUserApi.getBindUserId(UserTypeEnum.MEMBER.getValue(), reqVO.getType(),
|
||||
reqVO.getCode(), reqVO.getState());
|
||||
@ -122,31 +99,30 @@ public class MemberAuthServiceImpl implements MemberAuthService {
|
||||
throw exception(USER_NOT_EXISTS);
|
||||
}
|
||||
|
||||
// 创建 LoginUser 对象
|
||||
LoginUser loginUser = AuthConvert.INSTANCE.convert(user);
|
||||
|
||||
// 缓存登录用户到 Redis 中,返回 Token 令牌
|
||||
return createUserSessionAfterLoginSuccess(loginUser, LoginLogTypeEnum.LOGIN_SOCIAL, userIp, userAgent);
|
||||
// 创建 Token 令牌,记录登录日志
|
||||
return createTokenAfterLoginSuccess(user, user.getMobile(), LoginLogTypeEnum.LOGIN_SOCIAL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String socialBindLogin(AppAuthSocialBindLoginReqVO reqVO, String userIp, String userAgent) {
|
||||
public AppAuthLoginRespVO socialBindLogin(AppAuthSocialBindLoginReqVO reqVO) {
|
||||
// 使用手机号、手机验证码登录
|
||||
AppAuthSmsLoginReqVO loginReqVO = AppAuthSmsLoginReqVO.builder()
|
||||
.mobile(reqVO.getMobile()).code(reqVO.getSmsCode()).build();
|
||||
String token = this.smsLogin(loginReqVO, userIp, userAgent);
|
||||
LoginUser loginUser = userSessionApi.getLoginUser(token);
|
||||
AppAuthLoginRespVO token = smsLogin(loginReqVO);
|
||||
|
||||
// 绑定社交用户
|
||||
socialUserApi.bindSocialUser(AuthConvert.INSTANCE.convert(loginUser.getId(), getUserType().getValue(), reqVO));
|
||||
socialUserApi.bindSocialUser(AuthConvert.INSTANCE.convert(token.getUserId(), getUserType().getValue(), reqVO));
|
||||
return token;
|
||||
}
|
||||
|
||||
private String createUserSessionAfterLoginSuccess(LoginUser loginUser, LoginLogTypeEnum logType, String userIp, String userAgent) {
|
||||
private AppAuthLoginRespVO createTokenAfterLoginSuccess(MemberUserDO user, String mobile, LoginLogTypeEnum logType) {
|
||||
// 插入登陆日志
|
||||
createLoginLog(loginUser.getUsername(), logType, LoginResultEnum.SUCCESS);
|
||||
// 缓存登录用户到 Redis 中,返回 Token 令牌
|
||||
return userSessionApi.createUserSession(loginUser, userIp, userAgent);
|
||||
createLoginLog(user.getId(), mobile, logType, LoginResultEnum.SUCCESS);
|
||||
// 创建 Token 令牌
|
||||
OAuth2AccessTokenRespDTO accessTokenRespDTO = oauth2TokenApi.createAccessToken(new OAuth2AccessTokenCreateReqDTO()
|
||||
.setUserId(user.getId()).setUserType(getUserType().getValue()).setClientId(OAuth2ClientConstants.CLIENT_ID_DEFAULT));
|
||||
// 构建返回结果
|
||||
return AuthConvert.INSTANCE.convert(accessTokenRespDTO);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -154,40 +130,32 @@ public class MemberAuthServiceImpl implements MemberAuthService {
|
||||
return socialUserApi.getAuthorizeUrl(type, redirectUri);
|
||||
}
|
||||
|
||||
private LoginUser login0(String username, String password) {
|
||||
final LoginLogTypeEnum logType = LoginLogTypeEnum.LOGIN_USERNAME;
|
||||
// 用户验证
|
||||
Authentication authentication;
|
||||
try {
|
||||
// 调用 Spring Security 的 AuthenticationManager#authenticate(...) 方法,使用账号密码进行认证
|
||||
// 在其内部,会调用到 loadUserByUsername 方法,获取 User 信息
|
||||
authentication = authenticationManager.authenticate(new MultiUsernamePasswordAuthenticationToken(
|
||||
username, password, getUserType()));
|
||||
} catch (BadCredentialsException badCredentialsException) {
|
||||
this.createLoginLog(username, logType, LoginResultEnum.BAD_CREDENTIALS);
|
||||
private MemberUserDO login0(String mobile, String password) {
|
||||
final LoginLogTypeEnum logTypeEnum = LoginLogTypeEnum.LOGIN_MOBILE;
|
||||
// 校验账号是否存在
|
||||
MemberUserDO user = userService.getUserByMobile(mobile);
|
||||
if (user == null) {
|
||||
createLoginLog(null, mobile, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS);
|
||||
throw exception(AUTH_LOGIN_BAD_CREDENTIALS);
|
||||
} catch (DisabledException disabledException) {
|
||||
this.createLoginLog(username, logType, LoginResultEnum.USER_DISABLED);
|
||||
throw exception(AUTH_LOGIN_USER_DISABLED);
|
||||
} catch (AuthenticationException authenticationException) {
|
||||
log.error("[login0][username({}) 发生未知异常]", username, authenticationException);
|
||||
this.createLoginLog(username, logType, LoginResultEnum.UNKNOWN_ERROR);
|
||||
throw exception(AUTH_LOGIN_FAIL_UNKNOWN);
|
||||
}
|
||||
Assert.notNull(authentication.getPrincipal(), "Principal 不会为空");
|
||||
return (LoginUser) authentication.getPrincipal();
|
||||
if (!userService.isPasswordMatch(password, user.getPassword())) {
|
||||
createLoginLog(user.getId(), mobile, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS);
|
||||
throw exception(AUTH_LOGIN_BAD_CREDENTIALS);
|
||||
}
|
||||
// 校验是否禁用
|
||||
if (ObjectUtil.notEqual(user.getStatus(), CommonStatusEnum.ENABLE.getStatus())) {
|
||||
createLoginLog(user.getId(), mobile, logTypeEnum, LoginResultEnum.USER_DISABLED);
|
||||
throw exception(AUTH_LOGIN_USER_DISABLED);
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
private void createLoginLog(String mobile, LoginLogTypeEnum logType, LoginResultEnum loginResult) {
|
||||
// 获得用户
|
||||
MemberUserDO user = userService.getUserByMobile(mobile);
|
||||
private void createLoginLog(Long userId, String mobile, LoginLogTypeEnum logType, LoginResultEnum loginResult) {
|
||||
// 插入登录日志
|
||||
LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO();
|
||||
reqDTO.setLogType(logType.getType());
|
||||
reqDTO.setTraceId(TracerUtils.getTraceId());
|
||||
if (user != null) {
|
||||
reqDTO.setUserId(user.getId());
|
||||
}
|
||||
reqDTO.setUserId(userId);
|
||||
reqDTO.setUserType(getUserType().getValue());
|
||||
reqDTO.setUsername(mobile);
|
||||
reqDTO.setUserAgent(ServletUtils.getUserAgent());
|
||||
@ -195,72 +163,20 @@ public class MemberAuthServiceImpl implements MemberAuthService {
|
||||
reqDTO.setResult(loginResult.getResult());
|
||||
loginLogApi.createLoginLog(reqDTO);
|
||||
// 更新最后登录时间
|
||||
if (user != null && Objects.equals(LoginResultEnum.SUCCESS.getResult(), loginResult.getResult())) {
|
||||
userService.updateUserLogin(user.getId(), getClientIP());
|
||||
if (userId != null && Objects.equals(LoginResultEnum.SUCCESS.getResult(), loginResult.getResult())) {
|
||||
userService.updateUserLogin(userId, getClientIP());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoginUser verifyTokenAndRefresh(String token) {
|
||||
// 获得 LoginUser
|
||||
LoginUser loginUser = userSessionApi.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() <
|
||||
userSessionApi.getSessionTimeoutMillis() / 3) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 重新加载 UserDO 信息
|
||||
MemberUserDO user = userService.getUser(loginUser.getId());
|
||||
if (user == null || CommonStatusEnum.DISABLE.getStatus().equals(user.getStatus())) {
|
||||
// 校验 token 时,用户被禁用的情况下,也认为 token 过期,方便前端跳转到登录界面
|
||||
throw exception(AUTH_TOKEN_EXPIRED);
|
||||
}
|
||||
|
||||
// 刷新 LoginUser 缓存
|
||||
userSessionApi.refreshUserSession(token, loginUser);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoginUser mockLogin(Long userId) {
|
||||
// 获取用户编号对应的 UserDO
|
||||
MemberUserDO user = userService.getUser(userId);
|
||||
if (user == null) {
|
||||
throw new UsernameNotFoundException(String.valueOf(userId));
|
||||
}
|
||||
|
||||
// 执行登陆
|
||||
this.createLoginLog(user.getMobile(), LoginLogTypeEnum.LOGIN_MOCK, LoginResultEnum.SUCCESS);
|
||||
|
||||
// 创建 LoginUser 对象
|
||||
return AuthConvert.INSTANCE.convert(user);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logout(String token) {
|
||||
// 查询用户信息
|
||||
LoginUser loginUser = userSessionApi.getLoginUser(token);
|
||||
if (loginUser == null) {
|
||||
// 删除访问令牌
|
||||
OAuth2AccessTokenRespDTO accessTokenRespDTO = oauth2TokenApi.removeAccessToken(token);
|
||||
if (accessTokenRespDTO == null) {
|
||||
return;
|
||||
}
|
||||
// 删除 session
|
||||
userSessionApi.deleteUserSession(token);
|
||||
// 记录登出日志
|
||||
this.createLogoutLog(loginUser.getId(), loginUser.getUsername());
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserTypeEnum getUserType() {
|
||||
return UserTypeEnum.MEMBER;
|
||||
// 删除成功,则记录登出日志
|
||||
createLogoutLog(accessTokenRespDTO.getUserId());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -269,6 +185,7 @@ public class MemberAuthServiceImpl implements MemberAuthService {
|
||||
MemberUserDO userDO = checkOldPassword(userId, reqVO.getOldPassword());
|
||||
|
||||
// 更新用户密码
|
||||
// TODO 芋艿:需要重构到用户模块
|
||||
userMapper.updateById(MemberUserDO.builder().id(userDO.getId())
|
||||
.password(passwordEncoder.encode(reqVO.getPassword())).build());
|
||||
}
|
||||
@ -293,6 +210,12 @@ public class MemberAuthServiceImpl implements MemberAuthService {
|
||||
smsCodeApi.sendSmsCode(AuthConvert.INSTANCE.convert(reqVO).setCreateIp(getClientIP()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public AppAuthLoginRespVO refreshToken(String refreshToken) {
|
||||
OAuth2AccessTokenRespDTO accessTokenDO = oauth2TokenApi.refreshAccessToken(refreshToken, OAuth2ClientConstants.CLIENT_ID_DEFAULT);
|
||||
return AuthConvert.INSTANCE.convert(accessTokenDO);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验旧密码
|
||||
*
|
||||
@ -321,17 +244,29 @@ public class MemberAuthServiceImpl implements MemberAuthService {
|
||||
return user;
|
||||
}
|
||||
|
||||
private void createLogoutLog(Long userId, String username) {
|
||||
private void createLogoutLog(Long userId) {
|
||||
LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO();
|
||||
reqDTO.setLogType(LoginLogTypeEnum.LOGOUT_SELF.getType());
|
||||
reqDTO.setTraceId(TracerUtils.getTraceId());
|
||||
reqDTO.setUserId(userId);
|
||||
reqDTO.setUserType(getUserType().getValue());
|
||||
reqDTO.setUsername(username);
|
||||
reqDTO.setUsername(getMobile(userId));
|
||||
reqDTO.setUserAgent(ServletUtils.getUserAgent());
|
||||
reqDTO.setUserIp(getClientIP());
|
||||
reqDTO.setResult(LoginResultEnum.SUCCESS.getResult());
|
||||
loginLogApi.createLoginLog(reqDTO);
|
||||
}
|
||||
|
||||
private String getMobile(Long userId) {
|
||||
if (userId == null) {
|
||||
return null;
|
||||
}
|
||||
MemberUserDO user = userService.getUser(userId);
|
||||
return user != null ? user.getMobile() : null;
|
||||
}
|
||||
|
||||
private UserTypeEnum getUserType() {
|
||||
return UserTypeEnum.MEMBER;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -69,4 +69,13 @@ public interface MemberUserService {
|
||||
*/
|
||||
void updateUserMobile(Long userId, AppUserUpdateMobileReqVO reqVO);
|
||||
|
||||
/**
|
||||
* 判断密码是否匹配
|
||||
*
|
||||
* @param rawPassword 未加密的密码
|
||||
* @param encodedPassword 加密后的密码
|
||||
* @return 是否匹配
|
||||
*/
|
||||
boolean isPasswordMatch(String rawPassword, String encodedPassword);
|
||||
|
||||
}
|
||||
|
@ -69,7 +69,7 @@ public class MemberUserServiceImpl implements MemberUserService {
|
||||
MemberUserDO user = new MemberUserDO();
|
||||
user.setMobile(mobile);
|
||||
user.setStatus(CommonStatusEnum.ENABLE.getStatus()); // 默认开启
|
||||
user.setPassword(passwordEncoder.encode(password)); // 加密密码
|
||||
user.setPassword(encodePassword(password)); // 加密密码
|
||||
user.setRegisterIp(registerIp);
|
||||
memberUserMapper.insert(user);
|
||||
return user;
|
||||
@ -127,6 +127,21 @@ public class MemberUserServiceImpl implements MemberUserService {
|
||||
memberUserMapper.updateById(MemberUserDO.builder().id(userId).mobile(reqVO.getMobile()).build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPasswordMatch(String rawPassword, String encodedPassword) {
|
||||
return passwordEncoder.matches(rawPassword, encodedPassword);
|
||||
}
|
||||
|
||||
/**
|
||||
* 对密码进行加密
|
||||
*
|
||||
* @param password 密码
|
||||
* @return 加密后的密码
|
||||
*/
|
||||
private String encodePassword(String password) {
|
||||
return passwordEncoder.encode(password);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public MemberUserDO checkUserExists(Long id) {
|
||||
if (id == null) {
|
||||
|
@ -9,14 +9,13 @@ import cn.iocoder.yudao.module.member.controller.app.auth.vo.AppAuthUpdatePasswo
|
||||
import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
|
||||
import cn.iocoder.yudao.module.member.dal.mysql.user.MemberUserMapper;
|
||||
import cn.iocoder.yudao.module.member.service.user.MemberUserService;
|
||||
import cn.iocoder.yudao.module.system.api.auth.UserSessionApi;
|
||||
import cn.iocoder.yudao.module.system.api.auth.OAuth2TokenApi;
|
||||
import cn.iocoder.yudao.module.system.api.logger.LoginLogApi;
|
||||
import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi;
|
||||
import cn.iocoder.yudao.module.system.api.social.SocialUserApi;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
@ -38,8 +37,8 @@ import static org.mockito.Mockito.when;
|
||||
@Import({MemberAuthServiceImpl.class, YudaoRedisAutoConfiguration.class})
|
||||
public class MemberAuthServiceTest extends BaseDbAndRedisUnitTest {
|
||||
|
||||
@MockBean
|
||||
private AuthenticationManager authenticationManager;
|
||||
// TODO @芋艿:登录相关的单测,待补全
|
||||
|
||||
@MockBean
|
||||
private MemberUserService userService;
|
||||
@MockBean
|
||||
@ -47,7 +46,7 @@ public class MemberAuthServiceTest extends BaseDbAndRedisUnitTest {
|
||||
@MockBean
|
||||
private LoginLogApi loginLogApi;
|
||||
@MockBean
|
||||
private UserSessionApi userSessionApi;
|
||||
private OAuth2TokenApi oauth2TokenApi;
|
||||
@MockBean
|
||||
private SocialUserApi socialUserApi;
|
||||
@MockBean
|
||||
|
Reference in New Issue
Block a user