Optimize two-factor api. Add remove two-factor api.
This commit is contained in:
@@ -3,6 +3,7 @@ package top.fatweb.oxygen.api.controller.permission
|
|||||||
import io.swagger.v3.oas.annotations.Operation
|
import io.swagger.v3.oas.annotations.Operation
|
||||||
import jakarta.servlet.http.HttpServletRequest
|
import jakarta.servlet.http.HttpServletRequest
|
||||||
import jakarta.validation.Valid
|
import jakarta.validation.Valid
|
||||||
|
import org.springframework.web.bind.annotation.DeleteMapping
|
||||||
import org.springframework.web.bind.annotation.GetMapping
|
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
|
||||||
@@ -163,7 +164,7 @@ class AuthenticationController(
|
|||||||
* @see ResponseResult
|
* @see ResponseResult
|
||||||
* @see TwoFactorVo
|
* @see TwoFactorVo
|
||||||
*/
|
*/
|
||||||
@Operation(summary = "创建二步验证")
|
@Operation(summary = "创建双因素验证码")
|
||||||
@GetMapping("/two-factor")
|
@GetMapping("/two-factor")
|
||||||
fun createTwoFactor(): ResponseResult<TwoFactorVo> =
|
fun createTwoFactor(): ResponseResult<TwoFactorVo> =
|
||||||
ResponseResult.success(data = authenticationService.createTwoFactor())
|
ResponseResult.success(data = authenticationService.createTwoFactor())
|
||||||
@@ -174,12 +175,24 @@ class AuthenticationController(
|
|||||||
* @author FatttSnake, fatttsnake@gmail.com
|
* @author FatttSnake, fatttsnake@gmail.com
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
@Operation(summary = "验证二步验证")
|
@Operation(summary = "验证双因素")
|
||||||
@PostMapping("/two-factor")
|
@PostMapping("/two-factor")
|
||||||
fun validateTwoFactor(@RequestBody @Valid twoFactorValidateParam: TwoFactorValidateParam): ResponseResult<Nothing> =
|
fun validateTwoFactor(@RequestBody @Valid twoFactorValidateParam: TwoFactorValidateParam): ResponseResult<Nothing> =
|
||||||
if (authenticationService.validateTwoFactor(twoFactorValidateParam)) ResponseResult.success()
|
if (authenticationService.validateTwoFactor(twoFactorValidateParam)) ResponseResult.success()
|
||||||
else ResponseResult.fail()
|
else ResponseResult.fail()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove two-factor
|
||||||
|
*
|
||||||
|
* @author FatttSnake, fatttsnake@gmail.com
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
@Operation(summary = "移除双因素")
|
||||||
|
@DeleteMapping("/two-factor")
|
||||||
|
fun removeTwoFactor(@RequestBody @Valid twoFactorRemoveParam: TwoFactorRemoveParam): ResponseResult<Nothing> =
|
||||||
|
if (authenticationService.removeTwoFactor(twoFactorRemoveParam)) ResponseResult.success()
|
||||||
|
else ResponseResult.fail()
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logout
|
* Logout
|
||||||
|
|||||||
@@ -194,7 +194,7 @@ class SettingsController(
|
|||||||
* @see ResponseResult
|
* @see ResponseResult
|
||||||
* @see TwoFactorSettingsVo
|
* @see TwoFactorSettingsVo
|
||||||
*/
|
*/
|
||||||
@Operation(summary = "获取二步验证设置")
|
@Operation(summary = "获取双因素设置")
|
||||||
@GetMapping("/two-factor")
|
@GetMapping("/two-factor")
|
||||||
@PreAuthorize("hasAnyAuthority('system:settings:query:two-factor')")
|
@PreAuthorize("hasAnyAuthority('system:settings:query:two-factor')")
|
||||||
fun getTwoFactor(): ResponseResult<TwoFactorSettingsVo> =
|
fun getTwoFactor(): ResponseResult<TwoFactorSettingsVo> =
|
||||||
@@ -211,7 +211,7 @@ class SettingsController(
|
|||||||
* @see ResponseResult
|
* @see ResponseResult
|
||||||
*/
|
*/
|
||||||
@Trim
|
@Trim
|
||||||
@Operation(summary = "更新二步验证设置")
|
@Operation(summary = "更新双因素设置")
|
||||||
@PutMapping("/two-factor")
|
@PutMapping("/two-factor")
|
||||||
@PreAuthorize("hasAnyAuthority('system:settings:modify:two-factor')")
|
@PreAuthorize("hasAnyAuthority('system:settings:modify:two-factor')")
|
||||||
fun updateTwoFactor(@RequestBody twoFactorSettingsParam: TwoFactorSettingsParam): ResponseResult<Nothing> {
|
fun updateTwoFactor(@RequestBody twoFactorSettingsParam: TwoFactorSettingsParam): ResponseResult<Nothing> {
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ enum class ResponseCode(val code: Int) {
|
|||||||
PERMISSION_NEED_TWO_FACTOR(BusinessCode.PERMISSION, 68),
|
PERMISSION_NEED_TWO_FACTOR(BusinessCode.PERMISSION, 68),
|
||||||
PERMISSION_ALREADY_HAS_TWO_FACTOR(BusinessCode.PERMISSION, 69),
|
PERMISSION_ALREADY_HAS_TWO_FACTOR(BusinessCode.PERMISSION, 69),
|
||||||
PERMISSION_NO_TWO_FACTOR_FOUND(BusinessCode.PERMISSION, 70),
|
PERMISSION_NO_TWO_FACTOR_FOUND(BusinessCode.PERMISSION, 70),
|
||||||
|
PERMISSION_TWO_FACTOR_VERIFICATION_CODE_ERROR(BusinessCode.PERMISSION, 71),
|
||||||
|
|
||||||
DATABASE_SELECT_SUCCESS(BusinessCode.DATABASE, 0),
|
DATABASE_SELECT_SUCCESS(BusinessCode.DATABASE, 0),
|
||||||
DATABASE_SELECT_FAILED(BusinessCode.DATABASE, 5),
|
DATABASE_SELECT_FAILED(BusinessCode.DATABASE, 5),
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package top.fatweb.oxygen.api.exception
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Two-factor verification code error exception
|
||||||
|
*
|
||||||
|
* @author FatttSnake, fatttsnake@gmail.com
|
||||||
|
* @since 1.0.0
|
||||||
|
* @see RuntimeException
|
||||||
|
*/
|
||||||
|
class TwoFactorVerificationCodeErrorException : RuntimeException("Two-factor verification code error")
|
||||||
@@ -180,6 +180,11 @@ class ExceptionHandler {
|
|||||||
ResponseResult.fail(ResponseCode.PERMISSION_NO_TWO_FACTOR_FOUND, e.localizedMessage, null)
|
ResponseResult.fail(ResponseCode.PERMISSION_NO_TWO_FACTOR_FOUND, e.localizedMessage, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is TwoFactorVerificationCodeErrorException -> {
|
||||||
|
logger.debug(e.localizedMessage, e)
|
||||||
|
ResponseResult.fail(ResponseCode.PERMISSION_TWO_FACTOR_VERIFICATION_CODE_ERROR, e.localizedMessage, null)
|
||||||
|
}
|
||||||
|
|
||||||
/* SQL */
|
/* SQL */
|
||||||
is DatabaseSelectException -> {
|
is DatabaseSelectException -> {
|
||||||
logger.debug(e.localizedMessage, e)
|
logger.debug(e.localizedMessage, e)
|
||||||
|
|||||||
@@ -42,6 +42,6 @@ data class LoginParam(
|
|||||||
* @author FatttSnake, fatttsnake@gmail.com
|
* @author FatttSnake, fatttsnake@gmail.com
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
@Schema(description = "二步验证码")
|
@Schema(description = "双因素验证码")
|
||||||
val twoFactorCode: String?
|
val twoFactorCode: String?
|
||||||
) : CaptchaCodeParam()
|
) : CaptchaCodeParam()
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package top.fatweb.oxygen.api.param.permission
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema
|
||||||
|
import jakarta.validation.constraints.NotBlank
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove two-factor parameters
|
||||||
|
*
|
||||||
|
* @author FatttSnake, fatttsnake@gmail.com
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
@Schema(description = "移除双因素请求参数")
|
||||||
|
data class TwoFactorRemoveParam(
|
||||||
|
/**
|
||||||
|
* Code
|
||||||
|
*
|
||||||
|
* @author FatttSnake, fatttsnake@gmail.com
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
@Schema(description = "验证码")
|
||||||
|
@field:NotBlank(message = "Code can not be blank")
|
||||||
|
val code: String?
|
||||||
|
)
|
||||||
@@ -9,7 +9,7 @@ import jakarta.validation.constraints.NotBlank
|
|||||||
* @author FatttSnake, fatttsnake@gmail.com
|
* @author FatttSnake, fatttsnake@gmail.com
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
@Schema(description = "验证二步验证请求参数")
|
@Schema(description = "验证双因素请求参数")
|
||||||
data class TwoFactorValidateParam(
|
data class TwoFactorValidateParam(
|
||||||
/**
|
/**
|
||||||
* Code
|
* Code
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import top.fatweb.oxygen.api.annotation.Trim
|
|||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
@Trim
|
@Trim
|
||||||
@Schema(description = "二步验证设置请求参数")
|
@Schema(description = "双因素设置请求参数")
|
||||||
data class TwoFactorSettingsParam(
|
data class TwoFactorSettingsParam(
|
||||||
/**
|
/**
|
||||||
* Issuer
|
* Issuer
|
||||||
|
|||||||
@@ -104,6 +104,17 @@ interface IAuthenticationService {
|
|||||||
*/
|
*/
|
||||||
fun validateTwoFactor(twoFactorValidateParam: TwoFactorValidateParam): Boolean
|
fun validateTwoFactor(twoFactorValidateParam: TwoFactorValidateParam): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove two-factor
|
||||||
|
*
|
||||||
|
* @param twoFactorRemoveParam Remove two-factor parameters
|
||||||
|
* @return Result
|
||||||
|
* @author FatttSnake, fatttsnake@gmail.com
|
||||||
|
* @since 1.0.0
|
||||||
|
* @see TwoFactorRemoveParam
|
||||||
|
*/
|
||||||
|
fun removeTwoFactor(twoFactorRemoveParam: TwoFactorRemoveParam): Boolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logout
|
* Logout
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -202,7 +202,9 @@ 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 {
|
||||||
verifyCaptcha(loginParam.captchaCode!!)
|
if (loginParam.twoFactorCode.isNullOrBlank()) {
|
||||||
|
verifyCaptcha(loginParam.captchaCode!!)
|
||||||
|
}
|
||||||
|
|
||||||
return this.login(request, loginParam.account!!, loginParam.password!!, loginParam.twoFactorCode)
|
return this.login(request, loginParam.account!!, loginParam.password!!, loginParam.twoFactorCode)
|
||||||
}
|
}
|
||||||
@@ -236,7 +238,26 @@ class AuthenticationServiceImpl(
|
|||||||
}
|
}
|
||||||
val secretKey = user.twoFactor!!.substring(0, user.twoFactor!!.length - 1)
|
val secretKey = user.twoFactor!!.substring(0, user.twoFactor!!.length - 1)
|
||||||
|
|
||||||
return TOTPUtil.validateCode(secretKey, twoFactorValidateParam.code!!)
|
if (TOTPUtil.validateCode(secretKey, twoFactorValidateParam.code!!)) {
|
||||||
|
userService.update(KtUpdateWrapper(User()).eq(User::id, user.id).set(User::twoFactor, secretKey))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun removeTwoFactor(twoFactorRemoveParam: TwoFactorRemoveParam): Boolean {
|
||||||
|
val user = userService.getById(WebUtil.getLoginUserId()) ?: throw UserNotFoundException()
|
||||||
|
if (user.twoFactor.isNullOrBlank() || user.twoFactor!!.endsWith("?")) {
|
||||||
|
throw NoTwoFactorFoundException()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TOTPUtil.validateCode(user.twoFactor!!, twoFactorRemoveParam.code!!)) {
|
||||||
|
userService.update(KtUpdateWrapper(User()).eq(User::id, user.id).set(User::twoFactor, null))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventLogRecord(EventLog.Event.LOGOUT)
|
@EventLogRecord(EventLog.Event.LOGOUT)
|
||||||
@@ -342,9 +363,13 @@ class AuthenticationServiceImpl(
|
|||||||
val userWithPowerByAccount = userService.getUserWithPowerByAccount(account) ?: throw UserNotFoundException()
|
val userWithPowerByAccount = userService.getUserWithPowerByAccount(account) ?: throw UserNotFoundException()
|
||||||
if (!userWithPowerByAccount.twoFactor.isNullOrBlank()
|
if (!userWithPowerByAccount.twoFactor.isNullOrBlank()
|
||||||
&& !userWithPowerByAccount.twoFactor!!.endsWith("?")
|
&& !userWithPowerByAccount.twoFactor!!.endsWith("?")
|
||||||
&& twoFactorCode.isNullOrBlank()
|
|
||||||
) {
|
) {
|
||||||
throw NeedTwoFactorException()
|
if (twoFactorCode.isNullOrBlank()) {
|
||||||
|
throw NeedTwoFactorException()
|
||||||
|
}
|
||||||
|
if (!TOTPUtil.validateCode(userWithPowerByAccount.twoFactor!!, twoFactorCode)) {
|
||||||
|
throw TwoFactorVerificationCodeErrorException()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val usernamePasswordAuthenticationToken =
|
val usernamePasswordAuthenticationToken =
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ object TOTPUtil {
|
|||||||
secretKey.append(allChars[Random.nextInt(allChars.length)])
|
secretKey.append(allChars[Random.nextInt(allChars.length)])
|
||||||
}
|
}
|
||||||
|
|
||||||
return secretKey.toString().toList().shuffled().joinToString()
|
return secretKey.toString().toList().shuffled().joinToString("")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import io.swagger.v3.oas.annotations.media.Schema
|
|||||||
* @author FatttSnake, fatttsnake@gmail.com
|
* @author FatttSnake, fatttsnake@gmail.com
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
@Schema(description = "二步验证返回参数")
|
@Schema(description = "双因素返回参数")
|
||||||
data class TwoFactorVo (
|
data class TwoFactorVo (
|
||||||
/**
|
/**
|
||||||
* QR code SVG as base64
|
* QR code SVG as base64
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ data class UserWithInfoVo(
|
|||||||
* @author FatttSnake, fatttsnake@gmail.com
|
* @author FatttSnake, fatttsnake@gmail.com
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
@Schema(description = "启用二步验证", example = "true")
|
@Schema(description = "启用双因素", example = "true")
|
||||||
val twoFactor: Boolean?,
|
val twoFactor: Boolean?,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ data class UserWithPasswordRoleInfoVo(
|
|||||||
* @author FatttSnake, fatttsnake@gmail.com
|
* @author FatttSnake, fatttsnake@gmail.com
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
@Schema(description = "启用二步验证", example = "true")
|
@Schema(description = "启用双因素", example = "true")
|
||||||
val twoFactor: Boolean?,
|
val twoFactor: Boolean?,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ data class UserWithPowerInfoVo(
|
|||||||
* @author FatttSnake, fatttsnake@gmail.com
|
* @author FatttSnake, fatttsnake@gmail.com
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
@Schema(description = "启用二步验证", example = "true")
|
@Schema(description = "启用双因素验证", example = "true")
|
||||||
val twoFactor: Boolean?,
|
val twoFactor: Boolean?,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ data class UserWithRoleInfoVo(
|
|||||||
* @author FatttSnake, fatttsnake@gmail.com
|
* @author FatttSnake, fatttsnake@gmail.com
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
@Schema(description = "启用二步验证", example = "true")
|
@Schema(description = "启用双因素", example = "true")
|
||||||
val twoFactor: Boolean?,
|
val twoFactor: Boolean?,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import io.swagger.v3.oas.annotations.media.Schema
|
|||||||
* @author FatttSnake, fatttsnake@gmail.com
|
* @author FatttSnake, fatttsnake@gmail.com
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
@Schema(description = "二步验证设置返回参数")
|
@Schema(description = "双因素设置返回参数")
|
||||||
data class TwoFactorSettingsVo(
|
data class TwoFactorSettingsVo(
|
||||||
/**
|
/**
|
||||||
* Issuer
|
* Issuer
|
||||||
|
|||||||
@@ -169,11 +169,11 @@ insert into t_s_operation(id, name, code, func_id)
|
|||||||
(1530101, '基础', 'system:settings:query:base', 1530100),
|
(1530101, '基础', 'system:settings:query:base', 1530100),
|
||||||
(1530102, '邮件', 'system:settings:query:mail', 1530100),
|
(1530102, '邮件', 'system:settings:query:mail', 1530100),
|
||||||
(1530103, '敏感词', 'system:settings:query:sensitive', 1530100),
|
(1530103, '敏感词', 'system:settings:query:sensitive', 1530100),
|
||||||
(1530104, '二步验证', 'system:settings:query:two-factor', 1530100),
|
(1530104, '双因素', 'system:settings:query:two-factor', 1530100),
|
||||||
(1530301, '基础', 'system:settings:modify:base', 1530300),
|
(1530301, '基础', 'system:settings:modify:base', 1530300),
|
||||||
(1530302, '邮件', 'system:settings:modify:mail', 1530300),
|
(1530302, '邮件', 'system:settings:modify:mail', 1530300),
|
||||||
(1530303, '敏感词', 'system:settings:modify:sensitive', 1530300),
|
(1530303, '敏感词', 'system:settings:modify:sensitive', 1530300),
|
||||||
(1530304, '二步验证', 'system:settings:modify:two-factor', 1530300),
|
(1530304, '双因素', 'system:settings:modify:two-factor', 1530300),
|
||||||
(1540101, '类别', 'system:tool:query:category', 1540100),
|
(1540101, '类别', 'system:tool:query:category', 1540100),
|
||||||
(1540102, '基板', 'system:tool:query:base', 1540100),
|
(1540102, '基板', 'system:tool:query:base', 1540100),
|
||||||
(1540103, '模板', 'system:tool:query:template', 1540100),
|
(1540103, '模板', 'system:tool:query:template', 1540100),
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ create table if not exists t_s_user
|
|||||||
id bigint not null primary key,
|
id bigint not null primary key,
|
||||||
username varchar(20) not null comment '用户名',
|
username varchar(20) not null comment '用户名',
|
||||||
password char(70) not null comment '密码',
|
password char(70) not null comment '密码',
|
||||||
two_factor varchar(40) null comment '二步验证',
|
two_factor varchar(40) null comment '双因素',
|
||||||
verify varchar(144) null comment '验证邮箱',
|
verify varchar(144) null comment '验证邮箱',
|
||||||
forget varchar(144) null comment '忘记密码',
|
forget varchar(144) null comment '忘记密码',
|
||||||
locking int not null comment '锁定',
|
locking int not null comment '锁定',
|
||||||
|
|||||||
Reference in New Issue
Block a user