多模块重构 2:在 yudao-admin-server 中,引入 yudao-module-member 模块

This commit is contained in:
YunaiV
2022-01-28 20:21:01 +08:00
parent 06fa85a353
commit 928b7dbe23
59 changed files with 622 additions and 460 deletions

View File

@ -7,6 +7,9 @@ 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;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@ -20,6 +23,7 @@ import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import javax.annotation.Resource;
import java.util.List;
/**
* Spring Security 自动配置类,主要用于相关组件的配置
@ -29,7 +33,7 @@ import javax.annotation.Resource;
*
* @author 芋道源码
*/
@Configuration
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(SecurityProperties.class)
public class YudaoSecurityAutoConfiguration {
@ -64,8 +68,8 @@ public class YudaoSecurityAutoConfiguration {
* 退出处理类 Bean
*/
@Bean
public LogoutSuccessHandler logoutSuccessHandler(SecurityAuthFrameworkService securityFrameworkService) {
return new LogoutSuccessHandlerImpl(securityProperties, securityFrameworkService);
public LogoutSuccessHandler logoutSuccessHandler(SecurityAuthService securityAuthService) {
return new LogoutSuccessHandlerImpl(securityProperties, securityAuthService);
}
/**
@ -83,9 +87,18 @@ public class YudaoSecurityAutoConfiguration {
* Token 认证过滤器 Bean
*/
@Bean
public JWTAuthenticationTokenFilter authenticationTokenFilter(SecurityAuthFrameworkService securityFrameworkService,
public JWTAuthenticationTokenFilter authenticationTokenFilter(SecurityAuthService securityAuthService,
GlobalExceptionHandler globalExceptionHandler) {
return new JWTAuthenticationTokenFilter(securityProperties, securityFrameworkService, globalExceptionHandler);
return new JWTAuthenticationTokenFilter(securityProperties, securityAuthService, globalExceptionHandler);
}
/**
* 安全认证的 Service Bean
*/
@Bean
public SecurityAuthService securityAuthService(List<SecurityAuthFrameworkService> securityFrameworkServices,
WebProperties webProperties) {
return new SecurityAuthServiceImpl(securityFrameworkServices, webProperties);
}
/**

View File

@ -65,13 +65,15 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap
*/
@Resource
private JWTAuthenticationTokenFilter authenticationTokenFilter;
/**
* 自定义的权限映射 Bean
*
* @see #configure(HttpSecurity)
*/
@Resource
private Customizer<ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry> authorizeRequestsCustomizer;
private Customizer<ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry>
authorizeRequestsCustomizer;
/**
* 由于 Spring Security 创建 AuthenticationManager 对象时,没声明 @Bean 注解,导致无法被注入
@ -89,8 +91,8 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder);
auth
.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
}
/**
@ -123,16 +125,16 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap
// 一堆自定义的 Spring Security 处理器
.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint)
.accessDeniedHandler(accessDeniedHandler).and()
.logout().logoutUrl(api("/logout")).logoutSuccessHandler(logoutSuccessHandler); // 登出
.logout().logoutUrl(buildAdminApi("/logout")).logoutSuccessHandler(logoutSuccessHandler); // 登出
// 设置每个请求的权限 ①:全局共享规则
httpSecurity.authorizeRequests()
// 登录的接口,可匿名访问
.antMatchers(api("/login")).anonymous()
.antMatchers(buildAdminApi("/login")).anonymous()
// 静态资源,可匿名访问
.antMatchers(HttpMethod.GET, "/*.html", "/**/*.html", "/**/*.css", "/**/*.js").permitAll()
// 文件的获取接口,可匿名访问
.antMatchers(api("/infra/file/get/**")).anonymous()
.antMatchers(buildAdminApi("/infra/file/get/**")).anonymous()
// Swagger 接口文档
.antMatchers("/swagger-ui.html").anonymous()
.antMatchers("/swagger-resources/**").anonymous()
@ -143,11 +145,11 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap
.antMatchers("/actuator/**").anonymous()
// Druid 监控 TODO 芋艿:等对接了 druid admin 后,在调整下。
.antMatchers("/druid/**").anonymous()
// oAuth2 auth2/login/gitee
.antMatchers(api("/auth2/login/**")).anonymous()
.antMatchers(api("/auth2/authorization/**")).anonymous()
// oAuth2 auth2/login/gitee TODO 芋艿:貌似可以删除
.antMatchers(buildAdminApi("/auth2/login/**")).anonymous()
.antMatchers(buildAdminApi("/auth2/authorization/**")).anonymous()
.antMatchers("/api/callback/**").anonymous()
// 设置每个请求的权限 ②:每个项目的自定义规则
// 设置每个请求的权限 ②:每个项目的自定义规则 TODO 芋艿:改造成多个,方便每个模块自定义规则
.and().authorizeRequests(authorizeRequestsCustomizer)
// 设置每个请求的权限 ③:兜底规则,必须认证
.authorizeRequests().anyRequest().authenticated()
@ -156,8 +158,14 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap
httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
}
private String api(String url) {
return webProperties.getApiPrefix() + url;
private String buildAdminApi(String url) {
// TODO 芋艿:多模块
return webProperties.getAdminApi().getPrefix() + url;
}
private String buildAppApi(String url) {
// TODO 芋艿:多模块
return webProperties.getAppApi().getPrefix() + url;
}
}

View File

@ -2,17 +2,15 @@ package cn.iocoder.yudao.framework.security.core.filter;
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.LoginUser;
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.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.annotation.Resource;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
@ -30,7 +28,7 @@ public class JWTAuthenticationTokenFilter extends OncePerRequestFilter {
private final SecurityProperties securityProperties;
private final SecurityAuthFrameworkService authService;
private final SecurityAuthService authService;
private final GlobalExceptionHandler globalExceptionHandler;
@ -42,10 +40,10 @@ public class JWTAuthenticationTokenFilter extends OncePerRequestFilter {
if (StrUtil.isNotEmpty(token)) {
try {
// 验证 token 有效性
LoginUser loginUser = authService.verifyTokenAndRefresh(token);
LoginUser loginUser = authService.verifyTokenAndRefresh(request, token);
// 模拟 Login 功能,方便日常开发调试
if (loginUser == null) {
loginUser = this.mockLoginUser(token);
loginUser = this.mockLoginUser(request, token);
}
// 设置当前用户
if (loginUser != null) {
@ -67,10 +65,11 @@ public class JWTAuthenticationTokenFilter extends OncePerRequestFilter {
*
* 注意,在线上环境下,一定要关闭该功能!!!
*
* @param request 请求
* @param token 模拟的 token格式为 {@link SecurityProperties#getTokenSecret()} + 用户编号
* @return 模拟的 LoginUser
*/
private LoginUser mockLoginUser(String token) {
private LoginUser mockLoginUser(HttpServletRequest request, String token) {
if (!securityProperties.getMockEnable()) {
return null;
}
@ -79,7 +78,7 @@ public class JWTAuthenticationTokenFilter extends OncePerRequestFilter {
return null;
}
Long userId = Long.valueOf(token.substring(securityProperties.getMockSecret().length()));
return authService.mockLogin(userId);
return authService.mockLogin(request, userId);
}
}

View File

@ -2,16 +2,14 @@ package cn.iocoder.yudao.framework.security.core.handler;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.security.config.SecurityProperties;
import cn.iocoder.yudao.framework.security.core.service.SecurityAuthFrameworkService;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
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.util.SecurityFrameworkUtils;
import lombok.AllArgsConstructor;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -26,14 +24,14 @@ public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler {
private final SecurityProperties securityProperties;
private final SecurityAuthFrameworkService securityFrameworkService;
private final SecurityAuthService authService;
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
// 执行退出
String token = SecurityFrameworkUtils.obtainAuthorization(request, securityProperties.getTokenHeader());
if (StrUtil.isNotBlank(token)) {
securityFrameworkService.logout(token);
authService.logout(request, token);
}
// 返回成功
ServletUtils.writeJSON(response, CommonResult.success(null));

View File

@ -1,10 +1,11 @@
package cn.iocoder.yudao.framework.security.core.service;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.security.core.LoginUser;
import org.springframework.security.core.userdetails.UserDetailsService;
/**
* Security 框架 Auth Service 接口,定义 security 组件需要的功能
* Security 框架 Auth Service 接口,定义不同用户类型的 {@link UserTypeEnum} 需要实现的方法
*
* @author 芋道源码
*/
@ -34,4 +35,11 @@ public interface SecurityAuthFrameworkService extends UserDetailsService {
*/
void logout(String token);
/**
* 获得用户类型。每个用户类型,对应一个 SecurityAuthFrameworkService 实现类。
*
* @return 用户类型
*/
UserTypeEnum getUserType();
}

View File

@ -0,0 +1,43 @@
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);
}

View File

@ -0,0 +1,64 @@
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()));
}
}