Optimize register api. Add verify Turnstile captcha to login api.
This commit is contained in:
5
pom.xml
5
pom.xml
@@ -121,6 +121,11 @@
|
||||
<artifactId>spring-security-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.lianjiatech</groupId>
|
||||
<artifactId>retrofit-spring-boot-starter</artifactId>
|
||||
<version>3.0.3</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package top.fatweb.oxygen.api.config
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature
|
||||
import com.fasterxml.jackson.databind.json.JsonMapper
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import retrofit2.converter.jackson.JacksonConverterFactory
|
||||
|
||||
/**
|
||||
* Jackson configuration
|
||||
*
|
||||
* @author FatttSnake, fatttsnake@gmail.com
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Configuration
|
||||
class JacksonConfig {
|
||||
@Bean
|
||||
fun jacksonConverterFactory(): JacksonConverterFactory {
|
||||
val mapper = JsonMapper.builder()
|
||||
.findAndAddModules()
|
||||
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
|
||||
.serializationInclusion(JsonInclude.Include.NON_NULL)
|
||||
.build()
|
||||
|
||||
return JacksonConverterFactory.create(mapper)
|
||||
}
|
||||
}
|
||||
@@ -40,9 +40,12 @@ class AuthenticationController(
|
||||
*/
|
||||
@Operation(summary = "注册")
|
||||
@PostMapping("/register")
|
||||
fun register(@Valid @RequestBody registerParam: RegisterParam): ResponseResult<RegisterVo> = ResponseResult.success(
|
||||
fun register(
|
||||
request: HttpServletRequest,
|
||||
@Valid @RequestBody registerParam: RegisterParam
|
||||
): ResponseResult<RegisterVo> = ResponseResult.success(
|
||||
ResponseCode.PERMISSION_REGISTER_SUCCESS,
|
||||
data = authenticationService.register(registerParam)
|
||||
data = authenticationService.register(request, registerParam)
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ enum class ResponseCode(val code: Int) {
|
||||
SYSTEM_TIMEOUT(BusinessCode.SYSTEM, 51),
|
||||
SYSTEM_REQUEST_ILLEGAL(BusinessCode.SYSTEM, 52),
|
||||
SYSTEM_ARGUMENT_NOT_VALID(BusinessCode.SYSTEM, 53),
|
||||
SYSTEM_INVALID_CAPTCHA_CODE(BusinessCode.SYSTEM, 54),
|
||||
|
||||
PERMISSION_LOGIN_SUCCESS(BusinessCode.PERMISSION, 0),
|
||||
PERMISSION_PASSWORD_CHANGE_SUCCESS(BusinessCode.PERMISSION, 1),
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package top.fatweb.oxygen.api.exception
|
||||
|
||||
/**
|
||||
* Invalid captcha code exception
|
||||
*
|
||||
* @author FatttSnake, fatttsnake@gmail.com
|
||||
* @since 1.0.0
|
||||
* @see RuntimeException
|
||||
*/
|
||||
class InvalidCaptchaCodeException : RuntimeException("Invalid captcha code")
|
||||
@@ -151,6 +151,11 @@ class ExceptionHandler {
|
||||
ResponseResult.fail(ResponseCode.PERMISSION_ACCOUNT_NEED_RESET_PASSWORD, e.localizedMessage, null)
|
||||
}
|
||||
|
||||
is InvalidCaptchaCodeException -> {
|
||||
logger.debug(e.localizedMessage, e)
|
||||
ResponseResult.fail(ResponseCode.SYSTEM_INVALID_CAPTCHA_CODE, e.localizedMessage, null)
|
||||
}
|
||||
|
||||
|
||||
is BadSqlGrammarException -> {
|
||||
logger.debug(e.localizedMessage, e)
|
||||
|
||||
33
src/main/kotlin/top/fatweb/oxygen/api/http/TurnstileApi.kt
Normal file
33
src/main/kotlin/top/fatweb/oxygen/api/http/TurnstileApi.kt
Normal file
@@ -0,0 +1,33 @@
|
||||
package top.fatweb.oxygen.api.http
|
||||
|
||||
import com.github.lianjiatech.retrofit.spring.boot.core.RetrofitClient
|
||||
import org.springframework.stereotype.Service
|
||||
import retrofit2.http.Field
|
||||
import retrofit2.http.FormUrlEncoded
|
||||
import retrofit2.http.POST
|
||||
import top.fatweb.oxygen.api.http.entity.turnstile.SiteverifyResponse
|
||||
import top.fatweb.oxygen.api.properties.ServerProperties
|
||||
|
||||
/**
|
||||
* Turnstile http request api
|
||||
*
|
||||
* @author FatttSnake, fatttsnake@gmail.com
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Service
|
||||
@RetrofitClient(baseUrl = "https://challenges.cloudflare.com/turnstile/v0/")
|
||||
interface TurnstileApi {
|
||||
/**
|
||||
* Turnstile post verify captcha code
|
||||
*
|
||||
* @author FatttSnake, fatttsnake@gmail.com
|
||||
* @since 1.0.0
|
||||
* @see SiteverifyResponse
|
||||
*/
|
||||
@FormUrlEncoded
|
||||
@POST("siteverify")
|
||||
fun siteverify(
|
||||
@Field("response") captchaCode: String,
|
||||
@Field("secret") secret: String = ServerProperties.turnstileSecretKey
|
||||
): SiteverifyResponse
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package top.fatweb.oxygen.api.http.entity.turnstile
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import java.time.LocalDateTime
|
||||
|
||||
/**
|
||||
* Turnstile verify captcha code response
|
||||
*
|
||||
* @author FatttSnake, fatttsnake@gmail.com
|
||||
* @since 1.0.0
|
||||
*/
|
||||
data class SiteverifyResponse(
|
||||
/**
|
||||
* Is success
|
||||
*
|
||||
* @author FatttSnake, fatttsnake@gmail.com
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@JsonProperty("success")
|
||||
val success: Boolean,
|
||||
|
||||
/**
|
||||
* Challenge time
|
||||
*
|
||||
* @author FatttSnake, fatttsnake@gmail.com
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@JsonProperty("challenge_ts")
|
||||
val challengeTs: LocalDateTime?,
|
||||
|
||||
/**
|
||||
* Hostname
|
||||
*
|
||||
* @author FatttSnake, fatttsnake@gmail.com
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@JsonProperty("hostname")
|
||||
val hostname: String?,
|
||||
|
||||
/**
|
||||
* Error codes list
|
||||
*
|
||||
* @author FatttSnake, fatttsnake@gmail.com
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@JsonProperty("error-codes")
|
||||
val errorCodes: List<String>?
|
||||
)
|
||||
@@ -29,5 +29,15 @@ data class LoginParam(
|
||||
*/
|
||||
@Schema(description = "密码", required = true)
|
||||
@field:NotBlank(message = "Password can not be blank")
|
||||
val password: String?
|
||||
val password: String?,
|
||||
|
||||
/**
|
||||
* Captcha code
|
||||
*
|
||||
* @author FatttSnake, fatttsnake@gmail.com
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Schema(description = "验证码", required = true)
|
||||
@field:NotBlank(message = "Captcha code can not be blank")
|
||||
val captchaCode: String?
|
||||
)
|
||||
@@ -48,6 +48,14 @@ object ServerProperties {
|
||||
*/
|
||||
val startupTime: LocalDateTime = LocalDateTime.now(ZoneOffset.UTC)
|
||||
|
||||
/**
|
||||
* Turnstile secret key
|
||||
*
|
||||
* @author FatttSnake, fatttsnake@gmail.com
|
||||
* @since 1.0.0
|
||||
*/
|
||||
lateinit var turnstileSecretKey: String
|
||||
|
||||
fun buildZoneDateTime(zoneId: ZoneId = ZoneId.systemDefault()): ZonedDateTime =
|
||||
LocalDateTime.parse(buildTime).atZone(ZoneId.of("UTC")).withZoneSameInstant(zoneId)
|
||||
}
|
||||
@@ -24,7 +24,7 @@ interface IAuthenticationService {
|
||||
* @see RegisterParam
|
||||
* @see RegisterVo
|
||||
*/
|
||||
fun register(registerParam: RegisterParam): RegisterVo
|
||||
fun register(request: HttpServletRequest, registerParam: RegisterParam): RegisterVo
|
||||
|
||||
/**
|
||||
* Send verify email
|
||||
|
||||
@@ -19,6 +19,7 @@ import top.fatweb.oxygen.api.entity.permission.User
|
||||
import top.fatweb.oxygen.api.entity.permission.UserInfo
|
||||
import top.fatweb.oxygen.api.entity.system.EventLog
|
||||
import top.fatweb.oxygen.api.exception.*
|
||||
import top.fatweb.oxygen.api.http.TurnstileApi
|
||||
import top.fatweb.oxygen.api.param.permission.*
|
||||
import top.fatweb.oxygen.api.properties.SecurityProperties
|
||||
import top.fatweb.oxygen.api.service.api.v1.IAvatarService
|
||||
@@ -56,6 +57,7 @@ class AuthenticationServiceImpl(
|
||||
private val authenticationManager: AuthenticationManager,
|
||||
private val passwordEncoder: PasswordEncoder,
|
||||
private val redisUtil: RedisUtil,
|
||||
private val turnstileApi: TurnstileApi,
|
||||
private val userService: IUserService,
|
||||
private val userInfoService: IUserInfoService,
|
||||
private val avatarService: IAvatarService
|
||||
@@ -64,7 +66,7 @@ class AuthenticationServiceImpl(
|
||||
|
||||
@EventLogRecord(EventLog.Event.REGISTER)
|
||||
@Transactional
|
||||
override fun register(registerParam: RegisterParam): RegisterVo {
|
||||
override fun register(request: HttpServletRequest, registerParam: RegisterParam): RegisterVo {
|
||||
val user = User().apply {
|
||||
username = registerParam.username
|
||||
password = passwordEncoder.encode(registerParam.password)
|
||||
@@ -85,7 +87,9 @@ class AuthenticationServiceImpl(
|
||||
|
||||
sendVerifyMail(user.username!!, user.verify!!, registerParam.email!!)
|
||||
|
||||
return RegisterVo(userId = user.id)
|
||||
val loginVo = this.login(request, registerParam.username!!, registerParam.password!!)
|
||||
|
||||
return RegisterVo(token = loginVo.token, userId = loginVo.userId)
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@@ -244,8 +248,21 @@ class AuthenticationServiceImpl(
|
||||
|
||||
@EventLogRecord(EventLog.Event.LOGIN)
|
||||
override fun login(request: HttpServletRequest, loginParam: LoginParam): LoginVo {
|
||||
try {
|
||||
val siteverifyResponse = turnstileApi.siteverify(loginParam.captchaCode!!)
|
||||
if (!siteverifyResponse.success) {
|
||||
throw InvalidCaptchaCodeException()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
throw InvalidCaptchaCodeException()
|
||||
}
|
||||
|
||||
return this.login(request, loginParam.account!!, loginParam.password!!)
|
||||
}
|
||||
|
||||
private fun login(request: HttpServletRequest, account: String, password: String): LoginVo {
|
||||
val usernamePasswordAuthenticationToken =
|
||||
UsernamePasswordAuthenticationToken(loginParam.account, loginParam.password)
|
||||
UsernamePasswordAuthenticationToken(account, password)
|
||||
val authentication = authenticationManager.authenticate(usernamePasswordAuthenticationToken)
|
||||
authentication ?: let {
|
||||
throw RuntimeException("Login failed")
|
||||
|
||||
@@ -12,6 +12,17 @@ import io.swagger.v3.oas.annotations.media.Schema
|
||||
*/
|
||||
@Schema(description = "注册返回参数")
|
||||
data class RegisterVo(
|
||||
/**
|
||||
* Token
|
||||
*
|
||||
* @author FatttSnake, fatttsnake@gmail.com
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Schema(
|
||||
description = "Token",
|
||||
example = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJkYTllYjFkYmVmZDQ0OWRkOThlOGNjNzZlNzZkMDgyNSIsInN1YiI6IjE3MDk5ODYwNTg2Nzk5NzU5MzgiLCJpc3MiOiJGYXRXZWIiLCJpYXQiOjE2OTY1MjgxMTcsImV4cCI6MTY5NjUzNTMxN30.U2ZsyrGk7NbsP-DJfdz9xgWSfect5r2iKQnlEsscAA8"
|
||||
) val token: String,
|
||||
|
||||
/**
|
||||
* User ID
|
||||
*
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
app:
|
||||
app-name: Oxygen Toolbox # Application name
|
||||
turnstile-secret-key: 1x0000000000000000000000000000000AA # Turnstile secret key
|
||||
admin:
|
||||
# username: admin # Username of administrator
|
||||
# password: admin # Default password of administrator
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
app:
|
||||
app-name: FatWeb
|
||||
app-name: Oxygen Toolbox
|
||||
version: @project.version@
|
||||
build-time: @build.timestamp@
|
||||
|
||||
|
||||
Reference in New Issue
Block a user