Add authentication #1
14
src/main/kotlin/top/fatweb/api/annotation/ApiVersion.kt
Normal file
14
src/main/kotlin/top/fatweb/api/annotation/ApiVersion.kt
Normal file
@@ -0,0 +1,14 @@
|
||||
package top.fatweb.api.annotation
|
||||
|
||||
import org.springframework.core.annotation.AliasFor
|
||||
|
||||
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class ApiVersion(
|
||||
@get:AliasFor("version")
|
||||
val value: Int = 1,
|
||||
|
||||
@get:AliasFor("value")
|
||||
val version: Int = 1
|
||||
)
|
||||
92
src/main/kotlin/top/fatweb/api/config/SecurityConfig.kt
Normal file
92
src/main/kotlin/top/fatweb/api/config/SecurityConfig.kt
Normal file
@@ -0,0 +1,92 @@
|
||||
package top.fatweb.api.config
|
||||
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.security.authentication.AuthenticationManager
|
||||
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||
import org.springframework.security.config.annotation.web.configurers.*
|
||||
import org.springframework.security.config.http.SessionCreationPolicy
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
|
||||
import org.springframework.security.web.SecurityFilterChain
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
|
||||
import org.springframework.web.cors.CorsConfiguration
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource
|
||||
import top.fatweb.api.filter.JwtAuthenticationTokenFilter
|
||||
import top.fatweb.api.handler.JwtAccessDeniedHandler
|
||||
import top.fatweb.api.handler.JwtAuthenticationEntryPointHandler
|
||||
|
||||
@Configuration
|
||||
@EnableMethodSecurity
|
||||
class SecurityConfig(
|
||||
val jwtAuthenticationTokenFilter: JwtAuthenticationTokenFilter,
|
||||
val authenticationEntryPointHandler: JwtAuthenticationEntryPointHandler,
|
||||
val accessDeniedHandler: JwtAccessDeniedHandler
|
||||
) {
|
||||
@Bean
|
||||
fun passwordEncoder() = BCryptPasswordEncoder()
|
||||
|
||||
@Bean
|
||||
fun authenticationManager(authenticationConfiguration: AuthenticationConfiguration): AuthenticationManager =
|
||||
authenticationConfiguration.authenticationManager
|
||||
|
||||
@Bean
|
||||
fun corsConfigurationSource(): UrlBasedCorsConfigurationSource {
|
||||
val corsConfiguration = CorsConfiguration()
|
||||
corsConfiguration.allowedMethods = listOf("*")
|
||||
corsConfiguration.allowedHeaders = listOf("*")
|
||||
corsConfiguration.maxAge = 3600L
|
||||
corsConfiguration.allowedOrigins = listOf("*")
|
||||
val source = UrlBasedCorsConfigurationSource()
|
||||
source.registerCorsConfiguration("/**", corsConfiguration)
|
||||
|
||||
return source
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun securityFilterChain(httpSecurity: HttpSecurity): SecurityFilterChain = httpSecurity
|
||||
// Disable CSRF
|
||||
.csrf { csrfConfigurer: CsrfConfigurer<HttpSecurity> -> csrfConfigurer.disable() }
|
||||
// Do not get SecurityContent by Session
|
||||
.sessionManagement { sessionManagementConfigurer: SessionManagementConfigurer<HttpSecurity?> ->
|
||||
sessionManagementConfigurer.sessionCreationPolicy(
|
||||
SessionCreationPolicy.STATELESS
|
||||
)
|
||||
}
|
||||
.authorizeHttpRequests { authorizeHttpRequests: AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry ->
|
||||
authorizeHttpRequests
|
||||
// Allow anonymous access
|
||||
.requestMatchers(
|
||||
"/api/v*/login",
|
||||
"/error/thrown",
|
||||
"/doc.html",
|
||||
"/swagger-ui/**",
|
||||
"/webjars/**",
|
||||
"/v3/**",
|
||||
"/swagger-ui.html",
|
||||
"/favicon.ico"
|
||||
).anonymous()
|
||||
// Authentication required
|
||||
.anyRequest().authenticated()
|
||||
}
|
||||
|
||||
.logout { logoutConfigurer: LogoutConfigurer<HttpSecurity> -> logoutConfigurer.disable() }
|
||||
|
||||
.exceptionHandling { exceptionHandlingConfigurer: ExceptionHandlingConfigurer<HttpSecurity?> ->
|
||||
exceptionHandlingConfigurer.authenticationEntryPoint(
|
||||
authenticationEntryPointHandler
|
||||
)
|
||||
exceptionHandlingConfigurer.accessDeniedHandler(
|
||||
accessDeniedHandler
|
||||
)
|
||||
}
|
||||
|
||||
.cors { cors: CorsConfigurer<HttpSecurity?> ->
|
||||
cors.configurationSource(
|
||||
corsConfigurationSource()
|
||||
)
|
||||
}
|
||||
|
||||
.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter::class.java).build()
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package top.fatweb.api.config
|
||||
|
||||
import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
|
||||
import top.fatweb.api.util.ApiResponseMappingHandlerMapping
|
||||
|
||||
@Configuration
|
||||
class WebMvcRegistrationsConfig : WebMvcRegistrations {
|
||||
override fun getRequestMappingHandlerMapping(): RequestMappingHandlerMapping = ApiResponseMappingHandlerMapping()
|
||||
}
|
||||
17
src/main/kotlin/top/fatweb/api/controller/UserController.kt
Normal file
17
src/main/kotlin/top/fatweb/api/controller/UserController.kt
Normal file
@@ -0,0 +1,17 @@
|
||||
package top.fatweb.api.controller
|
||||
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 用户 前端控制器
|
||||
* </p>
|
||||
*
|
||||
* @author FatttSnake
|
||||
* @since 2023-10-04
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/user")
|
||||
class UserController
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package top.fatweb.api.controller.permission
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation
|
||||
import io.swagger.v3.oas.annotations.tags.Tag
|
||||
import org.springframework.web.bind.annotation.*
|
||||
import top.fatweb.api.annotation.ApiVersion
|
||||
import top.fatweb.api.entity.common.ResponseCode
|
||||
import top.fatweb.api.entity.common.ResponseResult
|
||||
import top.fatweb.api.entity.permission.User
|
||||
import top.fatweb.api.service.permission.IAuthenticationService
|
||||
|
||||
@Tag(name = "身份认证", description = "身份认证相关接口")
|
||||
@RestController
|
||||
@RequestMapping("/api/{apiVersion}")
|
||||
@ApiVersion(2)
|
||||
class AuthenticationController(val loginService: IAuthenticationService) {
|
||||
@Operation(summary = "登录")
|
||||
@PostMapping("/login")
|
||||
fun login(@PathVariable apiVersion: String, @RequestBody user: User) =
|
||||
ResponseResult.success(ResponseCode.SYSTEM_LOGIN_SUCCESS, "Login success", loginService.login(user))
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package top.fatweb.api.entity.permission
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||
import org.springframework.security.core.GrantedAuthority
|
||||
import org.springframework.security.core.userdetails.UserDetails
|
||||
|
||||
class LoginUser() : UserDetails {
|
||||
lateinit var user: User
|
||||
|
||||
@JsonIgnore
|
||||
private var authorities: List<GrantedAuthority>? = null
|
||||
|
||||
constructor(user: User) : this() {
|
||||
this.user = user
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
override fun getAuthorities(): List<GrantedAuthority> {
|
||||
authorities?.let { return it }
|
||||
authorities = emptyList()
|
||||
|
||||
return authorities as List<GrantedAuthority>
|
||||
}
|
||||
|
||||
override fun getPassword(): String? = user.password
|
||||
|
||||
override fun getUsername(): String? = user.username
|
||||
|
||||
override fun isAccountNonExpired(): Boolean = true
|
||||
|
||||
override fun isAccountNonLocked(): Boolean = true
|
||||
|
||||
override fun isCredentialsNonExpired(): Boolean = true
|
||||
|
||||
override fun isEnabled(): Boolean = user.enable == 1
|
||||
}
|
||||
56
src/main/kotlin/top/fatweb/api/entity/permission/User.kt
Normal file
56
src/main/kotlin/top/fatweb/api/entity/permission/User.kt
Normal file
@@ -0,0 +1,56 @@
|
||||
package top.fatweb.api.entity.permission
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*
|
||||
import java.io.Serializable
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 用户
|
||||
* </p>
|
||||
*
|
||||
* @author FatttSnake
|
||||
* @since 2023-10-04
|
||||
*/
|
||||
@TableName("t_user")
|
||||
class User : Serializable {
|
||||
|
||||
@TableId("id")
|
||||
var id: Long? = null
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
@TableField("username")
|
||||
var username: String? = null
|
||||
|
||||
/**
|
||||
* 密码
|
||||
*/
|
||||
@TableField("password")
|
||||
var password: String? = null
|
||||
|
||||
/**
|
||||
* 启用
|
||||
*/
|
||||
@TableField("enable")
|
||||
var enable: Int? = null
|
||||
|
||||
@TableField("deleted")
|
||||
@TableLogic
|
||||
var deleted: Long? = null
|
||||
|
||||
@TableField("version")
|
||||
@Version
|
||||
var version: Int? = null
|
||||
|
||||
override fun toString(): String {
|
||||
return "User{" +
|
||||
"id=" + id +
|
||||
", username=" + username +
|
||||
", password=" + password +
|
||||
", enable=" + enable +
|
||||
", deleted=" + deleted +
|
||||
", version=" + version +
|
||||
"}"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
package top.fatweb.api.exception
|
||||
|
||||
class TokenHasExpiredException : RuntimeException("Token has expired")
|
||||
@@ -3,24 +3,44 @@ package top.fatweb.api.filter
|
||||
import jakarta.servlet.FilterChain
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
import jakarta.servlet.http.HttpServletResponse
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
|
||||
import org.springframework.security.core.context.SecurityContextHolder
|
||||
import org.springframework.stereotype.Component
|
||||
import org.springframework.util.StringUtils
|
||||
import org.springframework.web.filter.OncePerRequestFilter
|
||||
import top.fatweb.api.constants.SecurityConstants
|
||||
import top.fatweb.api.utils.RedisUtil
|
||||
import top.fatweb.api.constant.SecurityConstants
|
||||
import top.fatweb.api.entity.permission.LoginUser
|
||||
import top.fatweb.api.exception.TokenHasExpiredException
|
||||
import top.fatweb.api.util.JwtUtil
|
||||
import top.fatweb.api.util.RedisUtil
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
@Component
|
||||
class JwtAuthenticationTokenFilter(private val redisUtil: RedisUtil) : OncePerRequestFilter() {
|
||||
override fun doFilterInternal(
|
||||
request: HttpServletRequest,
|
||||
response: HttpServletResponse,
|
||||
filterChain: FilterChain
|
||||
) {
|
||||
val token = request.getHeader(SecurityConstants.headerString)
|
||||
val tokenWithPrefix = request.getHeader(SecurityConstants.headerString)
|
||||
|
||||
if (!StringUtils.hasText(token) || "/error/thrown" == request.servletPath) {
|
||||
if (!StringUtils.hasText(tokenWithPrefix) || "/error/thrown" == request.servletPath) {
|
||||
filterChain.doFilter(request, response)
|
||||
return
|
||||
}
|
||||
|
||||
val token = tokenWithPrefix.removePrefix(SecurityConstants.tokenPrefix)
|
||||
JwtUtil.parseJwt(token)
|
||||
|
||||
val redisKey = "${SecurityConstants.jwtIssuer}_login:" + token.substring(0, 32)
|
||||
val loginUser = redisUtil.getObject<LoginUser>(redisKey)
|
||||
loginUser ?: let { throw TokenHasExpiredException() }
|
||||
|
||||
redisUtil.setExpire(redisKey, 20, TimeUnit.MINUTES)
|
||||
|
||||
val authenticationToken = UsernamePasswordAuthenticationToken(loginUser, null, loginUser.authorities)
|
||||
SecurityContextHolder.getContext().authentication = authenticationToken
|
||||
|
||||
filterChain.doFilter(request, response)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package top.fatweb.api.handler
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
import jakarta.servlet.http.HttpServletResponse
|
||||
import org.springframework.security.access.AccessDeniedException
|
||||
import org.springframework.security.web.access.AccessDeniedHandler
|
||||
import org.springframework.stereotype.Component
|
||||
|
||||
@Component
|
||||
class JwtAccessDeniedHandler : AccessDeniedHandler {
|
||||
override fun handle(
|
||||
request: HttpServletRequest?,
|
||||
response: HttpServletResponse?,
|
||||
accessDeniedException: AccessDeniedException?
|
||||
) {
|
||||
request?.setAttribute("filter.error", accessDeniedException)
|
||||
request?.getRequestDispatcher("/error/thrown")?.forward(request, response)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package top.fatweb.api.handler
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
import jakarta.servlet.http.HttpServletResponse
|
||||
import org.springframework.security.core.AuthenticationException
|
||||
import org.springframework.security.web.AuthenticationEntryPoint
|
||||
import org.springframework.stereotype.Component
|
||||
|
||||
@Component
|
||||
class JwtAuthenticationEntryPointHandler : AuthenticationEntryPoint {
|
||||
override fun commence(
|
||||
request: HttpServletRequest?,
|
||||
response: HttpServletResponse?,
|
||||
authException: AuthenticationException?
|
||||
) {
|
||||
request?.setAttribute("filter.error", authException)
|
||||
request?.getRequestDispatcher("/error/thrown")?.forward(request, response)
|
||||
}
|
||||
}
|
||||
16
src/main/kotlin/top/fatweb/api/mapper/UserMapper.kt
Normal file
16
src/main/kotlin/top/fatweb/api/mapper/UserMapper.kt
Normal file
@@ -0,0 +1,16 @@
|
||||
package top.fatweb.api.mapper
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper
|
||||
import org.apache.ibatis.annotations.Mapper
|
||||
import top.fatweb.api.entity.permission.User
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 用户 Mapper 接口
|
||||
* </p>
|
||||
*
|
||||
* @author FatttSnake
|
||||
* @since 2023-10-04
|
||||
*/
|
||||
@Mapper
|
||||
interface UserMapper : BaseMapper<User>
|
||||
14
src/main/kotlin/top/fatweb/api/service/IUserService.kt
Normal file
14
src/main/kotlin/top/fatweb/api/service/IUserService.kt
Normal file
@@ -0,0 +1,14 @@
|
||||
package top.fatweb.api.service
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService
|
||||
import top.fatweb.api.entity.permission.User
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 用户 服务类
|
||||
* </p>
|
||||
*
|
||||
* @author FatttSnake
|
||||
* @since 2023-10-04
|
||||
*/
|
||||
interface IUserService : IService<User>
|
||||
@@ -0,0 +1,18 @@
|
||||
package top.fatweb.api.service.impl
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl
|
||||
import org.springframework.stereotype.Service
|
||||
import top.fatweb.api.entity.permission.User
|
||||
import top.fatweb.api.mapper.UserMapper
|
||||
import top.fatweb.api.service.IUserService
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 用户 服务实现类
|
||||
* </p>
|
||||
*
|
||||
* @author FatttSnake
|
||||
* @since 2023-10-04
|
||||
*/
|
||||
@Service
|
||||
class UserServiceImpl : ServiceImpl<UserMapper, User>(), IUserService
|
||||
@@ -0,0 +1,11 @@
|
||||
package top.fatweb.api.service.permission
|
||||
|
||||
import top.fatweb.api.entity.permission.User
|
||||
|
||||
interface IAuthenticationService {
|
||||
fun login(user: User): HashMap<String, String>
|
||||
|
||||
fun logout(token: String): Boolean
|
||||
|
||||
fun renewToken(token: String): HashMap<String, String>
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package top.fatweb.api.service.permission.impl
|
||||
|
||||
import org.springframework.security.authentication.AuthenticationManager
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
|
||||
import org.springframework.stereotype.Service
|
||||
import top.fatweb.api.constant.SecurityConstants
|
||||
import top.fatweb.api.entity.permission.LoginUser
|
||||
import top.fatweb.api.entity.permission.User
|
||||
import top.fatweb.api.service.permission.IAuthenticationService
|
||||
import top.fatweb.api.util.JwtUtil
|
||||
import top.fatweb.api.util.RedisUtil
|
||||
import top.fatweb.api.util.WebUtil
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
@Service
|
||||
class AuthenticationServiceImpl(
|
||||
private val authenticationManager: AuthenticationManager,
|
||||
private val redisUtil: RedisUtil
|
||||
) : IAuthenticationService {
|
||||
override fun login(user: User): HashMap<String, String> {
|
||||
val usernamePasswordAuthenticationToken = UsernamePasswordAuthenticationToken(user.username, user.password)
|
||||
val authentication = authenticationManager.authenticate(usernamePasswordAuthenticationToken)
|
||||
authentication ?: let {
|
||||
throw RuntimeException("Login failed")
|
||||
}
|
||||
|
||||
val loginUser = authentication.principal as LoginUser
|
||||
loginUser.user.password = ""
|
||||
val userId = loginUser.user.id.toString()
|
||||
val jwt = JwtUtil.createJwt(userId)
|
||||
|
||||
jwt ?: let {
|
||||
throw RuntimeException("Login failed")
|
||||
}
|
||||
|
||||
val hashMap = hashMapOf("token" to jwt)
|
||||
val redisKey = "${SecurityConstants.jwtIssuer}_login:" + jwt.substring(0, 32)
|
||||
redisUtil.setObject(redisKey, loginUser, 20, TimeUnit.MINUTES)
|
||||
|
||||
return hashMap
|
||||
}
|
||||
|
||||
override fun logout(token: String): Boolean =
|
||||
redisUtil.delObject("${SecurityConstants.jwtIssuer}_login:" + token.substring(0, 32))
|
||||
|
||||
override fun renewToken(token: String): HashMap<String, String> {
|
||||
val oldRedisKey = "${SecurityConstants.jwtIssuer}_login:" + token.substring(0, 32)
|
||||
redisUtil.delObject(oldRedisKey)
|
||||
val jwt = JwtUtil.createJwt(WebUtil.getLoginUserId().toString())
|
||||
|
||||
jwt ?: let {
|
||||
throw RuntimeException("Login failed")
|
||||
}
|
||||
|
||||
val hashMap = hashMapOf("token" to jwt)
|
||||
val redisKey = "${SecurityConstants.jwtIssuer}_login:" + jwt.substring(0, 32)
|
||||
redisUtil.setObject(redisKey, WebUtil.getLoginUser(), 20, TimeUnit.MINUTES)
|
||||
|
||||
return hashMap
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package top.fatweb.api.util
|
||||
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.servlet.mvc.condition.RequestCondition
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
|
||||
import top.fatweb.api.annotation.ApiVersion
|
||||
import java.lang.reflect.Method
|
||||
|
||||
class ApiResponseMappingHandlerMapping : RequestMappingHandlerMapping() {
|
||||
private val versionFlag = "{apiVersion}"
|
||||
|
||||
private fun createCondition(clazz: Class<*>): RequestCondition<ApiVersionCondition>? {
|
||||
val classRequestMapping = clazz.getAnnotation(RequestMapping::class.java)
|
||||
classRequestMapping ?: let { return null }
|
||||
val mappingUrlBuilder = StringBuilder()
|
||||
if (classRequestMapping.value.isNotEmpty()) {
|
||||
mappingUrlBuilder.append(classRequestMapping.value[0])
|
||||
}
|
||||
val mappingUrl = mappingUrlBuilder.toString()
|
||||
if (!mappingUrl.contains(versionFlag)) {
|
||||
return null
|
||||
}
|
||||
val apiVersion = clazz.getAnnotation(ApiVersion::class.java)
|
||||
|
||||
return if (apiVersion == null) ApiVersionCondition(1) else ApiVersionCondition(apiVersion.version)
|
||||
}
|
||||
|
||||
override fun getCustomMethodCondition(method: Method): RequestCondition<*>? = createCondition(method.javaClass)
|
||||
|
||||
override fun getCustomTypeCondition(handlerType: Class<*>): RequestCondition<*>? = createCondition(handlerType)
|
||||
}
|
||||
26
src/main/kotlin/top/fatweb/api/util/ApiVersionCondition.kt
Normal file
26
src/main/kotlin/top/fatweb/api/util/ApiVersionCondition.kt
Normal file
@@ -0,0 +1,26 @@
|
||||
package top.fatweb.api.util
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
import org.springframework.web.servlet.mvc.condition.RequestCondition
|
||||
import java.util.regex.Pattern
|
||||
|
||||
class ApiVersionCondition(private val apiVersion: Int) : RequestCondition<ApiVersionCondition> {
|
||||
private val versionPrefixPattern: Pattern = Pattern.compile(".*v(\\d+).*")
|
||||
|
||||
override fun combine(other: ApiVersionCondition): ApiVersionCondition = ApiVersionCondition(other.apiVersion)
|
||||
|
||||
override fun getMatchingCondition(request: HttpServletRequest): ApiVersionCondition? {
|
||||
val matcher = versionPrefixPattern.matcher(request.requestURI)
|
||||
if (matcher.find()) {
|
||||
val version = matcher.group(1).toInt()
|
||||
if (version >= this.apiVersion) {
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
override fun compareTo(other: ApiVersionCondition, request: HttpServletRequest): Int =
|
||||
other.apiVersion - this.apiVersion
|
||||
}
|
||||
67
src/main/kotlin/top/fatweb/api/util/JwtUtil.kt
Normal file
67
src/main/kotlin/top/fatweb/api/util/JwtUtil.kt
Normal file
@@ -0,0 +1,67 @@
|
||||
package top.fatweb.api.util
|
||||
|
||||
import com.auth0.jwt.JWT
|
||||
import com.auth0.jwt.algorithms.Algorithm
|
||||
import com.auth0.jwt.interfaces.DecodedJWT
|
||||
import top.fatweb.api.constant.SecurityConstants
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
object JwtUtil {
|
||||
private fun getUUID() = UUID.randomUUID().toString().replace("-", "")
|
||||
|
||||
/**
|
||||
* 生成加密后的秘钥 secretKey
|
||||
*
|
||||
* @return 密钥
|
||||
*/
|
||||
private fun generalKey(): SecretKeySpec {
|
||||
val encodeKey = Base64.getDecoder().decode(SecurityConstants.jwtKey)
|
||||
return SecretKeySpec(encodeKey, 0, encodeKey.size, "AES")
|
||||
}
|
||||
|
||||
private fun algorithm(): Algorithm = Algorithm.HMAC256(generalKey().toString())
|
||||
|
||||
/**
|
||||
* 创建 token
|
||||
*
|
||||
* @param subject token 中存放的数据(json格式)
|
||||
* @param ttl token 生存时间
|
||||
* @param timeUnit ttl 时间单位
|
||||
* @param uuid 唯一 ID
|
||||
* @return jwt 串
|
||||
*/
|
||||
fun createJwt(
|
||||
subject: String,
|
||||
ttl: Long = SecurityConstants.jwtTtl,
|
||||
timeUnit: TimeUnit = SecurityConstants.jwtTtlUnit,
|
||||
uuid: String = getUUID()
|
||||
): String? {
|
||||
val nowMillis = System.currentTimeMillis()
|
||||
val nowDate = Date(nowMillis)
|
||||
val unitTtl = (ttl * when (timeUnit) {
|
||||
TimeUnit.DAYS -> 24 * 60 * 60 * 1000
|
||||
TimeUnit.HOURS -> 60 * 60 * 1000
|
||||
TimeUnit.MINUTES -> 60 * 1000
|
||||
TimeUnit.SECONDS -> 1000
|
||||
TimeUnit.MILLISECONDS -> 1
|
||||
TimeUnit.NANOSECONDS -> 1 / 1000
|
||||
TimeUnit.MICROSECONDS -> 1 / 1000 / 1000
|
||||
})
|
||||
val expMillis = nowMillis + unitTtl
|
||||
val expDate = Date(expMillis)
|
||||
|
||||
return JWT.create().withJWTId(uuid).withSubject(subject).withIssuer(SecurityConstants.jwtIssuer)
|
||||
.withIssuedAt(nowDate).withExpiresAt(expDate).sign(algorithm())
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 jwt
|
||||
*
|
||||
* @param jwt jwt 串
|
||||
* @return 解析内容
|
||||
*/
|
||||
fun parseJwt(jwt: String): DecodedJWT =
|
||||
JWT.require(algorithm()).build().verify(jwt)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package top.fatweb.api.utils
|
||||
package top.fatweb.api.util
|
||||
|
||||
import org.springframework.data.redis.core.BoundSetOperations
|
||||
import org.springframework.data.redis.core.RedisTemplate
|
||||
@@ -141,7 +141,8 @@ class RedisUtil(private val redisTemplate: RedisTemplate<String, Any>) {
|
||||
* @param key 缓存的键
|
||||
* @return 缓存的键对应的 Map 数据
|
||||
*/
|
||||
fun <T> getMap(key: String): Map<String, T>? = redisTemplate.opsForHash<String, Any>().entries(key) as? Map<String, T>
|
||||
fun <T> getMap(key: String): Map<String, T>? =
|
||||
redisTemplate.opsForHash<String, Any>().entries(key) as? Map<String, T>
|
||||
|
||||
/**
|
||||
* 往 Hash 中存入数据
|
||||
10
src/main/kotlin/top/fatweb/api/util/WebUtil.kt
Normal file
10
src/main/kotlin/top/fatweb/api/util/WebUtil.kt
Normal file
@@ -0,0 +1,10 @@
|
||||
package top.fatweb.api.util
|
||||
|
||||
import org.springframework.security.core.context.SecurityContextHolder
|
||||
import top.fatweb.api.entity.permission.LoginUser
|
||||
|
||||
object WebUtil {
|
||||
fun getLoginUser() = SecurityContextHolder.getContext().authentication.principal as LoginUser
|
||||
|
||||
fun getLoginUserId() = getLoginUser().user.id
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
package top.fatweb.api.utils
|
||||
|
||||
class JwtUtil {
|
||||
|
||||
}
|
||||
5
src/main/resources/mapper/UserMapper.xml
Normal file
5
src/main/resources/mapper/UserMapper.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="top.fatweb.api.mapper.UserMapper">
|
||||
|
||||
</mapper>
|
||||
@@ -1,17 +1,19 @@
|
||||
package top.fatweb.api
|
||||
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.springframework.boot.test.context.SpringBootTest
|
||||
import top.fatweb.api.constants.SecurityConstants
|
||||
import java.security.MessageDigest
|
||||
import java.util.*
|
||||
import top.fatweb.api.constant.SecurityConstants
|
||||
|
||||
@SpringBootTest
|
||||
class FatWebApiApplicationTests {
|
||||
|
||||
@Test
|
||||
fun contextLoads() {
|
||||
SecurityConstants.jwtKey
|
||||
}
|
||||
|
||||
@Test
|
||||
fun removePrefixTest() {
|
||||
assertEquals("12312", "Bearer 12312".removePrefix(SecurityConstants.tokenPrefix))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user