去除 Spring Security 的 Member 的 loadUsername,使用自己定义的 login0 实现

This commit is contained in:
YunaiV 2022-05-08 02:33:34 +08:00
parent 3bd7e8e682
commit 5e8648508e
17 changed files with 63 additions and 383 deletions

View File

@ -1,13 +1,10 @@
package cn.iocoder.yudao.framework.security.config; package cn.iocoder.yudao.framework.security.config;
import cn.iocoder.yudao.framework.security.core.aop.PreAuthenticatedAspect; import cn.iocoder.yudao.framework.security.core.aop.PreAuthenticatedAspect;
import cn.iocoder.yudao.framework.security.core.authentication.MultiUserDetailsAuthenticationProvider;
import cn.iocoder.yudao.framework.security.core.context.TransmittableThreadLocalSecurityContextHolderStrategy; import cn.iocoder.yudao.framework.security.core.context.TransmittableThreadLocalSecurityContextHolderStrategy;
import cn.iocoder.yudao.framework.security.core.filter.TokenAuthenticationFilter; import cn.iocoder.yudao.framework.security.core.filter.TokenAuthenticationFilter;
import cn.iocoder.yudao.framework.security.core.handler.AccessDeniedHandlerImpl; import cn.iocoder.yudao.framework.security.core.handler.AccessDeniedHandlerImpl;
import cn.iocoder.yudao.framework.security.core.handler.AuthenticationEntryPointImpl; import cn.iocoder.yudao.framework.security.core.handler.AuthenticationEntryPointImpl;
import cn.iocoder.yudao.framework.security.core.service.SecurityAuthFrameworkService;
import cn.iocoder.yudao.framework.web.config.WebProperties;
import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler; import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
import org.springframework.beans.factory.config.MethodInvokingFactoryBean; import org.springframework.beans.factory.config.MethodInvokingFactoryBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
@ -20,7 +17,6 @@ import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.security.web.access.AccessDeniedHandler;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.List;
/** /**
* Spring Security 自动配置类主要用于相关组件的配置 * Spring Security 自动配置类主要用于相关组件的配置
@ -76,19 +72,8 @@ public class YudaoSecurityAutoConfiguration {
* Token 认证过滤器 Bean * Token 认证过滤器 Bean
*/ */
@Bean @Bean
public TokenAuthenticationFilter authenticationTokenFilter(MultiUserDetailsAuthenticationProvider authenticationProvider, public TokenAuthenticationFilter authenticationTokenFilter(GlobalExceptionHandler globalExceptionHandler) {
GlobalExceptionHandler globalExceptionHandler) { return new TokenAuthenticationFilter(securityProperties, globalExceptionHandler);
return new TokenAuthenticationFilter(securityProperties, authenticationProvider, globalExceptionHandler);
}
/**
* 身份验证的 Provider Bean通过它实现账号 + 密码的认证
*/
@Bean
public MultiUserDetailsAuthenticationProvider authenticationProvider(
List<SecurityAuthFrameworkService> securityFrameworkServices,
WebProperties webProperties, PasswordEncoder passwordEncoder) {
return new MultiUserDetailsAuthenticationProvider(securityFrameworkServices, webProperties, passwordEncoder);
} }
/** /**

View File

@ -1,6 +1,5 @@
package cn.iocoder.yudao.framework.security.config; package cn.iocoder.yudao.framework.security.config;
import cn.iocoder.yudao.framework.security.core.authentication.MultiUserDetailsAuthenticationProvider;
import cn.iocoder.yudao.framework.security.core.filter.TokenAuthenticationFilter; import cn.iocoder.yudao.framework.security.core.filter.TokenAuthenticationFilter;
import cn.iocoder.yudao.framework.web.config.WebProperties; import cn.iocoder.yudao.framework.web.config.WebProperties;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@ -8,7 +7,6 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@ -32,8 +30,6 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap
@Resource @Resource
private WebProperties webProperties; private WebProperties webProperties;
@Resource
private MultiUserDetailsAuthenticationProvider authenticationProvider;
/** /**
* 认证失败处理类 Bean * 认证失败处理类 Bean
*/ */
@ -69,14 +65,6 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap
return super.authenticationManagerBean(); return super.authenticationManagerBean();
} }
/**
* 身份认证接口
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider);
}
/** /**
* 配置 URL 的安全配置 * 配置 URL 的安全配置
* *
@ -130,11 +118,7 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap
// 添加 JWT Filter // 添加 JWT Filter
httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
} }
private String buildAdminApi(String url) {
return webProperties.getAdminApi().getPrefix() + url;
}
private String buildAppApi(String url) { private String buildAppApi(String url) {
return webProperties.getAppApi().getPrefix() + url; return webProperties.getAppApi().getPrefix() + url;
} }

View File

@ -1,128 +0,0 @@
package cn.iocoder.yudao.framework.security.core.authentication;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.security.core.LoginUser;
import cn.iocoder.yudao.framework.security.core.service.SecurityAuthFrameworkService;
import cn.iocoder.yudao.framework.web.config.WebProperties;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.password.PasswordEncoder;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 支持多用户类型的 AuthenticationProvider 实现类
*
* 为什么不用 {@link org.springframework.security.authentication.ProviderManager}
* 原因是需要每个用户类型实现对应的 {@link AuthenticationProvider} + authentication略显麻烦实际也是可以实现的
*
* 另外额外支持 verifyTokenAndRefresh 校验令牌logout 登出mockLogin 模拟登陆等操作
* 实际上它就是 {@link SecurityAuthFrameworkService} 定义的三个接口
* 因为需要支持多种类型所以需要根据请求的 URL判断出对应的用户类型从而使用对应的 SecurityAuthFrameworkService 是吸纳
*
* @see cn.iocoder.yudao.framework.common.enums.UserTypeEnum
* @author 芋道源码
*/
public class MultiUserDetailsAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
private final Map<UserTypeEnum, SecurityAuthFrameworkService> services = new HashMap<>();
private final WebProperties properties;
private final PasswordEncoder passwordEncoder;
public MultiUserDetailsAuthenticationProvider(List<SecurityAuthFrameworkService> serviceList,
WebProperties properties, PasswordEncoder passwordEncoder) {
serviceList.forEach(service -> services.put(service.getUserType(), service));
this.properties = properties;
this.passwordEncoder = passwordEncoder;
}
// ========== AuthenticationProvider 相关 ==========
@Override
protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
// 执行用户的加载
return selectService(authentication).loadUserByUsername(username);
}
private SecurityAuthFrameworkService selectService(UsernamePasswordAuthenticationToken authentication) {
// 第一步获得用户类型
UserTypeEnum userType = getUserType(authentication);
// 第二步获得 SecurityAuthFrameworkService
SecurityAuthFrameworkService service = services.get(userType);
Assert.notNull(service, "用户类型({}) 找不到 SecurityAuthFrameworkService 实现类", userType);
return service;
}
private UserTypeEnum getUserType(UsernamePasswordAuthenticationToken authentication) {
Assert.isInstanceOf(MultiUsernamePasswordAuthenticationToken.class, authentication);
MultiUsernamePasswordAuthenticationToken multiAuthentication = (MultiUsernamePasswordAuthenticationToken) authentication;
UserTypeEnum userType = multiAuthentication.getUserType();
Assert.notNull(userType, "用户类型不能为空");
return userType;
}
@Override // copy DaoAuthenticationProvider additionalAuthenticationChecks 方法
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
// 校验 credentials
if (authentication.getCredentials() == null) {
this.logger.debug("Failed to authenticate since no credentials provided");
throw new BadCredentialsException(this.messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
// 校验 password
String presentedPassword = authentication.getCredentials().toString();
if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
this.logger.debug("Failed to authenticate since password does not match stored value");
throw new BadCredentialsException(this.messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
}
// ========== SecurityAuthFrameworkService 相关 ==========
/**
* 校验 token 的有效性并获取用户信息
* 通过后刷新 token 的过期时间
*
* @param request 请求
* @param token token
* @return 用户信息
*/
public LoginUser verifyTokenAndRefresh(HttpServletRequest request, String token) {
return selectService(request).verifyTokenAndRefresh(token);
}
private SecurityAuthFrameworkService selectService(HttpServletRequest request) {
// 第一步获得用户类型
UserTypeEnum userType = getUserType(request);
// 第二步获得 SecurityAuthFrameworkService
SecurityAuthFrameworkService service = services.get(userType);
Assert.notNull(service, "URI({}) 用户类型({}) 找不到 SecurityAuthFrameworkService 实现类",
request.getRequestURI(), userType);
return service;
}
private UserTypeEnum getUserType(HttpServletRequest request) {
if (request.getRequestURI().startsWith(properties.getAdminApi().getPrefix())) {
return UserTypeEnum.ADMIN;
}
if (request.getRequestURI().startsWith(properties.getAppApi().getPrefix())) {
return UserTypeEnum.MEMBER;
}
throw new IllegalArgumentException(StrUtil.format("URI({}) 找不到匹配的用户类型", request.getRequestURI()));
}
}

View File

@ -1,43 +0,0 @@
package cn.iocoder.yudao.framework.security.core.authentication;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import lombok.Getter;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import java.util.Collection;
/**
* 支持多用户的 UsernamePasswordAuthenticationToken 实现类
*
* @author 芋道源码
*/
@Getter
public class MultiUsernamePasswordAuthenticationToken extends UsernamePasswordAuthenticationToken {
/**
* 用户类型
*/
private UserTypeEnum userType;
public MultiUsernamePasswordAuthenticationToken(Object principal, Object credentials) {
super(principal, credentials);
}
public MultiUsernamePasswordAuthenticationToken(Object principal, Object credentials,
Collection<? extends GrantedAuthority> authorities) {
super(principal, credentials, authorities);
}
public MultiUsernamePasswordAuthenticationToken(Object principal, Object credentials, UserTypeEnum userType) {
super(principal, credentials);
this.userType = userType;
}
public MultiUsernamePasswordAuthenticationToken(Object principal, Object credentials,
Collection<? extends GrantedAuthority> authorities, UserTypeEnum userType) {
super(principal, credentials, authorities);
this.userType = userType;
}
}

View File

@ -1,78 +0,0 @@
package cn.iocoder.yudao.framework.security.core.authentication;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.Collections;
/**
* 登录用户信息
*
* @author 芋道源码
*/
@Data
@AllArgsConstructor
public class SpringSecurityUser implements UserDetails {
/**
* 用户编号
*/
private Long id;
/**
* 用户名
*/
private String username;
/**
* 密码
*/
private String password;
/**
* 状态
*/
private Integer status;
/**
* 租户编号
*/
private Long tenantId;
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isEnabled() {
return CommonStatusEnum.ENABLE.getStatus().equals(status);
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Collections.emptyList();
}
@Override
public boolean isAccountNonExpired() {
return true; // 返回 true不依赖 Spring Security 判断
}
@Override
public boolean isAccountNonLocked() {
return true; // 返回 true不依赖 Spring Security 判断
}
@Override
public boolean isCredentialsNonExpired() {
return true; // 返回 true不依赖 Spring Security 判断
}
}

View File

@ -5,7 +5,6 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
import cn.iocoder.yudao.framework.security.config.SecurityProperties; import cn.iocoder.yudao.framework.security.config.SecurityProperties;
import cn.iocoder.yudao.framework.security.core.LoginUser; import cn.iocoder.yudao.framework.security.core.LoginUser;
import cn.iocoder.yudao.framework.security.core.authentication.MultiUserDetailsAuthenticationProvider;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler; import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils; import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
@ -29,8 +28,6 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
private final SecurityProperties securityProperties; private final SecurityProperties securityProperties;
private final MultiUserDetailsAuthenticationProvider authenticationProvider;
private final GlobalExceptionHandler globalExceptionHandler; private final GlobalExceptionHandler globalExceptionHandler;
@Override @Override
@ -42,7 +39,7 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
Integer userType = WebFrameworkUtils.getLoginUserType(request); Integer userType = WebFrameworkUtils.getLoginUserType(request);
try { try {
// 验证 token 有效性 // 验证 token 有效性
LoginUser loginUser = authenticationProvider.verifyTokenAndRefresh(request, token); LoginUser loginUser = null; // TODO 芋艿待实现
// 模拟 Login 功能方便日常开发调试 // 模拟 Login 功能方便日常开发调试
if (loginUser == null) { if (loginUser == null) {
loginUser = mockLoginUser(request, token, userType); loginUser = mockLoginUser(request, token, userType);

View File

@ -17,7 +17,6 @@ public interface ErrorCodeConstants {
// ========== AUTH 模块 1004003000 ========== // ========== AUTH 模块 1004003000 ==========
ErrorCode AUTH_LOGIN_BAD_CREDENTIALS = new ErrorCode(1004003000, "登录失败,账号密码不正确"); ErrorCode AUTH_LOGIN_BAD_CREDENTIALS = new ErrorCode(1004003000, "登录失败,账号密码不正确");
ErrorCode AUTH_LOGIN_USER_DISABLED = new ErrorCode(1004003001, "登录失败,账号被禁用"); 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_TOKEN_EXPIRED = new ErrorCode(1004003004, "Token 已经过期");
ErrorCode AUTH_THIRD_LOGIN_NOT_BIND = new ErrorCode(1004003005, "未绑定账号,需要进行绑定"); ErrorCode AUTH_THIRD_LOGIN_NOT_BIND = new ErrorCode(1004003005, "未绑定账号,需要进行绑定");

View File

@ -1,5 +1,5 @@
### 请求 /login 接口 => 成功 ### 请求 /login 接口 => 成功
POST {{appApi}}/member/login POST {{appApi}}/member/auth/login
Content-Type: application/json Content-Type: application/json
tenant-id: {{appTenentId}} tenant-id: {{appTenentId}}

View File

@ -1,7 +1,6 @@
package cn.iocoder.yudao.module.member.convert.auth; package cn.iocoder.yudao.module.member.convert.auth;
import cn.iocoder.yudao.framework.security.core.LoginUser; import cn.iocoder.yudao.framework.security.core.LoginUser;
import cn.iocoder.yudao.framework.security.core.authentication.SpringSecurityUser;
import cn.iocoder.yudao.module.member.controller.app.auth.vo.*; 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.controller.app.social.vo.AppSocialUserUnbindReqVO;
import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO; import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
@ -11,7 +10,6 @@ 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.api.social.dto.SocialUserUnbindReqDTO;
import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum; import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers; import org.mapstruct.factory.Mappers;
@Mapper @Mapper
@ -21,11 +19,6 @@ public interface AuthConvert {
LoginUser convert(MemberUserDO bean); LoginUser convert(MemberUserDO bean);
@Mapping(source = "mobile", target = "username")
SpringSecurityUser convert2(MemberUserDO user);
LoginUser convert(SpringSecurityUser bean);
SocialUserBindReqDTO convert(Long userId, Integer userType, AppAuthSocialBindLoginReqVO reqVO); SocialUserBindReqDTO convert(Long userId, Integer userType, AppAuthSocialBindLoginReqVO reqVO);
SocialUserBindReqDTO convert(Long userId, Integer userType, AppAuthSocialQuickLoginReqVO reqVO); SocialUserBindReqDTO convert(Long userId, Integer userType, AppAuthSocialQuickLoginReqVO reqVO);
SocialUserUnbindReqDTO convert(Long userId, Integer userType, AppSocialUserUnbindReqVO reqVO); SocialUserUnbindReqDTO convert(Long userId, Integer userType, AppSocialUserUnbindReqVO reqVO);

View File

@ -1,6 +1,5 @@
package cn.iocoder.yudao.module.member.service.auth; 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 cn.iocoder.yudao.module.member.controller.app.auth.vo.*;
import javax.validation.Valid; import javax.validation.Valid;
@ -12,7 +11,7 @@ import javax.validation.Valid;
* *
* @author 芋道源码 * @author 芋道源码
*/ */
public interface MemberAuthService extends SecurityAuthFrameworkService { public interface MemberAuthService {
/** /**
* 手机 + 密码登录 * 手机 + 密码登录

View File

@ -1,11 +1,12 @@
package cn.iocoder.yudao.module.member.service.auth; package cn.iocoder.yudao.module.member.service.auth;
import cn.hutool.core.lang.Assert; 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.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils; import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
import cn.iocoder.yudao.framework.security.core.LoginUser; 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.controller.app.auth.vo.*;
import cn.iocoder.yudao.module.member.convert.auth.AuthConvert; 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.dataobject.user.MemberUserDO;
@ -21,14 +22,6 @@ import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum;
import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum; import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import lombok.extern.slf4j.Slf4j; 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.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@ -49,10 +42,6 @@ import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.*;
@Slf4j @Slf4j
public class MemberAuthServiceImpl implements MemberAuthService { public class MemberAuthServiceImpl implements MemberAuthService {
@Resource
@Lazy // 延迟加载因为存在相互依赖的问题
private AuthenticationManager authenticationManager;
@Resource @Resource
private MemberUserService userService; private MemberUserService userService;
@Resource @Resource
@ -69,17 +58,6 @@ public class MemberAuthServiceImpl implements MemberAuthService {
@Resource @Resource
private MemberUserMapper userMapper; 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.convert2(user);
}
@Override @Override
public String login(AppAuthLoginReqVO reqVO, String userIp, String userAgent) { public String login(AppAuthLoginReqVO reqVO, String userIp, String userAgent) {
// 使用手机 + 密码进行登录 // 使用手机 + 密码进行登录
@ -157,43 +135,34 @@ public class MemberAuthServiceImpl implements MemberAuthService {
return socialUserApi.getAuthorizeUrl(type, redirectUri); return socialUserApi.getAuthorizeUrl(type, redirectUri);
} }
private LoginUser login0(String username, String password) { private LoginUser login0(String mobile, String password) {
final LoginLogTypeEnum logType = LoginLogTypeEnum.LOGIN_USERNAME; final LoginLogTypeEnum logTypeEnum = LoginLogTypeEnum.LOGIN_MOBILE;
// 用户验证 // 校验账号是否存在
Authentication authentication; MemberUserDO user = userService.getUserByMobile(mobile);
try { if (user == null) {
// 调用 Spring Security AuthenticationManager#authenticate(...) 方法使用账号密码进行认证 createLoginLog(null, mobile, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS);
// 在其内部会调用到 loadUserByUsername 方法获取 User 信息
authentication = authenticationManager.authenticate(new MultiUsernamePasswordAuthenticationToken(
username, password, getUserType()));
} catch (BadCredentialsException badCredentialsException) {
this.createLoginLog(null, username, logType, LoginResultEnum.BAD_CREDENTIALS);
throw exception(AUTH_LOGIN_BAD_CREDENTIALS); throw exception(AUTH_LOGIN_BAD_CREDENTIALS);
} catch (DisabledException disabledException) {
this.createLoginLog(null, username, logType, LoginResultEnum.USER_DISABLED);
throw exception(AUTH_LOGIN_USER_DISABLED);
} catch (AuthenticationException authenticationException) {
log.error("[login0][username({}) 发生未知异常]", username, authenticationException);
this.createLoginLog(null, username, logType, LoginResultEnum.UNKNOWN_ERROR);
throw exception(AUTH_LOGIN_FAIL_UNKNOWN);
} }
Assert.notNull(authentication.getPrincipal(), "Principal 不会为空"); if (!userService.isPasswordMatch(password, user.getPassword())) {
return (LoginUser) authentication.getPrincipal(); 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);
}
// 构建 User 对象
return buildLoginUser(user);
} }
private void createLoginLog(Long userId, String mobile, LoginLogTypeEnum logType, LoginResultEnum loginResult) { private void createLoginLog(Long userId, String mobile, LoginLogTypeEnum logType, LoginResultEnum loginResult) {
// 获得用户
if (userId == null) {
MemberUserDO user = userService.getUserByMobile(mobile);
userId = user != null ? user.getId() : null;
}
// 插入登录日志 // 插入登录日志
LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO(); LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO();
reqDTO.setLogType(logType.getType()); reqDTO.setLogType(logType.getType());
reqDTO.setTraceId(TracerUtils.getTraceId()); reqDTO.setTraceId(TracerUtils.getTraceId());
if (userId != null) { reqDTO.setUserId(userId);
reqDTO.setUserId(userId);
}
reqDTO.setUserType(getUserType().getValue()); reqDTO.setUserType(getUserType().getValue());
reqDTO.setUsername(mobile); reqDTO.setUsername(mobile);
reqDTO.setUserAgent(ServletUtils.getUserAgent()); reqDTO.setUserAgent(ServletUtils.getUserAgent());
@ -206,11 +175,6 @@ public class MemberAuthServiceImpl implements MemberAuthService {
} }
} }
@Override
public LoginUser verifyTokenAndRefresh(String token) {
return userSessionApi.getLoginUser(token);
}
@Override @Override
public void logout(String token) { public void logout(String token) {
// 查询用户信息 // 查询用户信息
@ -224,17 +188,13 @@ public class MemberAuthServiceImpl implements MemberAuthService {
createLogoutLog(loginUser.getId()); createLogoutLog(loginUser.getId());
} }
@Override
public UserTypeEnum getUserType() {
return UserTypeEnum.MEMBER;
}
@Override @Override
public void updatePassword(Long userId, AppAuthUpdatePasswordReqVO reqVO) { public void updatePassword(Long userId, AppAuthUpdatePasswordReqVO reqVO) {
// 检验旧密码 // 检验旧密码
MemberUserDO userDO = checkOldPassword(userId, reqVO.getOldPassword()); MemberUserDO userDO = checkOldPassword(userId, reqVO.getOldPassword());
// 更新用户密码 // 更新用户密码
// TODO 芋艿需要重构到用户模块
userMapper.updateById(MemberUserDO.builder().id(userDO.getId()) userMapper.updateById(MemberUserDO.builder().id(userDO.getId())
.password(passwordEncoder.encode(reqVO.getPassword())).build()); .password(passwordEncoder.encode(reqVO.getPassword())).build());
} }
@ -312,4 +272,8 @@ public class MemberAuthServiceImpl implements MemberAuthService {
return user != null ? user.getMobile() : null; return user != null ? user.getMobile() : null;
} }
private UserTypeEnum getUserType() {
return UserTypeEnum.MEMBER;
}
} }

View File

@ -69,4 +69,13 @@ public interface MemberUserService {
*/ */
void updateUserMobile(Long userId, AppUserUpdateMobileReqVO reqVO); void updateUserMobile(Long userId, AppUserUpdateMobileReqVO reqVO);
/**
* 判断密码是否匹配
*
* @param rawPassword 未加密的密码
* @param encodedPassword 加密后的密码
* @return 是否匹配
*/
boolean isPasswordMatch(String rawPassword, String encodedPassword);
} }

View File

@ -69,7 +69,7 @@ public class MemberUserServiceImpl implements MemberUserService {
MemberUserDO user = new MemberUserDO(); MemberUserDO user = new MemberUserDO();
user.setMobile(mobile); user.setMobile(mobile);
user.setStatus(CommonStatusEnum.ENABLE.getStatus()); // 默认开启 user.setStatus(CommonStatusEnum.ENABLE.getStatus()); // 默认开启
user.setPassword(passwordEncoder.encode(password)); // 加密密码 user.setPassword(encodePassword(password)); // 加密密码
user.setRegisterIp(registerIp); user.setRegisterIp(registerIp);
memberUserMapper.insert(user); memberUserMapper.insert(user);
return user; return user;
@ -127,6 +127,21 @@ public class MemberUserServiceImpl implements MemberUserService {
memberUserMapper.updateById(MemberUserDO.builder().id(userId).mobile(reqVO.getMobile()).build()); 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 @VisibleForTesting
public MemberUserDO checkUserExists(Long id) { public MemberUserDO checkUserExists(Long id) {
if (id == null) { if (id == null) {

View File

@ -16,7 +16,6 @@ import cn.iocoder.yudao.module.system.api.social.SocialUserApi;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;
import javax.annotation.Resource; import javax.annotation.Resource;
@ -38,8 +37,8 @@ import static org.mockito.Mockito.when;
@Import({MemberAuthServiceImpl.class, YudaoRedisAutoConfiguration.class}) @Import({MemberAuthServiceImpl.class, YudaoRedisAutoConfiguration.class})
public class MemberAuthServiceTest extends BaseDbAndRedisUnitTest { public class MemberAuthServiceTest extends BaseDbAndRedisUnitTest {
@MockBean // TODO @芋艿登录相关的单测待补全
private AuthenticationManager authenticationManager;
@MockBean @MockBean
private MemberUserService userService; private MemberUserService userService;
@MockBean @MockBean

View File

@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.system.convert.auth;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.security.core.LoginUser; import cn.iocoder.yudao.framework.security.core.LoginUser;
import cn.iocoder.yudao.framework.security.core.authentication.SpringSecurityUser;
import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeSendReqDTO; 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.sms.dto.code.SmsCodeUseReqDTO;
import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO; import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
@ -24,8 +23,6 @@ public interface AuthConvert {
LoginUser convert(AdminUserDO bean); LoginUser convert(AdminUserDO bean);
SpringSecurityUser convert2(AdminUserDO user);
default AuthPermissionInfoRespVO convert(AdminUserDO user, List<RoleDO> roleList, List<MenuDO> menuList) { default AuthPermissionInfoRespVO convert(AdminUserDO user, List<RoleDO> roleList, List<MenuDO> menuList) {
return AuthPermissionInfoRespVO.builder() return AuthPermissionInfoRespVO.builder()
.user(AuthPermissionInfoRespVO.UserVO.builder().id(user.getId()).nickname(user.getNickname()).avatar(user.getAvatar()).build()) .user(AuthPermissionInfoRespVO.UserVO.builder().id(user.getId()).nickname(user.getNickname()).avatar(user.getAvatar()).build())

View File

@ -12,7 +12,7 @@ import javax.validation.Valid;
* *
* @author 芋道源码 * @author 芋道源码
*/ */
public interface AdminAuthService extends SecurityAuthFrameworkService { public interface AdminAuthService {
/** /**
* 账号登录 * 账号登录

View File

@ -21,8 +21,6 @@ import cn.iocoder.yudao.module.system.service.social.SocialUserService;
import cn.iocoder.yudao.module.system.service.user.AdminUserService; import cn.iocoder.yudao.module.system.service.user.AdminUserService;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import javax.annotation.Resource; import javax.annotation.Resource;
@ -226,11 +224,6 @@ public class AdminAuthServiceImpl implements AdminAuthService {
createLogoutLog(loginUser.getId()); createLogoutLog(loginUser.getId());
} }
@Override
public UserTypeEnum getUserType() {
return UserTypeEnum.ADMIN;
}
private void createLogoutLog(Long userId) { private void createLogoutLog(Long userId) {
LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO(); LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO();
reqDTO.setLogType(LoginLogTypeEnum.LOGOUT_SELF.getType()); reqDTO.setLogType(LoginLogTypeEnum.LOGOUT_SELF.getType());
@ -244,11 +237,6 @@ public class AdminAuthServiceImpl implements AdminAuthService {
loginLogService.createLoginLog(reqDTO); loginLogService.createLoginLog(reqDTO);
} }
@Override
public LoginUser verifyTokenAndRefresh(String token) {
return userSessionService.getLoginUser(token);
}
private LoginUser buildLoginUser(AdminUserDO user) { private LoginUser buildLoginUser(AdminUserDO user) {
return AuthConvert.INSTANCE.convert(user).setUserType(getUserType().getValue()); return AuthConvert.INSTANCE.convert(user).setUserType(getUserType().getValue());
} }
@ -261,8 +249,8 @@ public class AdminAuthServiceImpl implements AdminAuthService {
return user != null ? user.getUsername() : null; return user != null ? user.getUsername() : null;
} }
@Override private UserTypeEnum getUserType() {
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { return UserTypeEnum.ADMIN;
return null;
} }
} }