Commit e9efff7076f66305b5bc80ee416a32ee7487b6dc

Authored by YunaiV
1 parent 928b7dbe

多模块重构 3:security 实现多用户的认证支持

Showing 23 changed files with 279 additions and 184 deletions
http-client.env.json
... ... @@ -2,6 +2,10 @@
2 2 "local": {
3 3 "baseUrl": "http://127.0.0.1:48080/api",
4 4 "userServerUrl": "http://127.0.0.1:28080/api",
5   - "token": "test1"
  5 + "token": "test1",
  6 +
  7 + "userApi": "http://127.0.0.1:48080/app-api",
  8 + "userToken": "test1",
  9 + "userTenentId": "1"
6 10 }
7 11 }
... ...
... ... @@ -14,7 +14,6 @@
14 14 <module>yudao-user-server</module>
15 15 <module>yudao-core-service</module>
16 16 <module>yudao-module-member</module>
17   - <module>yudao-server</module>
18 17 </modules>
19 18  
20 19 <name>${artifactId}</name>
... ...
yudao-admin-server/pom.xml
... ... @@ -13,7 +13,11 @@
13 13 <packaging>jar</packaging>
14 14  
15 15 <name>yudao-admin-server</name>
16   - <description>管理后台 Server,提供其 API 接口</description>
  16 + <description>
  17 + 后端 Server 的主项目,通过引入需要 yudao-module-xxx 的依赖,
  18 + 从而实现提供 RESTful API 给 yudao-ui-admin、yudao-ui-user 等前端项目。
  19 + 本质上来说,它就是个空壳(容器)!
  20 + </description>
17 21 <url>https://github.com/YunaiV/ruoyi-vue-pro</url>
18 22  
19 23 <dependencies>
... ...
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/framework/security/SecurityConfiguration.java
... ... @@ -33,7 +33,7 @@ public class SecurityConfiguration {
33 33 registry.antMatchers(buildAdminApi("/system/sms/callback/**")).anonymous();
34 34  
35 35 // 设置 App API 无需认证
36   - registry.antMatchers(buildAppApi("/**"));
  36 + registry.antMatchers(buildAppApi("/**")).permitAll();
37 37 };
38 38 }
39 39  
... ...
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/service/auth/impl/SysAuthServiceImpl.java
... ... @@ -26,6 +26,7 @@ import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
26 26 import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
27 27 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
28 28 import cn.iocoder.yudao.framework.security.core.LoginUser;
  29 +import cn.iocoder.yudao.framework.security.core.authentication.MultiUsernamePasswordAuthenticationToken;
29 30 import lombok.extern.slf4j.Slf4j;
30 31 import me.zhyd.oauth.model.AuthUser;
31 32 import org.springframework.context.annotation.Lazy;
... ... @@ -154,7 +155,8 @@ public class SysAuthServiceImpl implements SysAuthService {
154 155 try {
155 156 // 调用 Spring Security 的 AuthenticationManager#authenticate(...) 方法,使用账号密码进行认证
156 157 // 在其内部,会调用到 loadUserByUsername 方法,获取 User 信息
157   - authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
  158 + authentication = authenticationManager.authenticate(new MultiUsernamePasswordAuthenticationToken(
  159 + username, password, getUserType()));
158 160 // org.activiti.engine.impl.identity.Authentication.setAuthenticatedUserId(username);
159 161 } catch (BadCredentialsException badCredentialsException) {
160 162 this.createLoginLog(username, logTypeEnum, SysLoginResultEnum.BAD_CREDENTIALS);
... ...
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/enums/UserTypeEnum.java
1 1 package cn.iocoder.yudao.framework.common.enums;
2 2  
  3 +import cn.hutool.core.lang.Matcher;
  4 +import cn.hutool.core.util.ArrayUtil;
3 5 import lombok.AllArgsConstructor;
4 6 import lombok.Getter;
5 7  
... ... @@ -22,4 +24,8 @@ public enum UserTypeEnum {
22 24 */
23 25 private final String name;
24 26  
  27 + public static UserTypeEnum valueOf(Integer value) {
  28 + return ArrayUtil.firstMatch(userType -> userType.getValue().equals(value), UserTypeEnum.values());
  29 + }
  30 +
25 31 }
... ...
yudao-framework/yudao-spring-boot-starter-biz-operatelog/src/main/java/cn/iocoder/yudao/framework/operatelog/core/aop/OperateLogAspect.java
... ... @@ -149,6 +149,7 @@ public class OperateLogAspect {
149 149  
150 150 private static void fillUserFields(OperateLogCreateReqDTO operateLogDTO) {
151 151 operateLogDTO.setUserId(WebFrameworkUtils.getLoginUserId());
  152 + operateLogDTO.setUserType(WebFrameworkUtils.getLoginUserType());
152 153 }
153 154  
154 155 private static void fillModuleFields(OperateLogCreateReqDTO operateLogDTO,
... ...
yudao-framework/yudao-spring-boot-starter-biz-operatelog/src/main/java/cn/iocoder/yudao/framework/operatelog/core/dto/OperateLogCreateReqDTO.java
... ... @@ -21,6 +21,9 @@ public class OperateLogCreateReqDTO {
21 21 @ApiModelProperty(value = "用户编号", required = true, example = "1024")
22 22 @NotNull(message = "用户编号不能为空")
23 23 private Long userId;
  24 + @ApiModelProperty(value = "用户类型", required = true, example = "1")
  25 + @NotNull(message = "用户类型不能为空")
  26 + private Integer userType;
24 27  
25 28 @ApiModelProperty(value = "操作模块", required = true, example = "订单")
26 29 @NotEmpty(message = "操作模块不能为空")
... ...
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoSecurityAutoConfiguration.java
1 1 package cn.iocoder.yudao.framework.security.config;
2 2  
3 3 import cn.iocoder.yudao.framework.security.core.aop.PreAuthenticatedAspect;
  4 +import cn.iocoder.yudao.framework.security.core.authentication.MultiUserDetailsAuthenticationProvider;
4 5 import cn.iocoder.yudao.framework.security.core.context.TransmittableThreadLocalSecurityContextHolderStrategy;
5 6 import cn.iocoder.yudao.framework.security.core.filter.JWTAuthenticationTokenFilter;
6 7 import cn.iocoder.yudao.framework.security.core.handler.AccessDeniedHandlerImpl;
7 8 import cn.iocoder.yudao.framework.security.core.handler.AuthenticationEntryPointImpl;
8 9 import cn.iocoder.yudao.framework.security.core.handler.LogoutSuccessHandlerImpl;
9 10 import cn.iocoder.yudao.framework.security.core.service.SecurityAuthFrameworkService;
10   -import cn.iocoder.yudao.framework.security.core.service.SecurityAuthService;
11   -import cn.iocoder.yudao.framework.security.core.service.SecurityAuthServiceImpl;
12 11 import cn.iocoder.yudao.framework.web.config.WebProperties;
13 12 import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
14 13 import org.springframework.beans.factory.config.MethodInvokingFactoryBean;
... ... @@ -68,8 +67,8 @@ public class YudaoSecurityAutoConfiguration {
68 67 * 退出处理类 Bean
69 68 */
70 69 @Bean
71   - public LogoutSuccessHandler logoutSuccessHandler(SecurityAuthService securityAuthService) {
72   - return new LogoutSuccessHandlerImpl(securityProperties, securityAuthService);
  70 + public LogoutSuccessHandler logoutSuccessHandler(MultiUserDetailsAuthenticationProvider authenticationProvider) {
  71 + return new LogoutSuccessHandlerImpl(securityProperties, authenticationProvider);
73 72 }
74 73  
75 74 /**
... ... @@ -87,18 +86,19 @@ public class YudaoSecurityAutoConfiguration {
87 86 * Token 认证过滤器 Bean
88 87 */
89 88 @Bean
90   - public JWTAuthenticationTokenFilter authenticationTokenFilter(SecurityAuthService securityAuthService,
  89 + public JWTAuthenticationTokenFilter authenticationTokenFilter(MultiUserDetailsAuthenticationProvider authenticationProvider,
91 90 GlobalExceptionHandler globalExceptionHandler) {
92   - return new JWTAuthenticationTokenFilter(securityProperties, securityAuthService, globalExceptionHandler);
  91 + return new JWTAuthenticationTokenFilter(securityProperties, authenticationProvider, globalExceptionHandler);
93 92 }
94 93  
95 94 /**
96   - * 安全认证的 Service Bean
  95 + * 身份验证的 Provider Bean,通过它实现账号 + 密码的认证
97 96 */
98 97 @Bean
99   - public SecurityAuthService securityAuthService(List<SecurityAuthFrameworkService> securityFrameworkServices,
100   - WebProperties webProperties) {
101   - return new SecurityAuthServiceImpl(securityFrameworkServices, webProperties);
  98 + public MultiUserDetailsAuthenticationProvider authenticationProvider(
  99 + List<SecurityAuthFrameworkService> securityFrameworkServices,
  100 + WebProperties webProperties, PasswordEncoder passwordEncoder) {
  101 + return new MultiUserDetailsAuthenticationProvider(securityFrameworkServices, webProperties, passwordEncoder);
102 102 }
103 103  
104 104 /**
... ...
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoWebSecurityConfigurerAdapter.java
1 1 package cn.iocoder.yudao.framework.security.config;
2 2  
  3 +import cn.iocoder.yudao.framework.security.core.authentication.MultiUserDetailsAuthenticationProvider;
3 4 import cn.iocoder.yudao.framework.security.core.filter.JWTAuthenticationTokenFilter;
4 5 import cn.iocoder.yudao.framework.security.core.service.SecurityAuthFrameworkService;
5 6 import cn.iocoder.yudao.framework.web.config.WebProperties;
... ... @@ -35,16 +36,8 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap
35 36 @Resource
36 37 private WebProperties webProperties;
37 38  
38   - /**
39   - * 自定义用户【认证】逻辑
40   - */
41   - @Resource
42   - private SecurityAuthFrameworkService userDetailsService;
43   - /**
44   - * Spring Security 加密器
45   - */
46 39 @Resource
47   - private PasswordEncoder passwordEncoder;
  40 + private MultiUserDetailsAuthenticationProvider authenticationProvider;
48 41 /**
49 42 * 认证失败处理类 Bean
50 43 */
... ... @@ -91,8 +84,7 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap
91 84 */
92 85 @Override
93 86 protected void configure(AuthenticationManagerBuilder auth) throws Exception {
94   - auth
95   - .userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
  87 + auth.authenticationProvider(authenticationProvider);
96 88 }
97 89  
98 90 /**
... ...
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/authentication/MultiUserDetailsAuthenticationProvider.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.security.core.authentication;
  2 +
  3 +import cn.hutool.core.lang.Assert;
  4 +import cn.hutool.core.util.StrUtil;
  5 +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
  6 +import cn.iocoder.yudao.framework.security.core.LoginUser;
  7 +import cn.iocoder.yudao.framework.security.core.service.SecurityAuthFrameworkService;
  8 +import cn.iocoder.yudao.framework.web.config.WebProperties;
  9 +import org.springframework.security.authentication.AuthenticationProvider;
  10 +import org.springframework.security.authentication.BadCredentialsException;
  11 +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
  12 +import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider;
  13 +import org.springframework.security.core.AuthenticationException;
  14 +import org.springframework.security.core.userdetails.UserDetails;
  15 +import org.springframework.security.crypto.password.PasswordEncoder;
  16 +
  17 +import javax.servlet.http.HttpServletRequest;
  18 +import java.util.HashMap;
  19 +import java.util.List;
  20 +import java.util.Map;
  21 +
  22 +/**
  23 + * 支持多用户类型的 AuthenticationProvider 实现类
  24 + *
  25 + * 为什么不用 {@link org.springframework.security.authentication.ProviderManager} 呢?
  26 + * 原因是,需要每个用户类型实现对应的 {@link AuthenticationProvider} + authentication,略显麻烦。实际,也是可以实现的。
  27 + *
  28 + * 另外,额外支持 verifyTokenAndRefresh 校验令牌、logout 登出、mockLogin 模拟登陆等操作。
  29 + * 实际上,它就是 {@link SecurityAuthFrameworkService} 定义的三个接口。
  30 + * 因为需要支持多种类型,所以需要根据请求的 URL,判断出对应的用户类型,从而使用对应的 SecurityAuthFrameworkService 是吸纳
  31 + *
  32 + * @see cn.iocoder.yudao.framework.common.enums.UserTypeEnum
  33 + * @author 芋道源码
  34 + */
  35 +public class MultiUserDetailsAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
  36 +
  37 + private final Map<UserTypeEnum, SecurityAuthFrameworkService> services = new HashMap<>();
  38 +
  39 + private final WebProperties properties;
  40 +
  41 + private final PasswordEncoder passwordEncoder;
  42 +
  43 + public MultiUserDetailsAuthenticationProvider(List<SecurityAuthFrameworkService> serviceList,
  44 + WebProperties properties, PasswordEncoder passwordEncoder) {
  45 + serviceList.forEach(service -> services.put(service.getUserType(), service));
  46 + this.properties = properties;
  47 + this.passwordEncoder = passwordEncoder;
  48 + }
  49 +
  50 + // ========== AuthenticationProvider 相关 ==========
  51 +
  52 + @Override
  53 + protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
  54 + throws AuthenticationException {
  55 + // 执行用户的加载
  56 + return selectService(authentication).loadUserByUsername(username);
  57 + }
  58 +
  59 + private SecurityAuthFrameworkService selectService(UsernamePasswordAuthenticationToken authentication) {
  60 + // 第一步,获得用户类型
  61 + UserTypeEnum userType = getUserType(authentication);
  62 + // 第二步,获得 SecurityAuthFrameworkService
  63 + SecurityAuthFrameworkService service = services.get(userType);
  64 + Assert.notNull(service, "用户类型({}) 找不到 SecurityAuthFrameworkService 实现类", userType);
  65 + return service;
  66 + }
  67 +
  68 + private UserTypeEnum getUserType(UsernamePasswordAuthenticationToken authentication) {
  69 + Assert.isInstanceOf(MultiUsernamePasswordAuthenticationToken.class, authentication);
  70 + MultiUsernamePasswordAuthenticationToken multiAuthentication = (MultiUsernamePasswordAuthenticationToken) authentication;
  71 + UserTypeEnum userType = multiAuthentication.getUserType();
  72 + Assert.notNull(userType, "用户类型不能为空");
  73 + return userType;
  74 + }
  75 +
  76 + @Override // copy 自 DaoAuthenticationProvider 的 additionalAuthenticationChecks 方法
  77 + protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication)
  78 + throws AuthenticationException {
  79 + // 校验 credentials
  80 + if (authentication.getCredentials() == null) {
  81 + this.logger.debug("Failed to authenticate since no credentials provided");
  82 + throw new BadCredentialsException(this.messages.getMessage(
  83 + "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
  84 + }
  85 + // 校验 password
  86 + String presentedPassword = authentication.getCredentials().toString();
  87 + if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
  88 + this.logger.debug("Failed to authenticate since password does not match stored value");
  89 + throw new BadCredentialsException(this.messages.getMessage(
  90 + "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
  91 + }
  92 + }
  93 +
  94 + // ========== SecurityAuthFrameworkService 相关 ==========
  95 +
  96 + /**
  97 + * 校验 token 的有效性,并获取用户信息
  98 + * 通过后,刷新 token 的过期时间
  99 + *
  100 + * @param request 请求
  101 + * @param token token
  102 + * @return 用户信息
  103 + */
  104 + public LoginUser verifyTokenAndRefresh(HttpServletRequest request, String token) {
  105 + return selectService(request).verifyTokenAndRefresh(token);
  106 + }
  107 +
  108 + /**
  109 + * 模拟指定用户编号的 LoginUser
  110 + *
  111 + * @param request 请求
  112 + * @param userId 用户编号
  113 + * @return 登录用户
  114 + */
  115 + public LoginUser mockLogin(HttpServletRequest request, Long userId) {
  116 + return selectService(request).mockLogin(userId);
  117 + }
  118 +
  119 + /**
  120 + * 基于 token 退出登录
  121 + *
  122 + * @param request 请求
  123 + * @param token token
  124 + */
  125 + public void logout(HttpServletRequest request, String token) {
  126 + selectService(request).logout(token);
  127 + }
  128 +
  129 + private SecurityAuthFrameworkService selectService(HttpServletRequest request) {
  130 + // 第一步,获得用户类型
  131 + UserTypeEnum userType = getUserType(request);
  132 + // 第二步,获得 SecurityAuthFrameworkService
  133 + SecurityAuthFrameworkService service = services.get(userType);
  134 + Assert.notNull(service, "URI({}) 用户类型({}) 找不到 SecurityAuthFrameworkService 实现类",
  135 + request.getRequestURI(), userType);
  136 + return service;
  137 + }
  138 +
  139 + private UserTypeEnum getUserType(HttpServletRequest request) {
  140 + if (request.getRequestURI().startsWith(properties.getAdminApi().getPrefix())) {
  141 + return UserTypeEnum.ADMIN;
  142 + }
  143 + if (request.getRequestURI().startsWith(properties.getAppApi().getPrefix())) {
  144 + return UserTypeEnum.MEMBER;
  145 + }
  146 + throw new IllegalArgumentException(StrUtil.format("URI({}) 找不到匹配的用户类型", request.getRequestURI()));
  147 + }
  148 +
  149 +}
... ...
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/authentication/MultiUsernamePasswordAuthenticationToken.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.security.core.authentication;
  2 +
  3 +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
  4 +import lombok.Getter;
  5 +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
  6 +import org.springframework.security.core.GrantedAuthority;
  7 +
  8 +import java.util.Collection;
  9 +
  10 +/**
  11 + * 支持多用户的 UsernamePasswordAuthenticationToken 实现类
  12 + *
  13 + * @author 芋道源码
  14 + */
  15 +@Getter
  16 +public class MultiUsernamePasswordAuthenticationToken extends UsernamePasswordAuthenticationToken {
  17 +
  18 + /**
  19 + * 用户类型
  20 + */
  21 + private UserTypeEnum userType;
  22 +
  23 + public MultiUsernamePasswordAuthenticationToken(Object principal, Object credentials) {
  24 + super(principal, credentials);
  25 + }
  26 +
  27 + public MultiUsernamePasswordAuthenticationToken(Object principal, Object credentials,
  28 + Collection<? extends GrantedAuthority> authorities) {
  29 + super(principal, credentials, authorities);
  30 + }
  31 +
  32 + public MultiUsernamePasswordAuthenticationToken(Object principal, Object credentials, UserTypeEnum userType) {
  33 + super(principal, credentials);
  34 + this.userType = userType;
  35 + }
  36 +
  37 + public MultiUsernamePasswordAuthenticationToken(Object principal, Object credentials,
  38 + Collection<? extends GrantedAuthority> authorities, UserTypeEnum userType) {
  39 + super(principal, credentials, authorities);
  40 + this.userType = userType;
  41 + }
  42 +
  43 +}
... ...
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/filter/JWTAuthenticationTokenFilter.java
... ... @@ -5,10 +5,10 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
5 5 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
6 6 import cn.iocoder.yudao.framework.security.config.SecurityProperties;
7 7 import cn.iocoder.yudao.framework.security.core.LoginUser;
8   -import cn.iocoder.yudao.framework.security.core.service.SecurityAuthService;
  8 +import cn.iocoder.yudao.framework.security.core.authentication.MultiUserDetailsAuthenticationProvider;
9 9 import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
10 10 import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
11   -import lombok.AllArgsConstructor;
  11 +import lombok.RequiredArgsConstructor;
12 12 import org.springframework.web.filter.OncePerRequestFilter;
13 13  
14 14 import javax.servlet.FilterChain;
... ... @@ -23,12 +23,12 @@ import java.io.IOException;
23 23 *
24 24 * @author 芋道源码
25 25 */
26   -@AllArgsConstructor
  26 +@RequiredArgsConstructor
27 27 public class JWTAuthenticationTokenFilter extends OncePerRequestFilter {
28 28  
29 29 private final SecurityProperties securityProperties;
30 30  
31   - private final SecurityAuthService authService;
  31 + private final MultiUserDetailsAuthenticationProvider authenticationProvider;
32 32  
33 33 private final GlobalExceptionHandler globalExceptionHandler;
34 34  
... ... @@ -40,7 +40,7 @@ public class JWTAuthenticationTokenFilter extends OncePerRequestFilter {
40 40 if (StrUtil.isNotEmpty(token)) {
41 41 try {
42 42 // 验证 token 有效性
43   - LoginUser loginUser = authService.verifyTokenAndRefresh(request, token);
  43 + LoginUser loginUser = authenticationProvider.verifyTokenAndRefresh(request, token);
44 44 // 模拟 Login 功能,方便日常开发调试
45 45 if (loginUser == null) {
46 46 loginUser = this.mockLoginUser(request, token);
... ... @@ -78,7 +78,7 @@ public class JWTAuthenticationTokenFilter extends OncePerRequestFilter {
78 78 return null;
79 79 }
80 80 Long userId = Long.valueOf(token.substring(securityProperties.getMockSecret().length()));
81   - return authService.mockLogin(request, userId);
  81 + return authenticationProvider.mockLogin(request, userId);
82 82 }
83 83  
84 84 }
... ...
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/handler/LogoutSuccessHandlerImpl.java
... ... @@ -4,7 +4,7 @@ import cn.hutool.core.util.StrUtil;
4 4 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
5 5 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
6 6 import cn.iocoder.yudao.framework.security.config.SecurityProperties;
7   -import cn.iocoder.yudao.framework.security.core.service.SecurityAuthService;
  7 +import cn.iocoder.yudao.framework.security.core.authentication.MultiUserDetailsAuthenticationProvider;
8 8 import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
9 9 import lombok.AllArgsConstructor;
10 10 import org.springframework.security.core.Authentication;
... ... @@ -24,14 +24,14 @@ public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler {
24 24  
25 25 private final SecurityProperties securityProperties;
26 26  
27   - private final SecurityAuthService authService;
  27 + private final MultiUserDetailsAuthenticationProvider authenticationProvider;
28 28  
29 29 @Override
30 30 public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
31 31 // 执行退出
32 32 String token = SecurityFrameworkUtils.obtainAuthorization(request, securityProperties.getTokenHeader());
33 33 if (StrUtil.isNotBlank(token)) {
34   - authService.logout(request, token);
  34 + authenticationProvider.logout(request, token);
35 35 }
36 36 // 返回成功
37 37 ServletUtils.writeJSON(response, CommonResult.success(null));
... ...
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/service/SecurityAuthService.java deleted
1   -package cn.iocoder.yudao.framework.security.core.service;
2   -
3   -import cn.iocoder.yudao.framework.security.core.LoginUser;
4   -
5   -import javax.servlet.http.HttpServletRequest;
6   -
7   -/**
8   - * 安全认证的 Service 接口,对 security 组件提供统一的 Auth 相关的方法。
9   - * 主要是会基于 {@link HttpServletRequest} 参数,匹配对应的 {@link SecurityAuthFrameworkService} 实现,然后调用其方法。
10   - * 因此,在方法的定义上,和 {@link SecurityAuthFrameworkService} 差不多。
11   - *
12   - * @author 芋道源码
13   - */
14   -public interface SecurityAuthService {
15   -
16   - /**
17   - * 校验 token 的有效性,并获取用户信息
18   - * 通过后,刷新 token 的过期时间
19   - *
20   - * @param request 请求
21   - * @param token token
22   - * @return 用户信息
23   - */
24   - LoginUser verifyTokenAndRefresh(HttpServletRequest request, String token);
25   -
26   - /**
27   - * 模拟指定用户编号的 LoginUser
28   - *
29   - * @param request 请求
30   - * @param userId 用户编号
31   - * @return 登录用户
32   - */
33   - LoginUser mockLogin(HttpServletRequest request, Long userId);
34   -
35   - /**
36   - * 基于 token 退出登录
37   - *
38   - * @param request 请求
39   - * @param token token
40   - */
41   - void logout(HttpServletRequest request, String token);
42   -
43   -}
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/service/SecurityAuthServiceImpl.java deleted
1   -package cn.iocoder.yudao.framework.security.core.service;
2   -
3   -import cn.hutool.core.lang.Assert;
4   -import cn.hutool.core.util.StrUtil;
5   -import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
6   -import cn.iocoder.yudao.framework.security.core.LoginUser;
7   -import cn.iocoder.yudao.framework.web.config.WebProperties;
8   -
9   -import javax.servlet.http.HttpServletRequest;
10   -import java.util.HashMap;
11   -import java.util.List;
12   -import java.util.Map;
13   -
14   -/**
15   - * 安全认证的 Service 实现类,基于请求地址,计算到对应的 {@link UserTypeEnum} 枚举,从而拿到对应的 {@link SecurityAuthFrameworkService} 实现
16   - *
17   - * @author 芋道源码
18   - */
19   -public class SecurityAuthServiceImpl implements SecurityAuthService {
20   -
21   - private final Map<UserTypeEnum, SecurityAuthFrameworkService> services = new HashMap<>();
22   - private final WebProperties properties;
23   -
24   - public SecurityAuthServiceImpl(List<SecurityAuthFrameworkService> serviceList, WebProperties properties) {
25   - serviceList.forEach(service -> services.put(service.getUserType(), service));
26   - this.properties = properties;
27   - }
28   -
29   - @Override
30   - public LoginUser verifyTokenAndRefresh(HttpServletRequest request, String token) {
31   - return selectService(request).verifyTokenAndRefresh(token);
32   - }
33   -
34   - @Override
35   - public LoginUser mockLogin(HttpServletRequest request, Long userId) {
36   - return selectService(request).mockLogin(userId);
37   - }
38   -
39   - @Override
40   - public void logout(HttpServletRequest request, String token) {
41   - selectService(request).logout(token);
42   - }
43   -
44   - private SecurityAuthFrameworkService selectService(HttpServletRequest request) {
45   - // 第一步,获得用户类型
46   - UserTypeEnum userType = getUserType(request);
47   - // 第二步,获得 SecurityAuthFrameworkService
48   - SecurityAuthFrameworkService service = services.get(userType);
49   - Assert.notNull(service, "URI({}) 用户类型({}) 找不到 SecurityAuthFrameworkService 实现类",
50   - request.getRequestURI(), userType);
51   - return service;
52   - }
53   -
54   - private UserTypeEnum getUserType(HttpServletRequest request) {
55   - if (request.getRequestURI().startsWith(properties.getAdminApi().getPrefix())) {
56   - return UserTypeEnum.ADMIN;
57   - }
58   - if (request.getRequestURI().startsWith(properties.getAppApi().getPrefix())) {
59   - return UserTypeEnum.MEMBER;
60   - }
61   - throw new IllegalArgumentException(StrUtil.format("URI({}) 找不到匹配的用户类型", request.getRequestURI()));
62   - }
63   -
64   -}
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/util/WebFrameworkUtils.java
... ... @@ -55,6 +55,11 @@ public class WebFrameworkUtils {
55 55 return (Integer) request.getAttribute(REQUEST_ATTRIBUTE_LOGIN_USER_TYPE);
56 56 }
57 57  
  58 + public static Integer getLoginUserType() {
  59 + HttpServletRequest request = getRequest();
  60 + return getLoginUserType(request);
  61 + }
  62 +
58 63 public static Long getLoginUserId() {
59 64 HttpServletRequest request = getRequest();
60 65 return getLoginUserId(request);
... ...
yudao-module-member/yudao-module-member-impl/pom.xml
... ... @@ -32,6 +32,10 @@
32 32  
33 33 <dependency>
34 34 <groupId>cn.iocoder.boot</groupId>
  35 + <artifactId>yudao-spring-boot-starter-biz-operatelog</artifactId>
  36 + </dependency>
  37 + <dependency>
  38 + <groupId>cn.iocoder.boot</groupId>
35 39 <artifactId>yudao-spring-boot-starter-biz-sms</artifactId>
36 40 </dependency>
37 41 <dependency>
... ...
yudao-module-member/yudao-module-member-impl/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/AppAuthController.http
1 1 ### 请求 /login 接口 => 成功
2   -POST {{userServerUrl}}/login
  2 +POST {{userApi}}/login
3 3 Content-Type: application/json
  4 +tenant-id: {{userTenentId}}
4 5  
5 6 {
6 7 "mobile": "15601691300",
... ...
yudao-module-member/yudao-module-member-impl/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/AppAuthController.java
... ... @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.member.controller.app.auth;
3 3 import cn.iocoder.yudao.coreservice.modules.system.service.social.SysSocialCoreService;
4 4 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
5 5 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  6 +import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
6 7 import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
7 8 import cn.iocoder.yudao.module.member.controller.app.auth.vo.*;
8 9 import cn.iocoder.yudao.module.member.service.auth.AuthService;
... ... @@ -40,6 +41,7 @@ public class AppAuthController {
40 41  
41 42 @PostMapping("/login")
42 43 @ApiOperation("使用手机 + 密码登录")
  44 + @OperateLog(enable = false) // 避免 Post 请求被记录操作日志
43 45 public CommonResult<AppAuthLoginRespVO> login(@RequestBody @Valid AppAuthLoginReqVO reqVO) {
44 46 String token = authService.login(reqVO, getClientIP(), getUserAgent());
45 47 // 返回结果
... ... @@ -48,6 +50,7 @@ public class AppAuthController {
48 50  
49 51 @PostMapping("/sms-login")
50 52 @ApiOperation("使用手机 + 验证码登录")
  53 + @OperateLog(enable = false) // 避免 Post 请求被记录操作日志
51 54 public CommonResult<AppAuthLoginRespVO> smsLogin(@RequestBody @Valid AppAuthSmsLoginReqVO reqVO) {
52 55 String token = authService.smsLogin(reqVO, getClientIP(), getUserAgent());
53 56 // 返回结果
... ... @@ -56,12 +59,13 @@ public class AppAuthController {
56 59  
57 60 @PostMapping("/send-sms-code")
58 61 @ApiOperation(value = "发送手机验证码")
  62 + @OperateLog(enable = false) // 避免 Post 请求被记录操作日志
59 63 public CommonResult<Boolean> sendSmsCode(@RequestBody @Valid AppAuthSendSmsReqVO reqVO) {
60 64 smsCodeService.sendSmsCode(reqVO.getMobile(), reqVO.getScene(), getClientIP());
61 65 return success(true);
62 66 }
63 67  
64   - @GetMapping("/send-sms-code-login")
  68 + @GetMapping("/send-sms-code-login") // TODO 芋艿:post 比较合理
65 69 @ApiOperation(value = "向已登录用户发送验证码",notes = "修改手机时验证原手机号使用")
66 70 public CommonResult<Boolean> sendSmsCodeLogin() {
67 71 smsCodeService.sendSmsCodeLogin(getLoginUserId());
... ... @@ -71,6 +75,7 @@ public class AppAuthController {
71 75 @PostMapping("/reset-password")
72 76 @ApiOperation(value = "重置密码", notes = "用户忘记密码时使用")
73 77 @PreAuthenticated
  78 + @OperateLog(enable = false) // 避免 Post 请求被记录操作日志
74 79 public CommonResult<Boolean> resetPassword(@RequestBody @Valid AppAuthResetPasswordReqVO reqVO) {
75 80 authService.resetPassword(reqVO);
76 81 return success(true);
... ... @@ -106,6 +111,7 @@ public class AppAuthController {
106 111  
107 112 @PostMapping("/social-login2")
108 113 @ApiOperation("社交登录,使用 手机号 + 手机验证码")
  114 + @OperateLog(enable = false) // 避免 Post 请求被记录操作日志
109 115 public CommonResult<AppAuthLoginRespVO> socialLogin2(@RequestBody @Valid AppAuthSocialLogin2ReqVO reqVO) {
110 116 String token = authService.socialLogin2(reqVO, getClientIP(), getUserAgent());
111 117 return success(AppAuthLoginRespVO.builder().token(token).build());
... ... @@ -113,6 +119,7 @@ public class AppAuthController {
113 119  
114 120 @PostMapping("/social-bind")
115 121 @ApiOperation("社交绑定,使用 code 授权码")
  122 + @PreAuthenticated
116 123 public CommonResult<Boolean> socialBind(@RequestBody @Valid AppAuthSocialBindReqVO reqVO) {
117 124 authService.socialBind(getLoginUserId(), reqVO);
118 125 return CommonResult.success(true);
... ... @@ -120,6 +127,7 @@ public class AppAuthController {
120 127  
121 128 @DeleteMapping("/social-unbind")
122 129 @ApiOperation("取消社交绑定")
  130 + @PreAuthenticated
123 131 public CommonResult<Boolean> socialUnbind(@RequestBody AppAuthSocialUnbindReqVO reqVO) {
124 132 socialService.unbindSocialUser(getLoginUserId(), reqVO.getType(), reqVO.getUnionId(), UserTypeEnum.MEMBER);
125 133 return CommonResult.success(true);
... ...
yudao-module-member/yudao-module-member-impl/src/main/java/cn/iocoder/yudao/module/member/service/auth/AuthServiceImpl.java
... ... @@ -14,6 +14,7 @@ import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
14 14 import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
15 15 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
16 16 import cn.iocoder.yudao.framework.security.core.LoginUser;
  17 +import cn.iocoder.yudao.framework.security.core.authentication.MultiUsernamePasswordAuthenticationToken;
17 18 import cn.iocoder.yudao.module.member.controller.app.auth.vo.*;
18 19 import cn.iocoder.yudao.module.member.convert.auth.AuthConvert;
19 20 import cn.iocoder.yudao.module.member.dal.dataobject.user.UserDO;
... ... @@ -176,7 +177,8 @@ public class AuthServiceImpl implements AuthService {
176 177 try {
177 178 // 调用 Spring Security 的 AuthenticationManager#authenticate(...) 方法,使用账号密码进行认证
178 179 // 在其内部,会调用到 loadUserByUsername 方法,获取 User 信息
179   - authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
  180 + authentication = authenticationManager.authenticate(new MultiUsernamePasswordAuthenticationToken(
  181 + username, password, getUserType()));
180 182 } catch (BadCredentialsException badCredentialsException) {
181 183 this.createLoginLog(username, logTypeEnum, SysLoginResultEnum.BAD_CREDENTIALS);
182 184 throw exception(AUTH_LOGIN_BAD_CREDENTIALS);
... ...
yudao-server/pom.xml deleted
1   -<?xml version="1.0" encoding="UTF-8"?>
2   -<project xmlns="http://maven.apache.org/POM/4.0.0"
3   - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4   - xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5   - <parent>
6   - <artifactId>yudao</artifactId>
7   - <groupId>cn.iocoder.boot</groupId>
8   - <version>${revision}</version>
9   - </parent>
10   - <modelVersion>4.0.0</modelVersion>
11   -
12   - <artifactId>yudao-server</artifactId>
13   - <packaging>jar</packaging>
14   -
15   - <name>${artifactId}</name>
16   - <description>
17   - 后端 Server 的主项目,通过引入需要 yudao-module-xxx 的依赖,
18   - 从而实现提供 RESTful API 给 yudao-ui-admin、yudao-ui-user 等前端项目。
19   - 本质上来说,它就是个空壳(容器)!
20   - </description>
21   -
22   -</project>
更新日志.md
... ... @@ -4,29 +4,30 @@
4 4 * 钉钉、飞书等通知
5 5 * Vue3 支持
6 6  
7   -## [v1.4.0] 计划
8   -
9   -* 工作流
10   - * 修改表单为外置表单
11   - * 修改请假流程
12   - * 暂时以用户的岗位作为activiti 的用户组
13   - * 请假需要请假人部门下具有项目经理岗位, 部门经理, 和人事 岗位的用户
14   - * 新增 芋道源码部门下 用户 normal(岗位 普通用户) projectmgr(岗位 项目经理) depmgr(岗位 部门经理) hradmin (岗位 人事)
15   - * 请假流程如下
16   - 1. 请假人 normal (密码 123456) 登录在我的请假表单,点击新增,填写请假表单
17   - 2. 如果请假天数<=3, 项目经理 进行审批. 项目经理 projectmgr(密码:123456) 登录 待办请假,中进行审批,可以查看历史跟踪,和流程图
18   - 3. 如果请假天数>3 需部门经理 进行审批,部门经理depmgr(密码:123456) 登录 待办请假,中进行审批,可以查看历史跟踪,和流程图
19   - 4. 人事登陆(用户名:hradmin 密码:123456) 登录 待办请假, 中进行审批,可以查看历史跟踪,和流程图
20   - 5. 流程结束
21   - * 我的请假中,可以查询本人的请假申请, 和进度
22   -
23 7 ### 📝 TODO
24 8  
25 9 * 支付
26 10 * 用户前台的社交登陆
27 11 * 用户前台的修改手机、修改密码、忘记密码
28 12  
29   -## [v1.3.0] 进行中
  13 +## [v1.4.0] 计划,预计 2022.02.28 发布
  14 +
  15 +### ⚠️ Warning
  16 +
  17 +### 📈 Statistic
  18 +
  19 +### ⭐ New Features
  20 +
  21 +*【优化】操作日志新增用户类型,实现 APP 端的 API 的操作日志的记录
  22 +
  23 +### 🐞 Bug Fixes
  24 +
  25 +*【修复】用户无权限访问 指定 API 时,未返回 FORBIDDEN 结果码
  26 +
  27 +### 🔨 Dependency Upgrades
  28 +
  29 +
  30 +## [v1.3.0] 2022.01.24
30 31  
31 32 ### ⚠️ Warning
32 33  
... ...