增加认证

This commit is contained in:
05412 2024-07-17 22:34:59 +08:00
parent c01841e742
commit 53b38a3307
25 changed files with 313 additions and 209 deletions

View File

@ -58,6 +58,6 @@ dependencies {
testRuntimeOnly 'org.junit.platform:junit-platform-launcher' testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
} }
tasks.named('test') { test {
useJUnitPlatform() useJUnitPlatform()
} }

View File

@ -3,13 +3,19 @@ package dev.surl.surl.cfg
import dev.surl.surl.util.numberToKey import dev.surl.surl.util.numberToKey
import io.jsonwebtoken.security.Keys import io.jsonwebtoken.security.Keys
import org.springframework.boot.context.properties.ConfigurationProperties import org.springframework.boot.context.properties.ConfigurationProperties
import java.time.temporal.ChronoUnit
import java.util.Date import java.util.Date
import javax.crypto.SecretKey import javax.crypto.SecretKey
@ConfigurationProperties(prefix = "base.configs") @ConfigurationProperties(prefix = "base.configs")
class BaseConfig( class BaseConfig(
val site: String = "https://surl.org", val site: String = "http://127.0.0.1",
val expire: Long = 3600000, // token expire time 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 secretKey: SecretKey = Keys.hmacShaKeyFor(secret.toByteArray())
) val whiteList: List<Regex> = whiteList.map { it.toRegex() }
}

View File

@ -2,13 +2,6 @@ package dev.surl.surl.cfg
import org.slf4j.Logger import org.slf4j.Logger
import org.slf4j.LoggerFactory 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") @Suppress("UNUSED")
fun <T: Any> T.logger(): Logger = LoggerFactory.getLogger(this::class.java) fun <T: Any> T.logger(): Logger = LoggerFactory.getLogger(this::class.java)

View File

@ -2,6 +2,7 @@ package dev.surl.surl.cfg.security
import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration import org.springframework.context.annotation.Configuration
import org.springframework.security.crypto.bcrypt.BCrypt
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
@Configuration @Configuration
@ -14,4 +15,9 @@ open class EncoderConfig {
open fun passwordEncoder(): BCryptPasswordEncoder { open fun passwordEncoder(): BCryptPasswordEncoder {
return BCryptPasswordEncoder(BCryptPasswordEncoder.BCryptVersion.`$2B`) return BCryptPasswordEncoder(BCryptPasswordEncoder.BCryptVersion.`$2B`)
} }
@Bean
open fun cryoto(): BCrypt {
return BCrypt()
}
} }

View File

@ -1,7 +1,6 @@
package dev.surl.surl.cfg.security package dev.surl.surl.cfg.security
import jakarta.servlet.FilterChain import dev.surl.surl.util.JwtTokenUtil
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse import jakarta.servlet.http.HttpServletResponse
import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration 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.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.web.SecurityFilterChain import org.springframework.security.web.SecurityFilterChain
import org.springframework.security.config.annotation.web.invoke import org.springframework.security.config.annotation.web.invoke
import org.springframework.web.filter.OncePerRequestFilter
@Configuration @Configuration
@EnableWebSecurity @EnableWebSecurity
@ -18,9 +16,14 @@ open class WebSecurityConfig {
* 配置过滤器链 * 配置过滤器链
*/ */
@Bean @Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain { open fun filterChain(
http: HttpSecurity,
jwtTokenUtil: JwtTokenUtil,
response: HttpServletResponse): SecurityFilterChain {
http { http {
csrf { disable() } // 关闭csrf csrf { disable() } // 关闭csrf
formLogin { disable() }
httpBasic { disable() }
authorizeHttpRequests { authorizeHttpRequests {
authorize(anyRequest, permitAll) authorize(anyRequest, permitAll)
} }
@ -30,17 +33,4 @@ open class WebSecurityConfig {
} }
return http.build() return http.build()
} }
@Bean
open fun authenticationTokenFilter(): OncePerRequestFilter {
return object: OncePerRequestFilter() {
override fun doFilterInternal(
request: HttpServletRequest,
response: HttpServletResponse,
filterChain: FilterChain
) {
filterChain.doFilter(request, response)
}
}
}
} }

View File

@ -0,0 +1,5 @@
package dev.surl.surl.common.enums
enum class RedisStorage {
TOKEN
}

View File

@ -0,0 +1,3 @@
package dev.surl.surl.common.exception
class UnauthorizedExcecption(message: String? = null, cause: Throwable? = null) : Exception(message, cause)

View File

@ -5,27 +5,13 @@ import dev.surl.surl.common.Msg
import dev.surl.surl.dto.SurlDto import dev.surl.surl.dto.SurlDto
import dev.surl.surl.service.SurlService import dev.surl.surl.service.SurlService
import jakarta.validation.Valid 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.PostMapping
import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RestController import org.springframework.web.bind.annotation.RestController
@RestController @RestController
@EnableWebSecurity class SurlAddController(private val service: SurlService, private val cfg: BaseConfig) {
class SurlAddController { @PostMapping("/api/surl/add")
@PostMapping("/surl/add") fun addSurl(@Valid @RequestBody body: SurlDto) =
fun addSurl( Msg(code = 0, value = "${cfg.site}/${service.addSurl(body.url ?: "")}")
@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"
}
} }

View 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)
}
}

View File

@ -4,7 +4,7 @@ import org.jetbrains.exposed.dao.id.IdTable
object Surls: IdTable<Long>("surl") { object Surls: IdTable<Long>("surl") {
override val id = long("id").entityId() override val id = long("id").entityId()
val url = varchar("url", 2048).uniqueIndex() val url = varchar("url", 2048)
val user = reference("user", Users).index().nullable() val user = reference("user", Users).index().nullable()
override val primaryKey = PrimaryKey(id, name = "PK_surl_id") override val primaryKey = PrimaryKey(id, name = "PK_surl_id")
} }

View File

@ -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)
// }
//}

View File

@ -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)
}
}

View File

@ -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()
}
}
}

View File

@ -2,6 +2,7 @@ package dev.surl.surl.handler
import dev.surl.surl.common.Msg import dev.surl.surl.common.Msg
import dev.surl.surl.common.exception.UserRegistException import dev.surl.surl.common.exception.UserRegistException
import jakarta.validation.ConstraintViolationException
import org.springframework.http.HttpHeaders import org.springframework.http.HttpHeaders
import org.springframework.http.HttpStatus import org.springframework.http.HttpStatus
import org.springframework.http.HttpStatusCode import org.springframework.http.HttpStatusCode
@ -65,4 +66,9 @@ class DefaultExceptionHandler : ResponseEntityExceptionHandler() {
): ResponseEntity<Msg<String>>{ ): ResponseEntity<Msg<String>>{
return ResponseEntity(Msg(code = -1, msg = ex.message ?: "unknown regist error"), HttpStatus.BAD_REQUEST) 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)
}
} }

View File

@ -10,6 +10,7 @@ import org.springframework.stereotype.Service
@Service @Service
class SurlService { class SurlService {
private val userService: UserService by autowired()
fun addSurl(baseurl: String): String = runBlocking { fun addSurl(baseurl: String): String = runBlocking {
val id = genSnowflakeUID() val id = genSnowflakeUID()
transaction { transaction {
@ -34,4 +35,14 @@ class SurlService {
}.firstOrNull()?.get(Surls.url) ?: "" }.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
}
}
}
} }

View File

@ -6,9 +6,9 @@ import dev.surl.surl.common.exception.UserRegistException
import dev.surl.surl.dao.User import dev.surl.surl.dao.User
import dev.surl.surl.dao.UserAccess import dev.surl.surl.dao.UserAccess
import dev.surl.surl.dsl.Users import dev.surl.surl.dsl.Users
import dev.surl.surl.util.autowired import dev.surl.surl.dto.UserDto
import dev.surl.surl.util.genSnowflakeUID import dev.surl.surl.util.*
import dev.surl.surl.util.numberToKey import jakarta.validation.Validator
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.jetbrains.exposed.sql.transactions.transaction import org.jetbrains.exposed.sql.transactions.transaction
import org.springframework.security.core.authority.SimpleGrantedAuthority import org.springframework.security.core.authority.SimpleGrantedAuthority
@ -23,7 +23,8 @@ typealias AUser = org.springframework.security.core.userdetails.User
@Service @Service
class UserService: UserDetailsService { class UserService: UserDetailsService {
private val passwordEncoder: BCryptPasswordEncoder by autowired() 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 { val (id, accessId) = runBlocking {
Pair(genSnowflakeUID(), genSnowflakeUID()) Pair(genSnowflakeUID(), genSnowflakeUID())
@ -45,7 +46,7 @@ class UserService: UserDetailsService {
)) ))
} }
private fun getUserByUsername(username: String): User? { fun getUserByUsername(username: String): User? {
return transaction { return transaction {
User.find { User.find {
Users.username eq username 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 { override fun loadUserByUsername(username: String): UserDetails {
val user = getUserByUsername(username) ?: throw UsernameNotFoundException("user '$username' not found") val user = getUserByUsername(username) ?: throw UsernameNotFoundException("user '$username' not found")
return AUser.builder().apply { return AUser.builder().apply {

View File

@ -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 SurlApplication.context.getBean(name, type.java) as T
} }
} }
operator fun getValue(thisRef: Any?, property: KProperty<*>): T = value operator fun getValue(thisRef: Any?, property: KProperty<*>): T = value
} }
inline fun <reified T : Any> autowired(name: String? = null) = Autowired(T::class, name) inline fun <reified T : Any> autowired(name: String? = null) = Autowired(T::class, name)

View File

@ -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)
}

View File

@ -1,75 +1,48 @@
package dev.surl.surl.util package dev.surl.surl.util
import dev.surl.surl.cfg.BaseConfig import dev.surl.surl.cfg.BaseConfig
import dev.surl.surl.cfg.Logging
import io.jsonwebtoken.Claims import io.jsonwebtoken.Claims
import io.jsonwebtoken.Jwts import io.jsonwebtoken.Jwts
import org.hibernate.validator.internal.engine.DefaultClockProvider import org.springframework.oxm.ValidationFailureException
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.stereotype.Component import org.springframework.stereotype.Component
import java.time.Clock import java.time.LocalDateTime
import java.time.ZoneId
import java.util.Date import java.util.Date
@Component @Component
@ConfigurationProperties(prefix = "jwt") class JwtTokenUtil(private val cfg: BaseConfig) {
class JwtTokenUtil { fun getToken(identityId: String, authorizes: List<String>): Pair<Date, String> {
@Autowired val now = LocalDateTime.now()
private lateinit var cfg: BaseConfig val expireAt = Date.from(now.plus(cfg.expire, cfg.unit).atZone(ZoneId.systemDefault()).toInstant())
@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)
val token = Jwts.builder().run { val token = Jwts.builder().run {
subject(identityId) subject(identityId)
issuedAt(now) issuedAt(Date())
expiration(expireDate) expiration(expireAt)
signWith(cfg.secretKey) signWith(cfg.secretKey)
claim("roleAuthorizes", authorizes) claim("roleAuthorizes", authorizes)
compact() compact()
} }
return mapOf( return Pair(expireAt, token)
"expireAt" to expireAt,
"token" to token
)
} }
fun getTokenClaim(token: String?): Claims? { private fun getTokenClaim(token: String): Claims? {
if(token == null) return null
try {
return Jwts.parser().verifyWith(cfg.secretKey).build().parseSignedClaims(token).payload 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 getExpireDateFromToken(token: String): Date? { fun getUsernameFromToken(token: String): String {
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? {
return getClaimFromToken(token) { return getClaimFromToken(token) {
it?.subject it?.subject
} } ?: throw ValidationFailureException("invalid token, userinfo not found")
} }
private fun <T> getClaimFromToken(token: String, resolver: (Claims?) -> T): T { private fun <T> getClaimFromToken(token: String, resolver: (Claims?) -> T): T {
val claims = getTokenClaim(token) val claims = getTokenClaim(token)
return resolver(claims) return resolver(claims)
} }
fun getTokenFromHeader(header: String): String {
return if (header.startsWith(cfg.tokenHead)) {
header.substring(cfg.tokenHead.length)
} else throw ValidationFailureException("invalid token")
}
} }

View 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)
}
}

View File

@ -1,23 +1,37 @@
package dev.surl.surl.util.redis package dev.surl.surl.util.redis
import dev.surl.surl.cfg.BaseConfig import dev.surl.surl.cfg.BaseConfig
import dev.surl.surl.common.enums.RedisStorage
import org.springframework.data.redis.core.StringRedisTemplate import org.springframework.data.redis.core.StringRedisTemplate
import org.springframework.stereotype.Component import org.springframework.stereotype.Component
import java.time.temporal.ChronoUnit
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@Suppress("UNUSED") @Suppress("UNUSED")
@Component @Component
class RedisUtil(private val template: StringRedisTemplate, private val cfg: BaseConfig) { class RedisUtil(private val template: StringRedisTemplate, private val cfg: BaseConfig) {
private val ops = template.opsForValue() private val ops = template.opsForValue()
fun getString(key: String): String? { fun getString(key: String, type: RedisStorage? = null): String? {
if (type == null) {
return ops.get(key) return ops.get(key)
} }
fun setString(key: String, value: String, expire: Long = cfg.expire, unit: TimeUnit = TimeUnit.MILLISECONDS) { return ops.get("${type.name}_$key")
ops.set(key, value, expire, unit)
} }
fun delKey(key: String) { 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) ops.operations.delete(key)
return
}
ops.operations.delete("${type.name}_$key")
} }
fun flushdb() { fun flushdb() {
@ -25,4 +39,15 @@ class RedisUtil(private val template: StringRedisTemplate, private val cfg: Base
it.serverCommands().flushDb() 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")
}
}
} }

View File

@ -36,7 +36,7 @@ spring:
min-idle: 1 min-idle: 1
max-wait: 100ms max-wait: 100ms
exposed: exposed:
show-sql: false show-sql: true
generate-ddl: true generate-ddl: true
logging: logging:
level: level:
@ -46,3 +46,8 @@ base:
configs: configs:
site: http://127.0.0.1:18888 site: http://127.0.0.1:18888
expire: 3600000 expire: 3600000
secret: Is#45Ddw29apkbHawwaHb4d^&w29apkbHawwaHb4d^&
white-list:
- ^/login$
- ^/reg$
- ^(?!\/api).*$

View File

@ -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())
}
}

View File

@ -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() {
}
}

View File

@ -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"))
}
}