增加认证
This commit is contained in:
parent
c01841e742
commit
53b38a3307
@ -58,6 +58,6 @@ dependencies {
|
||||
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
|
||||
}
|
||||
|
||||
tasks.named('test') {
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
}
|
@ -3,13 +3,19 @@ package dev.surl.surl.cfg
|
||||
import dev.surl.surl.util.numberToKey
|
||||
import io.jsonwebtoken.security.Keys
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties
|
||||
import java.time.temporal.ChronoUnit
|
||||
import java.util.Date
|
||||
import javax.crypto.SecretKey
|
||||
|
||||
@ConfigurationProperties(prefix = "base.configs")
|
||||
class BaseConfig(
|
||||
val site: String = "https://surl.org",
|
||||
val site: String = "http://127.0.0.1",
|
||||
val expire: Long = 3600000, // token expire time
|
||||
private val secret: String = numberToKey(Date().time).repeat(5),
|
||||
val unit: ChronoUnit = ChronoUnit.MILLIS,
|
||||
val tokenHead: String = "Bearer ",
|
||||
whiteList: List<String> = listOf("/login"),
|
||||
secret: String = numberToKey(Date().time).repeat(5),
|
||||
) {
|
||||
val secretKey: SecretKey = Keys.hmacShaKeyFor(secret.toByteArray())
|
||||
)
|
||||
val whiteList: List<Regex> = whiteList.map { it.toRegex() }
|
||||
}
|
@ -2,13 +2,6 @@ package dev.surl.surl.cfg
|
||||
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.stereotype.Component
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
@Component
|
||||
open class Logging {
|
||||
fun <T: Any> logger(clazz: KClass<T>): Logger = LoggerFactory.getLogger(clazz::class.java)
|
||||
}
|
||||
|
||||
@Suppress("UNUSED")
|
||||
fun <T: Any> T.logger(): Logger = LoggerFactory.getLogger(this::class.java)
|
||||
|
@ -2,6 +2,7 @@ package dev.surl.surl.cfg.security
|
||||
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.security.crypto.bcrypt.BCrypt
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
|
||||
|
||||
@Configuration
|
||||
@ -14,4 +15,9 @@ open class EncoderConfig {
|
||||
open fun passwordEncoder(): BCryptPasswordEncoder {
|
||||
return BCryptPasswordEncoder(BCryptPasswordEncoder.BCryptVersion.`$2B`)
|
||||
}
|
||||
|
||||
@Bean
|
||||
open fun cryoto(): BCrypt {
|
||||
return BCrypt()
|
||||
}
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
package dev.surl.surl.cfg.security
|
||||
|
||||
import jakarta.servlet.FilterChain
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
import dev.surl.surl.util.JwtTokenUtil
|
||||
import jakarta.servlet.http.HttpServletResponse
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
@ -9,7 +8,6 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
|
||||
import org.springframework.security.web.SecurityFilterChain
|
||||
import org.springframework.security.config.annotation.web.invoke
|
||||
import org.springframework.web.filter.OncePerRequestFilter
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@ -18,29 +16,21 @@ open class WebSecurityConfig {
|
||||
* 配置过滤器链
|
||||
*/
|
||||
@Bean
|
||||
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
open fun filterChain(
|
||||
http: HttpSecurity,
|
||||
jwtTokenUtil: JwtTokenUtil,
|
||||
response: HttpServletResponse): SecurityFilterChain {
|
||||
http {
|
||||
csrf { disable() } // 关闭csrf
|
||||
formLogin { disable() }
|
||||
httpBasic { disable() }
|
||||
authorizeHttpRequests {
|
||||
authorize(anyRequest, permitAll)
|
||||
}
|
||||
headers {
|
||||
cacheControl { } // 禁用缓存
|
||||
cacheControl { } // 禁用缓存
|
||||
}
|
||||
}
|
||||
return http.build()
|
||||
}
|
||||
|
||||
@Bean
|
||||
open fun authenticationTokenFilter(): OncePerRequestFilter {
|
||||
return object: OncePerRequestFilter() {
|
||||
override fun doFilterInternal(
|
||||
request: HttpServletRequest,
|
||||
response: HttpServletResponse,
|
||||
filterChain: FilterChain
|
||||
) {
|
||||
filterChain.doFilter(request, response)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
5
src/main/java/dev/surl/surl/common/enums/RedisStorage.kt
Normal file
5
src/main/java/dev/surl/surl/common/enums/RedisStorage.kt
Normal file
@ -0,0 +1,5 @@
|
||||
package dev.surl.surl.common.enums
|
||||
|
||||
enum class RedisStorage {
|
||||
TOKEN
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
package dev.surl.surl.common.exception
|
||||
|
||||
class UnauthorizedExcecption(message: String? = null, cause: Throwable? = null) : Exception(message, cause)
|
@ -5,27 +5,13 @@ import dev.surl.surl.common.Msg
|
||||
import dev.surl.surl.dto.SurlDto
|
||||
import dev.surl.surl.service.SurlService
|
||||
import jakarta.validation.Valid
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.PostMapping
|
||||
import org.springframework.web.bind.annotation.RequestBody
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
|
||||
@RestController
|
||||
@EnableWebSecurity
|
||||
class SurlAddController {
|
||||
@PostMapping("/surl/add")
|
||||
fun addSurl(
|
||||
@Valid @RequestBody body: SurlDto, @Autowired service: SurlService, @Autowired cfg: BaseConfig
|
||||
): Any {
|
||||
return ResponseEntity(Msg(code = 0, value = "${cfg.site}/${service.addSurl(body.url ?: "")}"), HttpStatus.OK)
|
||||
}
|
||||
|
||||
@GetMapping("/login")
|
||||
fun login():String {
|
||||
return "loged in"
|
||||
}
|
||||
class SurlAddController(private val service: SurlService, private val cfg: BaseConfig) {
|
||||
@PostMapping("/api/surl/add")
|
||||
fun addSurl(@Valid @RequestBody body: SurlDto) =
|
||||
Msg(code = 0, value = "${cfg.site}/${service.addSurl(body.url ?: "")}")
|
||||
}
|
26
src/main/java/dev/surl/surl/controller/SurlGetController.kt
Normal file
26
src/main/java/dev/surl/surl/controller/SurlGetController.kt
Normal file
@ -0,0 +1,26 @@
|
||||
package dev.surl.surl.controller
|
||||
|
||||
import dev.surl.surl.cfg.logger
|
||||
import dev.surl.surl.common.Msg
|
||||
import dev.surl.surl.service.SurlService
|
||||
import dev.surl.surl.util.JwtTokenUtil
|
||||
import org.springframework.http.HttpHeaders
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.RequestHeader
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
|
||||
@RestController
|
||||
class SurlGetController(
|
||||
private val surlService: SurlService,
|
||||
private val jwtTokenUtil: JwtTokenUtil
|
||||
) {
|
||||
@GetMapping(path = ["/api/surl/get"])
|
||||
fun getUrlsByUser(@RequestHeader headers: HttpHeaders): Msg<List<String>> {
|
||||
logger().logger().info("headers: $headers")
|
||||
val token = jwtTokenUtil.getTokenFromHeader(headers[HttpHeaders.AUTHORIZATION]?.last() ?: "")
|
||||
logger().logger().info("token: $token")
|
||||
val username = jwtTokenUtil.getUsernameFromToken(token)
|
||||
val urls = surlService.getUrlsByUser(username)
|
||||
return Msg(value = urls)
|
||||
}
|
||||
}
|
@ -4,7 +4,7 @@ import org.jetbrains.exposed.dao.id.IdTable
|
||||
|
||||
object Surls: IdTable<Long>("surl") {
|
||||
override val id = long("id").entityId()
|
||||
val url = varchar("url", 2048).uniqueIndex()
|
||||
val url = varchar("url", 2048)
|
||||
val user = reference("user", Users).index().nullable()
|
||||
override val primaryKey = PrimaryKey(id, name = "PK_surl_id")
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
package dev.surl.surl.filter
|
||||
//
|
||||
//import dev.surl.surl.cfg.BaseConfiguration
|
||||
//import dev.surl.surl.util.JwtTokenUtil
|
||||
//import jakarta.servlet.FilterChain
|
||||
//import jakarta.servlet.http.HttpServletRequest
|
||||
//import jakarta.servlet.http.HttpServletResponse
|
||||
//import org.springframework.beans.factory.annotation.Autowired
|
||||
//import org.springframework.security.core.context.SecurityContextHolder
|
||||
//import org.springframework.stereotype.Component
|
||||
//import org.springframework.web.filter.OncePerRequestFilter
|
||||
//
|
||||
//@Component
|
||||
//class JwtAuthenticationFilter: OncePerRequestFilter() {
|
||||
// @Autowired
|
||||
// lateinit var jwtTokenUtil: JwtTokenUtil
|
||||
//
|
||||
// @Autowired
|
||||
// lateinit var cfg: BaseConfiguration
|
||||
// override fun doFilterInternal(
|
||||
// request: HttpServletRequest,
|
||||
// response: HttpServletResponse,
|
||||
// filterChain: FilterChain
|
||||
// ) {
|
||||
// val token: String? = request.getHeader(cfg.tokenHead)
|
||||
// if(token.isNullOrEmpty()) {
|
||||
// val claims = jwtTokenUtil.getTokenClaim(token)
|
||||
// if(claims != null) {
|
||||
// if(!jwtTokenUtil.isTokenExpired(token) && SecurityContextHolder.getContext().authentication == null) {
|
||||
//
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// filterChain.doFilter(request, response)
|
||||
// }
|
||||
//}
|
@ -0,0 +1,65 @@
|
||||
package dev.surl.surl.filter
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import dev.surl.surl.cfg.BaseConfig
|
||||
import dev.surl.surl.common.Msg
|
||||
import dev.surl.surl.common.enums.RedisStorage
|
||||
import dev.surl.surl.common.exception.UnauthorizedExcecption
|
||||
import dev.surl.surl.util.JwtTokenUtil
|
||||
import dev.surl.surl.util.redis.RedisUtil
|
||||
import jakarta.servlet.FilterChain
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
import jakarta.servlet.http.HttpServletResponse
|
||||
import org.springframework.http.HttpHeaders
|
||||
import org.springframework.stereotype.Component
|
||||
import org.springframework.web.filter.OncePerRequestFilter
|
||||
|
||||
@Component
|
||||
class JwtAuthenticationTokenFilter(
|
||||
private val jwtTokenUtil: JwtTokenUtil,
|
||||
private val om: ObjectMapper,
|
||||
private val cfg: BaseConfig,
|
||||
private val redisUtil: RedisUtil
|
||||
) : OncePerRequestFilter() {
|
||||
override fun doFilterInternal(
|
||||
request: HttpServletRequest,
|
||||
response: HttpServletResponse,
|
||||
filterChain: FilterChain
|
||||
) {
|
||||
if (request.servletPath notMatchedIn cfg.whiteList) {
|
||||
try {
|
||||
val exp = UnauthorizedExcecption("unauthorized")
|
||||
val authHeader = request.getHeader(HttpHeaders.AUTHORIZATION) ?: throw exp
|
||||
val token = jwtTokenUtil.getTokenFromHeader(authHeader)
|
||||
val cachedToken = run {
|
||||
try {
|
||||
return@run redisUtil.getString(jwtTokenUtil.getUsernameFromToken(token), RedisStorage.TOKEN)
|
||||
} catch (ignored: Exception) {
|
||||
throw exp
|
||||
}
|
||||
}
|
||||
if (cachedToken != token) throw exp
|
||||
} catch (e: UnauthorizedExcecption) {
|
||||
response.status = HttpServletResponse.SC_UNAUTHORIZED
|
||||
val responseBody = om.writeValueAsString(Msg<String>(code = -1, msg = e.message))
|
||||
response.writer.run {
|
||||
write(responseBody)
|
||||
flush()
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
filterChain.doFilter(request, response)
|
||||
}
|
||||
|
||||
private infix fun String.matchedIn(regexes: List<Regex>): Boolean {
|
||||
for (regex in regexes) {
|
||||
if (this.matches(regex)) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private infix fun String.notMatchedIn(regexes: List<Regex>): Boolean {
|
||||
return !(this matchedIn regexes)
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
package dev.surl.surl.filter
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import dev.surl.surl.common.Msg
|
||||
import dev.surl.surl.common.enums.RedisStorage
|
||||
import dev.surl.surl.dto.UserDto
|
||||
import dev.surl.surl.service.UserService
|
||||
import dev.surl.surl.util.JwtTokenUtil
|
||||
import dev.surl.surl.util.redis.RedisUtil
|
||||
import dev.surl.surl.util.validate
|
||||
import jakarta.servlet.FilterChain
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
import jakarta.servlet.http.HttpServletResponse
|
||||
import jakarta.validation.ConstraintViolationException
|
||||
import jakarta.validation.Validator
|
||||
import org.springframework.security.authentication.AuthenticationManager
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
|
||||
import org.springframework.security.core.Authentication
|
||||
import org.springframework.security.core.AuthenticationException
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
|
||||
import org.springframework.stereotype.Component
|
||||
import java.nio.charset.StandardCharsets
|
||||
|
||||
@Component
|
||||
class UsernamePasswordAuthenticationCheckFilter(
|
||||
private val om: ObjectMapper,
|
||||
private val validator: Validator,
|
||||
private val jwtTokenUtil: JwtTokenUtil,
|
||||
private val userService: UserService,
|
||||
private val redisUtil: RedisUtil
|
||||
) : UsernamePasswordAuthenticationFilter() {
|
||||
|
||||
init {
|
||||
setFilterProcessesUrl("/login")
|
||||
authenticationManager = AuthenticationManager { it }
|
||||
}
|
||||
|
||||
override fun attemptAuthentication(request: HttpServletRequest?, response: HttpServletResponse?): Authentication {
|
||||
request ?: throw IllegalArgumentException("request is null")
|
||||
val userDto = request.run {
|
||||
om.readValue(String(inputStream.readAllBytes(), StandardCharsets.UTF_8), UserDto::class.java)
|
||||
}
|
||||
try {
|
||||
validate(userDto, validator)
|
||||
} catch (e: ConstraintViolationException) {
|
||||
throw object : AuthenticationException(e.constraintViolations.joinToString(";") { it.message }) {}
|
||||
} catch (e: Exception) {
|
||||
throw object : AuthenticationException(e.message) {}
|
||||
}
|
||||
if (!userService.authUser(userDto)) throw object : AuthenticationException("auth faild") {}
|
||||
return authenticationManager.authenticate(
|
||||
UsernamePasswordAuthenticationToken(
|
||||
userDto.username, userDto.password, mutableListOf()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun successfulAuthentication(
|
||||
request: HttpServletRequest?, response: HttpServletResponse?, chain: FilterChain?, authResult: Authentication?
|
||||
) {
|
||||
val (expireAt, token) = jwtTokenUtil.getToken(authResult!!.name, authResult.authorities.map { it.authority })
|
||||
redisUtil.setString(authResult.name, token, RedisStorage.TOKEN)
|
||||
val responseBody = om.writeValueAsString(
|
||||
Msg(
|
||||
code = 0, value = mapOf("expireAt" to expireAt, "token" to token)
|
||||
)
|
||||
)
|
||||
response?.run {
|
||||
writer.write(responseBody)
|
||||
writer.flush()
|
||||
}
|
||||
}
|
||||
|
||||
override fun unsuccessfulAuthentication(
|
||||
request: HttpServletRequest?, response: HttpServletResponse?, failed: AuthenticationException?
|
||||
) {
|
||||
response?.run {
|
||||
status = HttpServletResponse.SC_UNAUTHORIZED
|
||||
writer.write(om.writeValueAsString(Msg<String>(code = -1, msg = failed?.message)))
|
||||
writer.flush()
|
||||
}
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@ package dev.surl.surl.handler
|
||||
|
||||
import dev.surl.surl.common.Msg
|
||||
import dev.surl.surl.common.exception.UserRegistException
|
||||
import jakarta.validation.ConstraintViolationException
|
||||
import org.springframework.http.HttpHeaders
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.http.HttpStatusCode
|
||||
@ -53,7 +54,7 @@ class DefaultExceptionHandler : ResponseEntityExceptionHandler() {
|
||||
return ResponseEntity(Msg<String>(code = -1, msg = ex.message ?: "unknown error"), status)
|
||||
}
|
||||
|
||||
@ExceptionHandler(value = [IllegalStateException::class ,Exception::class])
|
||||
@ExceptionHandler(value = [IllegalStateException::class, Exception::class])
|
||||
fun handleException(
|
||||
ex: Exception
|
||||
): ResponseEntity<Msg<String>> {
|
||||
@ -65,4 +66,9 @@ class DefaultExceptionHandler : ResponseEntityExceptionHandler() {
|
||||
): ResponseEntity<Msg<String>>{
|
||||
return ResponseEntity(Msg(code = -1, msg = ex.message ?: "unknown regist error"), HttpStatus.BAD_REQUEST)
|
||||
}
|
||||
|
||||
@ExceptionHandler(value = [ConstraintViolationException::class])
|
||||
fun handleConstraintViolationException(ex: Exception): ResponseEntity<Msg<String>> {
|
||||
return ResponseEntity(Msg(code = -1, msg = ex.message ?: "unknown validation error"), HttpStatus.BAD_REQUEST)
|
||||
}
|
||||
}
|
@ -10,6 +10,7 @@ import org.springframework.stereotype.Service
|
||||
|
||||
@Service
|
||||
class SurlService {
|
||||
private val userService: UserService by autowired()
|
||||
fun addSurl(baseurl: String): String = runBlocking {
|
||||
val id = genSnowflakeUID()
|
||||
transaction {
|
||||
@ -34,4 +35,14 @@ class SurlService {
|
||||
}.firstOrNull()?.get(Surls.url) ?: ""
|
||||
}
|
||||
}
|
||||
fun getUrlsByUser(username: String): List<String> {
|
||||
val user = userService.getUserByUsername(username) ?: return emptyList()
|
||||
return transaction {
|
||||
Surl.find {
|
||||
Surls.id eq user.id
|
||||
}.map {
|
||||
it.url
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -6,9 +6,9 @@ import dev.surl.surl.common.exception.UserRegistException
|
||||
import dev.surl.surl.dao.User
|
||||
import dev.surl.surl.dao.UserAccess
|
||||
import dev.surl.surl.dsl.Users
|
||||
import dev.surl.surl.util.autowired
|
||||
import dev.surl.surl.util.genSnowflakeUID
|
||||
import dev.surl.surl.util.numberToKey
|
||||
import dev.surl.surl.dto.UserDto
|
||||
import dev.surl.surl.util.*
|
||||
import jakarta.validation.Validator
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority
|
||||
@ -23,7 +23,8 @@ typealias AUser = org.springframework.security.core.userdetails.User
|
||||
@Service
|
||||
class UserService: UserDetailsService {
|
||||
private val passwordEncoder: BCryptPasswordEncoder by autowired()
|
||||
fun addUser(username: String, password: String): Msg<Any> {
|
||||
private val validator: Validator by autowired()
|
||||
fun addUser(username: String, password: String): Msg<Map<String, String>> {
|
||||
|
||||
val (id, accessId) = runBlocking {
|
||||
Pair(genSnowflakeUID(), genSnowflakeUID())
|
||||
@ -45,7 +46,7 @@ class UserService: UserDetailsService {
|
||||
))
|
||||
}
|
||||
|
||||
private fun getUserByUsername(username: String): User? {
|
||||
fun getUserByUsername(username: String): User? {
|
||||
return transaction {
|
||||
User.find {
|
||||
Users.username eq username
|
||||
@ -64,6 +65,12 @@ class UserService: UserDetailsService {
|
||||
}
|
||||
}
|
||||
|
||||
fun authUser(userDto: UserDto):Boolean {
|
||||
validate(userDto, validator)
|
||||
val user = getUserByUsername(userDto.username!!) ?: throw UsernameNotFoundException("user `${userDto.username}` not found")
|
||||
return passwordEncoder.matches(userDto.password!!, user.password)
|
||||
}
|
||||
|
||||
override fun loadUserByUsername(username: String): UserDetails {
|
||||
val user = getUserByUsername(username) ?: throw UsernameNotFoundException("user '$username' not found")
|
||||
return AUser.builder().apply {
|
||||
|
@ -12,7 +12,6 @@ class Autowired<T : Any>(private val type: KClass<T>, private val name: String?)
|
||||
SurlApplication.context.getBean(name, type.java) as T
|
||||
}
|
||||
}
|
||||
|
||||
operator fun getValue(thisRef: Any?, property: KProperty<*>): T = value
|
||||
}
|
||||
inline fun <reified T : Any> autowired(name: String? = null) = Autowired(T::class, name)
|
||||
|
@ -1,11 +0,0 @@
|
||||
package dev.surl.surl.util
|
||||
|
||||
import org.apache.commons.codec.digest.DigestUtils
|
||||
|
||||
fun md5(str: String): String {
|
||||
return DigestUtils.md5Hex(str)
|
||||
}
|
||||
|
||||
fun sha1(str: String): String {
|
||||
return DigestUtils.sha1Hex(str)
|
||||
}
|
@ -1,75 +1,48 @@
|
||||
package dev.surl.surl.util
|
||||
|
||||
import dev.surl.surl.cfg.BaseConfig
|
||||
import dev.surl.surl.cfg.Logging
|
||||
import io.jsonwebtoken.Claims
|
||||
import io.jsonwebtoken.Jwts
|
||||
import org.hibernate.validator.internal.engine.DefaultClockProvider
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties
|
||||
import org.springframework.oxm.ValidationFailureException
|
||||
import org.springframework.stereotype.Component
|
||||
import java.time.Clock
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneId
|
||||
import java.util.Date
|
||||
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "jwt")
|
||||
class JwtTokenUtil {
|
||||
@Autowired
|
||||
private lateinit var cfg: BaseConfig
|
||||
|
||||
@Autowired
|
||||
private lateinit var logging: Logging
|
||||
|
||||
private val clock: Clock = DefaultClockProvider.INSTANCE.clock
|
||||
|
||||
fun getToken(identityId: String, authorizes: List<String>): Map<String, Any> {
|
||||
val now = Date()
|
||||
val expireAt = now.time + cfg.expire
|
||||
val expireDate = Date(expireAt)
|
||||
class JwtTokenUtil(private val cfg: BaseConfig) {
|
||||
fun getToken(identityId: String, authorizes: List<String>): Pair<Date, String> {
|
||||
val now = LocalDateTime.now()
|
||||
val expireAt = Date.from(now.plus(cfg.expire, cfg.unit).atZone(ZoneId.systemDefault()).toInstant())
|
||||
val token = Jwts.builder().run {
|
||||
subject(identityId)
|
||||
issuedAt(now)
|
||||
expiration(expireDate)
|
||||
issuedAt(Date())
|
||||
expiration(expireAt)
|
||||
signWith(cfg.secretKey)
|
||||
claim("roleAuthorizes", authorizes)
|
||||
compact()
|
||||
}
|
||||
return mapOf(
|
||||
"expireAt" to expireAt,
|
||||
"token" to token
|
||||
)
|
||||
return Pair(expireAt, token)
|
||||
}
|
||||
|
||||
fun getTokenClaim(token: String?): Claims? {
|
||||
if(token == null) return null
|
||||
try {
|
||||
return Jwts.parser().verifyWith(cfg.secretKey).build().parseSignedClaims(token).payload
|
||||
} catch (e: Exception) {
|
||||
logging.logger(JwtTokenUtil::class).error("get token claim error", e)
|
||||
return null
|
||||
}
|
||||
private fun getTokenClaim(token: String): Claims? {
|
||||
return Jwts.parser().verifyWith(cfg.secretKey).build().parseSignedClaims(token).payload
|
||||
}
|
||||
|
||||
private fun getExpireDateFromToken(token: String): Date? {
|
||||
return getClaimFromToken(token) {
|
||||
it?.expiration
|
||||
}
|
||||
}
|
||||
|
||||
fun isTokenExpired(token: String?): Boolean {
|
||||
if(token == null) return true
|
||||
val expireDate = getExpireDateFromToken(token)
|
||||
return if(expireDate == null) true else expireDate.time < clock.millis()
|
||||
}
|
||||
|
||||
fun getUsernameFromToken(token: String): String? {
|
||||
fun getUsernameFromToken(token: String): String {
|
||||
return getClaimFromToken(token) {
|
||||
it?.subject
|
||||
}
|
||||
} ?: throw ValidationFailureException("invalid token, userinfo not found")
|
||||
}
|
||||
|
||||
private fun <T> getClaimFromToken(token: String, resolver: (Claims?) -> T): T {
|
||||
val claims = getTokenClaim(token)
|
||||
return resolver(claims)
|
||||
}
|
||||
|
||||
fun getTokenFromHeader(header: String): String {
|
||||
return if (header.startsWith(cfg.tokenHead)) {
|
||||
header.substring(cfg.tokenHead.length)
|
||||
} else throw ValidationFailureException("invalid token")
|
||||
}
|
||||
}
|
12
src/main/java/dev/surl/surl/util/ValidateUtil.kt
Normal file
12
src/main/java/dev/surl/surl/util/ValidateUtil.kt
Normal file
@ -0,0 +1,12 @@
|
||||
package dev.surl.surl.util
|
||||
|
||||
import jakarta.validation.ConstraintViolationException
|
||||
import jakarta.validation.Validator
|
||||
|
||||
fun <T: Any?> validate(dto: T,validator: Validator) {
|
||||
if(dto == null) throw IllegalArgumentException("dto for validation is null")
|
||||
val violations = validator.validate(dto)
|
||||
if(violations.isNotEmpty()) {
|
||||
throw ConstraintViolationException(violations)
|
||||
}
|
||||
}
|
@ -1,23 +1,37 @@
|
||||
package dev.surl.surl.util.redis
|
||||
|
||||
import dev.surl.surl.cfg.BaseConfig
|
||||
import dev.surl.surl.common.enums.RedisStorage
|
||||
import org.springframework.data.redis.core.StringRedisTemplate
|
||||
import org.springframework.stereotype.Component
|
||||
import java.time.temporal.ChronoUnit
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
@Suppress("UNUSED")
|
||||
@Component
|
||||
class RedisUtil(private val template: StringRedisTemplate, private val cfg: BaseConfig) {
|
||||
private val ops = template.opsForValue()
|
||||
fun getString(key: String): String? {
|
||||
return ops.get(key)
|
||||
}
|
||||
fun setString(key: String, value: String, expire: Long = cfg.expire, unit: TimeUnit = TimeUnit.MILLISECONDS) {
|
||||
ops.set(key, value, expire, unit)
|
||||
fun getString(key: String, type: RedisStorage? = null): String? {
|
||||
if (type == null) {
|
||||
return ops.get(key)
|
||||
}
|
||||
return ops.get("${type.name}_$key")
|
||||
}
|
||||
|
||||
fun delKey(key: String) {
|
||||
ops.operations.delete(key)
|
||||
fun setString(key: String, value: String, type: RedisStorage? = null) {
|
||||
if (type == null) {
|
||||
ops.set(key, value, cfg.expire, chronoUnitToTimeUnit(cfg.unit))
|
||||
} else {
|
||||
ops.set("${type.name}_$key", value, cfg.expire, chronoUnitToTimeUnit(cfg.unit))
|
||||
}
|
||||
}
|
||||
|
||||
fun delKey(key: String, type: RedisStorage? = null) {
|
||||
if (type == null) {
|
||||
ops.operations.delete(key)
|
||||
return
|
||||
}
|
||||
ops.operations.delete("${type.name}_$key")
|
||||
}
|
||||
|
||||
fun flushdb() {
|
||||
@ -25,4 +39,15 @@ class RedisUtil(private val template: StringRedisTemplate, private val cfg: Base
|
||||
it.serverCommands().flushDb()
|
||||
}
|
||||
}
|
||||
|
||||
private fun chronoUnitToTimeUnit(unit: ChronoUnit): TimeUnit {
|
||||
return when (unit) {
|
||||
ChronoUnit.MILLIS -> TimeUnit.MILLISECONDS
|
||||
ChronoUnit.SECONDS -> TimeUnit.SECONDS
|
||||
ChronoUnit.MINUTES -> TimeUnit.MINUTES
|
||||
ChronoUnit.HOURS -> TimeUnit.HOURS
|
||||
ChronoUnit.DAYS -> TimeUnit.DAYS
|
||||
else -> throw IllegalArgumentException("unsupported unit: $unit")
|
||||
}
|
||||
}
|
||||
}
|
@ -36,7 +36,7 @@ spring:
|
||||
min-idle: 1
|
||||
max-wait: 100ms
|
||||
exposed:
|
||||
show-sql: false
|
||||
show-sql: true
|
||||
generate-ddl: true
|
||||
logging:
|
||||
level:
|
||||
@ -45,4 +45,9 @@ logging:
|
||||
base:
|
||||
configs:
|
||||
site: http://127.0.0.1:18888
|
||||
expire: 3600000
|
||||
expire: 3600000
|
||||
secret: Is#45Ddw29apkbHawwaHb4d^&w29apkbHawwaHb4d^&
|
||||
white-list:
|
||||
- ^/login$
|
||||
- ^/reg$
|
||||
- ^(?!\/api).*$
|
@ -1,18 +0,0 @@
|
||||
package dev.surl.surl
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
|
||||
import org.springframework.boot.test.context.SpringBootTest
|
||||
import org.springframework.test.web.servlet.MockMvc
|
||||
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.*
|
||||
import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated
|
||||
|
||||
@SpringBootTest
|
||||
@AutoConfigureMockMvc
|
||||
class SecurityTests(@Autowired val mvc: MockMvc) {
|
||||
@Test
|
||||
fun `surl add page`() {
|
||||
mvc.perform(formLogin("/login").user("user").password("1233")).andExpect(authenticated())
|
||||
}
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
package dev.surl.surl;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
@SpringBootTest
|
||||
class SurlApplicationTests {
|
||||
|
||||
@Test
|
||||
void contextLoads() {
|
||||
}
|
||||
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
package dev.surl.surl
|
||||
|
||||
import dev.surl.surl.util.keyToNumber
|
||||
import dev.surl.surl.util.md5
|
||||
import dev.surl.surl.util.numberToKey
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class UtilTests {
|
||||
@Test
|
||||
fun md5Test() {
|
||||
assert(md5("sd") == "6226f7cbe59e99a90b5cef6f94f966fd")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun numberToUIDTest() {
|
||||
println(numberToKey(1810519439309799440))
|
||||
println(keyToNumber("0IYJbl*DiYE"))
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user