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>
|
<artifactId>spring-security-test</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.lianjiatech</groupId>
|
||||||
|
<artifactId>retrofit-spring-boot-starter</artifactId>
|
||||||
|
<version>3.0.3</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jetbrains.kotlin</groupId>
|
<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 = "注册")
|
@Operation(summary = "注册")
|
||||||
@PostMapping("/register")
|
@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,
|
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_TIMEOUT(BusinessCode.SYSTEM, 51),
|
||||||
SYSTEM_REQUEST_ILLEGAL(BusinessCode.SYSTEM, 52),
|
SYSTEM_REQUEST_ILLEGAL(BusinessCode.SYSTEM, 52),
|
||||||
SYSTEM_ARGUMENT_NOT_VALID(BusinessCode.SYSTEM, 53),
|
SYSTEM_ARGUMENT_NOT_VALID(BusinessCode.SYSTEM, 53),
|
||||||
|
SYSTEM_INVALID_CAPTCHA_CODE(BusinessCode.SYSTEM, 54),
|
||||||
|
|
||||||
PERMISSION_LOGIN_SUCCESS(BusinessCode.PERMISSION, 0),
|
PERMISSION_LOGIN_SUCCESS(BusinessCode.PERMISSION, 0),
|
||||||
PERMISSION_PASSWORD_CHANGE_SUCCESS(BusinessCode.PERMISSION, 1),
|
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)
|
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 -> {
|
is BadSqlGrammarException -> {
|
||||||
logger.debug(e.localizedMessage, e)
|
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)
|
@Schema(description = "密码", required = true)
|
||||||
@field:NotBlank(message = "Password can not be blank")
|
@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)
|
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 =
|
fun buildZoneDateTime(zoneId: ZoneId = ZoneId.systemDefault()): ZonedDateTime =
|
||||||
LocalDateTime.parse(buildTime).atZone(ZoneId.of("UTC")).withZoneSameInstant(zoneId)
|
LocalDateTime.parse(buildTime).atZone(ZoneId.of("UTC")).withZoneSameInstant(zoneId)
|
||||||
}
|
}
|
||||||
@@ -24,7 +24,7 @@ interface IAuthenticationService {
|
|||||||
* @see RegisterParam
|
* @see RegisterParam
|
||||||
* @see RegisterVo
|
* @see RegisterVo
|
||||||
*/
|
*/
|
||||||
fun register(registerParam: RegisterParam): RegisterVo
|
fun register(request: HttpServletRequest, registerParam: RegisterParam): RegisterVo
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send verify email
|
* 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.permission.UserInfo
|
||||||
import top.fatweb.oxygen.api.entity.system.EventLog
|
import top.fatweb.oxygen.api.entity.system.EventLog
|
||||||
import top.fatweb.oxygen.api.exception.*
|
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.param.permission.*
|
||||||
import top.fatweb.oxygen.api.properties.SecurityProperties
|
import top.fatweb.oxygen.api.properties.SecurityProperties
|
||||||
import top.fatweb.oxygen.api.service.api.v1.IAvatarService
|
import top.fatweb.oxygen.api.service.api.v1.IAvatarService
|
||||||
@@ -56,6 +57,7 @@ class AuthenticationServiceImpl(
|
|||||||
private val authenticationManager: AuthenticationManager,
|
private val authenticationManager: AuthenticationManager,
|
||||||
private val passwordEncoder: PasswordEncoder,
|
private val passwordEncoder: PasswordEncoder,
|
||||||
private val redisUtil: RedisUtil,
|
private val redisUtil: RedisUtil,
|
||||||
|
private val turnstileApi: TurnstileApi,
|
||||||
private val userService: IUserService,
|
private val userService: IUserService,
|
||||||
private val userInfoService: IUserInfoService,
|
private val userInfoService: IUserInfoService,
|
||||||
private val avatarService: IAvatarService
|
private val avatarService: IAvatarService
|
||||||
@@ -64,7 +66,7 @@ class AuthenticationServiceImpl(
|
|||||||
|
|
||||||
@EventLogRecord(EventLog.Event.REGISTER)
|
@EventLogRecord(EventLog.Event.REGISTER)
|
||||||
@Transactional
|
@Transactional
|
||||||
override fun register(registerParam: RegisterParam): RegisterVo {
|
override fun register(request: HttpServletRequest, registerParam: RegisterParam): RegisterVo {
|
||||||
val user = User().apply {
|
val user = User().apply {
|
||||||
username = registerParam.username
|
username = registerParam.username
|
||||||
password = passwordEncoder.encode(registerParam.password)
|
password = passwordEncoder.encode(registerParam.password)
|
||||||
@@ -85,7 +87,9 @@ class AuthenticationServiceImpl(
|
|||||||
|
|
||||||
sendVerifyMail(user.username!!, user.verify!!, registerParam.email!!)
|
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
|
@Transactional
|
||||||
@@ -244,8 +248,21 @@ class AuthenticationServiceImpl(
|
|||||||
|
|
||||||
@EventLogRecord(EventLog.Event.LOGIN)
|
@EventLogRecord(EventLog.Event.LOGIN)
|
||||||
override fun login(request: HttpServletRequest, loginParam: LoginParam): LoginVo {
|
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 =
|
val usernamePasswordAuthenticationToken =
|
||||||
UsernamePasswordAuthenticationToken(loginParam.account, loginParam.password)
|
UsernamePasswordAuthenticationToken(account, password)
|
||||||
val authentication = authenticationManager.authenticate(usernamePasswordAuthenticationToken)
|
val authentication = authenticationManager.authenticate(usernamePasswordAuthenticationToken)
|
||||||
authentication ?: let {
|
authentication ?: let {
|
||||||
throw RuntimeException("Login failed")
|
throw RuntimeException("Login failed")
|
||||||
|
|||||||
@@ -12,6 +12,17 @@ import io.swagger.v3.oas.annotations.media.Schema
|
|||||||
*/
|
*/
|
||||||
@Schema(description = "注册返回参数")
|
@Schema(description = "注册返回参数")
|
||||||
data class RegisterVo(
|
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
|
* User ID
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
app:
|
app:
|
||||||
|
app-name: Oxygen Toolbox # Application name
|
||||||
|
turnstile-secret-key: 1x0000000000000000000000000000000AA # Turnstile secret key
|
||||||
admin:
|
admin:
|
||||||
# username: admin # Username of administrator
|
# username: admin # Username of administrator
|
||||||
# password: admin # Default password of administrator
|
# password: admin # Default password of administrator
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
app:
|
app:
|
||||||
app-name: FatWeb
|
app-name: Oxygen Toolbox
|
||||||
version: @project.version@
|
version: @project.version@
|
||||||
build-time: @build.timestamp@
|
build-time: @build.timestamp@
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user