mirror of
https://gitee.com/huangge1199_admin/vue-pro.git
synced 2024-11-22 15:21:53 +08:00
多模块重构 3:security 实现多用户的认证支持
This commit is contained in:
parent
928b7dbe23
commit
e9efff7076
@ -2,6 +2,10 @@
|
||||
"local": {
|
||||
"baseUrl": "http://127.0.0.1:48080/api",
|
||||
"userServerUrl": "http://127.0.0.1:28080/api",
|
||||
"token": "test1"
|
||||
"token": "test1",
|
||||
|
||||
"userApi": "http://127.0.0.1:48080/app-api",
|
||||
"userToken": "test1",
|
||||
"userTenentId": "1"
|
||||
}
|
||||
}
|
||||
|
1
pom.xml
1
pom.xml
@ -14,7 +14,6 @@
|
||||
<module>yudao-user-server</module>
|
||||
<module>yudao-core-service</module>
|
||||
<module>yudao-module-member</module>
|
||||
<module>yudao-server</module>
|
||||
</modules>
|
||||
|
||||
<name>${artifactId}</name>
|
||||
|
@ -13,7 +13,11 @@
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>yudao-admin-server</name>
|
||||
<description>管理后台 Server,提供其 API 接口</description>
|
||||
<description>
|
||||
后端 Server 的主项目,通过引入需要 yudao-module-xxx 的依赖,
|
||||
从而实现提供 RESTful API 给 yudao-ui-admin、yudao-ui-user 等前端项目。
|
||||
本质上来说,它就是个空壳(容器)!
|
||||
</description>
|
||||
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
|
||||
|
||||
<dependencies>
|
||||
|
@ -33,7 +33,7 @@ public class SecurityConfiguration {
|
||||
registry.antMatchers(buildAdminApi("/system/sms/callback/**")).anonymous();
|
||||
|
||||
// 设置 App API 无需认证
|
||||
registry.antMatchers(buildAppApi("/**"));
|
||||
registry.antMatchers(buildAppApi("/**")).permitAll();
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -26,6 +26,7 @@ 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 lombok.extern.slf4j.Slf4j;
|
||||
import me.zhyd.oauth.model.AuthUser;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
@ -154,7 +155,8 @@ public class SysAuthServiceImpl implements SysAuthService {
|
||||
try {
|
||||
// 调用 Spring Security 的 AuthenticationManager#authenticate(...) 方法,使用账号密码进行认证
|
||||
// 在其内部,会调用到 loadUserByUsername 方法,获取 User 信息
|
||||
authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
|
||||
authentication = authenticationManager.authenticate(new MultiUsernamePasswordAuthenticationToken(
|
||||
username, password, getUserType()));
|
||||
// org.activiti.engine.impl.identity.Authentication.setAuthenticatedUserId(username);
|
||||
} catch (BadCredentialsException badCredentialsException) {
|
||||
this.createLoginLog(username, logTypeEnum, SysLoginResultEnum.BAD_CREDENTIALS);
|
||||
|
@ -1,5 +1,7 @@
|
||||
package cn.iocoder.yudao.framework.common.enums;
|
||||
|
||||
import cn.hutool.core.lang.Matcher;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@ -22,4 +24,8 @@ public enum UserTypeEnum {
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
public static UserTypeEnum valueOf(Integer value) {
|
||||
return ArrayUtil.firstMatch(userType -> userType.getValue().equals(value), UserTypeEnum.values());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -149,6 +149,7 @@ public class OperateLogAspect {
|
||||
|
||||
private static void fillUserFields(OperateLogCreateReqDTO operateLogDTO) {
|
||||
operateLogDTO.setUserId(WebFrameworkUtils.getLoginUserId());
|
||||
operateLogDTO.setUserType(WebFrameworkUtils.getLoginUserType());
|
||||
}
|
||||
|
||||
private static void fillModuleFields(OperateLogCreateReqDTO operateLogDTO,
|
||||
|
@ -21,6 +21,9 @@ public class OperateLogCreateReqDTO {
|
||||
@ApiModelProperty(value = "用户编号", required = true, example = "1024")
|
||||
@NotNull(message = "用户编号不能为空")
|
||||
private Long userId;
|
||||
@ApiModelProperty(value = "用户类型", required = true, example = "1")
|
||||
@NotNull(message = "用户类型不能为空")
|
||||
private Integer userType;
|
||||
|
||||
@ApiModelProperty(value = "操作模块", required = true, example = "订单")
|
||||
@NotEmpty(message = "操作模块不能为空")
|
||||
|
@ -1,14 +1,13 @@
|
||||
package cn.iocoder.yudao.framework.security.config;
|
||||
|
||||
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.filter.JWTAuthenticationTokenFilter;
|
||||
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.LogoutSuccessHandlerImpl;
|
||||
import cn.iocoder.yudao.framework.security.core.service.SecurityAuthFrameworkService;
|
||||
import cn.iocoder.yudao.framework.security.core.service.SecurityAuthService;
|
||||
import cn.iocoder.yudao.framework.security.core.service.SecurityAuthServiceImpl;
|
||||
import cn.iocoder.yudao.framework.web.config.WebProperties;
|
||||
import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
|
||||
import org.springframework.beans.factory.config.MethodInvokingFactoryBean;
|
||||
@ -68,8 +67,8 @@ public class YudaoSecurityAutoConfiguration {
|
||||
* 退出处理类 Bean
|
||||
*/
|
||||
@Bean
|
||||
public LogoutSuccessHandler logoutSuccessHandler(SecurityAuthService securityAuthService) {
|
||||
return new LogoutSuccessHandlerImpl(securityProperties, securityAuthService);
|
||||
public LogoutSuccessHandler logoutSuccessHandler(MultiUserDetailsAuthenticationProvider authenticationProvider) {
|
||||
return new LogoutSuccessHandlerImpl(securityProperties, authenticationProvider);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -87,18 +86,19 @@ public class YudaoSecurityAutoConfiguration {
|
||||
* Token 认证过滤器 Bean
|
||||
*/
|
||||
@Bean
|
||||
public JWTAuthenticationTokenFilter authenticationTokenFilter(SecurityAuthService securityAuthService,
|
||||
public JWTAuthenticationTokenFilter authenticationTokenFilter(MultiUserDetailsAuthenticationProvider authenticationProvider,
|
||||
GlobalExceptionHandler globalExceptionHandler) {
|
||||
return new JWTAuthenticationTokenFilter(securityProperties, securityAuthService, globalExceptionHandler);
|
||||
return new JWTAuthenticationTokenFilter(securityProperties, authenticationProvider, globalExceptionHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全认证的 Service Bean
|
||||
* 身份验证的 Provider Bean,通过它实现账号 + 密码的认证
|
||||
*/
|
||||
@Bean
|
||||
public SecurityAuthService securityAuthService(List<SecurityAuthFrameworkService> securityFrameworkServices,
|
||||
WebProperties webProperties) {
|
||||
return new SecurityAuthServiceImpl(securityFrameworkServices, webProperties);
|
||||
public MultiUserDetailsAuthenticationProvider authenticationProvider(
|
||||
List<SecurityAuthFrameworkService> securityFrameworkServices,
|
||||
WebProperties webProperties, PasswordEncoder passwordEncoder) {
|
||||
return new MultiUserDetailsAuthenticationProvider(securityFrameworkServices, webProperties, passwordEncoder);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,5 +1,6 @@
|
||||
package cn.iocoder.yudao.framework.security.config;
|
||||
|
||||
import cn.iocoder.yudao.framework.security.core.authentication.MultiUserDetailsAuthenticationProvider;
|
||||
import cn.iocoder.yudao.framework.security.core.filter.JWTAuthenticationTokenFilter;
|
||||
import cn.iocoder.yudao.framework.security.core.service.SecurityAuthFrameworkService;
|
||||
import cn.iocoder.yudao.framework.web.config.WebProperties;
|
||||
@ -35,16 +36,8 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap
|
||||
@Resource
|
||||
private WebProperties webProperties;
|
||||
|
||||
/**
|
||||
* 自定义用户【认证】逻辑
|
||||
*/
|
||||
@Resource
|
||||
private SecurityAuthFrameworkService userDetailsService;
|
||||
/**
|
||||
* Spring Security 加密器
|
||||
*/
|
||||
@Resource
|
||||
private PasswordEncoder passwordEncoder;
|
||||
private MultiUserDetailsAuthenticationProvider authenticationProvider;
|
||||
/**
|
||||
* 认证失败处理类 Bean
|
||||
*/
|
||||
@ -91,8 +84,7 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap
|
||||
*/
|
||||
@Override
|
||||
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
|
||||
auth
|
||||
.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
|
||||
auth.authenticationProvider(authenticationProvider);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -0,0 +1,149 @@
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 模拟指定用户编号的 LoginUser
|
||||
*
|
||||
* @param request 请求
|
||||
* @param userId 用户编号
|
||||
* @return 登录用户
|
||||
*/
|
||||
public LoginUser mockLogin(HttpServletRequest request, Long userId) {
|
||||
return selectService(request).mockLogin(userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 基于 token 退出登录
|
||||
*
|
||||
* @param request 请求
|
||||
* @param token token
|
||||
*/
|
||||
public void logout(HttpServletRequest request, String token) {
|
||||
selectService(request).logout(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()));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
@ -5,10 +5,10 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
|
||||
import cn.iocoder.yudao.framework.security.config.SecurityProperties;
|
||||
import cn.iocoder.yudao.framework.security.core.LoginUser;
|
||||
import cn.iocoder.yudao.framework.security.core.service.SecurityAuthService;
|
||||
import cn.iocoder.yudao.framework.security.core.authentication.MultiUserDetailsAuthenticationProvider;
|
||||
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
|
||||
import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
@ -23,12 +23,12 @@ import java.io.IOException;
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@RequiredArgsConstructor
|
||||
public class JWTAuthenticationTokenFilter extends OncePerRequestFilter {
|
||||
|
||||
private final SecurityProperties securityProperties;
|
||||
|
||||
private final SecurityAuthService authService;
|
||||
private final MultiUserDetailsAuthenticationProvider authenticationProvider;
|
||||
|
||||
private final GlobalExceptionHandler globalExceptionHandler;
|
||||
|
||||
@ -40,7 +40,7 @@ public class JWTAuthenticationTokenFilter extends OncePerRequestFilter {
|
||||
if (StrUtil.isNotEmpty(token)) {
|
||||
try {
|
||||
// 验证 token 有效性
|
||||
LoginUser loginUser = authService.verifyTokenAndRefresh(request, token);
|
||||
LoginUser loginUser = authenticationProvider.verifyTokenAndRefresh(request, token);
|
||||
// 模拟 Login 功能,方便日常开发调试
|
||||
if (loginUser == null) {
|
||||
loginUser = this.mockLoginUser(request, token);
|
||||
@ -78,7 +78,7 @@ public class JWTAuthenticationTokenFilter extends OncePerRequestFilter {
|
||||
return null;
|
||||
}
|
||||
Long userId = Long.valueOf(token.substring(securityProperties.getMockSecret().length()));
|
||||
return authService.mockLogin(request, userId);
|
||||
return authenticationProvider.mockLogin(request, userId);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
|
||||
import cn.iocoder.yudao.framework.security.config.SecurityProperties;
|
||||
import cn.iocoder.yudao.framework.security.core.service.SecurityAuthService;
|
||||
import cn.iocoder.yudao.framework.security.core.authentication.MultiUserDetailsAuthenticationProvider;
|
||||
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springframework.security.core.Authentication;
|
||||
@ -24,14 +24,14 @@ public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler {
|
||||
|
||||
private final SecurityProperties securityProperties;
|
||||
|
||||
private final SecurityAuthService authService;
|
||||
private final MultiUserDetailsAuthenticationProvider authenticationProvider;
|
||||
|
||||
@Override
|
||||
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
|
||||
// 执行退出
|
||||
String token = SecurityFrameworkUtils.obtainAuthorization(request, securityProperties.getTokenHeader());
|
||||
if (StrUtil.isNotBlank(token)) {
|
||||
authService.logout(request, token);
|
||||
authenticationProvider.logout(request, token);
|
||||
}
|
||||
// 返回成功
|
||||
ServletUtils.writeJSON(response, CommonResult.success(null));
|
||||
|
@ -1,43 +0,0 @@
|
||||
package cn.iocoder.yudao.framework.security.core.service;
|
||||
|
||||
import cn.iocoder.yudao.framework.security.core.LoginUser;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
* 安全认证的 Service 接口,对 security 组件提供统一的 Auth 相关的方法。
|
||||
* 主要是会基于 {@link HttpServletRequest} 参数,匹配对应的 {@link SecurityAuthFrameworkService} 实现,然后调用其方法。
|
||||
* 因此,在方法的定义上,和 {@link SecurityAuthFrameworkService} 差不多。
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface SecurityAuthService {
|
||||
|
||||
/**
|
||||
* 校验 token 的有效性,并获取用户信息
|
||||
* 通过后,刷新 token 的过期时间
|
||||
*
|
||||
* @param request 请求
|
||||
* @param token token
|
||||
* @return 用户信息
|
||||
*/
|
||||
LoginUser verifyTokenAndRefresh(HttpServletRequest request, String token);
|
||||
|
||||
/**
|
||||
* 模拟指定用户编号的 LoginUser
|
||||
*
|
||||
* @param request 请求
|
||||
* @param userId 用户编号
|
||||
* @return 登录用户
|
||||
*/
|
||||
LoginUser mockLogin(HttpServletRequest request, Long userId);
|
||||
|
||||
/**
|
||||
* 基于 token 退出登录
|
||||
*
|
||||
* @param request 请求
|
||||
* @param token token
|
||||
*/
|
||||
void logout(HttpServletRequest request, String token);
|
||||
|
||||
}
|
@ -1,64 +0,0 @@
|
||||
package cn.iocoder.yudao.framework.security.core.service;
|
||||
|
||||
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.web.config.WebProperties;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 安全认证的 Service 实现类,基于请求地址,计算到对应的 {@link UserTypeEnum} 枚举,从而拿到对应的 {@link SecurityAuthFrameworkService} 实现
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class SecurityAuthServiceImpl implements SecurityAuthService {
|
||||
|
||||
private final Map<UserTypeEnum, SecurityAuthFrameworkService> services = new HashMap<>();
|
||||
private final WebProperties properties;
|
||||
|
||||
public SecurityAuthServiceImpl(List<SecurityAuthFrameworkService> serviceList, WebProperties properties) {
|
||||
serviceList.forEach(service -> services.put(service.getUserType(), service));
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoginUser verifyTokenAndRefresh(HttpServletRequest request, String token) {
|
||||
return selectService(request).verifyTokenAndRefresh(token);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoginUser mockLogin(HttpServletRequest request, Long userId) {
|
||||
return selectService(request).mockLogin(userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logout(HttpServletRequest request, String token) {
|
||||
selectService(request).logout(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()));
|
||||
}
|
||||
|
||||
}
|
@ -55,6 +55,11 @@ public class WebFrameworkUtils {
|
||||
return (Integer) request.getAttribute(REQUEST_ATTRIBUTE_LOGIN_USER_TYPE);
|
||||
}
|
||||
|
||||
public static Integer getLoginUserType() {
|
||||
HttpServletRequest request = getRequest();
|
||||
return getLoginUserType(request);
|
||||
}
|
||||
|
||||
public static Long getLoginUserId() {
|
||||
HttpServletRequest request = getRequest();
|
||||
return getLoginUserId(request);
|
||||
|
@ -30,6 +30,10 @@
|
||||
<artifactId>yudao-core-service</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-biz-operatelog</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-biz-sms</artifactId>
|
||||
|
@ -1,6 +1,7 @@
|
||||
### 请求 /login 接口 => 成功
|
||||
POST {{userServerUrl}}/login
|
||||
POST {{userApi}}/login
|
||||
Content-Type: application/json
|
||||
tenant-id: {{userTenentId}}
|
||||
|
||||
{
|
||||
"mobile": "15601691300",
|
||||
|
@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.member.controller.app.auth;
|
||||
import cn.iocoder.yudao.coreservice.modules.system.service.social.SysSocialCoreService;
|
||||
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
|
||||
import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
|
||||
import cn.iocoder.yudao.module.member.controller.app.auth.vo.*;
|
||||
import cn.iocoder.yudao.module.member.service.auth.AuthService;
|
||||
@ -40,6 +41,7 @@ public class AppAuthController {
|
||||
|
||||
@PostMapping("/login")
|
||||
@ApiOperation("使用手机 + 密码登录")
|
||||
@OperateLog(enable = false) // 避免 Post 请求被记录操作日志
|
||||
public CommonResult<AppAuthLoginRespVO> login(@RequestBody @Valid AppAuthLoginReqVO reqVO) {
|
||||
String token = authService.login(reqVO, getClientIP(), getUserAgent());
|
||||
// 返回结果
|
||||
@ -48,6 +50,7 @@ public class AppAuthController {
|
||||
|
||||
@PostMapping("/sms-login")
|
||||
@ApiOperation("使用手机 + 验证码登录")
|
||||
@OperateLog(enable = false) // 避免 Post 请求被记录操作日志
|
||||
public CommonResult<AppAuthLoginRespVO> smsLogin(@RequestBody @Valid AppAuthSmsLoginReqVO reqVO) {
|
||||
String token = authService.smsLogin(reqVO, getClientIP(), getUserAgent());
|
||||
// 返回结果
|
||||
@ -56,12 +59,13 @@ public class AppAuthController {
|
||||
|
||||
@PostMapping("/send-sms-code")
|
||||
@ApiOperation(value = "发送手机验证码")
|
||||
@OperateLog(enable = false) // 避免 Post 请求被记录操作日志
|
||||
public CommonResult<Boolean> sendSmsCode(@RequestBody @Valid AppAuthSendSmsReqVO reqVO) {
|
||||
smsCodeService.sendSmsCode(reqVO.getMobile(), reqVO.getScene(), getClientIP());
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/send-sms-code-login")
|
||||
@GetMapping("/send-sms-code-login") // TODO 芋艿:post 比较合理
|
||||
@ApiOperation(value = "向已登录用户发送验证码",notes = "修改手机时验证原手机号使用")
|
||||
public CommonResult<Boolean> sendSmsCodeLogin() {
|
||||
smsCodeService.sendSmsCodeLogin(getLoginUserId());
|
||||
@ -71,6 +75,7 @@ public class AppAuthController {
|
||||
@PostMapping("/reset-password")
|
||||
@ApiOperation(value = "重置密码", notes = "用户忘记密码时使用")
|
||||
@PreAuthenticated
|
||||
@OperateLog(enable = false) // 避免 Post 请求被记录操作日志
|
||||
public CommonResult<Boolean> resetPassword(@RequestBody @Valid AppAuthResetPasswordReqVO reqVO) {
|
||||
authService.resetPassword(reqVO);
|
||||
return success(true);
|
||||
@ -106,6 +111,7 @@ public class AppAuthController {
|
||||
|
||||
@PostMapping("/social-login2")
|
||||
@ApiOperation("社交登录,使用 手机号 + 手机验证码")
|
||||
@OperateLog(enable = false) // 避免 Post 请求被记录操作日志
|
||||
public CommonResult<AppAuthLoginRespVO> socialLogin2(@RequestBody @Valid AppAuthSocialLogin2ReqVO reqVO) {
|
||||
String token = authService.socialLogin2(reqVO, getClientIP(), getUserAgent());
|
||||
return success(AppAuthLoginRespVO.builder().token(token).build());
|
||||
@ -113,6 +119,7 @@ public class AppAuthController {
|
||||
|
||||
@PostMapping("/social-bind")
|
||||
@ApiOperation("社交绑定,使用 code 授权码")
|
||||
@PreAuthenticated
|
||||
public CommonResult<Boolean> socialBind(@RequestBody @Valid AppAuthSocialBindReqVO reqVO) {
|
||||
authService.socialBind(getLoginUserId(), reqVO);
|
||||
return CommonResult.success(true);
|
||||
@ -120,6 +127,7 @@ public class AppAuthController {
|
||||
|
||||
@DeleteMapping("/social-unbind")
|
||||
@ApiOperation("取消社交绑定")
|
||||
@PreAuthenticated
|
||||
public CommonResult<Boolean> socialUnbind(@RequestBody AppAuthSocialUnbindReqVO reqVO) {
|
||||
socialService.unbindSocialUser(getLoginUserId(), reqVO.getType(), reqVO.getUnionId(), UserTypeEnum.MEMBER);
|
||||
return CommonResult.success(true);
|
||||
|
@ -14,6 +14,7 @@ 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.UserDO;
|
||||
@ -176,7 +177,8 @@ public class AuthServiceImpl implements AuthService {
|
||||
try {
|
||||
// 调用 Spring Security 的 AuthenticationManager#authenticate(...) 方法,使用账号密码进行认证
|
||||
// 在其内部,会调用到 loadUserByUsername 方法,获取 User 信息
|
||||
authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
|
||||
authentication = authenticationManager.authenticate(new MultiUsernamePasswordAuthenticationToken(
|
||||
username, password, getUserType()));
|
||||
} catch (BadCredentialsException badCredentialsException) {
|
||||
this.createLoginLog(username, logTypeEnum, SysLoginResultEnum.BAD_CREDENTIALS);
|
||||
throw exception(AUTH_LOGIN_BAD_CREDENTIALS);
|
||||
|
@ -1,22 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>yudao</artifactId>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>yudao-server</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>${artifactId}</name>
|
||||
<description>
|
||||
后端 Server 的主项目,通过引入需要 yudao-module-xxx 的依赖,
|
||||
从而实现提供 RESTful API 给 yudao-ui-admin、yudao-ui-user 等前端项目。
|
||||
本质上来说,它就是个空壳(容器)!
|
||||
</description>
|
||||
|
||||
</project>
|
35
更新日志.md
35
更新日志.md
@ -4,29 +4,30 @@
|
||||
* 钉钉、飞书等通知
|
||||
* Vue3 支持
|
||||
|
||||
## [v1.4.0] 计划
|
||||
|
||||
* 工作流
|
||||
* 修改表单为外置表单
|
||||
* 修改请假流程
|
||||
* 暂时以用户的岗位作为activiti 的用户组
|
||||
* 请假需要请假人部门下具有项目经理岗位, 部门经理, 和人事 岗位的用户
|
||||
* 新增 芋道源码部门下 用户 normal(岗位 普通用户) projectmgr(岗位 项目经理) depmgr(岗位 部门经理) hradmin (岗位 人事)
|
||||
* 请假流程如下
|
||||
1. 请假人 normal (密码 123456) 登录在我的请假表单,点击新增,填写请假表单
|
||||
2. 如果请假天数<=3, 项目经理 进行审批. 项目经理 projectmgr(密码:123456) 登录 待办请假,中进行审批,可以查看历史跟踪,和流程图
|
||||
3. 如果请假天数>3 需部门经理 进行审批,部门经理depmgr(密码:123456) 登录 待办请假,中进行审批,可以查看历史跟踪,和流程图
|
||||
4. 人事登陆(用户名:hradmin 密码:123456) 登录 待办请假, 中进行审批,可以查看历史跟踪,和流程图
|
||||
5. 流程结束
|
||||
* 我的请假中,可以查询本人的请假申请, 和进度
|
||||
|
||||
### 📝 TODO
|
||||
|
||||
* 支付
|
||||
* 用户前台的社交登陆
|
||||
* 用户前台的修改手机、修改密码、忘记密码
|
||||
|
||||
## [v1.3.0] 进行中
|
||||
## [v1.4.0] 计划,预计 2022.02.28 发布
|
||||
|
||||
### ⚠️ Warning
|
||||
|
||||
### 📈 Statistic
|
||||
|
||||
### ⭐ New Features
|
||||
|
||||
*【优化】操作日志新增用户类型,实现 APP 端的 API 的操作日志的记录
|
||||
|
||||
### 🐞 Bug Fixes
|
||||
|
||||
*【修复】用户无权限访问 指定 API 时,未返回 FORBIDDEN 结果码
|
||||
|
||||
### 🔨 Dependency Upgrades
|
||||
|
||||
|
||||
## [v1.3.0] 2022.01.24
|
||||
|
||||
### ⚠️ Warning
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user