<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)
}
}
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)
}
}
/**
* 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 权限