This commit is contained in:
2025-11-28 16:23:32 +08:00
commit a9e0e16c29
826 changed files with 89805 additions and 0 deletions

46
blade-core-secure/pom.xml Normal file
View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>BladeX-Tool</artifactId>
<groupId>org.springblade</groupId>
<version>${revision}</version>
</parent>
<artifactId>blade-core-secure</artifactId>
<name>${project.artifactId}</name>
<version>${project.parent.version}</version>
<packaging>jar</packaging>
<dependencies>
<!--Blade-->
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-starter-auth</artifactId>
</dependency>
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-starter-cache</artifactId>
</dependency>
<!--Jdbc-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<exclusions>
<exclusion>
<artifactId>tomcat-jdbc</artifactId>
<groupId>org.apache.tomcat</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- Auto -->
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-core-auto</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,50 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.secure.annotation;
import java.lang.annotation.*;
/**
* 权限注解 用于检查权限 规定访问权限
*
* @example @PreAuth("#userVO.id<10")
* @example @PreAuth("hasRole(#test, #test1)")
* @example @PreAuth("hasPermission(#test) and @PreAuth.hasPermission(#test)")
* @author Chill
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface PreAuth {
/**
* Spring el表达式
*/
String value();
}

View File

@@ -0,0 +1,132 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.secure.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springblade.core.secure.annotation.PreAuth;
import org.springblade.core.secure.auth.AuthFun;
import org.springblade.core.secure.exception.SecureException;
import org.springblade.core.tool.api.ResultCode;
import org.springblade.core.tool.utils.ClassUtil;
import org.springblade.core.tool.utils.StringUtil;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.core.MethodParameter;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.lang.NonNull;
import java.lang.reflect.Method;
/**
* AOP 鉴权
*
* @author Chill
*/
@Aspect
public class AuthAspect implements ApplicationContextAware {
/**
* 表达式处理
*/
private static final ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();
/**
* 切 方法 和 类上的 @PreAuth 注解
*
* @param point 切点
* @return Object
* @throws Throwable 没有权限的异常
*/
@Around(
"@annotation(org.springblade.core.secure.annotation.PreAuth) || " +
"@within(org.springblade.core.secure.annotation.PreAuth)"
)
public Object preAuth(ProceedingJoinPoint point) throws Throwable {
if (handleAuth(point)) {
return point.proceed();
}
throw new SecureException(ResultCode.UN_AUTHORIZED);
}
/**
* 处理权限
*
* @param point 切点
*/
private boolean handleAuth(ProceedingJoinPoint point) {
MethodSignature ms = (MethodSignature) point.getSignature();
Method method = ms.getMethod();
// 读取权限注解,优先方法上,没有则读取类
PreAuth preAuth = ClassUtil.getAnnotation(method, PreAuth.class);
// 判断表达式
String condition = preAuth.value();
if (StringUtil.isNotBlank(condition)) {
Expression expression = EXPRESSION_PARSER.parseExpression(condition);
// 方法参数值
Object[] args = point.getArgs();
StandardEvaluationContext context = getEvaluationContext(method, args);
return expression.getValue(context, Boolean.class);
}
return false;
}
/**
* 获取方法上的参数
*
* @param method 方法
* @param args 变量
* @return {SimpleEvaluationContext}
*/
private StandardEvaluationContext getEvaluationContext(Method method, Object[] args) {
// 初始化Sp el表达式上下文并设置 AuthFun
StandardEvaluationContext context = new StandardEvaluationContext(new AuthFun());
// 设置表达式支持spring bean
context.setBeanResolver(new BeanFactoryResolver(applicationContext));
for (int i = 0; i < args.length; i++) {
// 读取方法参数
MethodParameter methodParam = ClassUtil.getMethodParameter(method, i);
// 设置方法 参数名和值 为sp el变量
context.setVariable(methodParam.getParameterName(), args[i]);
}
return context;
}
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}

View File

@@ -0,0 +1,223 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.secure.auth;
import org.springblade.core.jwt.JwtUtil;
import org.springblade.core.launch.constant.TokenConstant;
import org.springblade.core.secure.BladeUser;
import org.springblade.core.secure.handler.IPermissionHandler;
import org.springblade.core.secure.utils.AuthUtil;
import org.springblade.core.tool.constant.RoleConstant;
import org.springblade.core.tool.utils.*;
import jakarta.servlet.http.HttpServletRequest;
import java.util.Objects;
/**
* 权限判断
*
* @author Chill
*/
public class AuthFun {
/**
* 权限校验处理器
*/
private static IPermissionHandler permissionHandler;
private static IPermissionHandler getPermissionHandler() {
if (permissionHandler == null) {
permissionHandler = SpringUtil.getBean(IPermissionHandler.class);
}
return permissionHandler;
}
/**
* 判断角色是否具有接口权限
*
* @return {boolean}
*/
public boolean permissionAll() {
return getPermissionHandler().permissionAll();
}
/**
* 判断角色是否具有接口权限
*
* @param permission 权限编号
* @return {boolean}
*/
public boolean hasPermission(String permission) {
return getPermissionHandler().hasPermission(permission);
}
/**
* 放行所有请求
*
* @return {boolean}
*/
public boolean permitAll() {
return true;
}
/**
* 只有超管角色才可访问
*
* @return {boolean}
*/
public boolean denyAll() {
return hasRole(RoleConstant.ADMIN);
}
/**
* 是否已授权
*
* @return {boolean}
*/
public boolean hasAuth() {
return Func.isNotEmpty(AuthUtil.getUser());
}
/**
* 是否有时间授权
*
* @param start 开始时间
* @param end 结束时间
* @return {boolean}
*/
public boolean hasTimeAuth(Integer start, Integer end) {
Integer hour = DateUtil.hour();
return hour >= start && hour <= end;
}
/**
* 判断是否有该角色权限
*
* @param role 单角色
* @return {boolean}
*/
public boolean hasRole(String role) {
return hasAnyRole(role);
}
/**
* 判断是否具有所有角色权限
*
* @param role 角色集合
* @return {boolean}
*/
public boolean hasAllRole(String... role) {
for (String r : role) {
if (!hasRole(r)) {
return false;
}
}
return true;
}
/**
* 判断是否有该角色权限
*
* @param role 角色集合
* @return {boolean}
*/
public boolean hasAnyRole(String... role) {
BladeUser user = AuthUtil.getUser();
if (user == null) {
return false;
}
String userRole = user.getRoleName();
if (StringUtil.isBlank(userRole)) {
return false;
}
String[] roles = Func.toStrArray(userRole);
for (String r : role) {
if (CollectionUtil.contains(roles, r)) {
return true;
}
}
return false;
}
/**
* 判断请求是否为加密token
*
* @return {boolean}
*/
public boolean hasCrypto() {
HttpServletRequest request = WebUtil.getRequest();
String auth = Objects.requireNonNull(request).getHeader(TokenConstant.HEADER);
return JwtUtil.isCrypto(
StringUtil.isNotBlank(auth) ? auth : request.getParameter(TokenConstant.HEADER)
);
}
/**
* 判断令牌是否符合严格模式
*
* @return {boolean}
*/
public boolean hasStrictToken() {
BladeUser currentUser = AuthUtil.getUser();
return AuthUtil.userIncomplete(currentUser);
}
/**
* 判断是否包含安全请求头
*
* @return {boolean}
*/
public boolean hasStrictHeader() {
return !AuthUtil.secureHeaderIncomplete();
}
/**
* 判断是否有该请求头
*
* @param header 请求头
* @return {boolean}
*/
public boolean hasHeader(String header) {
HttpServletRequest request = WebUtil.getRequest();
String value = Objects.requireNonNull(request).getHeader(header);
return StringUtil.isNotBlank(value);
}
/**
* 判断是否有该请求头
*
* @param header 请求头
* @param key 请求值
* @return {boolean}
*/
public boolean hasHeader(String header, String key) {
HttpServletRequest request = WebUtil.getRequest();
String value = Objects.requireNonNull(request).getHeader(header);
return StringUtil.equals(value, key);
}
}

View File

@@ -0,0 +1,71 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.secure.config;
import lombok.AllArgsConstructor;
import org.springblade.core.secure.handler.BladePermissionHandler;
import org.springblade.core.secure.handler.IPermissionHandler;
import org.springblade.core.secure.handler.ISecureHandler;
import org.springblade.core.secure.handler.SecureHandlerHandler;
import org.springblade.core.secure.registry.SecureRegistry;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.core.annotation.Order;
import org.springframework.jdbc.core.JdbcTemplate;
/**
* secure注册默认配置
*
* @author Chill
*/
@Order
@AutoConfiguration(before = SecureConfiguration.class)
@AllArgsConstructor
public class RegistryConfiguration {
private final JdbcTemplate jdbcTemplate;
@Bean
@ConditionalOnMissingBean(SecureRegistry.class)
public SecureRegistry secureRegistry() {
return new SecureRegistry();
}
@Bean
@ConditionalOnMissingBean(ISecureHandler.class)
public ISecureHandler secureHandler() {
return new SecureHandlerHandler();
}
@Bean
@ConditionalOnMissingBean(IPermissionHandler.class)
public IPermissionHandler permissionHandler() {
return new BladePermissionHandler(jdbcTemplate);
}
}

View File

@@ -0,0 +1,135 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.secure.config;
import lombok.AllArgsConstructor;
import org.springblade.core.launch.props.BladeProperties;
import org.springblade.core.secure.aspect.AuthAspect;
import org.springblade.core.secure.handler.ISecureHandler;
import org.springblade.core.secure.props.AuthSecure;
import org.springblade.core.secure.props.BasicSecure;
import org.springblade.core.secure.props.BladeSecureProperties;
import org.springblade.core.secure.props.SignSecure;
import org.springblade.core.secure.registry.SecureRegistry;
import org.springblade.core.tool.utils.StringPool;
import org.springblade.core.tool.utils.StringUtil;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.core.annotation.Order;
import org.springframework.lang.NonNull;
import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.List;
import java.util.stream.Collectors;
/**
* 安全配置类
*
* @author Chill
*/
@Order
@AutoConfiguration
@AllArgsConstructor
@EnableConfigurationProperties({BladeSecureProperties.class})
public class SecureConfiguration implements WebMvcConfigurer {
private final SecureRegistry secureRegistry;
private final BladeProperties bladeProperties;
private final BladeSecureProperties secureProperties;
private final ISecureHandler secureHandler;
@Override
public void addInterceptors(@NonNull InterceptorRegistry registry) {
// 设置请求授权
if (secureRegistry.isAuthEnabled() || secureProperties.getAuthEnabled()) {
List<AuthSecure> authSecures = this.secureRegistry.addAuthPatterns(secureProperties.getAuth()).getAuthSecures();
if (!authSecures.isEmpty()) {
registry.addInterceptor(secureHandler.authInterceptor(secureProperties, authSecures));
// 设置路径放行
secureRegistry.excludePathPatterns(authSecures.stream().map(AuthSecure::getPattern).collect(Collectors.toList()));
}
}
// 设置基础认证授权
if (secureRegistry.isBasicEnabled() || secureProperties.getBasicEnabled()) {
List<BasicSecure> basicSecures = this.secureRegistry.addBasicPatterns(secureProperties.getBasic()).getBasicSecures();
if (!basicSecures.isEmpty()) {
registry.addInterceptor(secureHandler.basicInterceptor(basicSecures));
// 设置路径放行
secureRegistry.excludePathPatterns(basicSecures.stream().map(BasicSecure::getPattern).collect(Collectors.toList()));
}
}
// 设置签名认证授权
if (secureRegistry.isSignEnabled() || secureProperties.getSignEnabled()) {
List<SignSecure> signSecures = this.secureRegistry.addSignPatterns(secureProperties.getSign()).getSignSecures();
if (!signSecures.isEmpty()) {
registry.addInterceptor(secureHandler.signInterceptor(signSecures));
// 设置路径放行
secureRegistry.excludePathPatterns(signSecures.stream().map(SignSecure::getPattern).collect(Collectors.toList()));
}
}
// 设置客户端授权
if (secureRegistry.isClientEnabled() || secureProperties.getClientEnabled()) {
secureProperties.getClient().forEach(
clientSecure -> registry.addInterceptor(secureHandler.clientInterceptor(clientSecure.getClientId()))
.addPathPatterns(clientSecure.getPathPatterns())
);
}
// 设置令牌严格模式
if (!secureRegistry.isStrictToken()) {
secureProperties.setStrictToken(false);
}
// 设置请求头严格模式
if (!secureRegistry.isStrictHeader()) {
secureProperties.setStrictHeader(false);
}
// 设置路径放行
if (secureRegistry.isEnabled() || secureProperties.getEnabled()) {
InterceptorRegistration interceptorRegistration = registry.addInterceptor(secureHandler.tokenInterceptor(secureProperties))
.excludePathPatterns(secureRegistry.getExcludePatterns())
.excludePathPatterns(secureRegistry.getDefaultExcludePatterns())
.excludePathPatterns(secureProperties.getSkipUrl());
// 宽松模式下获取放行路径且再新建一套自定义放行路径用于处理cloud网关虚拟路径导致未匹配的问题
// 严格模式下不予处理应严格按照cloud和boot的路由进行匹配
if (!secureProperties.getStrictToken()) {
interceptorRegistration.excludePathPatterns(secureProperties.getSkipUrl().stream()
.map(url -> StringUtil.removePrefix(url, StringPool.SLASH + bladeProperties.getName())).toList());
}
}
}
@Bean
public AuthAspect authAspect() {
return new AuthAspect();
}
}

View File

@@ -0,0 +1,95 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.secure.constant;
/**
* PreAuth权限表达式
*
* @author Chill
*/
public interface AuthConstant {
/**
* 超管别名
*/
String ADMINISTRATOR = "administrator";
/**
* 是有超管角色
*/
String HAS_ROLE_ADMINISTRATOR = "hasRole('" + ADMINISTRATOR + "')";
/**
* 管理员别名
*/
String ADMIN = "admin";
/**
* 是否有管理员角色
*/
String HAS_ROLE_ADMIN = "hasAnyRole('" + ADMINISTRATOR + "', '" + ADMIN + "')";
/**
* 用户别名
*/
String USER = "user";
/**
* 是否有用户角色
*/
String HAS_ROLE_USER = "hasRole('" + USER + "')";
/**
* 测试别名
*/
String TEST = "test";
/**
* 是否有测试角色
*/
String HAS_ROLE_TEST = "hasRole('" + TEST + "')";
/**
* 放行所有请求
*/
String PERMIT_ALL = "permitAll()";
/**
* 只有超管才能访问
*/
String DENY_ALL = "denyAll()";
/**
* 对所有请求进行接口权限校验
*/
String PERMISSION_ALL = "permissionAll()";
/**
* 是否对token加密传输
*/
String HAS_CRYPTO = "hasCrypto()";
}

View File

@@ -0,0 +1,71 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.secure.constant;
import org.springblade.core.tool.utils.StringUtil;
/**
* 权限校验常量
*
* @author Chill
*/
public interface PermissionConstant {
/**
* 获取角色所有的权限编号
*
* @param size 数量
* @return string
*/
static String permissionAllStatement(int size) {
return "select scope_path as path from blade_scope_api where id in (select scope_id from blade_role_scope where scope_category = 2 and role_id in (" + buildHolder(size) + "))";
}
/**
* 获取角色指定的权限编号
*
* @param size 数量
* @return string
*/
static String permissionStatement(int size) {
return "select resource_code as code from blade_scope_api where resource_code = ? and id in (select scope_id from blade_role_scope where scope_category = 2 and role_id in (" + buildHolder(size) + "))";
}
/**
* 获取Sql占位符
*
* @param size 数量
* @return String
*/
static String buildHolder(int size) {
StringBuilder builder = StringUtil.builder();
for (int i = 0; i < size; i++) {
builder.append("?,");
}
return StringUtil.removeSuffix(builder.toString(), ",");
}
}

View File

@@ -0,0 +1,91 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.secure.constant;
/**
* 授权校验常量
*
* @author Chill
*/
public interface SecureConstant {
/**
* 认证请求头
*/
String BASIC_HEADER_KEY = "Authorization";
/**
* 认证请求头前缀
*/
String BASIC_HEADER_PREFIX = "Basic ";
/**
* 认证请求头前缀
*/
String BASIC_HEADER_PREFIX_EXT = "Basic%20";
/**
* 认证请求头
*/
String BASIC_REALM_HEADER_KEY = "WWW-Authenticate";
/**
* 认证请求值
*/
String BASIC_REALM_HEADER_VALUE = "basic realm=\"no auth\"";
/**
* 授权认证失败
*/
String AUTHORIZATION_FAILED = "授权认证失败";
/**
* 签名认证失败
*/
String SIGN_FAILED = "签名认证失败";
/**
* 用户信息不完整
*/
String USER_INCOMPLETE = "用户信息不完整,签名认证失败";
/**
* 请求头信息不完整
*/
String SECURE_HEADER_INCOMPLETE = "请求头信息不完整,签名认证失败";
/**
* 客户端令牌解析失败
*/
String CLIENT_TOKEN_PARSE_FAILED = "客户端令牌解析失败";
/**
* 客户端令牌不合法
*/
String INVALID_CLIENT_TOKEN = "客户端令牌不合法";
/**
* Authorization未找到
*/
String AUTHORIZATION_NOT_FOUND = "请求头中未找到 [Authorization] 信息";
}

View File

@@ -0,0 +1,119 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.secure.handler;
import lombok.AllArgsConstructor;
import org.springblade.core.cache.utils.CacheUtil;
import org.springblade.core.secure.BladeUser;
import org.springblade.core.secure.utils.AuthUtil;
import org.springblade.core.tool.utils.Func;
import org.springblade.core.tool.utils.StringPool;
import org.springblade.core.tool.utils.WebUtil;
import org.springframework.jdbc.core.JdbcTemplate;
import jakarta.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static org.springblade.core.cache.constant.CacheConstant.SYS_CACHE;
import static org.springblade.core.secure.constant.PermissionConstant.permissionAllStatement;
import static org.springblade.core.secure.constant.PermissionConstant.permissionStatement;
/**
* 默认授权校验类
*
* @author Chill
*/
@AllArgsConstructor
public class BladePermissionHandler implements IPermissionHandler {
private static final String SCOPE_CACHE_CODE = "apiScope:code:";
private final JdbcTemplate jdbcTemplate;
@Override
public boolean permissionAll() {
HttpServletRequest request = WebUtil.getRequest();
BladeUser user = AuthUtil.getUser();
if (request == null || user == null) {
return false;
}
String uri = request.getRequestURI();
List<String> paths = permissionPath(user.getRoleId());
if (paths.size() == 0) {
return false;
}
return paths.stream().anyMatch(uri::contains);
}
@Override
public boolean hasPermission(String permission) {
HttpServletRequest request = WebUtil.getRequest();
BladeUser user = AuthUtil.getUser();
if (request == null || user == null) {
return false;
}
List<String> codes = permissionCode(permission, user.getRoleId());
return codes.size() != 0;
}
/**
* 获取接口权限地址
*
* @param roleId 角色id
* @return permissions
*/
private List<String> permissionPath(String roleId) {
List<String> permissions = CacheUtil.get(SYS_CACHE, SCOPE_CACHE_CODE, roleId, List.class, Boolean.FALSE);
if (permissions == null) {
List<Long> roleIds = Func.toLongList(roleId);
permissions = jdbcTemplate.queryForList(permissionAllStatement(roleIds.size()), roleIds.toArray(), String.class);
CacheUtil.put(SYS_CACHE, SCOPE_CACHE_CODE, roleId, permissions, Boolean.FALSE);
}
return permissions;
}
/**
* 获取接口权限信息
*
* @param permission 权限编号
* @param roleId 角色id
* @return permissions
*/
private List<String> permissionCode(String permission, String roleId) {
List<String> permissions = CacheUtil.get(SYS_CACHE, SCOPE_CACHE_CODE, permission + StringPool.COLON + roleId, List.class, Boolean.FALSE);
if (permissions == null) {
List<Object> args = new ArrayList<>(Collections.singletonList(permission));
List<Long> roleIds = Func.toLongList(roleId);
args.addAll(roleIds);
permissions = jdbcTemplate.queryForList(permissionStatement(roleIds.size()), args.toArray(), String.class);
CacheUtil.put(SYS_CACHE, SCOPE_CACHE_CODE, permission + StringPool.COLON + roleId, permissions, Boolean.FALSE);
}
return permissions;
}
}

View File

@@ -0,0 +1,50 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.secure.handler;
/**
* 权限校验通用接口
*
* @author Chill
*/
public interface IPermissionHandler {
/**
* 判断角色是否具有接口权限
*
* @return {boolean}
*/
boolean permissionAll();
/**
* 判断角色是否具有接口权限
*
* @param permission 权限编号
* @return {boolean}
*/
boolean hasPermission(String permission);
}

View File

@@ -0,0 +1,83 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.secure.handler;
import org.springblade.core.secure.props.AuthSecure;
import org.springblade.core.secure.props.BasicSecure;
import org.springblade.core.secure.props.BladeSecureProperties;
import org.springblade.core.secure.props.SignSecure;
import org.springframework.web.servlet.HandlerInterceptor;
import java.util.List;
/**
* secure 拦截器集合
*
* @author Chill
*/
public interface ISecureHandler {
/**
* token拦截器
*
* @param secureProperties 授权配置
* @return tokenInterceptor
*/
HandlerInterceptor tokenInterceptor(BladeSecureProperties secureProperties);
/**
* auth拦截器
*
* @param authSecures 授权集合
* @return HandlerInterceptor
*/
HandlerInterceptor authInterceptor(BladeSecureProperties secureProperties, List<AuthSecure> authSecures);
/**
* basic拦截器
*
* @param basicSecures 基础认证集合
* @return HandlerInterceptor
*/
HandlerInterceptor basicInterceptor(List<BasicSecure> basicSecures);
/**
* sign拦截器
*
* @param signSecures 签名认证集合
* @return HandlerInterceptor
*/
HandlerInterceptor signInterceptor(List<SignSecure> signSecures);
/**
* client拦截器
*
* @param clientId 客户端id
* @return clientInterceptor
*/
HandlerInterceptor clientInterceptor(String clientId);
}

View File

@@ -0,0 +1,69 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.secure.handler;
import org.springblade.core.secure.interceptor.*;
import org.springblade.core.secure.props.AuthSecure;
import org.springblade.core.secure.props.BasicSecure;
import org.springblade.core.secure.props.BladeSecureProperties;
import org.springblade.core.secure.props.SignSecure;
import org.springframework.web.servlet.HandlerInterceptor;
import java.util.List;
/**
* Secure处理器
*
* @author Chill
*/
public class SecureHandlerHandler implements ISecureHandler {
@Override
public HandlerInterceptor tokenInterceptor(BladeSecureProperties secureProperties) {
return new TokenInterceptor(secureProperties);
}
@Override
public HandlerInterceptor authInterceptor(BladeSecureProperties secureProperties, List<AuthSecure> authSecures) {
return new AuthInterceptor(authSecures);
}
@Override
public HandlerInterceptor basicInterceptor(List<BasicSecure> basicSecures) {
return new BasicInterceptor(basicSecures);
}
@Override
public HandlerInterceptor signInterceptor(List<SignSecure> signSecures) {
return new SignInterceptor(signSecures);
}
@Override
public HandlerInterceptor clientInterceptor(String clientId) {
return new ClientInterceptor(clientId);
}
}

View File

@@ -0,0 +1,117 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.secure.interceptor;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.secure.auth.AuthFun;
import org.springblade.core.secure.props.AuthSecure;
import org.springblade.core.secure.provider.HttpMethod;
import org.springblade.core.secure.provider.ResponseProvider;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.lang.NonNull;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.servlet.HandlerInterceptor;
import java.util.List;
import static org.springblade.core.secure.constant.SecureConstant.AUTHORIZATION_FAILED;
/**
* 自定义授权拦截器校验
*
* @author Chill
*/
@Slf4j
@AllArgsConstructor
public class AuthInterceptor implements HandlerInterceptor {
/**
* 表达式处理
*/
private static final ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();
private static final EvaluationContext EVALUATION_CONTEXT = new StandardEvaluationContext(new AuthFun());
private static final AntPathMatcher ANT_PATH_MATCHER = new AntPathMatcher();
/**
* 授权集合
*/
private final List<AuthSecure> authSecures;
@Override
public boolean preHandle(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler) {
boolean check = authSecures.stream().filter(authSecure -> checkAuth(request, authSecure)).findFirst().map(
authSecure -> checkExpression(authSecure.getExpression())
).orElse(Boolean.TRUE);
if (!check) {
ResponseProvider.logAuthFailure(request, response, AUTHORIZATION_FAILED);
return false;
}
return true;
}
/**
* 检测授权
*/
private boolean checkAuth(HttpServletRequest request, AuthSecure authSecure) {
return checkMethod(request, authSecure.getMethod()) && checkPath(request, authSecure.getPattern());
}
/**
* 检测请求方法
*/
private boolean checkMethod(HttpServletRequest request, HttpMethod method) {
return method == HttpMethod.ALL || (
method != null && method == HttpMethod.of(request.getMethod())
);
}
/**
* 检测路径匹配
*/
private boolean checkPath(HttpServletRequest request, String pattern) {
String servletPath = request.getServletPath();
String pathInfo = request.getPathInfo();
if (pathInfo != null && !pathInfo.isEmpty()) {
servletPath = servletPath + pathInfo;
}
return ANT_PATH_MATCHER.match(pattern, servletPath);
}
/**
* 检测表达式
*/
private boolean checkExpression(String expression) {
Boolean result = EXPRESSION_PARSER.parseExpression(expression).getValue(EVALUATION_CONTEXT, Boolean.class);
return result != null ? result : false;
}
}

View File

@@ -0,0 +1,122 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.secure.interceptor;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.secure.props.BasicSecure;
import org.springblade.core.secure.provider.HttpMethod;
import org.springblade.core.secure.provider.ResponseProvider;
import org.springblade.core.secure.utils.SecureUtil;
import org.springblade.core.tool.jackson.JsonUtil;
import org.springblade.core.tool.utils.WebUtil;
import org.springframework.lang.NonNull;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.servlet.HandlerInterceptor;
import java.util.List;
import static org.springblade.core.secure.constant.SecureConstant.BASIC_REALM_HEADER_KEY;
import static org.springblade.core.secure.constant.SecureConstant.BASIC_REALM_HEADER_VALUE;
/**
* 基础认证拦截器校验
*
* @author Chill
*/
@Slf4j
@AllArgsConstructor
public class BasicInterceptor implements HandlerInterceptor {
/**
* 表达式匹配
*/
private static final AntPathMatcher ANT_PATH_MATCHER = new AntPathMatcher();
/**
* 授权集合
*/
private final List<BasicSecure> basicSecures;
@Override
public boolean preHandle(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler) {
boolean check = basicSecures.stream().filter(basicSecure -> checkAuth(request, basicSecure)).findFirst().map(
authSecure -> checkBasic(authSecure.getUsername(), authSecure.getPassword())
).orElse(Boolean.TRUE);
if (!check) {
log.warn("授权认证失败,请求接口:{}请求IP{},请求参数:{}", request.getRequestURI(), WebUtil.getIP(request), JsonUtil.toJson(request.getParameterMap()));
response.setHeader(BASIC_REALM_HEADER_KEY, BASIC_REALM_HEADER_VALUE);
ResponseProvider.write(response);
return false;
}
return true;
}
/**
* 检测授权
*/
private boolean checkAuth(HttpServletRequest request, BasicSecure basicSecure) {
return checkMethod(request, basicSecure.getMethod()) && checkPath(request, basicSecure.getPattern());
}
/**
* 检测请求方法
*/
private boolean checkMethod(HttpServletRequest request, HttpMethod method) {
return method == HttpMethod.ALL || (
method != null && method == HttpMethod.of(request.getMethod())
);
}
/**
* 检测路径匹配
*/
private boolean checkPath(HttpServletRequest request, String pattern) {
String servletPath = request.getServletPath();
String pathInfo = request.getPathInfo();
if (pathInfo != null && !pathInfo.isEmpty()) {
servletPath = servletPath + pathInfo;
}
return ANT_PATH_MATCHER.match(pattern, servletPath);
}
/**
* 检测表达式
*/
private boolean checkBasic(String username, String password) {
try {
String[] tokens = SecureUtil.extractAndDecodeAuthorization();
return username.equals(tokens[0]) && password.equals(tokens[1]);
} catch (Exception e) {
log.warn("授权认证失败,错误信息:{}", e.getMessage());
return false;
}
}
}

View File

@@ -0,0 +1,69 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.secure.interceptor;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.secure.BladeUser;
import org.springblade.core.secure.provider.ResponseProvider;
import org.springblade.core.secure.utils.AuthUtil;
import org.springblade.core.secure.utils.SecureUtil;
import org.springblade.core.tool.jackson.JsonUtil;
import org.springblade.core.tool.utils.StringUtil;
import org.springblade.core.tool.utils.WebUtil;
import org.springframework.lang.NonNull;
import org.springframework.web.servlet.HandlerInterceptor;
/**
* 客户端校验拦截器
*
* @author Chill
*/
@Slf4j
@AllArgsConstructor
public class ClientInterceptor implements HandlerInterceptor {
private final String clientId;
@Override
public boolean preHandle(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler) {
BladeUser user = AuthUtil.getUser();
boolean check = (
user != null &&
StringUtil.equals(clientId, SecureUtil.getClientId()) &&
StringUtil.equals(clientId, user.getClientId())
);
if (!check) {
log.warn("客户端认证失败,请求接口:{}请求IP{},请求参数:{}", request.getRequestURI(), WebUtil.getIP(request), JsonUtil.toJson(request.getParameterMap()));
ResponseProvider.write(response);
return false;
}
return true;
}
}

View File

@@ -0,0 +1,179 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.secure.interceptor;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.secure.props.SignSecure;
import org.springblade.core.secure.provider.HttpMethod;
import org.springblade.core.secure.provider.ResponseProvider;
import org.springblade.core.tool.jackson.JsonUtil;
import org.springblade.core.tool.utils.DateUtil;
import org.springblade.core.tool.utils.DigestUtil;
import org.springblade.core.tool.utils.Func;
import org.springblade.core.tool.utils.WebUtil;
import org.springframework.lang.NonNull;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.servlet.HandlerInterceptor;
import java.time.Duration;
import java.util.Date;
import java.util.List;
/**
* 签名认证拦截器校验
*
* @author Chill
*/
@Slf4j
@AllArgsConstructor
public class SignInterceptor implements HandlerInterceptor {
/**
* 表达式匹配
*/
private static final AntPathMatcher ANT_PATH_MATCHER = new AntPathMatcher();
/**
* 授权集合
*/
private final List<SignSecure> signSecures;
/**
* 请求时间
*/
private final static String TIMESTAMP = "timestamp";
/**
* 随机数
*/
private final static String NONCE = "nonce";
/**
* 时间随机数组合加密串
*/
private final static String SIGNATURE = "signature";
/**
* sha1加密方式
*/
private final static String SHA1 = "sha1";
/**
* md5加密方式
*/
private final static String MD5 = "md5";
/**
* 时间差最小值
*/
private final static Integer SECOND_MIN = 0;
/**
* 时间差最大值
*/
private final static Integer SECOND_MAX = 10;
@Override
public boolean preHandle(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler) {
boolean check = signSecures.stream().filter(signSecure -> checkAuth(request, signSecure)).findFirst().map(
authSecure -> checkSign(authSecure.getCrypto())
).orElse(Boolean.TRUE);
if (!check) {
log.warn("授权认证失败,请求接口:{}请求IP{},请求参数:{}", request.getRequestURI(), WebUtil.getIP(request), JsonUtil.toJson(request.getParameterMap()));
ResponseProvider.write(response);
return false;
}
return true;
}
/**
* 检测授权
*/
private boolean checkAuth(HttpServletRequest request, SignSecure signSecure) {
return checkMethod(request, signSecure.getMethod()) && checkPath(request, signSecure.getPattern());
}
/**
* 检测请求方法
*/
private boolean checkMethod(HttpServletRequest request, HttpMethod method) {
return method == HttpMethod.ALL || (
method != null && method == HttpMethod.of(request.getMethod())
);
}
/**
* 检测路径匹配
*/
private boolean checkPath(HttpServletRequest request, String pattern) {
String servletPath = request.getServletPath();
String pathInfo = request.getPathInfo();
if (pathInfo != null && pathInfo.length() > 0) {
servletPath = servletPath + pathInfo;
}
return ANT_PATH_MATCHER.match(pattern, servletPath);
}
/**
* 检测表达式
*/
private boolean checkSign(String crypto) {
try {
HttpServletRequest request = WebUtil.getRequest();
if (request == null) {
return false;
}
// 获取头部动态签名信息
String timestamp = request.getHeader(TIMESTAMP);
// 判断是否在合法时间段
long seconds = Duration.between(new Date(Func.toLong(timestamp)).toInstant(), DateUtil.now().toInstant()).getSeconds();
if (seconds < SECOND_MIN || seconds > SECOND_MAX) {
log.warn("授权认证失败,错误信息:{}", "请求时间戳非法");
return false;
}
String nonce = request.getHeader(NONCE);
String signature = request.getHeader(SIGNATURE);
// 加密签名比对,可自行拓展加密规则
String sign;
if (crypto.equals(MD5)) {
sign = DigestUtil.md5Hex(timestamp + nonce);
} else if (crypto.equals(SHA1)) {
sign = DigestUtil.sha1Hex(timestamp + nonce);
} else {
sign = DigestUtil.sha1Hex(timestamp + nonce);
}
return sign.equalsIgnoreCase(signature);
} catch (Exception e) {
log.warn("授权认证失败,错误信息:{}", e.getMessage());
return false;
}
}
}

View File

@@ -0,0 +1,98 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.secure.interceptor;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.secure.BladeUser;
import org.springblade.core.secure.props.BladeSecureProperties;
import org.springblade.core.secure.provider.ResponseProvider;
import org.springblade.core.secure.utils.AuthUtil;
import org.springframework.lang.NonNull;
import org.springframework.web.servlet.HandlerInterceptor;
import java.util.ArrayList;
import java.util.List;
import static org.springblade.core.secure.constant.SecureConstant.*;
/**
* 签名认证拦截器
*
* @author Chill
*/
@Slf4j
@AllArgsConstructor
public class TokenInterceptor implements HandlerInterceptor {
private final BladeSecureProperties secureProperties;
private final static List<String> DEFAULT_STRICT_SKIP_URL = new ArrayList<>();
static {
DEFAULT_STRICT_SKIP_URL.add("/menu/routes");
DEFAULT_STRICT_SKIP_URL.add("/menu/buttons");
DEFAULT_STRICT_SKIP_URL.add("/menu/top-menu");
DEFAULT_STRICT_SKIP_URL.add("/blade-system/menu/routes");
DEFAULT_STRICT_SKIP_URL.add("/blade-system/menu/buttons");
DEFAULT_STRICT_SKIP_URL.add("/blade-system/menu/top-menu");
}
@Override
public boolean preHandle(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler) {
BladeUser currentUser = AuthUtil.getUser();
if (currentUser == null) {
ResponseProvider.logAuthFailure(request, response, SIGN_FAILED);
return false;
}
if (checkStrictToken(request, currentUser)) {
ResponseProvider.logAuthFailure(request, response, USER_INCOMPLETE);
return false;
}
if (checkStrictHeader()) {
ResponseProvider.logAuthFailure(request, response, SECURE_HEADER_INCOMPLETE);
return false;
}
return true;
}
private boolean checkStrictToken(HttpServletRequest request, BladeUser currentUser) {
String requestUrl = request.getRequestURI(); // 获取当前请求的URL
boolean skip = DEFAULT_STRICT_SKIP_URL.stream()
.anyMatch(requestUrl::equals); // 判断当前请求的URL是否在跳过列表中
// 如果请求的URL需要跳过检查或者不需要严格检查Token则返回false
return !skip && secureProperties.getStrictToken() && AuthUtil.userIncomplete(currentUser);
}
private boolean checkStrictHeader() {
return secureProperties.getStrictHeader() && AuthUtil.secureHeaderIncomplete();
}
}

View File

@@ -0,0 +1,54 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.secure.props;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springblade.core.secure.provider.HttpMethod;
/**
* 自定义授权规则
*
* @author Chill
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class AuthSecure {
/**
* 请求方法
*/
private HttpMethod method;
/**
* 请求路径
*/
private String pattern;
/**
* 规则表达式
*/
private String expression;
}

View File

@@ -0,0 +1,60 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.secure.props;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springblade.core.secure.provider.HttpMethod;
/**
* 基础授权规则
*
* @author Chill
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class BasicSecure {
/**
* 请求方法
*/
private HttpMethod method;
/**
* 请求路径
*/
private String pattern;
/**
* 客户端id
*/
private String username;
/**
* 客户端密钥
*/
private String password;
}

View File

@@ -0,0 +1,103 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.secure.props;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.ArrayList;
import java.util.List;
/**
* 客户端校验配置
*
* @author Chill
*/
@Data
@ConfigurationProperties("blade.secure")
public class BladeSecureProperties {
/**
* 开启鉴权规则
*/
private Boolean enabled = false;
/**
* 开启令牌严格模式
*/
private Boolean strictToken = true;
/**
* 开启请求头严格模式
*/
private Boolean strictHeader = true;
/**
* 鉴权放行请求
*/
private final List<String> skipUrl = new ArrayList<>();
/**
* 开启授权规则
*/
private Boolean authEnabled = true;
/**
* 授权配置
*/
private final List<AuthSecure> auth = new ArrayList<>();
/**
* 开启基础认证规则
*/
private Boolean basicEnabled = true;
/**
* 基础认证配置
*/
private final List<BasicSecure> basic = new ArrayList<>();
/**
* 开启签名认证规则
*/
private Boolean signEnabled = true;
/**
* 签名认证配置
*/
private final List<SignSecure> sign = new ArrayList<>();
/**
* 开启客户端规则
*/
private Boolean clientEnabled = true;
/**
* 客户端配置
*/
private final List<ClientSecure> client = new ArrayList<>();
}

View File

@@ -0,0 +1,50 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.secure.props;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* 客户端令牌认证信息
*
* @author Chill
*/
@Data
public class ClientSecure {
/**
* 客户端ID
*/
private String clientId;
/**
* 路径匹配
*/
private final List<String> pathPatterns = new ArrayList<>();
}

View File

@@ -0,0 +1,56 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.secure.props;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springblade.core.secure.provider.HttpMethod;
/**
* 签名授权规则
*
* @author Chill
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SignSecure {
/**
* 请求方法
*/
private HttpMethod method;
/**
* 请求路径
*/
private String pattern;
/**
* 加密方式
*/
private String crypto;
}

View File

@@ -0,0 +1,51 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.secure.provider;
/**
* HttpMethod枚举类
*
* @author Chill
*/
public enum HttpMethod {
/**
* 请求方法集合
*/
GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE, ALL;
/**
* 匹配枚举
*/
public static HttpMethod of(String method) {
try {
return valueOf(method);
} catch (Exception exception) {
return null;
}
}
}

View File

@@ -0,0 +1,74 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.secure.provider;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.api.ResultCode;
import org.springblade.core.tool.constant.BladeConstant;
import org.springblade.core.tool.jackson.JsonUtil;
import org.springblade.core.tool.utils.WebUtil;
import org.springframework.http.MediaType;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Map;
import java.util.Objects;
/**
* ResponseProvider
*
* @author Chill
*/
@Slf4j
public class ResponseProvider {
public static void logAuthFailure(HttpServletRequest request, HttpServletResponse response, String reason) {
try {
Map<String, String[]> parameterMap = request.getParameterMap();
String paramsJson = JsonUtil.toJson(parameterMap);
log.warn("{},请求接口:{}请求IP{},请求参数:{}", reason, request.getRequestURI(), WebUtil.getIP(request), paramsJson);
} catch (Exception e) {
log.error("日志记录失败", e);
}
ResponseProvider.write(response);
}
public static void write(HttpServletResponse response) {
R result = R.fail(ResultCode.UN_AUTHORIZED);
response.setCharacterEncoding(BladeConstant.UTF_8);
response.addHeader(BladeConstant.CONTENT_TYPE_NAME, MediaType.APPLICATION_JSON_VALUE);
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
try {
response.getWriter().write(Objects.requireNonNull(JsonUtil.toJson(result)));
} catch (IOException ex) {
log.error(ex.getMessage());
}
}
}

View File

@@ -0,0 +1,213 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.secure.registry;
import lombok.Data;
import lombok.Getter;
import org.springblade.core.secure.props.AuthSecure;
import org.springblade.core.secure.props.BasicSecure;
import org.springblade.core.secure.props.SignSecure;
import org.springblade.core.secure.provider.HttpMethod;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* 安全框架统一配置
*
* @author Chill
*/
@Data
public class SecureRegistry {
/**
* 是否开启鉴权
*/
private boolean enabled = true;
/**
* 开启令牌严格模式
*/
private boolean strictToken = true;
/**
* 开启请求头严格模式
*/
private boolean strictHeader = true;
/**
* 是否开启授权
*/
private boolean authEnabled = true;
/**
* 是否开启基础认证
*/
private boolean basicEnabled = true;
/**
* 是否开启签名认证
*/
private boolean signEnabled = true;
/**
* 是否开启客户端认证
*/
private boolean clientEnabled = true;
/**
* 默认放行规则
*/
private final List<String> defaultExcludePatterns = new ArrayList<>();
/**
* 自定义放行规则
*/
private final List<String> excludePatterns = new ArrayList<>();
/**
* 自定义授权集合
*/
@Getter
private final List<AuthSecure> authSecures = new ArrayList<>();
/**
* 基础认证集合
*/
@Getter
private final List<BasicSecure> basicSecures = new ArrayList<>();
/**
* 签名认证集合
*/
@Getter
private final List<SignSecure> signSecures = new ArrayList<>();
public SecureRegistry() {
this.defaultExcludePatterns.add("/actuator/health/**");
this.defaultExcludePatterns.add("/v3/api-docs/**");
this.defaultExcludePatterns.add("/swagger-ui/**");
this.defaultExcludePatterns.add("/oauth/**");
this.defaultExcludePatterns.add("/feign/client/**");
this.defaultExcludePatterns.add("/process/resource-view");
this.defaultExcludePatterns.add("/process/diagram-view");
this.defaultExcludePatterns.add("/manager/check-upload");
this.defaultExcludePatterns.add("/tenant/info");
this.defaultExcludePatterns.add("/static/**");
this.defaultExcludePatterns.add("/assets/**");
this.defaultExcludePatterns.add("/error");
this.defaultExcludePatterns.add("/favicon.ico");
}
/**
* 设置单个放行api
*/
public SecureRegistry excludePathPattern(String pattern) {
this.excludePatterns.add(pattern);
return this;
}
/**
* 设置放行api集合
*/
public SecureRegistry excludePathPatterns(String... patterns) {
this.excludePatterns.addAll(Arrays.asList(patterns));
return this;
}
/**
* 设置放行api集合
*/
public void excludePathPatterns(List<String> patterns) {
this.excludePatterns.addAll(patterns);
}
/**
* 设置单个自定义授权
*/
public SecureRegistry addAuthPattern(HttpMethod method, String pattern, String expression) {
this.authSecures.add(new AuthSecure(method, pattern, expression));
return this;
}
/**
* 设置自定义授权集合
*/
public SecureRegistry addAuthPatterns(List<AuthSecure> authSecures) {
this.authSecures.addAll(authSecures);
return this;
}
/**
* 设置基础认证
*/
public SecureRegistry addBasicPattern(HttpMethod method, String pattern, String username, String password) {
this.basicSecures.add(new BasicSecure(method, pattern, username, password));
return this;
}
/**
* 设置基础认证集合
*/
public SecureRegistry addBasicPatterns(List<BasicSecure> basicSecures) {
this.basicSecures.addAll(basicSecures);
return this;
}
/**
* 设置签名认证
*/
public SecureRegistry addSignPattern(HttpMethod method, String pattern, String crypto) {
this.signSecures.add(new SignSecure(method, pattern, crypto));
return this;
}
/**
* 设置签名认证集合
*/
public SecureRegistry addSignPatterns(List<SignSecure> signSecures) {
this.signSecures.addAll(signSecures);
return this;
}
/**
* 设置是否开启令牌严格模式
*/
public SecureRegistry strictToken(boolean strictToken) {
this.strictToken = strictToken;
return this;
}
/**
* 设置是否开启请求头严格模式
*/
public SecureRegistry strictHeader(boolean strictHeader) {
this.strictHeader = strictHeader;
return this;
}
}

View File

@@ -0,0 +1,186 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.secure.utils;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import lombok.SneakyThrows;
import org.springblade.core.jwt.JwtUtil;
import org.springblade.core.secure.TokenInfo;
import org.springblade.core.secure.constant.SecureConstant;
import org.springblade.core.tool.support.Kv;
import org.springblade.core.tool.utils.Charsets;
import org.springblade.core.tool.utils.Func;
import org.springblade.core.tool.utils.StringPool;
import org.springblade.core.tool.utils.WebUtil;
import javax.crypto.SecretKey;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import java.util.Base64;
import java.util.Date;
import java.util.Objects;
import java.util.Optional;
import static org.springblade.core.secure.constant.SecureConstant.*;
/**
* Secure工具类
*
* @author Chill
*/
public class SecureUtil extends AuthUtil {
public static final String TYP = "typ";
public static final String JWT = "JWT";
public static final String AUDIENCE = "bladex";
public static final String ISSUER = "bladex.cn";
/**
* 创建令牌
*
* @param kv 构建参数
* @return TokenInfo
*/
public static TokenInfo createToken(Kv kv) {
return createToken(kv, null, AUDIENCE, ISSUER);
}
/**
* 创建令牌
*
* @param kv 构建参数
* @param expire 过期秒数
* @return TokenInfo
*/
public static TokenInfo createToken(Kv kv, Integer expire) {
return createToken(kv, expire, AUDIENCE, ISSUER);
}
/**
* 创建令牌
*
* @param kv 构建参数
* @param expire 过期秒数
* @param audience audience
* @param issuer issuer
* @return TokenInfo
*/
public static TokenInfo createToken(Kv kv, Integer expire, String audience, String issuer) {
// 添加Token过期时间
Instant now = Instant.now();
int expireSeconds = Optional.ofNullable(expire)
.orElseGet(SecureUtil::getExpire); // 获取默认过期时间
Instant exp = now.plus(expireSeconds, ChronoUnit.SECONDS);
// 生成签名密钥
SecretKey signingKey = Keys.hmacShaKeyFor(Base64.getDecoder().decode(JwtUtil.getBase64Security()));
// 添加构成JWT的类
JwtBuilder builder = Jwts.builder().header().add(TYP, JWT)
.and().issuer(issuer).audience().add(audience)
.and().signWith(signingKey);
// 设置JWT参数
kv.forEach(builder::claim);
// 设置Token过期时间
builder.expiration(Date.from(exp)).notBefore(Date.from(now));
// 组装Token信息
TokenInfo tokenInfo = new TokenInfo();
tokenInfo.setToken(builder.compact());
tokenInfo.setExpire(expireSeconds);
// 返回Token信息
return tokenInfo;
}
/**
* 获取默认过期时间(次日凌晨3点)
*
* @return expire
*/
public static int getExpire() {
LocalTime threeAM = LocalTime.of(3, 0);
LocalDate tomorrow = LocalDate.now(ZoneId.systemDefault()).plusDays(1);
Instant threeAMTomorrow = tomorrow.atTime(threeAM).atZone(ZoneId.systemDefault()).toInstant();
return (int) ChronoUnit.SECONDS.between(Instant.now(), threeAMTomorrow);
}
/**
* 获取请求头中的客户端id
*/
public static String getClientId() {
String[] tokens = extractAndDecodeAuthorization();
assert tokens.length == 2;
return tokens[0];
}
/**
* 获取请求头中的客户端密钥
*/
public static String getClientSecret() {
String[] tokens = extractAndDecodeAuthorization();
assert tokens.length == 2;
return tokens[1];
}
/**
* 客户端信息解码
*/
@SneakyThrows
public static String[] extractAndDecodeAuthorization() {
// 获取请求头客户端信息
String header = Objects.requireNonNull(WebUtil.getRequest()).getHeader(SecureConstant.BASIC_HEADER_KEY);
header = Func.toStr(header).replace(SecureConstant.BASIC_HEADER_PREFIX_EXT, SecureConstant.BASIC_HEADER_PREFIX);
if (!header.startsWith(SecureConstant.BASIC_HEADER_PREFIX)) {
throw new SecurityException(AUTHORIZATION_NOT_FOUND);
}
byte[] base64Token = header.substring(6).getBytes(Charsets.UTF_8_NAME);
byte[] decoded;
try {
decoded = Base64.getDecoder().decode(base64Token);
} catch (IllegalArgumentException exception) {
throw new SecurityException(CLIENT_TOKEN_PARSE_FAILED);
}
String token = new String(decoded, Charsets.UTF_8_NAME);
int index = token.indexOf(StringPool.COLON);
if (index == -1) {
throw new SecurityException(INVALID_CLIENT_TOKEN);
} else {
return new String[]{token.substring(0, index), token.substring(index + 1)};
}
}
}