feat: 添加 spring-boot-security starter.

This commit is contained in:
lijiahang
2023-07-07 16:22:09 +08:00
parent f4923cc653
commit bf3ca41099
22 changed files with 981 additions and 2 deletions

View File

@@ -0,0 +1,37 @@
package com.orion.ops.framework.common.security;
import lombok.Data;
import java.util.List;
/**
* 当前登录用户
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/7/6 18:36
*/
@Data
public class LoginUser {
/**
* id
*/
private Long id;
/**
* 用户名
*/
private String username;
/**
* 花名
*/
private String nickname;
/**
* 角色
*/
private List<String> roles;
}

View File

@@ -0,0 +1,26 @@
package com.orion.ops.framework.common.security;
/**
* SecurityUtils 的 bean 对象
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/7/7 15:20
*/
public interface SecurityHolder {
/**
* 获取当前用户
*
* @return 当前用户
*/
LoginUser getLoginUser();
/**
* 获取当前用户id
*
* @return id
*/
Long getLoginUserId();
}

View File

@@ -0,0 +1,23 @@
package com.orion.ops.framework.security.config;
import org.springframework.core.Ordered;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
/**
* 自定义安全策略配置
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/7/7 12:58
*/
public abstract class AuthorizeRequestsCustomizer
implements Customizer<ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry>, Ordered {
@Override
public int getOrder() {
return 0;
}
}

View File

@@ -0,0 +1,220 @@
package com.orion.ops.framework.security.config;
import com.orion.ops.framework.security.core.context.TransmittableThreadLocalSecurityContextHolderStrategy;
import com.orion.ops.framework.security.core.filter.TokenAuthenticationFilter;
import com.orion.ops.framework.security.core.handler.AuthenticationEntryPointHandler;
import com.orion.ops.framework.security.core.handler.ForbiddenAccessDeniedHandler;
import com.orion.ops.framework.security.core.service.SecurityFrameworkService;
import com.orion.ops.framework.security.core.service.SecurityFrameworkServiceDelegate;
import com.orion.ops.framework.security.core.service.SecurityHolderDelegate;
import com.orion.ops.framework.security.core.strategy.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.MethodInvokingFactoryBean;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import javax.annotation.Resource;
import java.util.List;
/**
* 项目安全配置类
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/7/6 15:05
*/
@AutoConfiguration
@EnableConfigurationProperties(SecurityConfig.class)
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class OrionSecurityAutoConfiguration {
@Resource
private SecurityConfig securityConfig;
/**
* @return 认证失败处理器
*/
@Bean
public AuthenticationEntryPoint authenticationEntryPoint() {
return new AuthenticationEntryPointHandler();
}
/**
* @return 权限不足处理器
*/
@Bean
public AccessDeniedHandler accessDeniedHandler() {
return new ForbiddenAccessDeniedHandler();
}
/**
* @return 密码加密器
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(securityConfig.getPasswordEncoderLength());
}
/**
* AuthenticationManager 不是bean
* 重写父类方法可注入 AuthenticationManager
*
* @param authenticationConfiguration configuration
* @return AuthenticationManagerBean
* @throws Exception Exception
*/
@Bean
public AuthenticationManager authenticationManagerBean(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
/**
* 声明调用 {@link SecurityContextHolder#setStrategyName(String)} 方法
* 设置使用 {@link TransmittableThreadLocalSecurityContextHolderStrategy} 作为 Security 的上下文策略
*
* @return 替换策略
*/
@Bean
public MethodInvokingFactoryBean securityContextHolderMethodInvokingFactoryBean() {
MethodInvokingFactoryBean methodInvokingFactoryBean = new MethodInvokingFactoryBean();
methodInvokingFactoryBean.setTargetClass(SecurityContextHolder.class);
methodInvokingFactoryBean.setTargetMethod("setStrategyName");
methodInvokingFactoryBean.setArguments(TransmittableThreadLocalSecurityContextHolderStrategy.class.getName());
return methodInvokingFactoryBean;
}
/**
* @param impl impl
* @return 安全框架服务
*/
@Bean("ss")
@Primary
@ConditionalOnBean(SecurityFrameworkService.class)
public SecurityFrameworkServiceDelegate securityFrameworkService(SecurityFrameworkService impl) {
return new SecurityFrameworkServiceDelegate(impl);
}
/**
* @param delegate delegate
* @return token 认证过滤器
*/
@Bean
@ConditionalOnBean(SecurityFrameworkService.class)
public TokenAuthenticationFilter authenticationTokenFilter(SecurityFrameworkService delegate) {
return new TokenAuthenticationFilter(delegate);
}
/**
* @return security holder 代理用于内部 framework 调用
*/
@Bean
public SecurityHolderDelegate securityHolder() {
return new SecurityHolderDelegate();
}
/**
* @return 静态资源安全策略
*/
@Bean
public StaticResourceAuthorizeRequestsCustomizer staticResourceAuthorizeRequestsCustomizer() {
return new StaticResourceAuthorizeRequestsCustomizer();
}
/**
* @param applicationContext applicationContext
* @return 匿名接口安全策略
*/
@Bean
public PermitAllAnnotationAuthorizeRequestsCustomizer permitAllAnnotationAuthorizeRequestsCustomizer(ApplicationContext applicationContext) {
return new PermitAllAnnotationAuthorizeRequestsCustomizer(applicationContext);
}
/**
* @return 配置文件安全策略
*/
@Bean
public ConfigAuthorizeRequestsCustomizer configAuthorizeRequestsCustomizer() {
return new ConfigAuthorizeRequestsCustomizer(securityConfig);
}
/**
* @return websocket 安全策略
*/
@Bean
public WebsocketAuthorizeRequestsCustomizer websocketAuthorizeRequestsCustomizer() {
return new WebsocketAuthorizeRequestsCustomizer();
}
/**
* @param adminSeverContextPath adminSeverContextPath
* @return 控制台安全策略
*/
@Bean
public ConsoleAuthorizeRequestsCustomizer consoleAuthorizeRequestsCustomizer(@Value("${spring.boot.admin.context-path:''}") String adminSeverContextPath) {
return new ConsoleAuthorizeRequestsCustomizer(adminSeverContextPath);
}
/**
* 配置安全配置
* <p>
* anyRequest | 匹配所有请求路径
* access | SpringEl 表达式结果为 true 时可以访问
* anonymous | 匿名可以访问
* denyAll | 用户不能访问
* fullyAuthenticated | 用户完全认证可以访问非remember-me下自动登录
* hasAnyAuthority | 如果有参数, 参数表示权限, 则其中任何一个权限可以访问
* hasAnyRole | 如果有参数, 参数表示角色, 则其中任何一个角色可以访问
* hasAuthority | 如果有参数, 参数表示权限, 则其权限可以访问
* hasIpAddress | 如果有参数, 参数表示IP地址, 如果用户IP和参数匹配, 则可以访问
* hasRole | 如果有参数, 参数表示角色, 则其角色可以访问
* permitAll | 用户可以任意访问
* rememberMe | 允许通过 remember-me 登录的用户访问
* authenticated | 用户登录后可访问
*/
@Bean
protected SecurityFilterChain filterChain(List<AuthorizeRequestsCustomizer> authorizeRequestsCustomizers,
AuthenticationEntryPoint authenticationEntryPoint,
AccessDeniedHandler accessDeniedHandler,
TokenAuthenticationFilter authenticationTokenFilter,
HttpSecurity httpSecurity) throws Exception {
return httpSecurity
// 开启跨域
.cors().and()
// 因为不使用session 禁用CSRF
.csrf().disable()
// 基于 token 机制所以不需要 session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
// 不设置响应报头
.headers().frameOptions().disable().and()
// 认证失败处理器
.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint)
// 权限不足处理器
.accessDeniedHandler(accessDeniedHandler).and()
// 设置请求权限策略
.authorizeRequests(registry -> authorizeRequestsCustomizers.forEach(customizer -> customizer.customize(registry)))
// 兜底规则 必须认证
.authorizeRequests()
.anyRequest()
.authenticated().and()
// 在密码认证器之前添加 token 过滤器
.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class)
.build();
}
}

View File

@@ -0,0 +1,34 @@
package com.orion.ops.framework.security.config;
import com.orion.ops.framework.common.utils.ConfigUtils;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.List;
/**
* 安全配置
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/7/6 15:55
*/
@Data
@ConfigurationProperties("orion.security")
public class SecurityConfig {
/**
* 加密复杂度
*/
private Integer passwordEncoderLength = 4;
/**
* 匿名接口
*/
private List<String> permitUrl;
public void setPermitUrl(List<String> permitUrl) {
this.permitUrl = ConfigUtils.parseStringList(permitUrl);
}
}

View File

@@ -0,0 +1,50 @@
package com.orion.ops.framework.security.core.context;
import com.alibaba.ttl.TransmittableThreadLocal;
import com.orion.lang.utils.Valid;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.security.core.context.SecurityContextImpl;
/**
* 使用 TransmittableThreadLocal 实现 Security Context 持有者策略
* 避免异步执行时 ThreadLocal 的丢失问题
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/7/6 15:55
*/
public class TransmittableThreadLocalSecurityContextHolderStrategy implements SecurityContextHolderStrategy {
/**
* 使用 TransmittableThreadLocal 作为上下文
*/
private static final ThreadLocal<SecurityContext> CONTEXT_HOLDER = new TransmittableThreadLocal<>();
@Override
public void clearContext() {
CONTEXT_HOLDER.remove();
}
@Override
public SecurityContext getContext() {
SecurityContext ctx = CONTEXT_HOLDER.get();
if (ctx == null) {
ctx = this.createEmptyContext();
CONTEXT_HOLDER.set(ctx);
}
return ctx;
}
@Override
public void setContext(SecurityContext context) {
Valid.notNull(context, "Only non-null SecurityContext instances are permitted");
CONTEXT_HOLDER.set(context);
}
@Override
public SecurityContext createEmptyContext() {
return new SecurityContextImpl();
}
}

View File

@@ -0,0 +1,48 @@
package com.orion.ops.framework.security.core.filter;
import com.orion.lang.utils.Strings;
import com.orion.ops.framework.common.security.LoginUser;
import com.orion.ops.framework.security.core.service.SecurityFrameworkService;
import com.orion.ops.framework.security.core.utils.SecurityUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 认证过滤器
* 验证 token 有效后将其加入上下文
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/7/6 18:39
*/
public class TokenAuthenticationFilter extends OncePerRequestFilter {
private final SecurityFrameworkService securityFrameworkService;
public TokenAuthenticationFilter(SecurityFrameworkService securityFrameworkService) {
this.securityFrameworkService = securityFrameworkService;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
// 获取请求头 token
String token = SecurityUtils.obtainAuthorization(request);
if (!Strings.isBlank(token)) {
// 通过 token 获取用户信息
LoginUser loginUser = securityFrameworkService.getUserByToken(token);
// 设置上下文
if (loginUser != null) {
SecurityUtils.setLoginUser(loginUser, request);
}
}
// todo 全局异常返回 mock模式
// 继续执行
chain.doFilter(request, response);
}
}

View File

@@ -0,0 +1,30 @@
package com.orion.ops.framework.security.core.handler;
import com.orion.ops.framework.common.constant.ErrorCode;
import com.orion.web.servlet.web.Servlets;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 认证失败处理器
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/7/6 16:01
*/
@Slf4j
public class AuthenticationEntryPointHandler implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
log.debug("AuthenticationEntryPoint-commence-未登录 {}", request.getRequestURI(), e);
Servlets.writeHttpWrapper(response, ErrorCode.UNAUTHORIZED.getWrapper());
}
}

View File

@@ -0,0 +1,30 @@
package com.orion.ops.framework.security.core.handler;
import com.orion.ops.framework.common.constant.ErrorCode;
import com.orion.ops.framework.security.core.utils.SecurityUtils;
import com.orion.web.servlet.web.Servlets;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 权限不足处理器
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/7/6 16:01
*/
@Slf4j
public class ForbiddenAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException {
log.warn("AccessDeniedHandlerImpl-handle-无权限 {} {}", SecurityUtils.getLoginUserId(), request.getRequestURI());
Servlets.writeHttpWrapper(response, ErrorCode.FORBIDDEN.getWrapper());
}
}

View File

@@ -0,0 +1,42 @@
package com.orion.ops.framework.security.core.service;
import com.orion.ops.framework.common.security.LoginUser;
/**
* 权限校验服务
* <p>
* 在业务层定义 bean
* 使用 @PreAuthorize("@ss.hasPermission('xxx')")
* <p>
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/7/6 18:25
*/
public interface SecurityFrameworkService {
/**
* 检查是否有权限
*
* @param permission 权限
* @return has
*/
boolean hasPermission(String permission);
/**
* 检查是否有角色
*
* @param role 角色
* @return has
*/
boolean hasRole(String role);
/**
* 通过 token 获取用户信息
*
* @param token token
* @return user
*/
LoginUser getUserByToken(String token);
}

View File

@@ -0,0 +1,35 @@
package com.orion.ops.framework.security.core.service;
import com.orion.ops.framework.common.security.LoginUser;
/**
* 权限校验服务委托类
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/7/7 11:02
*/
public class SecurityFrameworkServiceDelegate implements SecurityFrameworkService {
private final SecurityFrameworkService delegate;
public SecurityFrameworkServiceDelegate(SecurityFrameworkService delegate) {
this.delegate = delegate;
}
@Override
public boolean hasPermission(String permission) {
return delegate.hasPermission(permission);
}
@Override
public boolean hasRole(String role) {
return delegate.hasRole(role);
}
@Override
public LoginUser getUserByToken(String token) {
return delegate.getUserByToken(token);
}
}

View File

@@ -0,0 +1,26 @@
package com.orion.ops.framework.security.core.service;
import com.orion.ops.framework.common.security.LoginUser;
import com.orion.ops.framework.common.security.SecurityHolder;
import com.orion.ops.framework.security.core.utils.SecurityUtils;
/**
* SecurityHolder 委托类
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/7/7 15:33
*/
public class SecurityHolderDelegate implements SecurityHolder {
@Override
public LoginUser getLoginUser() {
return SecurityUtils.getLoginUser();
}
@Override
public Long getLoginUserId() {
return SecurityUtils.getLoginUserId();
}
}

View File

@@ -0,0 +1,29 @@
package com.orion.ops.framework.security.core.strategy;
import com.orion.ops.framework.security.config.AuthorizeRequestsCustomizer;
import com.orion.ops.framework.security.config.SecurityConfig;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
/**
* 配置文件 认证策略
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/7/7 13:04
*/
public class ConfigAuthorizeRequestsCustomizer extends AuthorizeRequestsCustomizer {
private final SecurityConfig securityConfig;
public ConfigAuthorizeRequestsCustomizer(SecurityConfig securityConfig) {
this.securityConfig = securityConfig;
}
@Override
public void customize(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry) {
// 配置文件 无需认证
registry.antMatchers(securityConfig.getPermitUrl().toArray(new String[0])).permitAll();
}
}

View File

@@ -0,0 +1,36 @@
package com.orion.ops.framework.security.core.strategy;
import com.orion.ops.framework.security.config.AuthorizeRequestsCustomizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
/**
* 控制台 认证策略
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/7/7 13:04
*/
public class ConsoleAuthorizeRequestsCustomizer extends AuthorizeRequestsCustomizer {
private final String adminSeverContextPath;
public ConsoleAuthorizeRequestsCustomizer(String adminSeverContextPath) {
this.adminSeverContextPath = adminSeverContextPath;
}
@Override
public void customize(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry) {
registry
// swagger 接口文档
.antMatchers("/v3/api-docs/**", "/swagger-ui.html", "/swagger-ui/**").permitAll()
.antMatchers("/swagger-resources/**", "/webjars/**", "/*/api-docs").anonymous()
// druid 监控
.antMatchers("/druid/**").anonymous()
// actuator 安全配置 TODO TEST
.antMatchers("/actuator", "/actuator/**").anonymous()
// admin 安全配置 TODO TEST
.antMatchers(adminSeverContextPath, adminSeverContextPath + "/**").anonymous();
}
}

View File

@@ -0,0 +1,105 @@
package com.orion.ops.framework.security.core.strategy;
import com.orion.ops.framework.security.config.AuthorizeRequestsCustomizer;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import javax.annotation.security.PermitAll;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* API @PermitAll 认证策略
*
* @author Jiahang Li
* @version 1.0.0
* @see javax.annotation.security.PermitAll
* @since 2023/7/7 13:04
*/
public class PermitAllAnnotationAuthorizeRequestsCustomizer extends AuthorizeRequestsCustomizer {
private final ApplicationContext applicationContext;
public PermitAllAnnotationAuthorizeRequestsCustomizer(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
@Override
public void customize(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry) {
// 获取匿名接口
Map<HttpMethod, Set<String>> permitAllUrls = getPermitAllUrlsFromAnnotations();
// @PermitAll 无需认证
registry.antMatchers(permitAllUrls.get(null).toArray(new String[0])).permitAll()
.antMatchers(HttpMethod.GET, permitAllUrls.get(HttpMethod.GET).toArray(new String[0])).permitAll()
.antMatchers(HttpMethod.POST, permitAllUrls.get(HttpMethod.POST).toArray(new String[0])).permitAll()
.antMatchers(HttpMethod.PUT, permitAllUrls.get(HttpMethod.PUT).toArray(new String[0])).permitAll()
.antMatchers(HttpMethod.DELETE, permitAllUrls.get(HttpMethod.DELETE).toArray(new String[0])).permitAll();
}
/**
* 通过注解获取所有匿名接口
*
* @return 匿名接口
*/
private Map<HttpMethod, Set<String>> getPermitAllUrlsFromAnnotations() {
Set<String> getList = new HashSet<>();
Set<String> postList = new HashSet<>();
Set<String> putList = new HashSet<>();
Set<String> deleteList = new HashSet<>();
Set<String> requestList = new HashSet<>();
// 获取 RequestMappingHandlerMapping
RequestMappingHandlerMapping requestMappingHandlerMapping = applicationContext.getBean(RequestMappingHandlerMapping.class);
// 获得接口对应的 HandlerMethod
Map<RequestMappingInfo, HandlerMethod> handlerMethodMap = requestMappingHandlerMapping.getHandlerMethods();
// 获得有 @PermitAll 注解的接口
handlerMethodMap.forEach((mapping, method) -> {
// 非 @PermitAll 则跳过
if (!method.hasMethodAnnotation(PermitAll.class)) {
return;
}
if (mapping.getPatternsCondition() == null) {
return;
}
Set<String> urls = mapping.getPatternsCondition().getPatterns();
Set<RequestMethod> methods = mapping.getMethodsCondition().getMethods();
// 为空证明为 @RequestMapping
if (methods.isEmpty()) {
requestList.addAll(urls);
}
// 根据请求方法过滤
methods.forEach(requestMethod -> {
switch (requestMethod) {
case GET:
getList.addAll(urls);
break;
case POST:
postList.addAll(urls);
break;
case PUT:
putList.addAll(urls);
break;
case DELETE:
deleteList.addAll(urls);
break;
}
});
});
// 设置返回
Map<HttpMethod, Set<String>> result = new HashMap<>();
result.put(HttpMethod.GET, getList);
result.put(HttpMethod.POST, postList);
result.put(HttpMethod.PUT, putList);
result.put(HttpMethod.DELETE, deleteList);
result.put(null, requestList);
return result;
}
}

View File

@@ -0,0 +1,23 @@
package com.orion.ops.framework.security.core.strategy;
import com.orion.ops.framework.security.config.AuthorizeRequestsCustomizer;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
/**
* 静态资源 认证策略
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/7/7 13:04
*/
public class StaticResourceAuthorizeRequestsCustomizer extends AuthorizeRequestsCustomizer {
@Override
public void customize(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry) {
// 静态资源可匿名访问
registry.antMatchers(HttpMethod.GET, "/*.html", "/**/*.html", "/**/*.css", "/**/*.js").permitAll();
}
}

View File

@@ -0,0 +1,22 @@
package com.orion.ops.framework.security.core.strategy;
import com.orion.ops.framework.security.config.AuthorizeRequestsCustomizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
/**
* websocket 认证策略
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/7/7 13:04
*/
public class WebsocketAuthorizeRequestsCustomizer extends AuthorizeRequestsCustomizer {
@Override
public void customize(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry) {
// websocket 允许匿名访问
registry.antMatchers("/keep-alive/**").permitAll();
}
}

View File

@@ -0,0 +1,97 @@
package com.orion.ops.framework.security.core.utils;
import com.orion.lang.constant.StandardHttpHeader;
import com.orion.lang.utils.Strings;
import com.orion.ops.framework.common.constant.Const;
import com.orion.ops.framework.common.security.LoginUser;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import javax.servlet.http.HttpServletRequest;
import java.util.Collections;
/**
* 安全工具类
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/7/7 11:13
*/
public class SecurityUtils {
private SecurityUtils() {
}
/**
* 获取 token
*
* @param request request
* @return token
*/
public static String obtainAuthorization(HttpServletRequest request) {
String authorization = request.getHeader(StandardHttpHeader.AUTHORIZATION);
// todo mock
authorization = "Bearer 1213";
if (Strings.isEmpty(authorization)) {
return null;
}
if (!authorization.contains(Const.BEARER)) {
return null;
}
return authorization.substring(7).trim();
}
/**
* 获得当前认证信息
*
* @return 认证信息
*/
public static Authentication getAuthentication() {
SecurityContext context = SecurityContextHolder.getContext();
if (context == null) {
return null;
}
return context.getAuthentication();
}
/**
* 获取当前用户
*
* @return 当前用户
*/
public static LoginUser getLoginUser() {
Authentication authentication = getAuthentication();
if (authentication == null) {
return null;
}
return authentication.getPrincipal() instanceof LoginUser ? (LoginUser) authentication.getPrincipal() : null;
}
/**
* 获取当前用户id
*
* @return id
*/
public static Long getLoginUserId() {
LoginUser loginUser = getLoginUser();
return loginUser != null ? loginUser.getId() : null;
}
/**
* 设置当前用户
*
* @param loginUser 登录用户
* @param request 请求
*/
public static void setLoginUser(LoginUser loginUser, HttpServletRequest request) {
// 创建 authentication
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(loginUser, null, Collections.emptyList());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
// 设置上下文
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}

View File

@@ -0,0 +1,22 @@
{
"groups": [
{
"name": "orion.security",
"type": "com.orion.ops.framework.security.config.SecurityConfig",
"sourceType": "com.orion.ops.framework.security.config.SecurityConfig"
}
],
"properties": [
{
"name": "orion.security.password-encoder-length",
"type": "java.lang.Integer",
"description": "加密复杂度.",
"defaultValue": 4
},
{
"name": "orion.security.permit-url",
"type": "java.util.List",
"description": "匿名接口."
}
]
}

View File

@@ -0,0 +1 @@
com.orion.ops.framework.security.config.OrionSecurityAutoConfiguration

View File

@@ -0,0 +1,39 @@
package com.orion.ops.launch.service;
import com.orion.lang.utils.collect.Lists;
import com.orion.ops.framework.common.security.LoginUser;
import com.orion.ops.framework.security.core.service.SecurityFrameworkService;
import org.springframework.stereotype.Component;
/**
* TODO 基建模块实现 现在默认实现
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/7/7 10:57
*/
@Component
public class EmptySecurityImpl implements SecurityFrameworkService {
@Override
public boolean hasPermission(String permission) {
return true;
}
@Override
public boolean hasRole(String role) {
return true;
}
@Override
public LoginUser getUserByToken(String token) {
// TODO MOCK
LoginUser user = new LoginUser();
user.setId(123L);
user.setUsername("username");
user.setNickname("nickname");
user.setRoles(Lists.of("r1", "r2"));
return user;
}
}

View File

@@ -66,7 +66,7 @@ spring:
time-to-live: 1h time-to-live: 1h
output: output:
ansi: ansi:
enabled: detect enabled: DETECT
mybatis-plus: mybatis-plus:
configuration: configuration:
@@ -92,7 +92,7 @@ springdoc:
knife4j: knife4j:
enable: true enable: true
setting: setting:
language: zh_cn language: ZH_CN
logging: logging:
file: file:
@@ -144,3 +144,7 @@ orion:
nameAppendTraceId: true nameAppendTraceId: true
storagePath: ${user.home} storagePath: ${user.home}
basePath: /orion/storage/orion-ops-pro basePath: /orion/storage/orion-ops-pro
security:
password-encoder-length: 4
# 匿名接口
permit-url: