Spring Security
信息
教程
-
Spring Security 教程 已完结(IDEA 2023最新版)4K蓝光画质 基于Spring6的全新重制版本 起立到起飞
-
SpringSecurity框架教程-Spring Security+JWT实现项目级前端分离认证授权-B站最通俗易懂的Spring Security课程
-
RESTful API Authentication with Spring Security | Ji ZHANG's Blog
配置文件
src/main/resources/application.yaml
security:
user:
name: yueplus
password: yueplus
默认安全过滤器链(DefaultSecurityFilterChain)
org.springframework.security.web.session.DisableEncodeUrlFilter
org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter
org.springframework.security.web.context.SecurityContextHolderFilter
org.springframework.security.web.header.HeaderWriterFilter
org.springframework.web.filter.CorsFilter
org.springframework.security.web.csrf.CsrfFilter
org.springframework.security.web.authentication.logout.LogoutFilter
org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilte
org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter
org.springframework.security.web.authentication.www.BasicAuthenticationFilter
org.springframework.security.web.savedrequest.RequestCacheAwareFilter
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter
org.springframework.security.web.authentication.AnonymousAuthenticationFilter
org.springframework.security.web.access.ExceptionTranslationFilter
org.springframework.security.web.access.intercept.AuthorizationFilter
配置类
WebSecurityConfigurerAdapter
在 Spring Security 5.7.0-M2 中已弃用!- Servlet 应用 Kotlin 配置 :: Spring Security Reference
使用 Kotlin 时注意在类中导入 invoke 函数,有时IDE不会自动导入该函数,从而导致编译问题。
import org.springframework.security.config.annotation.web.invoke
配置允许访问特定路径
通过 HttpSecurity
package zone.yue.core
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.invoke
import org.springframework.security.web.SecurityFilterChain
@Configuration
@EnableWebSecurity
class SecurityConfiguration {
@Bean
fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
// csrf { disable() }
authorizeRequests {
authorize("/api/signup", permitAll)
authorize("/api/login", permitAll)
authorize(anyRequest, authenticated)
}
}
return http.build()
}
}
通过 WebSecurity
package zone.yue.core
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.web.builders.WebSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer
@Configuration
@EnableWebSecurity
class SecurityConfiguration {
@Bean
fun webSecurityCustomizer(): WebSecurityCustomizer? {
return WebSecurityCustomizer {
web: WebSecurity -> web.ignoring().requestMatchers("/signup")
}
}
}
配置禁用 CSRF
仅用于方便开发测试,请勿在生产环境配置! 详细参考:官方文档 | 中文文档
package zone.yue.core
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.invoke
import org.springframework.security.web.SecurityFilterChain
@Configuration
@EnableWebSecurity
class SecurityConfiguration {
@Bean
fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
csrf{
disable()
}
}
return http.build()
}
}
配置基于内存的身份校验
User.withDefaultPasswordEncoder()
已被标记弃用,不推荐在生产环境中使用!
package zone.yue.core
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.core.userdetails.User
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.crypto.factory.PasswordEncoderFactories
import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.security.provisioning.InMemoryUserDetailsManager
@Configuration
@EnableWebSecurity
class SecurityConfiguration {
@Bean
fun userDetailsService(): UserDetailsService {
val encoder: PasswordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder()
val test: UserDetails = User
.withUsername("Yue_test")
.password(encoder.encode("Yue_test"))
.roles("USER")
.build()
val admin: UserDetails = User
.withUsername("admin")
.password(encoder.encode("admin"))
.roles("ADMIN")
.build()
return InMemoryUserDetailsManager(test, admin)
}
}
配置基于数据库的身份校验
只要实现 UserDetailsService
接口即可:
@Configuration
@EnableWebSecurity
class SecurityConfiguration {
@Bean
fun userDetailsService(): UserDetailsService {
return UserDetailsService { username ->
userRepository.findByName(username)?.let { user ->
User.withUsername(user.name).password(user.password).build()
} ?: throw UsernameNotFoundException("未找到用户:$username")
}
}
}
或者实现功能更丰富的 UserDetailsManager
接口:
package zone.yue.core
import org.springframework.security.core.userdetails.User
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.security.crypto.factory.PasswordEncoderFactories
import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.security.provisioning.UserDetailsManager
import org.springframework.stereotype.Service
import zone.yue.core.user.UserEntity
import zone.yue.core.user.UserRepository
import java.util.*
@Service
class AccountService(val userRepository: UserRepository) : UserDetailsManager {
val passwordEncoder: PasswordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder()
fun signup(username: String, password: String) {
if (userExists(username)) return
val user: UserDetails = User
.withUsername(username)
.password(passwordEncoder.encode(password))
.build()
createUser(user)
}
override fun loadUserByUsername(username: String): UserDetails {
val user = userRepository.findByUsername(username)
return User.withUsername(user.username).password(user.password).build()
}
override fun createUser(user: UserDetails) {
userRepository.save(UserEntity(UUID.randomUUID(), user.username, user.password))
}
override fun updateUser(user: UserDetails?) {
TODO("Not yet implemented")
}
override fun deleteUser(username: String) {
val user = userRepository.findByUsername(username)
userRepository.delete(user)
}
override fun changePassword(oldPassword: String?, newPassword: String?) {
TODO("Not yet implemented")
}
override fun userExists(username: String): Boolean {
return userRepository.existsByUsername(username)
}
}
配置会话管理
参考 英文文档 | 中文文档 | Spring Security 控制 Session - spring 中文网
package zone.yue.demo
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.invoke
import org.springframework.security.core.session.SessionRegistryImpl
import org.springframework.security.core.userdetails.User
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.crypto.factory.PasswordEncoderFactories
import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.security.provisioning.InMemoryUserDetailsManager
import org.springframework.security.web.SecurityFilterChain
@Configuration
@EnableWebSecurity
class SecurityConfig {
@Bean
fun sessionRegistry() = SessionRegistryImpl()
@Bean
fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
authorizeHttpRequests {
authorize(anyRequest, authenticated)
}
formLogin { }
sessionManagement {
sessionConcurrency {
maximumSessions = 5
sessionRegistry = sessionRegistry()
}
}
}
return http.build()
}
@Bean
fun userDetailsService(): UserDetailsService {
val encoder: PasswordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder()
val user: UserDetails = User
.withUsername("user")
.password(encoder.encode("user"))
.roles("USER")
.build()
val admin: UserDetails = User
.withUsername("admin")
.password(encoder.encode("admin"))
.roles("ADMIN")
.build()
return InMemoryUserDetailsManager(user, admin)
}
}
package zone.yue.demo
import org.springframework.security.core.session.SessionRegistry
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController
@RestController
class SsRestController(
private val sessionRegistry: SessionRegistry,
) {
@GetMapping("/online-users")
fun getOnlineUsers(): List<Any> {
return sessionRegistry.allPrincipals
}
}
获取当前登入用户:
GET /online-users
[
{
"password": null,
"username": "admin",
"authorities": [
{
"authority": "ROLE_ADMIN"
}
],
"accountNonExpired": true,
"accountNonLocked": true,
"credentialsNonExpired": true,
"enabled": true
},
{
"password": null,
"username": "user",
"authorities": [
{
"authority": "ROLE_USER"
}
],
"accountNonExpired": true,
"accountNonLocked": true,
"credentialsNonExpired": true,
"enabled": true
}
]