跳到主要内容

Spring Security

信息

Spring Security 是一个框架,提供认证(authentication)、授权(authorization)和保护,以抵御常见的攻击。它对保护命令式和响应式应用程序有一流的支持,是保护基于Spring的应用程序的事实标准。

官方文档 | 中文文档

教程

配置文件

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

配置类

使用 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
}
]