当前位置: 首页 > 工具软件 > Spring Flower > 使用案例 >

Spring Security 使用【Kotlin】

穆华彩
2023-12-01

Spring Security 使用

引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>4.0.0</version>
</dependency>

创建拒绝访问和认证错误的处理类

JwtAccessDeniedHandler

/**
 * 处理拒绝访问
 */
@Component
class JwtAccessDeniedHandler(
    private val mapper: ObjectMapper
): AccessDeniedHandler {

    private val log: Logger = LoggerFactory.getLogger(JwtAccessDeniedHandler::class.java)
    
    override fun handle(request: HttpServletRequest, response: HttpServletResponse, exception: AccessDeniedException) {
        log.info("请求访问: {},没有权限被拒,信息:{}", request.servletPath, exception.message)
        R.sendErrorByResponse(response, mapper, "权限不足", 3000)
    }
}

JwtAuthenticationEntryPoint

/**
 * 处理认证错误
 */
@Component
class JwtAuthenticationEntryPoint(
    private val mapper: ObjectMapper
): AuthenticationEntryPoint {

    private val log: Logger = LoggerFactory.getLogger(JwtAuthenticationEntryPoint::class.java)

    override fun commence(request: HttpServletRequest, response: HttpServletResponse, exception: AuthenticationException) {
        log.info("请求访问: {},携带令牌错误,信息:{}", request.servletPath, exception.message)
        R.sendErrorByResponse(response, mapper, "令牌错误", 3000)
    }
}

创建 JwtUtil 类

package icu.twtool.conference.utils

import com.auth0.jwt.JWT
import com.auth0.jwt.algorithms.Algorithm
import com.auth0.jwt.exceptions.*
import com.auth0.jwt.interfaces.DecodedJWT
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.core.Authentication
import org.springframework.security.core.GrantedAuthority
import org.springframework.security.core.authority.SimpleGrantedAuthority

/**
 * JWT 工具
 */
object JwtUtil {

    /**
     * 创建一个 JWT Token
     */
    fun createToken(username: String, role: String, permissions: List<String> ,secret: String): String =
        JWT.create()
            .withSubject(username)
            .withClaim("role", role)
            .withClaim("permissions", permissions)
            .sign(Algorithm.HMAC512(secret))

    /**
     * 验证 JWT 是否正确
     *
     * @param token 需要验证的 token.
     * @return 一个经过验证和解码的 JWT.
     * @throws AlgorithmMismatchException     如果令牌标头中声明的算法不等于 JWTVerifier 中定义的算法。
     * @throws SignatureVerificationException 如果签名无效。
     * @throws TokenExpiredException          如果令牌已过期。
     * @throws MissingClaimException          如果缺少要验证的声明。
     * @throws IncorrectClaimException        如果索赔包含的值与预期值不同。
     */
    private fun verity(token: String, secret: String): DecodedJWT =
        JWT.require(Algorithm.HMAC512(secret)).build()
            .verify(token)

    /**
     * 获取认证信息
     */
    fun getAuthentication(token: String, secret: String): Authentication = verity(token, secret).run {
        UsernamePasswordAuthenticationToken(
            subject,
            null,
            mutableListOf(SimpleGrantedAuthority("ROLE_" + getClaim("role").asString())).apply {
                getClaim("permissions").asList(String::class.java).map {
                    add(SimpleGrantedAuthority(it))
                }
            }
        )
    }
}

创建处理认证的过滤器

SecurityProperties

@Data
@ConfigurationProperties(prefix = "conference.security")
public class SecurityProperties {

    /**
     * 请求头的 JWT 的前缀
     */
    public String prefix = "Bearer ";

    /**
     * JWT 密钥
     */
    public String secret = "secret";
}

JwtAuthenticationManager

@Component
class JwtAuthenticationManager: AuthenticationManager {

    // JWT Token 只要能获取到信息就一定认证通过了,直接设置 isAuthenticated 为 true
    override fun authenticate(authentication: Authentication): Authentication {
        authentication.isAuthenticated = true
        return authentication
    }
}

JwtAuthenticationFilter

/**
 * Jwt 认证过滤器
 */
class JwtAuthenticationFilter(private val properties: SecurityProperties, manager: AuthenticationManager): BasicAuthenticationFilter(manager) {

    override fun doFilterInternal(request: HttpServletRequest, response: HttpServletResponse, chain: FilterChain) {
        val token: String? = request.getHeader(HttpHeaders.AUTHORIZATION)
        // 如果没有带 JWT 直接放行
        if (token?.startsWith(properties.prefix) != true) return chain.doFilter(request, response)
        val jwtToken: String = token.replace(properties.prefix, "")
        try {
            // 设置上下文给 AuthenticationManager 使用
            SecurityContextHolder.getContext().authentication = JwtUtil.getAuthentication(jwtToken, properties.secret)
        } catch (_: Exception) {

        }
        super.doFilterInternal(request, response, chain)
    }
}

创建配置 Security 的 SecurityConfiguration

/**
 * SpringSecurity 配置
 */
@EnableWebSecurity
@SpringBootConfiguration
@EnableGlobalMethodSecurity(prePostEnabled = true)
class SecurityConfiguration(
    private val properties: SecurityProperties,
    private val manager: AuthenticationManager,
    private val authenticationEntryPoint: JwtAuthenticationEntryPoint,
    private val jwtAccessDeniedHandler: JwtAccessDeniedHandler,
) {

    @Bean
    fun configure(http: HttpSecurity): SecurityFilterChain {
        val filter = JwtAuthenticationFilter(properties, manager)
        return http.cors().configurationSource(corsConfigurationSource()) // 允许跨域访问
            .and()
            .csrf().disable() // 禁用 csrf 验证
            .httpBasic().disable() // 禁用基础的 Http 表当认证
            .authorizeRequests {
                it.antMatchers("/login", "/logout").permitAll() // 放行登录/登出接口
                    .antMatchers("/**").authenticated() // 其它接口都需要认证
            }
            .sessionManagement {
                it.sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 无状态,不常见 Session
            }
            .addFilterAt(filter, UsernamePasswordAuthenticationFilter::class.java) // 添加过滤器
            .exceptionHandling { // 添加处理程序
                it.authenticationEntryPoint(authenticationEntryPoint)
                    .accessDeniedHandler(jwtAccessDeniedHandler)
            }
            .build()
    }

    fun corsConfigurationSource() = UrlBasedCorsConfigurationSource().apply {
        registerCorsConfiguration("/**", CorsConfiguration().apply {
            allowedHeaders = listOf("*")
            allowedMethods = listOf("*")
            allowedOrigins = listOf("*")
            maxAge = 3600
        })
    }
}

@PreAuthorize

  • hasRole(String role):请求需要包含 role 角色
  • hasAnyRole(String... roles):包含其中的容易一个角色
  • hasAuthority(String authority):请求需要包含权限
  • hasAnyAuthority(String... authorities):包含其中的任意一个权限

eg:

@PreAuthorize("hasRole('user')") 是 user 角色

@PreAuthorize("hasRole('user') and hasAuthority('delete')") 是 user 角色且 有 delete 权限

 类似资料: