feat(security): 添加安全认证模块

- 新增安全认证相关的多个子模块,包括核心模块、认证模块和授权模块
- 实现了用户认证、权限控制、登录请求解析等功能
- 添加了安全认证的自动配置和相关Bean定义
- 定义了多种认证策略和处理机制,支持灵活的认证流程定制
This commit is contained in:
gewuyou 2025-06-24 17:25:10 +08:00
parent 1ef75d7c17
commit 9c11b7e2e5
92 changed files with 3535 additions and 12 deletions

View File

@ -11,6 +11,7 @@ plugins {
// Kotlin kapt 支持
alias(libs.plugins.kotlin.kapt)
id(libs.plugins.kotlin.jvm.get().pluginId)
alias(libs.plugins.gradleMavenPublishPlugin)
}
java {
withSourcesJar()
@ -58,7 +59,7 @@ allprojects {
}
}
}
project.group = "com.gewuyou.forgeboot"
project.group = "io.github.gewuyou"
}
// 子项目配置
@ -104,9 +105,15 @@ subprojects {
plugin(libs.plugins.kotlin.jvm.get().pluginId)
plugin(libs.plugins.axionRelease.get().pluginId)
plugin(libs.plugins.kotlin.kapt.get().pluginId)
plugin(libs.plugins.gradleMavenPublishPlugin.get().pluginId)
// 导入仓库配置
from(file("$configDir/repositories.gradle.kts"))
}
// 中央仓库
mavenPublishing {
publishToMavenCentral("DEFAULT", true)
signAllPublications()
}
// 发布配置
publishing {
repositories {
@ -128,9 +135,8 @@ subprojects {
val host = System.getenv("GITEA_HOST")
host?.let {
maven {
isAllowInsecureProtocol = true
name = "Gitea"
url = uri("http://${it}/api/packages/gewuyou/maven")
url = uri("${it}/api/packages/gewuyou/maven")
credentials(HttpHeaderCredentials::class.java) {
name = "Authorization"
value = "token ${System.getenv("GITEA_TOKEN")}"
@ -204,9 +210,88 @@ subprojects {
tasks.named<Test>("test") {
useJUnitPlatform()
}
}
/**
* 注册一个 Gradle 任务用于清理项目中的无用文件。
*
* 该任务会清理以下内容:
* - gradlew 和 gradlew.batGradle 包装器脚本)
* - gradle-wrapper.jar 和 gradle-wrapper.propertiesGradle 包装器库和配置)
* - .gradle 和 gradle 目录Gradle 缓存和配置目录)
* - src/main/resources/application.properties开发环境配置文件
* - src/test 目录(测试资源)
*/
tasks.register<CleanUselessFilesTask>("cleanUselessFiles") {
group = "project"
description = "删除根目录及子模块的无用文件"
includedProjectPaths.set(rootProject.subprojects.map { it.projectDir.absolutePath })
}
// 获取项目布尔属性的辅助函数
/**
* 获取项目的布尔属性值。
*
* 用于从项目属性中获取指定键对应的布尔值,如果未设置或无法解析为布尔值,则返回默认值 false。
*
* @param key 属性键名
* @return 属性对应的布尔值,若不存在或无效则返回 false
*/
fun Project.getPropertyByBoolean(key: String): Boolean {
return properties[key]?.toString()?.toBoolean() ?: false
}
/**
* CleanUselessFilesTask 是一个 Gradle 自定义任务类,用于清理项目中的无用文件。
*
* 该任务会遍历所有子项目,并删除预定义的无用文件和目录。
*/
abstract class CleanUselessFilesTask : DefaultTask() {
/**
* 获取需要清理的项目路径列表。
*
* 每个路径代表一个项目的根目录,任务将基于这些目录查找并删除无用文件。
*/
@get:Input
abstract val includedProjectPaths: ListProperty<String>
/**
* 执行清理操作的主要方法。
*
* 此方法会构建待删除文件列表,并逐个检查是否存在并删除,最后输出删除结果。
*/
@TaskAction
fun clean() {
val targets = mutableListOf<File>()
// 构建需要清理的文件和目录列表
includedProjectPaths.get().forEach { path ->
val projectDir = File(path)
targets.add(projectDir.resolve("gradlew"))
targets.add(projectDir.resolve("gradlew.bat"))
targets.add(projectDir.resolve("gradle/wrapper/gradle-wrapper.jar"))
targets.add(projectDir.resolve("gradle/wrapper/gradle-wrapper.properties"))
targets.add(projectDir.resolve(".gradle"))
targets.add(projectDir.resolve("gradle"))
targets.add(projectDir.resolve("src/main/resources/application.properties"))
targets.add(projectDir.resolve("src/test"))
targets.add(projectDir.resolve("HELP.md"))
targets.add(projectDir.resolve("settings.gradle.kts"))
}
// 执行删除操作并统计结果
var count = 0
targets.filter { it.exists() }.forEach {
logger.lifecycle("删除:${it.absolutePath}")
count++
it.deleteRecursively()
}
if (count > 0) {
logger.lifecycle("已删除 $count 个无用文件。")
} else {
logger.lifecycle("无无用文件。")
}
}
}

View File

@ -7,39 +7,114 @@
*/
object Modules {
object Context{
/**
* Context模块Spring Boot Starter上下文支持模块
* 提供基础的上下文功能包含API实现及自动配置模块
*/
object Context {
const val STARTER = ":forgeboot-context-spring-boot-starter"
const val API = ":forgeboot-context-spring-boot-starter:forgeboot-context-api"
const val IMPL = ":forgeboot-context-spring-boot-starter:forgeboot-context-impl"
const val AUTOCONFIGURE = ":forgeboot-context-spring-boot-starter:forgeboot-context-autoconfigure"
}
/**
* Webmvc模块Spring Boot Starter对WebMVC的支持模块
* 提供Web开发相关的功能包括数据传输对象DTO验证版本控制及日志功能
*/
object Webmvc {
const val STARTER = ":forgeboot-webmvc-spring-boot-starter"
const val DTO = ":forgeboot-webmvc-spring-boot-starter:forgeboot-webmvc-dto"
const val EXCEPTION = ":forgeboot-webmvc-spring-boot-starter:forgeboot-webmvc-exception-spring-boot-starter"
const val VALIDATION = ":forgeboot-webmvc-spring-boot-starter:forgeboot-webmvc-validation"
const val VERSION = ":forgeboot-webmvc-spring-boot-starter:forgeboot-webmvc-version-spring-boot-starter"
const val LOGGER = ":forgeboot-webmvc-spring-boot-starter:forgeboot-webmvc-logger-spring-boot-starter"
}
object Core{
/**
* Core模块项目的基础核心模块
* 包含项目的通用核心功能以及扩展支持
*/
object Core {
const val ROOT = ":forgeboot-core"
const val EXTENSION = ":forgeboot-core:forgeboot-core-extension"
const val SERIALIZATION = ":forgeboot-core:forgeboot-core-serialization"
}
/**
* Cache模块缓存支持模块
* 提供缓存功能包含API实现及自动配置模块
*/
object Cache {
const val STARTER = ":forgeboot-cache-spring-boot-starter"
const val API = ":forgeboot-cache-spring-boot-starter:forgeboot-cache-api"
const val IMPL = ":forgeboot-cache-spring-boot-starter:forgeboot-cache-impl"
const val AUTOCONFIGURE = ":forgeboot-cache-spring-boot-starter:forgeboot-cache-autoconfigure"
}
/**
* I18n模块国际化支持模块
* 提供多语言支持功能包含API实现及自动配置模块
*/
object I18n {
const val STARTER = ":forgeboot-i18n-spring-boot-starter"
const val API = ":forgeboot-i18n-spring-boot-starter:forgeboot-i18n-api"
const val IMPL = ":forgeboot-i18n-spring-boot-starter:forgeboot-i18n-impl"
const val AUTOCONFIGURE = ":forgeboot-i18n-spring-boot-starter:forgeboot-i18n-autoconfigure"
}
object TRACE{
/**
* TRACE模块分布式链路追踪支持模块
* 提供请求链路追踪能力包含API实现及自动配置模块
*/
object TRACE {
const val STARTER = ":forgeboot-trace-spring-boot-starter"
const val API = ":forgeboot-trace-spring-boot-starter:forgeboot-trace-api"
const val IMPL = ":forgeboot-trace-spring-boot-starter:forgeboot-trace-impl"
const val AUTOCONFIGURE = ":forgeboot-trace-spring-boot-starter:forgeboot-trace-autoconfigure"
}
/**
* Banner模块启动横幅定制模块
* 负责自定义应用启动时显示的横幅信息
*/
object Banner {
const val STARTER = ":forgeboot-banner"
const val API = ":forgeboot-banner:forgeboot-banner-api"
const val IMPL = ":forgeboot-banner:forgeboot-banner-impl"
const val AUTOCONFIGURE = ":forgeboot-banner:forgeboot-banner-autoconfigure"
}
/**
* Security模块安全认证与授权模块集合
* 包含认证(Authenticate)和授权(Authorize)两个子模块
*/
object Security {
private const val SECURITY = ":forgeboot-security-spring-boot-starter"
private const val AUTHENTICATE = "${SECURITY}:forgeboot-security-authenticate-spring-boot-starter"
private const val AUTHORIZE = "${SECURITY}:forgeboot-security-authorize-spring-boot-starter"
const val CORE = "${SECURITY}:forgeboot-security-core"
/**
* Authenticate模块身份认证支持模块
* 提供用户身份认证相关功能包含API实现及自动配置模块
*/
object Authenticate {
const val STARTER = AUTHENTICATE
const val API = "${AUTHENTICATE}:forgeboot-security-authenticate-api"
const val IMPL = "${AUTHENTICATE}:forgeboot-security-authenticate-impl"
const val AUTOCONFIGURE =
"${AUTHENTICATE}:forgeboot-security-authenticate"
}
/**
* Authorize模块权限控制支持模块
* 提供基于角色或策略的权限控制功能包含API实现及自动配置模块
*/
object Authorize {
const val STARTER = AUTHORIZE
const val API = "${AUTHORIZE}:forgeboot-security-authorize-api"
const val IMPL = "${AUTHORIZE}:forgeboot-security-authorize-impl"
const val AUTOCONFIGURE =
"${AUTHORIZE}:forgeboot-security-authorize-autoconfigure"
}
}
}

View File

@ -0,0 +1,5 @@
extra{
setProperty(ProjectFlags.IS_ROOT_MODULE,true)
}
dependencies {
}

View File

@ -0,0 +1,8 @@
dependencies {
api(project(Modules.Webmvc.DTO))
compileOnly(project(Modules.Security.CORE))
compileOnly(libs.springBootStarter.web)
compileOnly(libs.springBootStarter.security)
kapt(libs.springBoot.configuration.processor)
}

View File

@ -0,0 +1,45 @@
package com.gewuyou.forgeboot.security.authenticate.api.config
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.http.HttpMethod
/**
* 安全身份验证属性配置类
*
* 该类用于定义安全认证相关的配置属性包含路径权限设置异常响应信息和基础URL配置
* 属性值通过@ConfigurationProperties绑定前缀"forgeboot.security.authenticate"的配置项
*
* @since 2025-06-11 15:20:27
* @author gewuyou
*/
@ConfigurationProperties(prefix = "forgeboot.security.authenticate")
class SecurityAuthenticateProperties {
/**
* 登录请求的HTTP方法默认为POST
*/
var loginHttpMethod: String = HttpMethod.POST.name()
/**
* 默认认证异常响应内容当请求缺少认证时返回此字符串
*/
var defaultExceptionResponse = "Full authentication is required to access this resource"
/**
* 默认认证失败响应内容当认证失败时返回此字符串
*/
var defaultAuthenticationFailureResponse = "If the authentication fails, please report the request ID for more information!"
/**
* 认证模块的基础URL前缀
*/
var baseUrl = "/api/auth"
/**
* 登录接口的URL路径
*/
var loginUrl = "/login"
/**
* 登出接口的URL路径
*/
var logoutUrl = "/logout"
}

View File

@ -0,0 +1,24 @@
package com.gewuyou.forgeboot.security.authenticate.api.customizer
import org.springframework.security.config.annotation.web.builders.HttpSecurity
/**
* 登录过滤器定制器接口
*
* 该函数式接口用于自定义Spring Security的登录过滤器配置
* 实现此接口的类或lambda表达式可用于调整安全配置中的HttpSecurity设置
* 特别是在登录认证流程中对请求的安全策略进行定制化处理
*
* @since 2025-06-11 18:53:25
* @author gewuyou
*/
fun interface LoginFilterCustomizer {
/**
* 自定义方法用于配置或修改HttpSecurity对象
*
* 此方法将被调用以应用特定的安全性规则到登录过滤器上
*
* @param http HttpSecurity对象提供了配置Web安全性的API
*/
fun customize(http: HttpSecurity)
}

View File

@ -0,0 +1,28 @@
package com.gewuyou.forgeboot.security.authenticate.api.enums
/**
*登录类型
*
* @since 2025-06-12 21:11:02
* @author gewuyou
*/
object LoginTypes {
/**
* 默认登录类型标识
*/
const val DEFAULT = "default"
/**
* 用户名密码登录类型标识
*/
const val USERNAME_PASSWORD = "username_password"
/**
* OAuth2 认证登录类型标识
*/
const val OAUTH2 = "oauth2"
/**
* 短信验证码登录类型标识
*/
const val SMS = "sms"
}

View File

@ -0,0 +1,18 @@
package com.gewuyou.forgeboot.security.authenticate.api.exception
import org.springframework.security.core.AuthenticationException
/**
*Forge Boot身份验证异常
*
* @since 2025-06-11 15:49:33
* @author gewuyou
*/
open class ForgeBootAuthenticationException(
msg: String,
cause: Throwable? = null
): AuthenticationException(
msg,
cause
)

View File

@ -0,0 +1,27 @@
package com.gewuyou.forgeboot.security.authenticate.api.handler
import jakarta.servlet.http.HttpServletRequest
/**
* 委派处理程序接口用于定义委派处理器的行为规范
*
* 该接口设计为泛型函数式接口要求实现类提供一个方法
* 将给定的 HTTP 请求解析为指定类型的委派对象
*
* @param <T> 委派对象的类型必须是非空类型
*
* @since 2025-06-11 14:51:48
* @author gewuyou
*/
fun interface DelegatingHandler<T: Any> {
/**
* 根据提供的 HTTP 请求解析出对应的委派对象
*
* 此方法通常用于从请求中提取认证信息或上下文数据
* 并将其转换为具体的业务对象以供后续处理使用
*
* @param request 要解析的 HTTP 请求对象不能为 null
* @return 返回解析得到的委派对象具体类型由接口泛型 T 决定
*/
fun resolveDelegate(request: HttpServletRequest): T
}

View File

@ -0,0 +1,47 @@
package com.gewuyou.forgeboot.security.authenticate.api.registry
import com.gewuyou.forgeboot.security.core.authenticate.entities.request.LoginRequest
/**
* 登录请求类型注册表
*
* 用于管理不同登录类型的请求类映射关系支持动态注册和获取登录请求类型
*
* ```kt
* @Bean
* fun loginRequestTypeRegistry(): LoginRequestTypeRegistry {
* return LoginRequestTypeRegistry().apply {
* register("default", DefaultLoginRequest::class.java)
* register("sms", SmsLoginRequest::class.java)
* }
* }
* ```
* @since 2025-06-10 16:33:43
* @author gewuyou
*/
class LoginRequestTypeRegistry {
/**
* 内部使用可变Map保存登录类型与对应LoginRequest子类的映射关系
*/
private val mapping = mutableMapOf<String, Class<out LoginRequest>>()
/**
* 注册指定登录类型对应的请求类
*
* @param loginType 登录类型的标识字符串
* @param clazz 继承自LoginRequest的具体请求类
*/
fun register(loginType: String, clazz: Class<out LoginRequest>) {
mapping[loginType] = clazz
}
/**
* 根据登录类型获取对应的请求类
*
* @param loginType 登录类型的标识字符串
* @return 返回对应的LoginRequest子类若未找到则返回null
*/
fun getTypeForLoginType(loginType: String): Class<out LoginRequest>? {
return mapping[loginType]
}
}

View File

@ -0,0 +1,31 @@
package com.gewuyou.forgeboot.security.authenticate.api.resolver
import com.gewuyou.forgeboot.security.core.authenticate.entities.request.LoginRequest
import jakarta.servlet.http.HttpServletRequest
/**
* 登录请求解析器接口
* 用于定义处理登录请求的通用规范包括内容类型支持判断和请求参数解析功能
*
* @since 2025-06-10 16:32:32
* @author gewuyou
*/
interface LoginRequestResolver {
/**
* 判断当前解析器是否支持指定的内容类型
*
* @param contentType 请求内容类型
* @return Boolean 返回true表示支持该内容类型否则不支持
*/
fun supports(contentType: String): Boolean
/**
* 解析HTTP请求为具体的登录请求对象
* 该方法应从HttpServletRequest中提取并封装登录所需参数
*
* @param request HTTP请求对象
* @return LoginRequest 解析后的登录请求实体对象
*/
fun resolve(request: HttpServletRequest): LoginRequest
}

View File

@ -0,0 +1,31 @@
package com.gewuyou.forgeboot.security.authenticate.api.service
import org.springframework.security.core.userdetails.UserDetails
/**
*用户缓存服务
*
* @since 2025-02-15 17:15:17
* @author gewuyou
*/
interface UserCacheService {
/**
* 从缓存中获取用户信息
* @param principal 用户标识
* @return 用户信息 返回null表示缓存中没有该用户信息或信息已过期
*/
fun getUserFromCache(principal: String): UserDetails?
/**
* 将用户信息缓存到缓存中 注意请将过期时间设置得尽可能短以防止缓存与数据库出现数据不一致
* @param userDetails 用户信息
*/
fun putUserToCache(userDetails: UserDetails)
/**
* 从缓存中移除用户信息
* @param principal 用户标识
*/
fun removeUserFromCache(principal: String)
}

View File

@ -0,0 +1,19 @@
package com.gewuyou.forgeboot.security.authenticate.api.service
import org.springframework.security.core.userdetails.UserDetails
/**
* 用户详情服务
*
* @author gewuyou
* @since 2024-11-05 19:34:50
*/
fun interface UserDetailsService {
/**
* 根据用户身份标识获取用户
* @param principal 用户身份标识 通常为用户名 手机号 邮箱等
* @return 用户详情 不能为空
*/
fun loadUserByPrincipal(principal: Any): UserDetails
}

View File

@ -0,0 +1,11 @@
package com.gewuyou.forgeboot.security.authenticate.api.strategy
import org.springframework.security.web.authentication.AuthenticationFailureHandler
/**
* 身份验证失败策略接口用于定义不同登录类型的身份验证失败处理机制
*
* @since 2025-06-10 23:43:43
* @author gewuyou
*/
interface AuthenticationFailureStrategy : AuthenticationHandlerSupportStrategy, AuthenticationFailureHandler

View File

@ -0,0 +1,16 @@
package com.gewuyou.forgeboot.security.authenticate.api.strategy
/**
*身份验证处理程序支持策略
*
* @since 2025-06-11 00:03:28
* @author gewuyou
*/
fun interface AuthenticationHandlerSupportStrategy{
/**
* 获取当前策略支持的登录类型标识符
*
* @return 返回当前策略支持的登录类型字符串
*/
fun supportedLoginType(): String
}

View File

@ -0,0 +1,11 @@
package com.gewuyou.forgeboot.security.authenticate.api.strategy
import org.springframework.security.web.authentication.AuthenticationSuccessHandler
/**
* 身份验证成功策略接口用于定义不同登录类型的成功处理策略
*
* @since 2025-06-10 23:42:54
* @author gewuyou
*/
interface AuthenticationSuccessStrategy : AuthenticationHandlerSupportStrategy, AuthenticationSuccessHandler

View File

@ -0,0 +1,26 @@
package com.gewuyou.forgeboot.security.authenticate.api.strategy
import com.gewuyou.forgeboot.security.core.authenticate.entities.request.LoginRequest
import org.springframework.security.core.Authentication
/**
*登录请求转换器策略
* 请注意该接口仅用于定义转换器的接口只负责转换登录请求为认证对象不负责提供认证逻辑
* 具体的认证逻辑由具体的认证提供类实现因此当需要自定义认证逻辑时请实现具体的认证提供类
* @since 2025-02-15 03:23:34
* @author gewuyou
*/
interface LoginRequestConverterStrategy {
/**
* 转换登录请求为认证对象
* @param loginRequest 登录请求
* @return 认证对象
*/
fun convert(loginRequest: LoginRequest): Authentication
/**
* 获取支持的登录类型
* @return 请求的登录类型
*/
fun getSupportedLoginType(): String
}

View File

@ -0,0 +1,13 @@
package com.gewuyou.forgeboot.security.authenticate.api.strategy
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler
/**
* 注销成功策略接口用于定义在用户注销成功后的处理逻辑
*
* 实现此类的策略应当提供具体地登出后操作例如清理会话记录日志等
*
* @since 2025-06-10 23:49:35
* @author gewuyou
*/
interface LogoutSuccessStrategy : AuthenticationHandlerSupportStrategy, LogoutSuccessHandler

View File

@ -0,0 +1,9 @@
plugins {
alias(libs.plugins.kotlin.plugin.spring)
}
dependencies {
compileOnly(libs.springBootStarter.security)
compileOnly(libs.springBootStarter.web)
compileOnly(project(Modules.Security.Authenticate.IMPL))
implementation(project(Modules.Security.CORE))
}

View File

@ -0,0 +1,309 @@
package com.gewuyou.forgeboot.security.authenticate.autoconfigure
import com.fasterxml.jackson.databind.ObjectMapper
import com.gewuyou.forgeboot.security.authenticate.api.config.SecurityAuthenticateProperties
import com.gewuyou.forgeboot.security.authenticate.api.customizer.LoginFilterCustomizer
import com.gewuyou.forgeboot.security.authenticate.api.resolver.LoginRequestResolver
import com.gewuyou.forgeboot.security.authenticate.api.strategy.AuthenticationFailureStrategy
import com.gewuyou.forgeboot.security.authenticate.api.strategy.AuthenticationSuccessStrategy
import com.gewuyou.forgeboot.security.authenticate.api.strategy.LogoutSuccessStrategy
import com.gewuyou.forgeboot.security.core.common.extension.cleanUnNeedConfig
import com.gewuyou.forgeboot.security.authenticate.impl.filter.UnifiedAuthenticationFilter
import com.gewuyou.forgeboot.security.authenticate.impl.handler.AuthenticationExceptionHandler
import com.gewuyou.forgeboot.security.authenticate.impl.handler.CompositeAuthenticationFailureHandler
import com.gewuyou.forgeboot.security.authenticate.impl.handler.CompositeAuthenticationSuccessHandler
import com.gewuyou.forgeboot.security.authenticate.impl.handler.CompositeLogoutSuccessHandler
import com.gewuyou.forgeboot.security.authenticate.impl.resolver.CompositeLoginRequestResolver
import com.gewuyou.forgeboot.security.authenticate.impl.strategy.context.AuthenticationFailureHandlerContext
import com.gewuyou.forgeboot.security.authenticate.impl.strategy.context.AuthenticationSuccessHandlerContext
import com.gewuyou.forgeboot.security.authenticate.impl.strategy.context.LoginRequestConverterContext
import com.gewuyou.forgeboot.security.authenticate.impl.strategy.context.LogoutSuccessHandlerContext
import org.springframework.beans.factory.ObjectProvider
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass
import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.ApplicationContext
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.ComponentScan
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Primary
import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.authentication.AuthenticationProvider
import org.springframework.security.authentication.ProviderManager
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.web.AuthenticationEntryPoint
import org.springframework.security.web.SecurityFilterChain
import org.springframework.security.web.authentication.AuthenticationFailureHandler
import org.springframework.security.web.authentication.AuthenticationSuccessHandler
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
import org.springframework.security.web.util.matcher.AntPathRequestMatcher
/**
* 安全身份验证自动配置
*
* 主要用于配置与安全认证相关的 Bean Spring Security 的过滤器链
*
* @since 2025-06-11 15:04:58
* @author gewuyou
*/
@Configuration
@EnableConfigurationProperties(SecurityAuthenticateProperties::class)
class SecurityAuthenticateAutoConfiguration {
/**
* Spring Security 认证流程的自动配置类
*
* 该内部类负责构建主要的安全过滤器链处理登录登出以及异常入口点等核心功能
*
* @property logoutSuccessHandler 处理登出成功的处理器
* @property authenticationExceptionHandler 处理认证异常的入口点
* @property unifiedAuthenticationFilter 统一的身份验证过滤器
* @property customizers 可选的登录过滤器定制器集合
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(SecurityFilterChain::class)
class SpringSecurityAuthenticateAutoConfiguration(
private val logoutSuccessHandler: CompositeLogoutSuccessHandler,
private val authenticationExceptionHandler: AuthenticationEntryPoint,
private val unifiedAuthenticationFilter: UnifiedAuthenticationFilter,
private val customizers: ObjectProvider<List<LoginFilterCustomizer>>,
) {
/**
* 构建并返回一个自定义的安全过滤器链
*
* 该方法配置了
* - 清除不必要地默认安全配置
* - 登出功能及其成功处理器
* - 异常处理认证失败入口点
* - 请求匹配规则和访问控制策略
* - 添加统一的身份验证过滤器到过滤器链中
* - 对可排序的登录过滤器定制器进行排序并应用
*
* @param http HttpSecurity 实例用于构建安全过滤器链
* @param properties 安全认证属性配置对象
* @return 构建完成的 SecurityFilterChain 实例
*/
@Bean(name = ["forgebootSecurityAuthenticationChain"])
fun loginFilterChain(
http: HttpSecurity,
properties: SecurityAuthenticateProperties,
): SecurityFilterChain {
return http
.cleanUnNeedConfig()
.logout {
it.logoutUrl(properties.logoutUrl)
.logoutSuccessHandler(logoutSuccessHandler)
}
.exceptionHandling {
it.authenticationEntryPoint(authenticationExceptionHandler)
}
.securityMatcher(AntPathRequestMatcher(properties.baseUrl))
.addFilterBefore(unifiedAuthenticationFilter, UsernamePasswordAuthenticationFilter::class.java)
.also {
customizers.ifAvailable?.sortedBy { c -> (c as? OrderedLoginFilterCustomizer)?.order ?: 0 }
?.forEach { it.customize(http) }
}
.build()
}
}
/**
* ForgeBoot安全组件的自动配置类
*
* 负责注册多个用于处理身份验证流程的组合式组件如解析器上下文处理器等
*/
@Configuration(proxyBeanMethods = false)
class ForgeBootSecurityAuthenticateAutoConfiguration {
/**
* 创建并返回一个组合式的登录请求解析器
*
* 将多个 LoginRequestResolver 实现组合成一个统一的接口调用入口
*
* @param resolvers 所有可用的 LoginRequestResolver 实例列表
* @return 组合后的 CompositeLoginRequestResolver 实例
*/
@Bean
fun compositeLoginRequestResolver(
resolvers: List<LoginRequestResolver>,
) = CompositeLoginRequestResolver(resolvers)
/**
* 创建并返回一个登出成功处理的上下文实例
*
* 用于根据注册的策略动态选择合适的 LogoutSuccessStrategy
*
* @param strategies 所有可用的 LogoutSuccessStrategy 实例列表
* @return 初始化好的 LogoutSuccessHandlerContext 实例
*/
@Bean
fun logoutSuccessHandlerContext(
strategies: List<LogoutSuccessStrategy>,
) = LogoutSuccessHandlerContext(strategies)
/**
* 创建并返回一个组合式的登出成功处理器
*
* 使用解析器和上下文来决定最终使用的登出成功策略
*
* @param resolver 登录请求解析器
* @param context 登出成功处理上下文
* @return 初始化好的 CompositeLogoutSuccessHandler 实例
*/
@Bean(name = ["logoutSuccessHandler"])
fun compositeLogoutSuccessHandler(
resolver: CompositeLoginRequestResolver,
context: LogoutSuccessHandlerContext,
) = CompositeLogoutSuccessHandler(resolver, context)
/**
* 创建并返回一个认证异常处理器
*
* 当用户未认证或认证失败时通过此处理器响应客户端
*
* @param objectMapper JSON 序列化工具
* @param properties 安全认证属性配置
* @return 初始化好的 AuthenticationExceptionHandler 实例
*/
@Bean
fun authenticationExceptionHandler(
objectMapper: ObjectMapper,
properties: SecurityAuthenticateProperties,
): AuthenticationEntryPoint = AuthenticationExceptionHandler(objectMapper, properties)
/**
* 创建并返回一个认证管理器
*
* 使用一组 AuthenticationProvider 来支持多种认证方式
*
* @param authenticationProviders 所有可用的认证提供者
* @return 初始化好的 ProviderManager 实例
*/
@Bean(name = ["authenticationManager"])
@Primary
fun authenticationManager(
authenticationProviders: List<AuthenticationProvider>,
): AuthenticationManager = ProviderManager(authenticationProviders)
/**
* 创建并返回一个认证成功处理的上下文
*
* 根据当前请求上下文动态选择合适的认证成功策略
*
* @param strategies 所有可用的 AuthenticationSuccessStrategy 实例列表
* @return 初始化好的 AuthenticationSuccessHandlerContext 实例
*/
@Bean
fun authenticationSuccessHandlerContext(
strategies: List<AuthenticationSuccessStrategy>,
) = AuthenticationSuccessHandlerContext(strategies)
/**
* 创建并返回一个组合式的认证成功处理器
*
* 委托给具体的策略实现来进行响应
*
* @param resolver 登录请求解析器
* @param context 认证成功处理上下文
* @return 初始化好的 CompositeAuthenticationSuccessHandler 实例
*/
@Bean(name = ["authenticationSuccessHandler"])
fun authenticationSuccessHandler(
resolver: CompositeLoginRequestResolver,
context: AuthenticationSuccessHandlerContext,
): AuthenticationSuccessHandler = CompositeAuthenticationSuccessHandler(resolver, context)
/**
* 创建并返回一个认证失败处理的上下文
*
* 根据当前请求上下文动态选择合适的认证失败策略
*
* @param strategies 所有可用的 AuthenticationFailureStrategy 实例列表
* @return 初始化好的 AuthenticationFailureHandlerContext 实例
*/
@Bean
fun authenticationFailureHandlerContext(
strategies: List<AuthenticationFailureStrategy>,
) = AuthenticationFailureHandlerContext(strategies)
/**
* 创建并返回一个组合式的认证失败处理器
*
* 委托给具体的策略实现来进行响应
*
* @param resolver 登录请求解析器
* @param context 认证失败处理上下文
* @return 初始化好的 CompositeAuthenticationFailureHandler 实例
*/
@Bean(name = ["authenticationFailureHandler"])
fun authenticationFailureHandler(
resolver: CompositeLoginRequestResolver,
context: AuthenticationFailureHandlerContext,
): AuthenticationFailureHandler = CompositeAuthenticationFailureHandler(resolver, context)
/**
* 创建并返回一个登录请求转换上下文
*
* 用于根据当前请求上下文动态选择合适的登录请求转换逻辑
*
* @param applicationContext Spring 应用上下文
* @return 初始化好的 LoginRequestConverterContext 实例
*/
@Bean
fun loginRequestConverterContext(
applicationContext: ApplicationContext,
) = LoginRequestConverterContext(applicationContext)
/**
* 创建并返回一个统一的身份验证过滤器
*
* 该过滤器是整个认证流程的核心处理登录请求并触发相应的成功/失败处理器
*
* @param properties 安全认证属性配置
* @param authenticationManager 认证管理器
* @param authenticationSuccessHandler 成功认证处理器
* @param authenticationFailureHandler 失败认证处理器
* @param compositeLoginRequestResolver 组合登录请求解析器
* @param loginRequestConverterContext 登录请求转换上下文
* @return 初始化好的 UnifiedAuthenticationFilter 实例
*/
@Bean
fun unifiedAuthenticationFilter(
properties: SecurityAuthenticateProperties,
authenticationManager: AuthenticationManager,
authenticationSuccessHandler: AuthenticationSuccessHandler,
authenticationFailureHandler: AuthenticationFailureHandler,
compositeLoginRequestResolver: CompositeLoginRequestResolver,
loginRequestConverterContext: LoginRequestConverterContext,
): UnifiedAuthenticationFilter =
UnifiedAuthenticationFilter(
AntPathRequestMatcher(
properties.baseUrl + properties.loginUrl,
properties.loginHttpMethod
),
authenticationManager,
authenticationSuccessHandler,
authenticationFailureHandler,
compositeLoginRequestResolver,
loginRequestConverterContext
)
}
@Configuration(proxyBeanMethods = false)
@ComponentScan(
basePackages = [
"com.gewuyou.forgeboot.security.authenticate.impl.strategy.impl",
"com.gewuyou.forgeboot.security.authenticate.impl.provider.impl",
]
)
class ForgeBootDefaultSecurityAuthenticateAutoConfiguration
}
/**
* 用于支持登录过滤器扩展器排序
*
* 通过 order 属性为 LoginFilterCustomizer 提供排序能力确保其按预期顺序执行
*/
interface OrderedLoginFilterCustomizer : LoginFilterCustomizer {
val order: Int
}

View File

@ -0,0 +1 @@
com.gewuyou.forgeboot.security.authenticate.autoconfigure.SecurityAuthenticateAutoConfiguration

View File

@ -0,0 +1,6 @@
extra{
setProperty(ProjectFlags.IS_ROOT_MODULE,true)
}
dependencies {
}

View File

@ -0,0 +1,11 @@
plugins{
alias(libs.plugins.kotlin.plugin.spring)
}
dependencies {
api(project(Modules.Security.Authenticate.API))
implementation(project(Modules.Security.CORE))
implementation(project(Modules.Core.EXTENSION))
implementation(project(Modules.Webmvc.DTO))
compileOnly(libs.springBootStarter.web)
compileOnly(libs.springBootStarter.security)
}

View File

@ -0,0 +1,49 @@
package com.gewuyou.forgeboot.security.authenticate.impl.constants
/**
* Forge Boot安全身份验证响应信息
*
* 该对象包含用于身份验证过程中的各种预定义错误响应消息
* 每个属性代表一种特定的身份验证失败或系统错误情况供在认证流程中抛出或返回给调用者
*
* @since 2025-06-11 17:05:09
* @author gewuyou
*/
object ForgeBootSecurityAuthenticationResponseInformation {
const val INCORRECT_USERNAME_OR_PASSWORD: String = "the username or password is incorrect"
/**
* 当用户未提供密码时使用的响应消息
*/
const val PASSWORD_NOT_PROVIDED: String = "the password cannot be empty"
/**
* 当用户的凭证已过期时使用的响应消息
*/
const val ACCOUNT_CREDENTIALS_HAVE_EXPIRED: String = "the account credentials have expired"
/**
* 当用户账户被禁用时使用的响应消息
*/
const val ACCOUNT_IS_DISABLED: String = "the account is disabled"
/**
* 当用户账户被锁定时使用的响应消息
*/
const val ACCOUNT_IS_LOCKED: String = "the account is locked"
/**
* 当发生内部服务器错误时使用的通用错误响应消息
*/
const val INTERNAL_SERVER_ERROR: String = "internal server error occurred"
/**
* 当认证主体principal未提供时使用的常量字符串
* 这通常意味着认证请求缺少必要的身份标识信息
*/
const val PRINCIPAL_NOT_PROVIDED = "the authentication principal cannot be empty"
const val LOGIN_SUCCESS: String = "login success"
const val LOGOUT_SUCCESS: String = "logout success"
}

View File

@ -0,0 +1,69 @@
package com.gewuyou.forgeboot.security.authenticate.impl.filter
import com.gewuyou.forgeboot.security.authenticate.impl.resolver.CompositeLoginRequestResolver
import com.gewuyou.forgeboot.security.authenticate.impl.strategy.context.LoginRequestConverterContext
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.core.Authentication
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter
import org.springframework.security.web.authentication.AuthenticationFailureHandler
import org.springframework.security.web.authentication.AuthenticationSuccessHandler
import org.springframework.security.web.util.matcher.AntPathRequestMatcher
/**
* 统一的身份验证过滤器
*
* 该类实现了身份验证流程的核心逻辑通过组合不同的解析策略和转换上下文
* 来支持多种登录请求的处理方式
*
* @param pathRequestMatcher 请求路径匹配器用于判断当前请求是否需要处理
* @param authenticationManager 身份验证管理器用于执行实际的身份验证操作
* @param authenticationSuccessHandler 认证成功处理器用于处理认证成功后的响应
* @param authenticationFailureHandler 认证失败处理器用于处理认证失败后的响应
* @param compositeLoginRequestResolver 组合登录请求解析器用于解析不同类型的登录请求
* @param loginRequestConverterContext 登录请求转换上下文用于选择合适地转换策略
*
* @since 2025-06-10 20:28:08
* @author gewuyou
*/
class UnifiedAuthenticationFilter(
pathRequestMatcher: AntPathRequestMatcher,
authenticationManager: AuthenticationManager,
authenticationSuccessHandler: AuthenticationSuccessHandler,
authenticationFailureHandler: AuthenticationFailureHandler,
private val compositeLoginRequestResolver: CompositeLoginRequestResolver,
private val loginRequestConverterContext: LoginRequestConverterContext,
) : AbstractAuthenticationProcessingFilter(pathRequestMatcher) {
init {
// 初始化身份验证管理器、成功处理器和失败处理器
setAuthenticationManager(authenticationManager)
setAuthenticationSuccessHandler(authenticationSuccessHandler)
setAuthenticationFailureHandler(authenticationFailureHandler)
}
/**
* 尝试进行身份验证
*
* 此方法负责从请求中提取登录信息并使用组合解析器和转换上下文来构建认证请求
* 最终调用身份验证管理器执行认证操作
*
* @param request HTTP请求对象
* @param response HTTP响应对象
* @return 返回经过认证的Authentication对象
*/
override fun attemptAuthentication(
request: HttpServletRequest,
response: HttpServletResponse,
): Authentication {
// 尝试认证:通过组合解析器解析请求内容,再根据转换上下文选择合适的策略进行认证
return authenticationManager.authenticate(
// 通过上下文执行转换策略
loginRequestConverterContext.executeStrategy(
// 使用组合解析器解析HTTP请求
compositeLoginRequestResolver.resolve(request)
)
)
}
}

View File

@ -0,0 +1,37 @@
package com.gewuyou.forgeboot.security.authenticate.impl.handler
import com.gewuyou.forgeboot.security.authenticate.api.handler.DelegatingHandler
import com.gewuyou.forgeboot.security.authenticate.impl.resolver.CompositeLoginRequestResolver
import jakarta.servlet.http.HttpServletRequest
/**
* 抽象委派处理程序
*
* 该类为泛型抽象类用于实现委派处理逻辑通过解析请求获取登录类型并使用上下文解析器获取对应的处理上下文
*
* @param resolver 用于解析请求并获取登录类型的组件不可为空
* @param contextResolver 根据登录类型生成对应上下文对象的函数式接口不可为空
*
* @since 2025-06-11 14:53:46
* @author gewuyou
*/
abstract class AbstractDelegatingHandler<T : Any>(
private val resolver: CompositeLoginRequestResolver,
private val contextResolver: (String) -> T,
): DelegatingHandler<T> {
/**
* 解析请求并获取对应的处理上下文
*
* 该方法会调用注入的 [resolver] 来解析传入的 HTTP 请求获取当前请求的登录类型
* 然后通过 [contextResolver] 函数将登录类型转换为具体地处理上下文对象
*
* @param request HTTP 请求对象用于解析登录类型
* @return 返回与登录类型对应的处理上下文对象
*/
override fun resolveDelegate(request: HttpServletRequest): T {
// 解析请求获取登录类型
val loginType = resolver.resolve(request).getType()
// 使用上下文解析器生成对应类型的处理上下文
return contextResolver(loginType)
}
}

View File

@ -0,0 +1,37 @@
package com.gewuyou.forgeboot.security.authenticate.impl.handler
import com.fasterxml.jackson.databind.ObjectMapper
import com.gewuyou.forgeboot.core.extension.log
import com.gewuyou.forgeboot.security.authenticate.api.config.SecurityAuthenticateProperties
import com.gewuyou.forgeboot.webmvc.dto.R
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.security.core.AuthenticationException
import org.springframework.security.web.AuthenticationEntryPoint
/**
*身份验证异常处理程序
*
* @since 2025-01-03 14:50:46
* @author gewuyou
*/
class AuthenticationExceptionHandler(
private val objectMapper: ObjectMapper,
private val securityAuthenticateProperties: SecurityAuthenticateProperties
) : AuthenticationEntryPoint {
override fun commence(
request: HttpServletRequest,
response: HttpServletResponse,
authException: AuthenticationException
) {
log.error("身份验证异常: ", authException)
response.status = HttpStatus.OK.value()
response.contentType = MediaType.APPLICATION_JSON_VALUE
val writer = response.writer
writer.print(objectMapper.writeValueAsString(R.failure<String>(HttpStatus.UNAUTHORIZED.value(), authException.message?:securityAuthenticateProperties.defaultExceptionResponse)))
writer.flush()
writer.close()
}
}

View File

@ -0,0 +1,43 @@
package com.gewuyou.forgeboot.security.authenticate.impl.handler
import com.gewuyou.forgeboot.security.authenticate.impl.resolver.CompositeLoginRequestResolver
import com.gewuyou.forgeboot.security.authenticate.impl.strategy.context.AuthenticationFailureHandlerContext
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import org.springframework.security.web.authentication.AuthenticationFailureHandler
/**
* 组合身份验证故障处理程序
*
* 该类通过组合不同的身份验证失败处理器来处理认证失败场景利用解析器和上下文策略来动态选择实际的处理器
*
* @property resolver 用于解析请求并确定适用的身份验证失败处理器的组件
* @property context 提供身份验证失败处理策略的上下文对象
*
* @since 2025-06-11 00:11:52
* @author gewuyou
*/
class CompositeAuthenticationFailureHandler(
private val resolver: CompositeLoginRequestResolver,
private val context: AuthenticationFailureHandlerContext,
) : AbstractDelegatingHandler<AuthenticationFailureHandler>(
resolver, context::resolve
), AuthenticationFailureHandler {
/**
* 在身份验证失败时调用的方法
*
* 此方法会解析出适用于当前请求的具体身份验证失败处理器并委托其进行失败处理
*
* @param request 来自客户端的 HTTP 请求
* @param response 发送回客户端的 HTTP 响应
* @param exception 身份验证过程中抛出的异常信息
*/
override fun onAuthenticationFailure(
request: HttpServletRequest,
response: HttpServletResponse,
exception: org.springframework.security.core.AuthenticationException,
) {
resolveDelegate(request).onAuthenticationFailure(request, response, exception)
}
}

View File

@ -0,0 +1,45 @@
package com.gewuyou.forgeboot.security.authenticate.impl.handler
import com.gewuyou.forgeboot.security.authenticate.impl.resolver.CompositeLoginRequestResolver
import com.gewuyou.forgeboot.security.authenticate.impl.strategy.context.AuthenticationSuccessHandlerContext
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import org.springframework.security.core.Authentication
import org.springframework.security.web.authentication.AuthenticationSuccessHandler
/**
* 统一身份验证成功处理程序
*
* 该类实现了 [AuthenticationSuccessHandler] 接口用于在身份验证成功后执行相应的处理逻辑
* 它通过组合多个具体的处理器来实现灵活的身份验证成功响应策略
*
* @property resolver 用于解析登录请求并确定使用哪个具体的成功处理器
* @property context 提供处理上下文信息用于解析和执行成功处理逻辑
*
* @since 2025-06-10 23:48:32
* @author gewuyou
*/
class CompositeAuthenticationSuccessHandler(
private val resolver: CompositeLoginRequestResolver,
private val context: AuthenticationSuccessHandlerContext,
) : AbstractDelegatingHandler<AuthenticationSuccessHandler>(
resolver, context::resolve
), AuthenticationSuccessHandler {
/**
* 身份验证成功时的回调方法
*
* 此方法会在用户成功通过身份验证后被调用它使用 [resolveDelegate] 方法根据当前请求解析出
* 合适的具体处理器并委托给该处理器执行后续操作
*
* @param request HTTP 请求对象提供客户端的请求信息
* @param response HTTP 响应对象用于向客户端发送响应
* @param authentication 包含身份验证成功的用户信息和权限等数据的对象
*/
override fun onAuthenticationSuccess(
request: HttpServletRequest,
response: HttpServletResponse,
authentication: Authentication,
) {
resolveDelegate(request).onAuthenticationSuccess(request, response, authentication)
}
}

View File

@ -0,0 +1,44 @@
package com.gewuyou.forgeboot.security.authenticate.impl.handler
import com.gewuyou.forgeboot.security.authenticate.impl.resolver.CompositeLoginRequestResolver
import com.gewuyou.forgeboot.security.authenticate.impl.strategy.context.LogoutSuccessHandlerContext
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import org.springframework.security.core.Authentication
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler
/**
* 组合注销成功处理程序
*
* 该类通过组合多种注销成功处理器实现统一的注销成功处理逻辑
*
* @property resolver 用于解析请求并确定适用的注销成功处理器的解析器实例
* @property context 提供具体地注销成功处理上下文用于动态获取处理器
*
* @since 2025-06-11 00:13:04
* @author gewuyou
*/
class CompositeLogoutSuccessHandler(
private val resolver: CompositeLoginRequestResolver,
private val context: LogoutSuccessHandlerContext,
) : AbstractDelegatingHandler<LogoutSuccessHandler>(
resolver, context::resolve
), LogoutSuccessHandler {
/**
* 在用户成功注销时执行的具体操作
*
* 该方法通过解析当前请求获取对应的注销成功处理器并委托其执行实际的注销后操作
*
* @param request HTTP请求对象提供客户端发送的请求信息
* @param response HTTP响应对象用于向客户端发送响应
* @param authentication 包含认证信息的对象在注销时通常表示已认证的用户信息
*/
override fun onLogoutSuccess(
request: HttpServletRequest,
response: HttpServletResponse,
authentication: Authentication,
) {
resolveDelegate(request).onLogoutSuccess(request, response, authentication)
}
}

View File

@ -0,0 +1,162 @@
package com.gewuyou.forgeboot.security.authenticate.impl.provider
import com.gewuyou.forgeboot.security.authenticate.api.exception.ForgeBootAuthenticationException
import com.gewuyou.forgeboot.security.authenticate.api.service.UserCacheService
import com.gewuyou.forgeboot.security.authenticate.api.service.UserDetailsService
import com.gewuyou.forgeboot.security.authenticate.impl.constants.ForgeBootSecurityAuthenticationResponseInformation
import org.springframework.security.authentication.AuthenticationProvider
import org.springframework.security.core.Authentication
import org.springframework.security.core.AuthenticationException
import org.springframework.security.core.userdetails.UserDetails
import java.util.concurrent.atomic.AtomicBoolean
/**
* 通用主体凭据认证提供器抽象类
* 负责封装认证流程中通用的逻辑凭据校验和构建成功认证对象由子类完成
*
* @author gewuyou
* @since 2025-06-11
*/
abstract class AbstractPrincipalCredentialsAuthenticationProvider(
private val userCacheService: UserCacheService,
private val userDetailsService: UserDetailsService
) : AuthenticationProvider {
/**
* 执行认证的核心方法
*
* @param authentication 待认证的 Authentication 对象
* @return 成功认证后的 Authentication 对象
* @throws ForgeBootAuthenticationException 认证失败时抛出异常
*/
override fun authenticate(authentication: Authentication): Authentication {
val principal = getPrincipal(authentication)
val fromCache = AtomicBoolean(true)
// Step 1: 获取用户(缓存优先)
var user = userCacheService.getUserFromCache(principal) ?: run {
fromCache.set(false)
retrieveUser(principal)
}
try {
preAuthenticationCheck(user)
verifyCredentials(user, authentication)
postAuthenticationCheck(user)
} catch (e: AuthenticationException) {
if (fromCache.get()) {
// 如果缓存失败则尝试数据库
user = retrieveUser(principal)
preAuthenticationCheck(user)
verifyCredentials(user, authentication)
postAuthenticationCheck(user)
} else {
throw e
}
}
if (!fromCache.get()) {
userCacheService.putUserToCache(user)
}
return createSuccessAuthentication(authentication, user)
}
/**
* 判断该 Provider 是否支持指定类型的认证对象
*
* @param authentication 需要判断的认证类型
* @return Boolean 表示是否支持该类型认证
*/
abstract override fun supports(authentication: Class<*>): Boolean
/**
* 从数据源获取用户信息
*
* @param principal 用户标识
* @return UserDetails 用户详细信息
* @throws ForgeBootAuthenticationException principal 为空或加载失败时抛出异常
*/
protected open fun retrieveUser(principal: String): UserDetails {
if (principal.isBlank()) {
ForgeBootAuthenticationException(ForgeBootSecurityAuthenticationResponseInformation.PRINCIPAL_NOT_PROVIDED)
}
return try {
userDetailsService.loadUserByPrincipal(principal)
} catch (e: Exception) {
throw ForgeBootAuthenticationException(
ForgeBootSecurityAuthenticationResponseInformation.INTERNAL_SERVER_ERROR,
e
)
}
}
/**
* 在验证凭据前进行账户状态检查
*
* @param user 用户详情对象
* @throws ForgeBootAuthenticationException 当账户锁定或禁用时抛出异常
*/
protected open fun preAuthenticationCheck(user: UserDetails) {
if (!user.isAccountNonLocked) {
throw ForgeBootAuthenticationException(ForgeBootSecurityAuthenticationResponseInformation.ACCOUNT_IS_LOCKED)
}
if (!user.isEnabled) {
throw ForgeBootAuthenticationException(ForgeBootSecurityAuthenticationResponseInformation.ACCOUNT_IS_DISABLED)
}
}
/**
* 在验证凭据后进行凭证有效期检查
*
* @param user 用户详情对象
* @throws ForgeBootAuthenticationException 当凭证已过期时抛出异常
*/
protected open fun postAuthenticationCheck(user: UserDetails) {
if (!user.isCredentialsNonExpired) {
throw ForgeBootAuthenticationException(ForgeBootSecurityAuthenticationResponseInformation.ACCOUNT_CREDENTIALS_HAVE_EXPIRED)
}
}
/**
* 检查凭证是否为 null
*
* @param credentials 待检查的凭证对象
* @throws ForgeBootAuthenticationException 当凭证为 null 时抛出异常
*/
protected fun checkCredentialsNotNull(credentials: Any?) {
credentials ?: ForgeBootAuthenticationException(ForgeBootSecurityAuthenticationResponseInformation.PASSWORD_NOT_PROVIDED)
}
/**
* 提取认证对象中的用户标识
*
* @param authentication Authentication 对象
* @return String 用户标识字符串
*/
protected fun getPrincipal(authentication: Authentication): String =
authentication.principal.toString()
/**
* 验证用户凭据的具体实现由子类完成
*
* @param user 用户详情对象
* @param authentication 待验证的 Authentication 对象
* @throws ForgeBootAuthenticationException 凭据错误时抛出异常
*/
protected abstract fun verifyCredentials(user: UserDetails, authentication: Authentication)
/**
* 构建成功认证后的 Authentication 对象由子类实现
*
* @param authentication 原始的 Authentication 对象
* @param user 用户详情对象
* @return 成功认证后的 Authentication 对象
*/
protected abstract fun createSuccessAuthentication(
authentication: Authentication,
user: UserDetails
): Authentication
}

View File

@ -0,0 +1,77 @@
package com.gewuyou.forgeboot.security.authenticate.impl.provider.impl
import com.gewuyou.forgeboot.security.authenticate.api.exception.ForgeBootAuthenticationException
import com.gewuyou.forgeboot.security.authenticate.api.service.UserCacheService
import com.gewuyou.forgeboot.security.authenticate.api.service.UserDetailsService
import com.gewuyou.forgeboot.security.authenticate.impl.constants.ForgeBootSecurityAuthenticationResponseInformation
import com.gewuyou.forgeboot.security.authenticate.impl.provider.AbstractPrincipalCredentialsAuthenticationProvider
import com.gewuyou.forgeboot.security.core.common.token.UsernamePasswordAuthenticationToken
import org.springframework.security.core.Authentication
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.security.crypto.password.PasswordEncoder
/**
* 用户名密码身份验证提供商
*
* 该类用于处理基于用户名和密码的身份验证流程继承自 AbstractPrincipalCredentialsAuthenticationProvider
* 提供了验证凭证创建成功认证对象以及判断支持的认证类型的功能
*
* @property userCacheService 用户缓存服务用于获取用户缓存信息
* @property userDetailsService 用户详情服务用于加载用户详细信息
* @property passwordEncoder 密码编码器用于密码匹配校验
*
* @since 2025-06-11 17:12:30
* @author gewuyou
*/
class UsernamePasswordAuthenticationProvider(
userCacheService: UserCacheService,
userDetailsService: UserDetailsService,
private val passwordEncoder: PasswordEncoder
) : AbstractPrincipalCredentialsAuthenticationProvider(userCacheService, userDetailsService) {
/**
* 验证用户提供的凭证是否有效
*
* 此方法会检查认证对象中的凭证是否为空并使用密码编码器对原始密码和编码后的密码进行比对
*
* @param user 已加载的用户详情对象
* @param authentication 当前的认证对象包含用户提交的凭证信息
* @throws com.gewuyou.forgeboot.security.authenticate.api.exception.ForgeBootAuthenticationException 如果凭证不匹配或为空
*/
override fun verifyCredentials(user: UserDetails, authentication: Authentication) {
checkCredentialsNotNull(authentication.credentials)
val raw = authentication.credentials.toString()
val encoded = user.password.toString()
if (!passwordEncoder.matches(raw, encoded)) {
throw ForgeBootAuthenticationException(ForgeBootSecurityAuthenticationResponseInformation.INCORRECT_USERNAME_OR_PASSWORD)
}
}
/**
* 创建一个成功的认证对象
*
* 在验证通过后此方法将生成一个新的认证对象表示已认证的用户状态
*
* @param authentication 原始的认证请求对象
* @param user 已验证的用户详情对象
* @return 返回一个表示成功认证的新 Authentication 实例
*/
override fun createSuccessAuthentication(
authentication: Authentication,
user: UserDetails
): Authentication {
return UsernamePasswordAuthenticationToken(user, null, user.authorities)
}
/**
* 判断当前提供者是否支持给定的认证类型
*
* 此方法确保只处理 UsernamePasswordAuthenticationToken 类型的认证请求
*
* @param authentication 认证类类型
* @return Boolean 表示是否支持该认证类型
*/
override fun supports(authentication: Class<*>): Boolean {
return UsernamePasswordAuthenticationToken::class.java.isAssignableFrom(authentication)
}
}

View File

@ -0,0 +1,38 @@
package com.gewuyou.forgeboot.security.authenticate.impl.resolver
import com.gewuyou.forgeboot.security.authenticate.api.resolver.LoginRequestResolver
import com.gewuyou.forgeboot.security.core.authenticate.entities.request.LoginRequest
import jakarta.servlet.http.HttpServletRequest
import kotlin.collections.find
/**
* 复合登录请求解析器
*
* 该类用于根据请求的内容类型(contentType)动态选择合适的登录请求解析器
* 它通过组合多种 LoginRequestResolver实现支持不同格式的登录请求解析
*
* @property resolvers 提供支持的各种登录请求解析器列表
*
* @since 2025-06-10 16:46:57
* @author gewuyou
*/
class CompositeLoginRequestResolver(
private val resolvers: List<LoginRequestResolver>
) {
/**
* 解析登录请求
*
* 根据给定的 HTTP 请求中的内容类型(contentType)查找支持该类型的解析器
* 并使用该解析器解析登录请求
*
* @param request 需要解析的 HTTP 请求
* @return 解析后的登录请求对象(LoginRequest)
* @throws IllegalArgumentException 如果没有找到支持当前内容类型的解析器
*/
fun resolve(request: HttpServletRequest): LoginRequest {
val contentType = request.contentType ?: ""
val resolver = resolvers.find { it.supports(contentType) }
?: throw IllegalArgumentException("Unsupported content type: $contentType")
return resolver.resolve(request)
}
}

View File

@ -0,0 +1,64 @@
package com.gewuyou.forgeboot.security.authenticate.impl.resolver
import com.fasterxml.jackson.databind.ObjectMapper
import com.gewuyou.forgeboot.security.authenticate.api.registry.LoginRequestTypeRegistry
import com.gewuyou.forgeboot.security.authenticate.api.resolver.LoginRequestResolver
import com.gewuyou.forgeboot.security.core.authenticate.entities.request.AuthenticationRequest
import com.gewuyou.forgeboot.security.core.authenticate.entities.request.LoginRequest
import jakarta.servlet.http.HttpServletRequest
import org.springframework.http.MediaType
/**
* JSON登录请求解析器
*
* 该解析器用于处理JSON格式的登录请求数据通过两次反序列化过程来支持动态类型的登录请求
* 首先读取请求中的type字段以确定具体的登录类型然后根据该类型再次反序列化为对应的对象
*
* @property objectMapper 用于JSON序列化和反序列化的Jackson ObjectMapper实例
* @property loginRequestTypeRegistry 登录请求类型的注册表用于根据登录类型获取实际类
*
* @since 2025-06-10 16:37:22
* @author gewuyou
*/
class JsonLoginRequestResolver(
private val objectMapper: ObjectMapper,
private val loginRequestTypeRegistry: LoginRequestTypeRegistry, // 动态注册哪些类型
) : LoginRequestResolver {
/**
* 判断是否支持给定的内容类型
*
* 该方法检查内容类型字符串中是否包含JSON媒体类型application/json
* 表示当前解析器可以处理该类型的内容
*
* @param contentType 请求的内容类型字符串
* @return 如果支持返回true否则返回false
*/
override fun supports(contentType: String): Boolean {
return contentType.contains(MediaType.APPLICATION_JSON_VALUE)
}
/**
* 解析登录请求
*
* 从HttpServletRequest中读取JSON输入流并进行两次反序列化操作
* 1. 首次反序列化为AuthenticationRequest以提取type字段
* 2. 根据type字段从注册表中查找对应的实际类
* 3. 再次反序列化为实际类的对象
*
* @param request HTTP请求对象
* @return 解析后的登录请求对象
* @throws IllegalArgumentException 如果不支持的登录类型
*/
override fun resolve(request: HttpServletRequest): LoginRequest {
val json = request.inputStream.reader().readText()
// 1. 先反序列化为 AuthenticationRequest读取 type 字段
val tempRequest = objectMapper.readValue(json, AuthenticationRequest::class.java)
val loginType = tempRequest.type
// 2. 从注册表中获取真正对应的类型
val clazz = loginRequestTypeRegistry.getTypeForLoginType(loginType)
?: throw IllegalArgumentException("Unsupported login type: $loginType")
// 3. 再次反序列化为真正请求体类型
return objectMapper.readValue(json, clazz)
}
}

View File

@ -0,0 +1,67 @@
package com.gewuyou.forgeboot.security.authenticate.impl.strategy
import com.fasterxml.jackson.databind.ObjectMapper
import com.gewuyou.forgeboot.security.authenticate.api.config.SecurityAuthenticateProperties
import com.gewuyou.forgeboot.security.authenticate.api.strategy.AuthenticationFailureStrategy
import com.gewuyou.forgeboot.webmvc.dto.R
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.security.core.AuthenticationException
/**
* 抽象验证失败策略基类
* 提供统一的认证失败处理机制实现模板
*
* @property objectMapper 用于JSON序列化响应结果
* @property properties 安全认证配置属性
* @author gewuyou
* @since 2025-06-14 20:14:14
*/
abstract class AbstractAuthenticationFailureStrategy(
private val objectMapper: ObjectMapper,
private val properties: SecurityAuthenticateProperties
) : AuthenticationFailureStrategy {
/**
* 处理认证失败的通用实现
* 将认证失败结果转换为JSON格式的HTTP响应
*
* @param request HTTP请求对象
* @param response HTTP响应对象
* @param exception 认证异常信息
*/
override fun onAuthenticationFailure(
request: HttpServletRequest,
response: HttpServletResponse,
exception: AuthenticationException,
) {
// 设置响应内容类型为JSON
response.contentType = MediaType.APPLICATION_JSON_VALUE
// 获取响应输出流并写入序列化后的失败结果
val writer = response.writer
writer.print(
objectMapper.writeValueAsString(buildFailureResult(exception))
)
}
/**
* 构建认证失败的响应数据
* 可被子类重写以提供自定义失败响应
*
* @param authenticationException 认证异常信息
* @return 序列化为JSON的响应数据
*/
protected open fun buildFailureResult(authenticationException: AuthenticationException): Any {
// 获取异常消息,若为空则使用默认配置的失败响应
val message = authenticationException.message
return R.failure<String>(
HttpStatus.UNAUTHORIZED.value(),
message?.let {
message
} ?: properties.defaultAuthenticationFailureResponse
)
}
}

View File

@ -0,0 +1,63 @@
package com.gewuyou.forgeboot.security.authenticate.impl.strategy
import com.fasterxml.jackson.databind.ObjectMapper
import com.gewuyou.forgeboot.security.authenticate.api.strategy.AuthenticationSuccessStrategy
import com.gewuyou.forgeboot.security.authenticate.impl.constants.ForgeBootSecurityAuthenticationResponseInformation
import com.gewuyou.forgeboot.webmvc.dto.R
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.security.core.Authentication
/**
* 抽象认证成功策略
*
* 该基础类为认证成功后的处理提供统一模板
*
* @property objectMapper 用于将响应对象序列化为 JSON 字符串
* @since 2025-06-12 21:26:11
* @author gewuyou
*/
abstract class AbstractAuthenticationSuccessStrategy(
private val objectMapper: ObjectMapper,
) : AuthenticationSuccessStrategy {
/**
* 认证成功时触发的方法
*
* 处理 HTTP 响应的设置包括状态码内容类型和写入响应数据
*
* @param request 本次请求的 HttpServletRequest 对象
* @param response 本次响应的 HttpServletResponse 对象
* @param authentication 包含认证信息的对象
*/
override fun onAuthenticationSuccess(
request: HttpServletRequest,
response: HttpServletResponse,
authentication: Authentication,
) {
// 构建认证成功响应数据
val result = buildSuccessResponse(authentication)
// 设置响应状态码为 200 OK
response.status = HttpStatus.OK.value()
// 设置响应内容类型为 application/json
response.contentType = MediaType.APPLICATION_JSON_VALUE
// 将响应结果序列化为 JSON 并写入响应输出流
response.writer.write(objectMapper.writeValueAsString(result))
response.writer.flush()
}
/**
* 构建认证成功响应体对象
*
* @param authentication 包含认证信息的对象
* @return 返回准备序列化并发送给客户端的响应对象
*/
protected open fun buildSuccessResponse(authentication: Authentication): Any {
return R.success(
HttpStatus.OK.value(),
ForgeBootSecurityAuthenticationResponseInformation.LOGIN_SUCCESS, authentication.principal
)
}
}

View File

@ -0,0 +1,59 @@
package com.gewuyou.forgeboot.security.authenticate.impl.strategy
import com.fasterxml.jackson.databind.ObjectMapper
import com.gewuyou.forgeboot.security.authenticate.api.strategy.LogoutSuccessStrategy
import com.gewuyou.forgeboot.security.authenticate.impl.constants.ForgeBootSecurityAuthenticationResponseInformation
import com.gewuyou.forgeboot.webmvc.dto.R
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.security.core.Authentication
/**
* 抽象注销成功策略
*
* 该抽象类为注销成功时的响应处理提供了统一的策略框架
* 子类应继承此类并根据需要定制具体注销成功逻辑
*
* @property objectMapper 用于将响应结果序列化为 JSON 格式
* @constructor 接收一个 ObjectMapper 实例作为参数
*
* @since 2025-06-14 20:29:25
* @author gewuyou
*/
abstract class AbstractLogoutSuccessStrategy(
private val objectMapper: ObjectMapper,
): LogoutSuccessStrategy {
/**
* 注销成功回调方法
*
* 当用户成功注销时该方法会被调用以处理响应默认实现会返回一个包含成功状态码和消息的 JSON 响应
*
* @param request 表示 HTTP 请求对象提供有关客户端请求的信息
* @param response 表示 HTTP 响应对象用于向客户端发送响应
* @param authentication 包含认证信息的对象在注销时提供额外上下文数据
*/
override fun onLogoutSuccess(
request: HttpServletRequest,
response: HttpServletResponse,
authentication: Authentication,
) {
// 创建表示注销成功响应数据对象
val result = R.success<String>(
HttpStatus.OK.value(),
ForgeBootSecurityAuthenticationResponseInformation.LOGOUT_SUCCESS
)
// 设置响应状态码为 200 OK
response.status = HttpStatus.OK.value()
// 设置响应内容类型为 application/json
response.contentType = MediaType.APPLICATION_JSON_VALUE
// 将响应结果序列化为 JSON 并写入响应输出流
response.writer.write(objectMapper.writeValueAsString(result))
response.writer.flush()
}
}

View File

@ -0,0 +1,82 @@
package com.gewuyou.forgeboot.security.authenticate.impl.strategy.context
import com.gewuyou.forgeboot.core.extension.log
import com.gewuyou.forgeboot.security.authenticate.api.enums.LoginTypes
/**
* 抽象处理程序上下文
* 用于构建策略处理器的通用框架支持通过类型解析对应的处理器实例
*
* @since 2025-06-11 21:59:40
* @author gewuyou
*/
abstract class AbstractHandlerContext<T : Any, H : Any>(
/**
* 策略对象列表用于从中提取类型和对应的处理器
*/
strategies: List<T>,
/**
* 当前处理器类型的名称用于日志输出时标识处理器种类
*/
private val typeName: String,
/**
* 提取策略对应类型的函数用于确定每个策略的标识
*/
extractType: (T) -> String,
/**
* 提取策略对应处理器的函数用于获取实际可执行的处理器逻辑
*/
extractHandler: (T) -> H
) {
/**
* 默认处理器用于处理未指定登录类型的请求
*/
private var defaultStrategy: T? = null
/**
* 存储登录类型与处理器之间的映射关系
*/
private val handlerMap: Map<String, H>
init {
val map = mutableMapOf<String, H>()
for (strategy in strategies) {
// 提取当前策略的登录类型标识
val loginType = extractType(strategy)
if (loginType == LoginTypes.DEFAULT) {
defaultStrategy = strategy
continue
}
// 获取当前策略对应的处理器实例
val handler = extractHandler(strategy)
// 检查是否已存在相同类型的处理器
val existingHandlerName = map[loginType]?.javaClass?.name
existingHandlerName?.let {
log.warn("重复注册登录类型 [$loginType] 的$typeName,已存在 $existingHandlerName,被 ${strategy::class.java.name} 覆盖")
}?.run {
log.info("注册${typeName}策略: $loginType -> ${strategy::class.java.name}")
}
// 注册或覆盖处理器映射
map[loginType] = handler
}
// 不可变化处理器映射表
this.handlerMap = map.toMap()
}
/**
* 根据指定的登录类型解析对应的处理器实例
*
* 该方法首先尝试通过给定的登录类型从处理器映射中查找对应的策略处理器
* 如果找不到匹配项则尝试使用默认类型的处理器
* 如果仍然无法找到默认处理器也不存在则抛出异常
*
* @param loginType 登录类型标识用于查找对应的处理器
* @return H 类型的处理器实例与给定的登录类型或默认类型匹配
* @throws IllegalArgumentException 如果既没有找到与登录类型匹配的处理器也没有定义默认处理器
*/
fun resolve(loginType: String): H {
return handlerMap[loginType]
?: handlerMap[LoginTypes.DEFAULT]
?: throw IllegalArgumentException("未找到 loginType 为 [$loginType] 的$typeName,且未定义默认$typeName")
}
}

View File

@ -0,0 +1,19 @@
package com.gewuyou.forgeboot.security.authenticate.impl.strategy.context
import com.gewuyou.forgeboot.security.authenticate.api.strategy.AuthenticationFailureStrategy
import org.springframework.security.web.authentication.AuthenticationFailureHandler
/**
*身份验证故障处理程序上下文
*
* @since 2025-06-10 23:48:02
* @author gewuyou
*/
class AuthenticationFailureHandlerContext(
strategies: List<AuthenticationFailureStrategy>
) : AbstractHandlerContext<AuthenticationFailureStrategy, AuthenticationFailureHandler>(
strategies,
"认证失败处理器",
{ it.supportedLoginType() },
{ it }
)

View File

@ -0,0 +1,20 @@
package com.gewuyou.forgeboot.security.authenticate.impl.strategy.context
import com.gewuyou.forgeboot.security.authenticate.api.strategy.AuthenticationSuccessStrategy
import org.springframework.security.web.authentication.AuthenticationSuccessHandler
/**
*身份验证成功处理程序上下文
*
* @since 2025-06-10 23:45:40
* @author gewuyou
*/
class AuthenticationSuccessHandlerContext(
strategies: List<AuthenticationSuccessStrategy>,
) : AbstractHandlerContext<AuthenticationSuccessStrategy, AuthenticationSuccessHandler>(
strategies,
"认证成功处理器"
,
{ it.supportedLoginType() },
{ it }
)

View File

@ -0,0 +1,65 @@
package com.gewuyou.forgeboot.security.authenticate.impl.strategy.context
import com.gewuyou.forgeboot.core.extension.log
import com.gewuyou.forgeboot.security.authenticate.api.strategy.LoginRequestConverterStrategy
import com.gewuyou.forgeboot.security.core.authenticate.entities.request.LoginRequest
import jakarta.annotation.PostConstruct
import org.springframework.context.ApplicationContext
import org.springframework.security.core.Authentication
import kotlin.collections.component1
import kotlin.collections.component2
import kotlin.collections.iterator
import kotlin.jvm.java
/**
* 登录请求转换器上下文
* 负责管理不同登录类型的请求转换策略实现策略模式的上下文类
*
* @since 2025-06-10 20:48:09
* @author gewuyou
*/
class LoginRequestConverterContext(
private val applicationContext: ApplicationContext
) {
/**
* 存储登录类型与对应转换策略的映射关系
*/
private val converterStrategies = mutableMapOf<String, LoginRequestConverterStrategy>()
/**
* 初始化转换器策略
* 通过Spring容器获取所有LoginRequestConverterStrategy实例
* 并根据支持的登录类型注册到converterStrategies中
*/
@PostConstruct
fun initConverterStrategies() {
val strategies = applicationContext.getBeansOfType(LoginRequestConverterStrategy::class.java).toMap()
for ((_, strategy) in strategies) {
val loginType = strategy.getSupportedLoginType()
addConverter(loginType, strategy)
}
}
/**
* 添加登录请求转换器策略
* @param loginType 登录类型标识符
* @param strategy 转换器策略实现
*/
private fun addConverter(loginType: String, strategy: LoginRequestConverterStrategy) {
converterStrategies[loginType] = strategy
log.info("为登录类型: $loginType 添加登录请求转换器策略 ${strategy::class.java.name}")
}
/**
* 执行对应的转换策略
* 根据登录请求类型查找并执行对应的转换器策略
*
* @param loginRequest 登录请求对象
* @return Authentication 认证对象
* @throws IllegalArgumentException 当找不到匹配的转换策略时抛出异常
*/
fun executeStrategy(loginRequest: LoginRequest): Authentication {
return converterStrategies[loginRequest.getType()]?.convert(loginRequest)
?: throw IllegalArgumentException("No converter strategy found for login type ${loginRequest.getType()}")
}
}

View File

@ -0,0 +1,19 @@
package com.gewuyou.forgeboot.security.authenticate.impl.strategy.context
import com.gewuyou.forgeboot.security.authenticate.api.strategy.LogoutSuccessStrategy
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler
/**
*注销成功处理程序上下文
*
* @since 2025-06-11 00:12:33
* @author gewuyou
*/
class LogoutSuccessHandlerContext(
strategies: List<LogoutSuccessStrategy>,
) : AbstractHandlerContext<LogoutSuccessStrategy, LogoutSuccessHandler>(
strategies,
"登出处理器",
{ it.supportedLoginType() },
{ it }
)

View File

@ -0,0 +1,28 @@
package com.gewuyou.forgeboot.security.authenticate.impl.strategy.impl
import com.fasterxml.jackson.databind.ObjectMapper
import com.gewuyou.forgeboot.security.authenticate.api.config.SecurityAuthenticateProperties
import com.gewuyou.forgeboot.security.authenticate.api.enums.LoginTypes
import com.gewuyou.forgeboot.security.authenticate.impl.strategy.AbstractAuthenticationFailureStrategy
import org.springframework.stereotype.Component
/**
*默认身份验证失败策略
*
* @since 2025-06-14 20:26:46
* @author gewuyou
*/
@Component("defaultAuthenticationFailureStrategy")
class DefaultAuthenticationFailureStrategy(
objectMapper: ObjectMapper,
properties: SecurityAuthenticateProperties
): AbstractAuthenticationFailureStrategy(objectMapper,properties) {
/**
* 获取当前策略支持的登录类型标识符
*
* @return 返回当前策略支持的登录类型字符串
*/
override fun supportedLoginType(): String {
return LoginTypes.DEFAULT
}
}

View File

@ -0,0 +1,31 @@
package com.gewuyou.forgeboot.security.authenticate.impl.strategy.impl
import com.fasterxml.jackson.databind.ObjectMapper
import com.gewuyou.forgeboot.security.authenticate.api.enums.LoginTypes
import com.gewuyou.forgeboot.security.authenticate.impl.strategy.AbstractAuthenticationSuccessStrategy
import org.springframework.stereotype.Component
/**
* 默认身份验证成功策略
*
* 该类实现了一个基础的身份验证成功处理策略用于处理默认类型的登录认证
* 继承自 [AbstractAuthenticationSuccessStrategy]提供具体的登录类型标识
*
* @property objectMapper 用于序列化/反序列化 JSON 数据的对象
* @since 2025-06-12 22:13:42
* @author gewuyou
*/
@Component("defaultAuthenticationSuccessStrategy")
class DefaultAuthenticationSuccessStrategy(
objectMapper: ObjectMapper,
) : AbstractAuthenticationSuccessStrategy(objectMapper) {
/**
* 获取当前策略支持的登录类型标识
*
* 此方法返回 "default" 字符串表示该策略适用于默认登录类型
* 在多策略环境下通过此标识来匹配相应的处理逻辑
*
* @return 返回支持的登录类型标识字符串
*/
override fun supportedLoginType(): String = LoginTypes.DEFAULT
}

View File

@ -0,0 +1,27 @@
package com.gewuyou.forgeboot.security.authenticate.impl.strategy.impl
import com.fasterxml.jackson.databind.ObjectMapper
import com.gewuyou.forgeboot.security.authenticate.api.enums.LoginTypes
import com.gewuyou.forgeboot.security.authenticate.impl.strategy.AbstractLogoutSuccessStrategy
import org.springframework.stereotype.Component
/**
*默认注销成功策略
*
* @since 2025-06-14 20:38:16
* @author gewuyou
*/
@Component("defaultLogoutSuccessStrategy")
class DefaultLogoutSuccessStrategy(
objectMapper: ObjectMapper
): AbstractLogoutSuccessStrategy(objectMapper) {
/**
* 获取当前策略支持的登录类型标识符
*
* @return 返回当前策略支持的登录类型字符串
*/
override fun supportedLoginType(): String {
return LoginTypes.DEFAULT
}
}

View File

@ -0,0 +1,37 @@
package com.gewuyou.forgeboot.security.authenticate.impl.strategy.impl
import com.gewuyou.forgeboot.security.authenticate.api.enums.LoginTypes
import com.gewuyou.forgeboot.security.authenticate.api.strategy.LoginRequestConverterStrategy
import com.gewuyou.forgeboot.security.core.authenticate.entities.request.LoginRequest
import com.gewuyou.forgeboot.security.core.authenticate.entities.request.UsernamePasswordAuthenticationRequest
import com.gewuyou.forgeboot.security.core.common.token.UsernamePasswordAuthenticationToken
import org.springframework.security.core.Authentication
import org.springframework.stereotype.Component
/**
* 用户名 密码 认证 Token 转换器策略
* @since 2025-02-15 03:25:14
* @author gewuyou
*/
@Component("usernamePasswordLoginRequestConverterStrategy")
class UsernamePasswordLoginRequestConverterStrategy : LoginRequestConverterStrategy {
/**
* 转换登录请求为认证对象
* @param loginRequest 登录请求
* @return 认证对象
*/
override fun convert(loginRequest: LoginRequest): Authentication {
if (loginRequest is UsernamePasswordAuthenticationRequest) {
return UsernamePasswordAuthenticationToken.Companion.unauthenticated(loginRequest.username, loginRequest.password)
}
throw IllegalArgumentException("Unsupported login request type: ${loginRequest.javaClass.name}")
}
/**
* 获取支持的登录类型
* @return 请求的登录类型
*/
override fun getSupportedLoginType(): String {
return LoginTypes.USERNAME_PASSWORD
}
}

View File

@ -0,0 +1,7 @@
dependencies {
api(project(Modules.Security.CORE))
compileOnly(libs.springBootStarter.web)
compileOnly(libs.springBootStarter.security)
kapt(libs.springBoot.configuration.processor)
}

View File

@ -0,0 +1,16 @@
package com.gewuyou.forgeboot.security.authorize.api.annotations
/**
* 权限校验注解用于标记需要特定权限才能访问的方法
*
* @property value 需要校验的权限字符串例如"user:read"
* @property dynamic 是否为动态权限默认为 false若为 true 表示该权限需要在运行时动态解析
*
* 注解作用于方法级别运行时生效常用于接口权限控制
*/
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class RequiresPermission(
val value: String,
val dynamic: Boolean = false
)

View File

@ -0,0 +1,14 @@
package com.gewuyou.forgeboot.security.authorize.api.config
import org.springframework.boot.context.properties.ConfigurationProperties
/**
*security 授权属性
*
* @since 2025-06-15 19:26:20
* @author gewuyou
*/
@ConfigurationProperties(prefix = "forgeboot.security.authorize")
class SecurityAuthorizeProperties {
var defaultExceptionResponse: String = "Sorry, you don't have access to the resource!"
}

View File

@ -0,0 +1,20 @@
package com.gewuyou.forgeboot.security.authorize.api.manager
import org.springframework.security.core.Authentication
/**
* 访问管理器接口用于定义鉴权逻辑
*
* @since 2025-06-15 18:38:07
* @author gewuyou
*/
fun interface AccessManager {
/**
* 判断用户是否具有指定权限
*
* @param authentication Spring Security提供的身份认证信息
* @param permission 需要验证的权限字符串
* @return Boolean 返回是否有权限的布尔值
*/
fun hasPermission(authentication: Authentication, permission: String): Boolean
}

View File

@ -0,0 +1,16 @@
package com.gewuyou.forgeboot.security.authorize.api.manager
import org.springframework.security.authorization.AuthorizationManager
/**
* 动态授权管理器接口
*
* 该接口用于处理请求级别的动态权限控制逻辑基于Spring Security的AuthorizationManager接口进行扩展
* 通过泛型类型T定义需要处理的授权上下文类型例如Web请求或方法调用等
*
* @param <T> 授权操作涉及的具体上下文类型如RequestAuthorizationContext或其他自定义上下文对象
*
* @since 2025-06-24 15:52:01
* @author gewuyou
*/
interface DynamicAuthorizationManager<T> : AuthorizationManager<T>

View File

@ -0,0 +1,11 @@
package com.gewuyou.forgeboot.security.authorize.api.manager
import org.springframework.security.authorization.ReactiveAuthorizationManager
/**
*动态反应式授权管理器
*
* @since 2025-06-24 13:16:25
* @author gewuyou
*/
fun interface DynamicReactiveAuthorizationManager<T> : ReactiveAuthorizationManager<T>

View File

@ -0,0 +1,20 @@
package com.gewuyou.forgeboot.security.authorize.api.provider
import org.springframework.security.core.Authentication
/**
* 权限提供程序接口用于定义权限集合的生成逻辑
*
* @since 2025-06-15 13:08:18
* @author gewuyou
*/
fun interface PermissionProvider {
/**
* 提供基于认证信息的权限集合
*
* @param authentication 包含用户认证信息的对象用于权限判断
* @return 返回与当前认证信息关联的权限字符串集合
*/
fun listPermissions(authentication: Authentication): Collection<String>
}

View File

@ -0,0 +1,21 @@
package com.gewuyou.forgeboot.security.authorize.api.resolver
/**
* 权限解析程序接口用于将请求路径和HTTP方法转换为具体的权限标识符
*
* @since 2025-06-24 13:23:29
* @author gewuyou
*/
fun interface PermissionResolver {
/**
* 解析给定的请求路径和HTTP方法生成对应的权限标识字符串
*
* 此方法通常用于安全框架中用来判断用户是否拥有执行特定操作的权限
*
* @param path 请求的URL路径例如 "/api/users"
* @param method HTTP请求方法 GET, POST
* @return 返回一个表示权限的字符串 "user:read", "user:write"
*/
fun resolve(path: String, method: String): String
}

View File

@ -0,0 +1,32 @@
package com.gewuyou.forgeboot.security.authorize.api.strategy
import com.gewuyou.forgeboot.security.authorize.api.provider.PermissionProvider
import org.springframework.security.core.Authentication
/**
* 授权策略接口
*
* 定义了实现授权逻辑的统一接口所有具体授权策略都需要实现该接口
* 该接口设计为函数式接口便于通过Lambda表达式或函数引用实现简洁的策略定义
*
* @since 2025-06-15 13:01:27
* @author gewuyou
*/
fun interface AuthorizationStrategy {
/**
* 执行授权判断
*
* 根据提供的认证信息权限标识和权限提供者列表判断当前认证是否具备指定权限
*
* @param authentication 当前用户的认证信息包含主体身份和凭证等
* @param permission 需要验证的权限标识通常为特定资源的操作权限字符串
* @param providers 权限提供者列表用于获取与权限相关的数据源或校验逻辑
* @return Boolean 返回授权结果true表示授权通过false表示拒绝访问
*/
fun authorize(
authentication: Authentication,
permission: String,
providers: List<PermissionProvider>
): Boolean
}

View File

@ -0,0 +1,12 @@
plugins {
alias(libs.plugins.kotlin.plugin.spring)
}
dependencies {
compileOnly(libs.springBootStarter.web)
// webflux
compileOnly(libs.springBootStarter.webflux)
compileOnly(libs.springBootStarter.security)
compileOnly(project(Modules.Security.Authorize.IMPL))
compileOnly(project(Modules.Security.Authorize.API))
implementation(project(Modules.Security.CORE))
}

View File

@ -0,0 +1,15 @@
package com.gewuyou.forgeboot.security.authorize.autoconfigure
import com.gewuyou.forgeboot.security.authorize.api.config.SecurityAuthorizeProperties
import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.annotation.Configuration
/**
*forge security 授权自动配置
*
* @since 2025-06-15 19:28:11
* @author gewuyou
*/
@Configuration
@EnableConfigurationProperties(SecurityAuthorizeProperties::class)
class ForgeSecurityAuthorizeAutoConfiguration

View File

@ -0,0 +1,4 @@
com.gewuyou.forgeboot.security.authorize.autoconfigure.ForgeSecurityAuthorizeCoreConfiguration
com.gewuyou.forgeboot.security.authorize.autoconfigure.ReactiveAuthorizeSecurityConfiguration
com.gewuyou.forgeboot.security.authorize.autoconfigure.ForgeSecurityAuthorizeAutoConfiguration
com.gewuyou.forgeboot.security.authorize.autoconfigure.ServletAuthorizeSecurityConfiguration

View File

@ -0,0 +1,6 @@
extra{
setProperty(ProjectFlags.IS_ROOT_MODULE,true)
}
dependencies {
}

View File

@ -0,0 +1,14 @@
plugins {
alias(libs.plugins.kotlin.plugin.spring)
}
dependencies {
implementation(libs.springBootStarter.aop)
implementation(libs.springExpression)
implementation(project(Modules.Core.EXTENSION))
implementation(project(Modules.Cache.STARTER))
implementation(project(Modules.Webmvc.DTO)) // webflux
compileOnly(libs.springBootStarter.webflux)
compileOnly(project(Modules.Security.Authorize.API))
compileOnly(libs.springBootStarter.web)
compileOnly(libs.springBootStarter.security)
}

View File

@ -0,0 +1,54 @@
package com.gewuyou.forgeboot.security.authorize.impl.adapter
import com.gewuyou.forgeboot.core.extension.log
import com.gewuyou.forgeboot.security.authorize.api.manager.AccessManager
import com.gewuyou.forgeboot.security.authorize.api.resolver.PermissionResolver
import org.springframework.security.authorization.AuthorizationDecision
import org.springframework.security.authorization.AuthorizationManager
import org.springframework.security.core.Authentication
import org.springframework.security.web.access.intercept.RequestAuthorizationContext
import java.util.function.Supplier
/**
* 授权管理器适配器用于将 AccessManager 适配为 Spring Security AuthorizationManager
*
* @property accessManager 提供权限判断的访问管理器实例
* @property permissionResolver 用于解析请求路径和方法对应的权限需求
* @since 2025-06-23 21:42:19
* @author gewuyou
*/
class DynamicAuthorizationManagerAdapter(
private val accessManager: AccessManager,
private val permissionResolver: PermissionResolver,
) : AuthorizationManager<RequestAuthorizationContext> {
/**
* 执行权限校验的核心方法
*
* @param authentication 提供认证信息的 Supplier从中获取当前用户的身份认证对象
* @param context 请求上下文包含请求相关的额外信息未在此实现中使用
* @return 返回一个 AuthorizationDecision 对象表示是否通过授权
*/
override fun check(
authentication: Supplier<Authentication>,
context: RequestAuthorizationContext,
): AuthorizationDecision {
try {
// 获取 HTTP 请求的基本信息
val request = context.request
val method = request.method
val path = request.requestURI
// 解析当前请求所需的权限
val requiredPermission = permissionResolver.resolve(path, method)
// 校验用户是否拥有该权限
val granted = accessManager.hasPermission(authentication.get(), requiredPermission)
// 返回授权决策结果
return AuthorizationDecision(granted)
} catch (e: Exception) {
log.error("权限校验异常", e)
return AuthorizationDecision(false)
}
}
}

View File

@ -0,0 +1,60 @@
package com.gewuyou.forgeboot.security.authorize.impl.adapter
import com.gewuyou.forgeboot.security.authorize.api.manager.AccessManager
import com.gewuyou.forgeboot.security.authorize.api.manager.DynamicReactiveAuthorizationManager
import com.gewuyou.forgeboot.security.authorize.api.resolver.PermissionResolver
import org.springframework.security.authorization.AuthorizationDecision
import org.springframework.security.core.Authentication
import org.springframework.security.web.server.authorization.AuthorizationContext
import reactor.core.publisher.Mono
/**
* 反应式授权管理器适配器
* 用于将访问控制逻辑适配为 Spring Security ReactiveAuthorizationManager 接口规范
*
* @property accessManager 访问控制管理器用于执行具体的权限判断逻辑
* @property permissionResolver 权限解析器用于从请求中动态解析所需权限
* @since 2025-06-24 13:13:40
* @author gewuyou
*/
class DynamicReactiveAuthorizationManagerAdapter(
private val accessManager: AccessManager,
private val permissionResolver: PermissionResolver
) : DynamicReactiveAuthorizationManager<AuthorizationContext> {
/**
* 检查当前请求是否满足所需的授权条件
*
* 此方法从认证信息和授权上下文中提取关键数据通过权限解析器获取所需权限
* 并使用访问控制管理器验证用户是否具备该权限
*
* @param authentication 当前的认证信息流包含用户身份和权限
* @param context 授权上下文包含请求和交换信息
* @return Mono<AuthorizationDecision> 返回一个授权决策对象的流
*/
override fun check(
authentication: Mono<Authentication>,
context: AuthorizationContext
): Mono<AuthorizationDecision> {
return authentication
.map { auth ->
// 获取请求相关的信息
val exchange = context.exchange
val request = exchange.request
// 提取请求路径和方法
val path = request.path.toString()
val method = request.method
// 动态获取要校验的权限(可从 path、header、注解、上下文等推断
val requiredPermission = permissionResolver.resolve(path, method.name())
// 判断用户是否拥有该权限
val granted = accessManager.hasPermission(auth, requiredPermission)
// 创建授权决策结果
AuthorizationDecision(granted)
}
.defaultIfEmpty(AuthorizationDecision(false))
}
}

View File

@ -0,0 +1,85 @@
package com.gewuyou.forgeboot.security.authorize.impl.aspect
import com.gewuyou.forgeboot.security.authorize.api.annotations.RequiresPermission
import com.gewuyou.forgeboot.security.authorize.api.manager.AccessManager
import com.gewuyou.forgeboot.security.authorize.api.strategy.AuthorizationStrategy
import com.gewuyou.forgeboot.security.authorize.impl.resolver.SpELResolver
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.springframework.security.access.AccessDeniedException
import org.springframework.security.core.context.SecurityContextHolder
/**
* 授权切面
*
* 该切面用于处理带有 [RequiresPermission] 注解的方法执行权限校验逻辑
* 通过查找适用的 [AuthorizationStrategy] 来决定是否允许当前用户执行目标方法
*
* @property spELResolver SpEL 表达式解析器用于动态解析权限字符串
* @property accessManager 访问控制管理器用于最终的权限判断
*
* @since 2025-06-15 13:17:18
* @author gewuyou
*/
@Aspect
class RequiresPermissionAspect(
private val spELResolver: SpELResolver,
private val accessManager: AccessManager
) {
/**
* 环绕通知方法处理带有 [RequiresPermission] 注解的方法调用
*
* 执行流程
* 1. 获取当前用户的认证信息
* 2. 获取目标方法及其参数
* 3. 根据注解配置决定是否解析 SpEL 表达式获取实际权限标识
* 4. 查找适用的授权策略并执行授权逻辑
* 5. 如果授权成功则继续执行目标方法
*
* @param joinPoint ProceedingJoinPoint代表被拦截的方法执行连接点
* @param permission 方法上标注的 [RequiresPermission] 注解实例
* @return 目标方法的返回值
*/
@Around("@annotation(permission)")
fun around(joinPoint: ProceedingJoinPoint, permission: RequiresPermission): Any {
// 获取当前用户的安全上下文认证信息
val auth = SecurityContextHolder.getContext().authentication
// 根据 dynamic 配置决定是否解析 SpEL 表达式,得到最终权限标识
val perm = if (permission.dynamic) {
resolveSpEl(permission.value, joinPoint)
} else permission.value
// 使用 accessManager 进行权限校验,失败时抛出访问拒绝异常
if (!accessManager.hasPermission(auth, perm))
throw AccessDeniedException("权限不足,需要 [$perm]")
// 权限校验通过,继续执行目标方法
return joinPoint.proceed()
}
/**
* 解析 SpEL 表达式形参名可直接用 #变量 方式引用
*
* @RequiresPermission(dynamic = true, value = "'article:read:' + #articleId")
*
* @param expression 要解析的 SpEL 表达式
* @param joinPoint ProceedingJoinPoint从中提取方法参数
* @return 解析后的实际权限字符串
*/
private fun resolveSpEl(expression: String, joinPoint: ProceedingJoinPoint): String {
val methodSignature = joinPoint.signature as MethodSignature
val paramNames = methodSignature.parameterNames // ["articleId", "commentId", …]
val args = joinPoint.args // [123L, 456L, …]
// 组装成 Map<String, Any?>
val argsMap = paramNames
.mapIndexed { index, name -> name to args[index] }
.toMap()
return spELResolver.parse(expression, argsMap)
}
}

View File

@ -0,0 +1,71 @@
package com.gewuyou.forgeboot.security.authorize.impl.builder
import com.gewuyou.forgeboot.security.core.common.builder.SecurityFilterChainRegistrar
import com.gewuyou.forgeboot.security.core.common.customizer.HttpSecurityCustomizer
import com.gewuyou.forgeboot.security.core.common.extension.cleanUnNeedConfig
import com.gewuyou.forgeboot.security.core.common.wrapper.SecurityFilterChainWrapper
import org.springframework.http.HttpMethod
import org.springframework.security.authorization.AuthorizationManager
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.web.access.AccessDeniedHandler
import org.springframework.security.web.access.intercept.RequestAuthorizationContext
import org.springframework.security.web.util.matcher.RequestMatcher
/**
* 无状态安全过滤器链注册商
*
* 此类用于注册和构建无状态的安全过滤器链适用于基于令牌的身份验证场景
* 它通过定制 HttpSecurity 配置来实现特定的访问控制策略
*
* @property httpSecurityCustomizer 提供针对特定链的自定义配置逻辑
* @property authorizationManager 负责处理请求的授权逻辑
* @property accessDeniedHandler 处理未授权访问时的响应逻辑
* @since 2025-06-24 16:14:55
* @author gewuyou
*/
class StatelessSecurityFilterChainRegistrar(
private val httpSecurityCustomizer: List<HttpSecurityCustomizer>,
private val authorizationManager: AuthorizationManager<RequestAuthorizationContext>,
private val accessDeniedHandler: AccessDeniedHandler,
) : SecurityFilterChainRegistrar {
/**
* 构建安全过滤器链
*
* 此方法用于基于提供的匹配器和配置逻辑创建一个定制化的安全过滤器链
*
* @param chainId 唯一标识此过滤器链的字符串用于后续引用或管理
* @param http 提供基础配置的 HttpSecurity 对象用于构建 Web 安全策略
* @param matcher 用于匹配请求的 RequestMatcher 对象决定是否应用该过滤器链
* @param config 用于配置安全策略的函数接受 ServerHttpSecurity 对象作为参数进行自定义配置
* @return 返回构建完成的 SecurityWebFilterChainWrapper 实例封装了最终的安全过滤器链
*/
override fun buildChain(
chainId: String,
http: HttpSecurity,
matcher: RequestMatcher,
config: (HttpSecurity) -> Unit,
): SecurityFilterChainWrapper {
// 清除不必要的默认安全配置,优化无状态场景下的行为
// 设置请求匹配器以确定过滤器链适用范围
// 配置 HTTP 请求的授权规则:
// - 允许所有 OPTIONS 请求(通常用于跨域预检)
// - 所有其他请求都需要通过授权管理器验证
// 配置异常处理逻辑,使用指定的访问拒绝处理器
// 应用额外的自定义配置闭包
http
.cleanUnNeedConfig()
.securityMatcher(matcher)
.authorizeHttpRequests {
it.requestMatchers(HttpMethod.OPTIONS).permitAll()
it.anyRequest().access(authorizationManager)
}
.exceptionHandling { it.accessDeniedHandler(accessDeniedHandler) }
.also { config(it) }
// 过滤出支持当前链 ID 的定制器并依次应用它们的自定义逻辑
httpSecurityCustomizer.filter { it.supports(chainId) }.forEach { it.customize(http) }
// 构建并返回封装好的安全过滤器链包装对象
return SecurityFilterChainWrapper(http.build(), chainId)
}
}

View File

@ -0,0 +1,95 @@
package com.gewuyou.forgeboot.security.authorize.impl.builder
import com.gewuyou.forgeboot.security.core.common.builder.SecurityWebFilterChainRegistrar
import com.gewuyou.forgeboot.security.core.common.customizer.ServerHttpSecurityCustomizer
import com.gewuyou.forgeboot.security.core.common.extension.cleanUnNeedConfig
import com.gewuyou.forgeboot.security.core.common.wrapper.SecurityWebFilterChainWrapper
import org.springframework.http.HttpMethod
import org.springframework.security.authorization.ReactiveAuthorizationManager
import org.springframework.security.config.web.server.ServerHttpSecurity
import org.springframework.security.web.server.authorization.AuthorizationContext
import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers
/**
* 无状态安全 Web 过滤器链注册商
*
* 用于创建和管理无状态认证场景下的安全过滤器链
*
* @property serverHttpSecurityCustomizer 安全配置定制器列表按需对安全配置进行个性化调整
* @since 2025-06-24 12:40:01
* @author gewuyou
*/
class StatelessSecurityWebFilterChainRegistrar(
private val serverHttpSecurityCustomizer: List<ServerHttpSecurityCustomizer>,
private val reactiveAuthorizationManager: ReactiveAuthorizationManager<AuthorizationContext>,
private val reactiveAccessDeniedHandler: ServerAccessDeniedHandler,
) : SecurityWebFilterChainRegistrar {
/**
* 构建安全过滤器链
*
* 此方法基于传入的匹配器和配置逻辑创建一个定制化的安全策略并将其封装为可管理的安全过滤器链实例
* 方法内部主要完成基础安全配置清理请求匹配规则设置权限验证管理异常处理机制以及自定义配置扩展
* 最终构建并返回封装好的安全过滤器链对象
*
* @param chainId 唯一标识此过滤器链的字符串用于后续引用或管理
* @param http 提供基础配置的 ServerHttpSecurity 对象用于构建安全策略
* @param matcher 用于匹配请求的 ServerWebExchangeMatcher 对象
* @param config 用于配置安全策略的函数接受 ServerHttpSecurity 对象作为参数
* @return 返回构建完成的 SecurityWebFilterChainWrapper 实例包含已配置的安全策略及链标识
*/
override fun buildChain(
chainId: String,
http: ServerHttpSecurity,
matcher: ServerWebExchangeMatcher,
config: (ServerHttpSecurity) -> Unit,
): SecurityWebFilterChainWrapper {
/**
* 安全配置流程
* 1. 清理不必要且默认的安全配置
* 2. 设置请求匹配规则
* 3. 配置访问控制策略允许 OPTIONS 请求并限制其他请求
* 4. 设置访问被拒绝时的异常处理器
* 5. 应用额外的自定义配置
*/
http
.cleanUnNeedConfig()
.securityMatcher(matcher)
.authorizeExchange {
/**
* 显式允许所有 OPTIONS 预检请求确保跨域请求能正常进行预检
*/
it.matchers(ServerWebExchangeMatchers.pathMatchers(HttpMethod.OPTIONS, "/**"))
.permitAll()
/**
* 所有其他类型的请求都需要通过 reactiveAuthorizationManager 进行访问控制
*/
it
.anyExchange()
.access(reactiveAuthorizationManager)
}
.exceptionHandling {
/**
* 设置全局访问被拒绝处理器用于统一处理无权访问的请求
*/
it.accessDeniedHandler(reactiveAccessDeniedHandler)
}
.also { config(it) }
/**
* 筛选出支持当前 chainId 的定制器并依次对当前 http 配置进行扩展定制
* 实现更细粒度的链级安全策略调整
*/
serverHttpSecurityCustomizer.filter { it.supports(chainId) }.forEach { it.customize(http) }
/**
* 使用构建完成的 SecurityWebFilterChain 实例和链 ID 创建包装对象并返回
* 便于上层组件管理和使用该安全链
*/
return SecurityWebFilterChainWrapper(
http.build(),
chainId
)
}
}

View File

@ -0,0 +1,58 @@
package com.gewuyou.forgeboot.security.authorize.impl.handler
import com.fasterxml.jackson.databind.ObjectMapper
import com.gewuyou.forgeboot.core.extension.log
import com.gewuyou.forgeboot.security.authorize.api.config.SecurityAuthorizeProperties
import com.gewuyou.forgeboot.webmvc.dto.R
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.security.access.AccessDeniedException
import org.springframework.security.web.access.AccessDeniedHandler
/**
* 授权异常处理程序用于处理访问被拒绝的请求并返回对应的 JSON 响应
*
* @property objectMapper 用于将响应对象序列化为 JSON 字符串
* @property securityAuthorizeProperties 安全授权相关配置属性
* @author gewuyou
* @since 2025-06-24 16:35:48
*/
class AuthorizationExceptionHandler(
private val objectMapper: ObjectMapper,
private val securityAuthorizeProperties: SecurityAuthorizeProperties
) : AccessDeniedHandler {
/**
* 处理访问被拒绝的异常并写入 JSON 格式的响应体
*
* @param request 发生异常的 HTTP 请求
* @param response 需要返回的 HTTP 响应
* @param accessDeniedException 具体访问被拒绝异常
*/
override fun handle(
request: HttpServletRequest,
response: HttpServletResponse,
accessDeniedException: AccessDeniedException
) {
// 记录访问被拒绝的异常日志,包含请求路径和异常信息
log.error("授权异常:路径=${request.requestURI}", accessDeniedException)
response.status = HttpStatus.OK.value()
// 设置响应内容类型为 JSON 格式
response.contentType = MediaType.APPLICATION_JSON_VALUE
// 设置字符编码为 UTF-8
response.characterEncoding = "UTF-8"
// 构建错误响应 JSON 字符串
val errorJson = objectMapper.writeValueAsString(
R.failure<String>(
HttpStatus.FORBIDDEN.value(),
accessDeniedException.message ?: securityAuthorizeProperties.defaultExceptionResponse
)
)
// 将错误响应写入 HTTP 响应输出流
response.writer.write(errorJson)
}
}

View File

@ -0,0 +1,63 @@
package com.gewuyou.forgeboot.security.authorize.impl.handler
import com.fasterxml.jackson.databind.ObjectMapper
import com.gewuyou.forgeboot.core.extension.log
import com.gewuyou.forgeboot.security.authorize.api.config.SecurityAuthorizeProperties
import com.gewuyou.forgeboot.webmvc.dto.R
import org.springframework.http.HttpHeaders
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.security.access.AccessDeniedException
import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler
import org.springframework.web.server.ServerWebExchange
import reactor.core.publisher.Mono
/**
* 反应式授权异常处理程序
*
* 处理访问被拒绝的授权异常生成相应的错误响应
*
* @property objectMapper 用于将响应对象序列化为 JSON 字符串
* @property securityAuthorizeProperties 安全授权配置属性
* @since 2025-06-23 22:03:09
* @author gewuyou
*/
class ReactiveAuthorizationExceptionHandler(
private val objectMapper: ObjectMapper,
private val securityAuthorizeProperties: SecurityAuthorizeProperties,
) : ServerAccessDeniedHandler {
/**
* 处理访问被拒绝的异常并返回自定义错误响应
*
* 记录异常日志设置响应状态码和内容类型并将错误信息以 JSON 格式写入响应
*
* @param exchange 当前的服务器网络交换对象
* @param denied 抛出的访问被拒绝异常
* @return 返回一个 Mono<Void> 表示处理完成
*/
override fun handle(exchange: ServerWebExchange, denied: AccessDeniedException): Mono<Void> {
// 记录访问被拒绝的异常信息及请求路径
log.error("反应式授权异常 路径:${exchange.request.uri}", denied)
// 设置响应状态码为 200 OK
exchange.response.statusCode = HttpStatus.OK
// 设置响应内容类型为 application/json
exchange.response.headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
// 将错误响应体写入响应
return exchange.response.writeWith(
Mono.just(
exchange.response.bufferFactory().wrap(
objectMapper.writeValueAsBytes(
R.failure<String>(
HttpStatus.FORBIDDEN.value(),
denied.message ?: securityAuthorizeProperties.defaultExceptionResponse
)
)
)
)
)
}
}

View File

@ -0,0 +1,28 @@
package com.gewuyou.forgeboot.security.authorize.impl.manager
import com.gewuyou.forgeboot.security.authorize.api.manager.AccessManager
import com.gewuyou.forgeboot.security.authorize.api.provider.PermissionProvider
import com.gewuyou.forgeboot.security.authorize.api.strategy.AuthorizationStrategy
import org.springframework.security.core.Authentication
/**
*默认 Access Manager
*
* @since 2025-06-15 18:41:22
* @author gewuyou
*/
class DefaultAccessManager(
private val providers: List<PermissionProvider>,
private val strategy : AuthorizationStrategy
) : AccessManager {
/**
* 判断用户是否具有指定权限
*
* @param authentication Spring Security提供的身份认证信息
* @param permission 需要验证的权限字符串
* @return Boolean 返回是否有权限的布尔值
*/
override fun hasPermission(authentication: Authentication, permission: String): Boolean =
strategy.authorize(authentication, permission, providers)
}

View File

@ -0,0 +1,37 @@
package com.gewuyou.forgeboot.security.authorize.impl.provider
import com.gewuyou.forgeboot.core.extension.log
import com.gewuyou.forgeboot.security.authorize.api.provider.PermissionProvider
import org.springframework.security.core.Authentication
/**
*抽象权限提供程序
*
* @since 2025-06-23 21:45:49
* @author gewuyou
*/
abstract class AbstractPermissionProvider : PermissionProvider {
/**
* 获取权限集合模板方法
*/
override fun listPermissions(authentication: Authentication): Collection<String> {
if (!supports(authentication)) return emptySet()
return try {
doProvide(authentication).toSet()
} catch (ex: Exception) {
log.error("获取权限失败", ex)
emptySet()
}
}
/**
* 子类实现获取权限的具体逻辑
*/
protected abstract fun doProvide(authentication: Authentication): Collection<String>
/**
* 是否支持当前 Authentication 类型
*/
protected open fun supports(authentication: Authentication): Boolean = true
}

View File

@ -0,0 +1,37 @@
package com.gewuyou.forgeboot.security.authorize.impl.resolver
import com.gewuyou.forgeboot.security.authorize.api.resolver.PermissionResolver
/**
* 默认权限解析程序
*
* @since 2025-06-24 13:42:20
* @author gewuyou
*/
class DefaultPermissionResolver : PermissionResolver {
/**
* 将请求路径和HTTP方法解析为标准化的权限标识符
*
* 此方法会对原始路径进行标准化处理替换其中的动态参数部分为统一占位符
* 然后将HTTP方法和标准化后的路径组合成最终的权限标识符
*
* @param path 请求路径
* @param method HTTP请求方法
* @return 标准化的权限标识符格式为"METHOD:/path/template"
*/
override fun resolve(path: String, method: String): String {
// 对路径进行标准化处理:
// 1. 转换为小写
// 2. 替换数字ID为{id}通配符
// 3. 替换MongoDB ObjectId为{objectId}
// 4. 替换UUID为{uuid}
val normalizedPath = path
.lowercase()
.replace(Regex("/\\d+"), "/{id}")
.replace(Regex("/[a-f0-9]{24}"), "/{objectId}")
.replace(Regex("/[a-z0-9\\-]{36}"), "/{uuid}")
return "${method}:$normalizedPath"
}
}

View File

@ -0,0 +1,50 @@
package com.gewuyou.forgeboot.security.authorize.impl.resolver
import org.springframework.beans.factory.config.ConfigurableBeanFactory
import org.springframework.context.expression.BeanFactoryResolver
import org.springframework.expression.spel.standard.SpelExpressionParser
import org.springframework.expression.spel.support.StandardEvaluationContext
/**
* SpEL 解析器用于解析和执行 Spring Expression Language (SpEL) 表达式
*
* 该类封装了 SpEL 的解析和求值过程结合 Spring BeanFactory 支持表达式中对 Bean 的引用
*
* @property beanFactory 提供 Spring 容器的 Bean 工厂用于解析表达式中的 Bean 引用
* @since 2025-06-15 13:32:31
* @author gewuyou
*/
class SpELResolver(private val beanFactory: ConfigurableBeanFactory) {
// 初始化 SpEL 表达式解析器
private val parser = SpelExpressionParser()
// 初始化 SpEL 求值上下文,并设置 Bean 解析器
private val context = StandardEvaluationContext()
/**
* 在初始化阶段配置上下文使其能够通过 BeanFactory 解析表达式中的 Bean
*/
init {
context.beanResolver = BeanFactoryResolver(beanFactory)
}
/**
* 解析并计算给定的 SpEL 表达式返回结果作为字符串
*
* @param expression 表达式字符串例如 `#user.name` `'Hello ' + #name`
* @param rootObject 根对象表达式中的属性访问将基于此对象进行解析
* @return 解析后的字符串结果如果表达式结果为 null则返回空字符串
*/
fun parse(expression: String, rootObject: Any): String {
context.setRootObject(rootObject)
// 如果是 Map则把每个键当作变量注入支持 #varName 直接取值
if (rootObject is Map<*, *>) {
rootObject.forEach { (k, v) ->
if (k is String) context.setVariable(k, v)
}
}
return parser
.parseExpression(expression)
.getValue(context, String::class.java) ?: ""
}
}

View File

@ -0,0 +1,29 @@
package com.gewuyou.forgeboot.security.authorize.impl.strategy
import com.gewuyou.forgeboot.security.authorize.api.provider.PermissionProvider
import com.gewuyou.forgeboot.security.authorize.api.strategy.AuthorizationStrategy
import org.springframework.security.core.Authentication
/**
* 任何匹配策略 - 实现授权策略接口采用任意权限匹配机制
*
* @since 2025-06-15 18:51:23
* @author gewuyou
*/
class AnyMatchStrategy : AuthorizationStrategy {
/**
* 执行授权验证
*
* @param authentication 当前用户身份认证信息
* @param permission 需要验证的权限标识
* @param providers 权限提供者列表
* @return Boolean 返回授权结果true表示授权通过false表示拒绝访问
*
* 该方法通过遍历所有权限提供者只要有一个提供者包含目标权限即视为授权成功
*/
override fun authorize(
authentication: Authentication,
permission: String,
providers: List<PermissionProvider>
) = providers.any { permission in it.listPermissions(authentication) }
}

View File

@ -0,0 +1,7 @@
dependencies {
compileOnly(libs.springBootStarter.security)
compileOnly(libs.springBootStarter.web)
kapt(libs.springBoot.configuration.processor)
}

View File

@ -0,0 +1,18 @@
package com.gewuyou.forgeboot.security.core.authenticate.entities.request
/**
*认证请求
*
* @since 2025-06-10 15:39:04
* @author gewuyou
*/
open class AuthenticationRequest(
/**
* 认证类型
*/
open val type: String
): LoginRequest {
override fun getType(): String {
return type
}
}

View File

@ -0,0 +1,10 @@
package com.gewuyou.forgeboot.security.core.authenticate.entities.request
/**
*默认登录请求
*
* @since 2025-06-10 16:29:43
* @author gewuyou
*/
class DefaultLoginRequest {
}

View File

@ -0,0 +1,18 @@
package com.gewuyou.forgeboot.security.core.authenticate.entities.request
/**
* JWT身份验证请求数据类
*
* 该类用于封装基于JWT的身份验证请求信息继承自[AuthenticationRequest]
* 主要包含身份验证类型和JWT令牌两个属性适用于系统中的身份认证流程
*
* @property type 身份验证的类型覆盖父类属性默认值为"jwt"
* @property token JWT访问令牌用于身份验证和授权
*
* @since 2025-06-10 15:41:22
* @author gewuyou
*/
data class JwtAuthenticationRequest(
override val type: String = "jwt",
val token: String
) : AuthenticationRequest(type)

View File

@ -0,0 +1,11 @@
package com.gewuyou.forgeboot.security.core.authenticate.entities.request
/**
*登录请求
*
* @since 2025-02-15 02:13:16
* @author gewuyou
*/
fun interface LoginRequest {
fun getType(): String
}

View File

@ -0,0 +1,20 @@
package com.gewuyou.forgeboot.security.core.authenticate.entities.request
/**
* 用户名密码认证请求类
*
* 该类用于封装基于用户名和密码的认证请求数据继承自[AuthenticationRequest]
* 提供了用户名和密码字段并设置默认认证类型为"password"
*
* @property type 认证类型默认值为"password"
* @property username 用户输入的用户名
* @property password 用户输入的密码
*
* @author gewuyou
* @since 2025-06-10 15:40:17
*/
data class UsernamePasswordAuthenticationRequest(
override val type: String = "username",
val username: String,
val password: String
) : AuthenticationRequest(type)

View File

@ -0,0 +1,18 @@
package com.gewuyou.forgeboot.security.core.authenticate.entities.response
/**
* 身份验证响应基类
*
* 该类用于封装身份验证成功后的基础响应数据包含认证主体和凭证信息
* 主要用于框架内部在完成身份验证后构造统一的响应结构
*
* @property principal 认证主体对象可以是用户信息或其他认证实体
* @property credentials 认证凭证信息例如密码令牌等敏感数据载体
*
* @since 2025-06-10 15:49:03
* @author gewuyou
*/
open class AuthenticationResponse(
open val principal: Any? = null,
open val credentials: Any? = null,
)

View File

@ -0,0 +1,17 @@
package com.gewuyou.forgeboot.security.core.authenticate.entities.response
/**
*JWT身份验证响应
*
* @since 2025-06-10 15:50:10
* @author gewuyou
*/
data class JwtAuthenticationResponse(
override val principal: Any?,
val accessToken: String,
val refreshToken: String,
val expiresIn: Long,
) : AuthenticationResponse(
principal = principal,
credentials = accessToken
)

View File

@ -0,0 +1,35 @@
package com.gewuyou.forgeboot.security.core.common.builder
import com.gewuyou.forgeboot.security.core.common.wrapper.SecurityFilterChainWrapper
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.web.util.matcher.RequestMatcher
/**
* 安全过滤器链注册商
*
* 该函数接口用于定义构建安全过滤器链的契约通过提供唯一的链标识基础配置对象请求匹配规则以及配置逻辑
* 可以创建一个定制化的安全过滤器链实例
*
* @since 2025-06-24 16:07:19
* @author gewuyou
*/
fun interface SecurityFilterChainRegistrar {
/**
* 构建安全过滤器链
*
* 此方法用于基于提供的匹配器和配置逻辑创建一个定制化的安全过滤器链
* 方法内部应实现将给定的配置应用到 HTTP 安全对象并根据 matcher 决定是否启用该链
*
* @param chainId 唯一标识此过滤器链的字符串用于后续引用或管理
* @param http 提供基础配置的 HttpSecurity 对象用于构建 Web 安全策略
* @param matcher 用于匹配请求的 RequestMatcher 对象决定是否应用该过滤器链
* @param config 用于配置安全策略的函数接受 HttpSecurity 对象作为参数进行自定义配置
* @return 返回构建完成的 SecurityFilterChainWrapper 实例封装了最终的安全过滤器链
*/
fun buildChain(
chainId: String,
http: HttpSecurity,
matcher: RequestMatcher,
config: (HttpSecurity) -> Unit
): SecurityFilterChainWrapper
}

View File

@ -0,0 +1,33 @@
package com.gewuyou.forgeboot.security.core.common.builder
import com.gewuyou.forgeboot.security.core.common.wrapper.SecurityWebFilterChainWrapper
import org.springframework.security.config.web.server.ServerHttpSecurity
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher
/**
* 安全 Web 过滤器链注册商接口
*
* 用于构建和注册自定义的安全过滤器链适用于响应式 Spring Security 配置
*
* @since 2025-06-24 12:35:00
* @author gewuyou
*/
fun interface SecurityWebFilterChainRegistrar {
/**
* 构建安全过滤器链
*
* 此方法用于基于提供的匹配器和配置逻辑创建一个定制化的安全过滤器链
*
* @param chainId 唯一标识此过滤器链的字符串用于后续引用或管理
* @param http 提供基础配置的 ServerHttpSecurity 对象用于构建安全策略
* @param matcher 用于匹配请求的 ServerWebExchangeMatcher 对象
* @param config 用于配置安全策略的函数接受 ServerHttpSecurity 对象作为参数
* @return 返回构建完成的 ForgeBootSecurityWebFilterChain 实例
*/
fun buildChain(
chainId: String,
http: ServerHttpSecurity,
matcher: ServerWebExchangeMatcher,
config: (ServerHttpSecurity) -> Unit
): SecurityWebFilterChainWrapper
}

View File

@ -0,0 +1,32 @@
package com.gewuyou.forgeboot.security.core.common.customizer
import org.springframework.security.config.annotation.web.builders.HttpSecurity
/**
* HTTP 安全定制器接口
*
* 该函数式接口用于定义 HTTP 安全配置的定制逻辑通常用于
* Spring Security HttpSecurity 配置阶段
*
* @since 2025-06-24 10:46:32
* @author gewuyou
*/
interface HttpSecurityCustomizer {
/**
* 判断当前定制器是否支持处理指定的安全链配置
*
* 此方法用于标识该定制器是否适用于特定的安全链配置
* 实现类应根据 chainId 参数决定是否启用此定制器的逻辑
*
* @param chainId 安全链的唯一标识符用于区分不同的安全配置场景
* @return Boolean 返回 true 表示支持该 chainId否则不支持
*/
fun supports(chainId: String): Boolean
/**
* 执行安全配置的定制逻辑
*
* @param http 用于构建 HTTP 安全策略的 HttpSecurity 实例
* 通过此参数可添加或修改安全规则如认证授权等
*/
fun customize(http: HttpSecurity)
}

View File

@ -0,0 +1,36 @@
package com.gewuyou.forgeboot.security.core.common.customizer
import org.springframework.security.config.web.server.ServerHttpSecurity
/**
* 服务器 HTTP 安全定制器接口
*
* 该接口用于定义对 Spring Security ServerHttpSecurity 配置的自定义逻辑
* 实现类可以通过重写 customize 方法来添加或修改安全配置规则
*
* @since 2025-06-24 10:45:42
* @author gewuyou
*/
interface ServerHttpSecurityCustomizer {
/**
* 判断当前定制器是否支持处理指定的安全链配置
*
* 此方法用于标识该定制器是否适用于特定的安全链配置
* 实现类应根据 chainId 参数决定是否启用此定制器的逻辑
*
* @param chainId 安全链的唯一标识符用于区分不同的安全配置场景
* @return Boolean 返回 true 表示支持该 chainId否则不支持
*/
fun supports(chainId: String): Boolean
/**
* 自定义 ServerHttpSecurity 配置的方法
*
* 此方法由框架调用允许开发者插入自定义的安全配置逻辑
* 方法参数提供了 ServerHttpSecurity 实例可用于链式配置
*
* @param http ServerHttpSecurity 实例用于构建 WebFlux 安全配置
*/
fun customize(http: ServerHttpSecurity)
}

View File

@ -0,0 +1,57 @@
package com.gewuyou.forgeboot.security.core.common.extension
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.http.SessionCreationPolicy
import org.springframework.security.web.context.NullSecurityContextRepository
/**
* 清理不需要的配置禁用指定的安全功能模块
*
* 此方法用于关闭一系列默认的安全配置项适用于无状态stateless认证场景
* 例如基于Token或API密钥的身份验证架构
*
* @return 返回当前 HttpSecurity 实例用于链式调用
*/
fun HttpSecurity.cleanUnNeedConfig(): HttpSecurity {
return this
/**
* 禁用 CSRF 保护机制
* 在无状态 API 场景中通常不需要 CSRF 保护
*/
.csrf { it.disable() }
/**
* 禁用表单登录认证机制
* 表单登录不适用于 RESTful API Token 认证场景
*/
.formLogin { it.disable() }
/**
* 设置 SecurityContextRepository NullSecurityContextRepository
* 避免将安全上下文存储到 HTTP 请求中
*/
.securityContext { it.securityContextRepository(NullSecurityContextRepository()) }
/**
* 设置会话创建策略为 STATELESS
* 表示应用不会创建或使用 HTTP 会话进行身份识别
*/
.sessionManagement { it.sessionCreationPolicy(SessionCreationPolicy.STATELESS) }
/**
* 禁用匿名用户访问支持
* 所有未认证用户将被视为无权限
*/
.anonymous { it.disable() }
/**
* 禁用请求缓存
* 在无状态认证流程中不需要缓存请求
*/
.requestCache { it.disable() }
/**
* 禁用 HTTP Basic 认证
* 使用 Token 或其他自定义认证方式时可安全禁用此项
*/
.httpBasic { it.disable() }
/**
* 禁用 Remember-Me 自动登录功能
* 无状态认证场景下此功能不再适用
*/
.rememberMe { it.disable() }
}

View File

@ -0,0 +1,30 @@
package com.gewuyou.forgeboot.security.core.common.extension
import org.springframework.security.config.web.server.ServerHttpSecurity
import org.springframework.security.web.server.context.NoOpServerSecurityContextRepository
/**
* 清理不需要的配置关闭清单如下
*
* 禁用以下 Spring Security 功能以简化安全配置
* - CSRF 保护
* - 表单登录认证
* - 匿名用户支持
* - 请求缓存
* - HTTP Basic 认证
* - 登出功能
*
* 同时设置一个无操作的安全上下文仓库适用于无需持久化安全上下文的场景
*
* @return 返回配置修改后的 [ServerHttpSecurity] 实例
*/
fun ServerHttpSecurity.cleanUnNeedConfig(): ServerHttpSecurity {
return this
.csrf { it.disable() }
.formLogin { it.disable() }
.anonymous { it.disable() }
.requestCache { it.disable() }
.httpBasic { it.disable() }
.logout { it.disable() }
.securityContextRepository(NoOpServerSecurityContextRepository.getInstance())
}

View File

@ -0,0 +1,21 @@
package com.gewuyou.forgeboot.security.core.common.identifier
/**
* 安全过滤器链标识符函数式接口
*
* 该接口用于定义获取安全过滤器链唯一标识的方法
* 在多安全链环境中每个实现类应提供一个唯一且可读的标识符
* 以便于日志记录调试和配置识别
*/
fun interface SecurityFilterChainIdentifier {
/**
* 获取当前过滤器链的唯一标识符
*
* 在复杂的多安全链环境中此方法返回的标识符用于唯一标识一个过滤器链实例
* 标识符应在实现类中确保其唯一性和可读性通常用于日志记录调试和配置识别
*
* @return 当前过滤器链实例的唯一标识字符串非空且不应重复
*/
fun getChainId(): String
}

View File

@ -0,0 +1,101 @@
package com.gewuyou.forgeboot.security.core.common.token
import org.springframework.security.authentication.AbstractAuthenticationToken
import org.springframework.security.core.GrantedAuthority
import org.springframework.util.Assert
/**
* 主体凭据身份验证令牌
*
* @author gewuyou
* @since 2025-02-16 15:46:05
*/
open class PrincipalCredentialsAuthenticationToken(
/**
* 主体
*/
@Transient
private val principal: Any,
/**
* 凭证
*/
@Transient
private val credentials: Any?,
/**
* 权限列表
*/
authorities: Collection<GrantedAuthority>? = null
) : AbstractAuthenticationToken(authorities) {
init {
if (authorities == null) {
isAuthenticated = false
} else {
super.setAuthenticated(true)
}
}
companion object {
/**
* 创建未认证的令牌
*/
@JvmStatic
fun unauthenticated(principal: Any, credentials: Any): PrincipalCredentialsAuthenticationToken {
return PrincipalCredentialsAuthenticationToken(principal, credentials)
}
/**
* 创建已认证的令牌
*/
@JvmStatic
fun authenticated(
principal: Any,
credentials: Any?,
authorities: Collection<GrantedAuthority>
): PrincipalCredentialsAuthenticationToken {
return PrincipalCredentialsAuthenticationToken(principal, credentials, authorities)
}
}
/**
* The credentials that prove the principal is correct. This is usually a password,
* but could be anything relevant to the `AuthenticationManager`. Callers
* are expected to populate the credentials.
*
* @return the credentials that prove the identity of the `Principal`
*/
override fun getCredentials(): Any? {
return if (isAuthenticated) null else this.credentials
}
/**
* The identity of the principal being authenticated. In the case of an authentication
* request with username and password, this would be the username. Callers are
* expected to populate the principal for an authentication request.
*
*
* The <tt>AuthenticationManager</tt> implementation will often return an
* <tt>Authentication</tt> containing richer information as the principal for use by
* the application. Many of the authentication providers will create a
* `UserDetails` object as the principal.
*
* @return the `Principal` being authenticated or the authenticated
* principal after authentication.
*/
override fun getPrincipal(): Any {
return this.principal
}
/**
* 重写方法防止滥用
* @param authenticated `true` if the token should be trusted (which may
* result in an exception) or `false` if the token should not be trusted
*/
override fun setAuthenticated(authenticated: Boolean) {
Assert.isTrue(
!isAuthenticated,
"无法将此令牌设置为受信任 - 请使用采用 GrantedAuthority 列表的构造函数"
)
super.setAuthenticated(false)
}
}

View File

@ -0,0 +1,37 @@
package com.gewuyou.forgeboot.security.core.common.token
import org.springframework.security.core.GrantedAuthority
/**
*用户名 密码 认证 Token
*
* @since 2025-02-16 16:00:18
* @author gewuyou
*/
class UsernamePasswordAuthenticationToken(
principal: Any,
credentials: Any?,
authorities: Collection<GrantedAuthority>? = null
) : PrincipalCredentialsAuthenticationToken(principal, credentials, authorities) {
companion object {
/**
* 创建未认证的令牌
*/
@JvmStatic
fun unauthenticated(principal: Any, credentials: Any): UsernamePasswordAuthenticationToken {
return UsernamePasswordAuthenticationToken(principal, credentials)
}
/**
* 创建已认证的令牌
*/
@JvmStatic
fun authenticated(
principal: Any,
credentials: Any?,
authorities: Collection<GrantedAuthority>
): UsernamePasswordAuthenticationToken {
return UsernamePasswordAuthenticationToken(principal, credentials, authorities)
}
}
}

View File

@ -0,0 +1,31 @@
package com.gewuyou.forgeboot.security.core.common.wrapper
import com.gewuyou.forgeboot.security.core.common.identifier.SecurityFilterChainIdentifier
import org.springframework.security.web.SecurityFilterChain
/**
* 安全过滤器链包装器
*
* 该类为 [SecurityFilterChain] 提供了装饰功能同时实现了 [SecurityFilterChainIdentifier] 接口
* 用于标识当前安全过滤器链的唯一 ID
*
* @property delegate 被包装的真实 [SecurityFilterChain] 实例所有方法调用将委托给它
* @property chainId 当前过滤器链的唯一标识符
*
* @see SecurityFilterChain
* @see SecurityFilterChainIdentifier
*
* @since 2025-06-24 16:11:43
* @author gewuyou
*/
class SecurityFilterChainWrapper(
private val delegate: SecurityFilterChain,
private val chainId: String,
) : SecurityFilterChain by delegate, SecurityFilterChainIdentifier {
/**
* 获取当前过滤器链的唯一标识符
*
* @return 返回当前链的标识符 [chainId]
*/
override fun getChainId(): String = chainId
}

View File

@ -0,0 +1,28 @@
package com.gewuyou.forgeboot.security.core.common.wrapper
import com.gewuyou.forgeboot.security.core.common.identifier.SecurityFilterChainIdentifier
import org.springframework.security.web.server.SecurityWebFilterChain
/**
* Forge Boot Security Web 过滤器链包装器
*
* 该类是一个 Kotlin 实现的 [SecurityWebFilterChain] 包装器用于代理原始的 [SecurityWebFilterChain] 实例
* 并通过实现 [SecurityFilterChainIdentifier] 接口提供过滤器链标识符功能
*
* @property delegate 被包装的 [SecurityWebFilterChain] 实例用于执行实际的安全过滤逻辑
* @property chainId 当前过滤器链的唯一标识符用于区分不同的安全配置链
*
* @see SecurityWebFilterChain
* @see SecurityFilterChainIdentifier
*/
class SecurityWebFilterChainWrapper(
private val delegate: SecurityWebFilterChain,
private val chainId: String
) : SecurityWebFilterChain by delegate, SecurityFilterChainIdentifier {
/**
* 获取当前过滤器链的唯一标识符
*
* @return 返回当前链的标识符 [chainId]
*/
override fun getChainId(): String = chainId
}

View File

@ -1,4 +1,3 @@
/**
* This settings.gradle.kts file configures the Gradle build for the forgeboot project.
* It sets up dependency resolution, plugins, and includes all relevant subprojects.
@ -95,10 +94,12 @@ project(":forgeboot-webmvc:spec").name = "forgeboot-webmvc-spec"
*/
include(
"forgeboot-core",
":forgeboot-core:forgeboot-core-extension"
":forgeboot-core:forgeboot-core-extension",
":forgeboot-core:forgeboot-core-serialization",
)
project(":forgeboot-core").name = "forgeboot-core"
project(":forgeboot-core:forgeboot-core-extension").name = "forgeboot-core-extension"
project(":forgeboot-core:forgeboot-core-serialization").name = "forgeboot-core-serialization"
//endregion
//region module i18n
@ -118,7 +119,6 @@ project(":forgeboot-i18n:forgeboot-i18n-impl").name = "forgeboot-i18n-impl"
project(":forgeboot-i18n:forgeboot-i18n-autoconfigure").name = "forgeboot-i18n-autoconfigure"
//endregion
//region module trace
/**
* Includes and configures projects related to 'forgeboot-trace'
@ -135,3 +135,53 @@ project(":forgeboot-trace:forgeboot-trace-api").name = "forgeboot-trace-api"
project(":forgeboot-trace:forgeboot-trace-impl").name = "forgeboot-trace-impl"
project(":forgeboot-trace:forgeboot-trace-autoconfigure").name = "forgeboot-trace-autoconfigure"
//endregion
//region module security
/**
* Includes and configures projects related to 'forgeboot-security'
* This module handles security-related functionality.
*/
include(
"forgeboot-security",
":forgeboot-security:forgeboot-security-core",
":forgeboot-security:forgeboot-security-authenticate",
":forgeboot-security:forgeboot-security-authenticate:api",
":forgeboot-security:forgeboot-security-authenticate:impl",
":forgeboot-security:forgeboot-security-authenticate:autoconfigure",
":forgeboot-security:forgeboot-security-authorize",
":forgeboot-security:forgeboot-security-authorize:api",
":forgeboot-security:forgeboot-security-authorize:impl",
":forgeboot-security:forgeboot-security-authorize:autoconfigure"
)
project(":forgeboot-security").name = "forgeboot-security-spring-boot-starter"
project(":forgeboot-security:forgeboot-security-core").name = "forgeboot-security-core"
project(":forgeboot-security:forgeboot-security-authenticate").name =
"forgeboot-security-authenticate-spring-boot-starter"
project(":forgeboot-security:forgeboot-security-authenticate:api").name = "forgeboot-security-authenticate-api"
project(":forgeboot-security:forgeboot-security-authenticate:impl").name = "forgeboot-security-authenticate-impl"
project(":forgeboot-security:forgeboot-security-authenticate:autoconfigure").name =
"forgeboot-security-authenticate-autoconfigure"
project(":forgeboot-security:forgeboot-security-authorize").name = "forgeboot-security-authorize-spring-boot-starter"
project(":forgeboot-security:forgeboot-security-authorize:api").name = "forgeboot-security-authorize-api"
project(":forgeboot-security:forgeboot-security-authorize:impl").name = "forgeboot-security-authorize-impl"
project(":forgeboot-security:forgeboot-security-authorize:autoconfigure").name =
"forgeboot-security-authorize-autoconfigure"
//endregion
//region module cache
include(
"forgeboot-cache",
":forgeboot-cache:forgeboot-cache-api",
":forgeboot-cache:forgeboot-cache-impl",
":forgeboot-cache:forgeboot-cache-autoconfigure"
)
project(":forgeboot-cache").name = "forgeboot-cache-spring-boot-starter"
project(":forgeboot-cache:forgeboot-cache-api").name = "forgeboot-cache-api"
project(":forgeboot-cache:forgeboot-cache-impl").name = "forgeboot-cache-impl"
project(":forgeboot-cache:forgeboot-cache-autoconfigure").name = "forgeboot-cache-autoconfigure"
//endregion