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