diff --git a/http-client.env.json b/http-client.env.json index c4686b274..c18fbb054 100644 --- a/http-client.env.json +++ b/http-client.env.json @@ -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" } } diff --git a/pom.xml b/pom.xml index c76c9a22d..36015806e 100644 --- a/pom.xml +++ b/pom.xml @@ -14,7 +14,6 @@ yudao-user-server yudao-core-service yudao-module-member - yudao-server ${artifactId} diff --git a/yudao-admin-server/pom.xml b/yudao-admin-server/pom.xml index 40943697f..bd32454b9 100644 --- a/yudao-admin-server/pom.xml +++ b/yudao-admin-server/pom.xml @@ -13,7 +13,11 @@ jar yudao-admin-server - 管理后台 Server,提供其 API 接口 + + 后端 Server 的主项目,通过引入需要 yudao-module-xxx 的依赖, + 从而实现提供 RESTful API 给 yudao-ui-admin、yudao-ui-user 等前端项目。 + 本质上来说,它就是个空壳(容器)! + https://github.com/YunaiV/ruoyi-vue-pro diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/framework/security/SecurityConfiguration.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/framework/security/SecurityConfiguration.java index a5086b92e..2d911a675 100644 --- a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/framework/security/SecurityConfiguration.java +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/framework/security/SecurityConfiguration.java @@ -33,7 +33,7 @@ public class SecurityConfiguration { registry.antMatchers(buildAdminApi("/system/sms/callback/**")).anonymous(); // 设置 App API 无需认证 - registry.antMatchers(buildAppApi("/**")); + registry.antMatchers(buildAppApi("/**")).permitAll(); }; } diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/service/auth/impl/SysAuthServiceImpl.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/service/auth/impl/SysAuthServiceImpl.java index 3f795f83d..c9e537350 100644 --- a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/service/auth/impl/SysAuthServiceImpl.java +++ b/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; 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); diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/enums/UserTypeEnum.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/enums/UserTypeEnum.java index 7318fb045..edaba1dd9 100644 --- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/enums/UserTypeEnum.java +++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/enums/UserTypeEnum.java @@ -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()); + } + } diff --git a/yudao-framework/yudao-spring-boot-starter-biz-operatelog/src/main/java/cn/iocoder/yudao/framework/operatelog/core/aop/OperateLogAspect.java b/yudao-framework/yudao-spring-boot-starter-biz-operatelog/src/main/java/cn/iocoder/yudao/framework/operatelog/core/aop/OperateLogAspect.java index a7f23a391..6cda19f82 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-operatelog/src/main/java/cn/iocoder/yudao/framework/operatelog/core/aop/OperateLogAspect.java +++ b/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 { private static void fillUserFields(OperateLogCreateReqDTO operateLogDTO) { operateLogDTO.setUserId(WebFrameworkUtils.getLoginUserId()); + operateLogDTO.setUserType(WebFrameworkUtils.getLoginUserType()); } private static void fillModuleFields(OperateLogCreateReqDTO operateLogDTO, diff --git a/yudao-framework/yudao-spring-boot-starter-biz-operatelog/src/main/java/cn/iocoder/yudao/framework/operatelog/core/dto/OperateLogCreateReqDTO.java b/yudao-framework/yudao-spring-boot-starter-biz-operatelog/src/main/java/cn/iocoder/yudao/framework/operatelog/core/dto/OperateLogCreateReqDTO.java index 1c8f49a26..d676001f2 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-operatelog/src/main/java/cn/iocoder/yudao/framework/operatelog/core/dto/OperateLogCreateReqDTO.java +++ b/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 { @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 = "操作模块不能为空") diff --git a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoSecurityAutoConfiguration.java b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoSecurityAutoConfiguration.java index ae8b12422..d5953823a 100644 --- a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoSecurityAutoConfiguration.java +++ b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoSecurityAutoConfiguration.java @@ -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 securityFrameworkServices, - WebProperties webProperties) { - return new SecurityAuthServiceImpl(securityFrameworkServices, webProperties); + public MultiUserDetailsAuthenticationProvider authenticationProvider( + List securityFrameworkServices, + WebProperties webProperties, PasswordEncoder passwordEncoder) { + return new MultiUserDetailsAuthenticationProvider(securityFrameworkServices, webProperties, passwordEncoder); } /** diff --git a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoWebSecurityConfigurerAdapter.java b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoWebSecurityConfigurerAdapter.java index a00b2c2a6..276ce1ed5 100644 --- a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoWebSecurityConfigurerAdapter.java +++ b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoWebSecurityConfigurerAdapter.java @@ -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); } /** diff --git a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/authentication/MultiUserDetailsAuthenticationProvider.java b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/authentication/MultiUserDetailsAuthenticationProvider.java new file mode 100644 index 000000000..dc8533f96 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/authentication/MultiUserDetailsAuthenticationProvider.java @@ -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 services = new HashMap<>(); + + private final WebProperties properties; + + private final PasswordEncoder passwordEncoder; + + public MultiUserDetailsAuthenticationProvider(List 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())); + } + +} diff --git a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/authentication/MultiUsernamePasswordAuthenticationToken.java b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/authentication/MultiUsernamePasswordAuthenticationToken.java new file mode 100644 index 000000000..f0bc8dfac --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/authentication/MultiUsernamePasswordAuthenticationToken.java @@ -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 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 authorities, UserTypeEnum userType) { + super(principal, credentials, authorities); + this.userType = userType; + } + +} diff --git a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/filter/JWTAuthenticationTokenFilter.java b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/filter/JWTAuthenticationTokenFilter.java index 61685e902..804c88d35 100644 --- a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/filter/JWTAuthenticationTokenFilter.java +++ b/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; 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); } } diff --git a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/handler/LogoutSuccessHandlerImpl.java b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/handler/LogoutSuccessHandlerImpl.java index e61b351c2..1a642304c 100644 --- a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/handler/LogoutSuccessHandlerImpl.java +++ b/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; 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)); diff --git a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/service/SecurityAuthService.java b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/service/SecurityAuthService.java deleted file mode 100644 index 4fee9cd0d..000000000 --- a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/service/SecurityAuthService.java +++ /dev/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); - -} diff --git a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/service/SecurityAuthServiceImpl.java b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/service/SecurityAuthServiceImpl.java deleted file mode 100644 index d9ab8eb0e..000000000 --- a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/service/SecurityAuthServiceImpl.java +++ /dev/null @@ -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 services = new HashMap<>(); - private final WebProperties properties; - - public SecurityAuthServiceImpl(List 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())); - } - -} diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/util/WebFrameworkUtils.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/util/WebFrameworkUtils.java index acbfb9c2b..273f34072 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/util/WebFrameworkUtils.java +++ b/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 { 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); diff --git a/yudao-module-member/yudao-module-member-impl/pom.xml b/yudao-module-member/yudao-module-member-impl/pom.xml index de4650806..ce7ff84a0 100644 --- a/yudao-module-member/yudao-module-member-impl/pom.xml +++ b/yudao-module-member/yudao-module-member-impl/pom.xml @@ -30,6 +30,10 @@ yudao-core-service + + cn.iocoder.boot + yudao-spring-boot-starter-biz-operatelog + cn.iocoder.boot yudao-spring-boot-starter-biz-sms diff --git a/yudao-module-member/yudao-module-member-impl/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/AppAuthController.http b/yudao-module-member/yudao-module-member-impl/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/AppAuthController.http index c9cadedcc..dbe82122a 100644 --- a/yudao-module-member/yudao-module-member-impl/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/AppAuthController.http +++ b/yudao-module-member/yudao-module-member-impl/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/AppAuthController.http @@ -1,6 +1,7 @@ ### 请求 /login 接口 => 成功 -POST {{userServerUrl}}/login +POST {{userApi}}/login Content-Type: application/json +tenant-id: {{userTenentId}} { "mobile": "15601691300", diff --git a/yudao-module-member/yudao-module-member-impl/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/AppAuthController.java b/yudao-module-member/yudao-module-member-impl/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/AppAuthController.java index 3876e708b..f52e96d1a 100644 --- a/yudao-module-member/yudao-module-member-impl/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/AppAuthController.java +++ b/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; 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 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 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 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 sendSmsCodeLogin() { smsCodeService.sendSmsCodeLogin(getLoginUserId()); @@ -71,6 +75,7 @@ public class AppAuthController { @PostMapping("/reset-password") @ApiOperation(value = "重置密码", notes = "用户忘记密码时使用") @PreAuthenticated + @OperateLog(enable = false) // 避免 Post 请求被记录操作日志 public CommonResult 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 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 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 socialUnbind(@RequestBody AppAuthSocialUnbindReqVO reqVO) { socialService.unbindSocialUser(getLoginUserId(), reqVO.getType(), reqVO.getUnionId(), UserTypeEnum.MEMBER); return CommonResult.success(true); diff --git a/yudao-module-member/yudao-module-member-impl/src/main/java/cn/iocoder/yudao/module/member/service/auth/AuthServiceImpl.java b/yudao-module-member/yudao-module-member-impl/src/main/java/cn/iocoder/yudao/module/member/service/auth/AuthServiceImpl.java index a28b6572d..6deabba12 100644 --- a/yudao-module-member/yudao-module-member-impl/src/main/java/cn/iocoder/yudao/module/member/service/auth/AuthServiceImpl.java +++ b/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; 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); diff --git a/yudao-server/pom.xml b/yudao-server/pom.xml deleted file mode 100644 index 004051314..000000000 --- a/yudao-server/pom.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - yudao - cn.iocoder.boot - ${revision} - - 4.0.0 - - yudao-server - jar - - ${artifactId} - - 后端 Server 的主项目,通过引入需要 yudao-module-xxx 的依赖, - 从而实现提供 RESTful API 给 yudao-ui-admin、yudao-ui-user 等前端项目。 - 本质上来说,它就是个空壳(容器)! - - - diff --git a/更新日志.md b/更新日志.md index 5ce39fb84..9ac18dd8e 100644 --- a/更新日志.md +++ b/更新日志.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