diff --git a/pom.xml b/pom.xml index ddec85f..81e5aad 100644 --- a/pom.xml +++ b/pom.xml @@ -74,6 +74,10 @@ org.springframework.boot spring-boot-starter-web + + org.springframework.boot + spring-boot-starter-validation + com.fasterxml.jackson.module jackson-module-kotlin @@ -112,12 +116,12 @@ com.baomidou mybatis-plus-boot-starter - 3.5.3.1 + 3.5.3.2 com.baomidou mybatis-plus-boot-starter-test - 3.5.3.1 + 3.5.3.2 test @@ -125,11 +129,13 @@ druid-spring-boot-starter 1.2.19 + org.apache.velocity velocity-engine-core @@ -142,7 +148,7 @@ com.auth0 java-jwt - 4.3.0 + 4.4.0 org.springframework.boot @@ -152,6 +158,16 @@ com.fasterxml.jackson.datatype jackson-datatype-jsr310 + + org.mapstruct + mapstruct + 1.5.5.Final + + + org.mapstruct + mapstruct-processor + 1.5.5.Final + @@ -180,7 +196,7 @@ compile - compile + process-sources compile diff --git a/src/main/kotlin/top/fatweb/api/FatWebApiApplication.kt b/src/main/kotlin/top/fatweb/api/FatWebApiApplication.kt index 9d83e83..1920cd3 100644 --- a/src/main/kotlin/top/fatweb/api/FatWebApiApplication.kt +++ b/src/main/kotlin/top/fatweb/api/FatWebApiApplication.kt @@ -18,14 +18,12 @@ fun main(args: Array) { runApplication(*args) } else { logger.warn("File ‘application.yml’ cannot be found in the running path. The configuration file template 'application.example.yml' has been created. Please change the configuration file content and rename it to 'application.yml', and then restart the server.") - FatWebApiApplication::class.java.getResource("/application-config-template.yml")?.readText() - ?.let { - File("application-config.example.yml").writeText( - it.replace( - "\$uuid\$", - UUID.randomUUID().toString() - ) + FatWebApiApplication::class.java.getResource("/application-config-template.yml")?.readText()?.let { + File("application-config.example.yml").writeText( + it.replace( + "\$uuid\$", UUID.randomUUID().toString().replace("-", "") ) - } + ) + } } } diff --git a/src/main/kotlin/top/fatweb/api/annotation/ApiVersion.kt b/src/main/kotlin/top/fatweb/api/annotation/ApiVersion.kt index f087e97..a0b064c 100644 --- a/src/main/kotlin/top/fatweb/api/annotation/ApiVersion.kt +++ b/src/main/kotlin/top/fatweb/api/annotation/ApiVersion.kt @@ -2,13 +2,10 @@ package top.fatweb.api.annotation import org.springframework.core.annotation.AliasFor - @Target(AnnotationTarget.CLASS) @Retention(AnnotationRetention.RUNTIME) annotation class ApiVersion( - @get:AliasFor("version") - val value: Int = 1, + @get:AliasFor("version") val value: Int = 1, - @get:AliasFor("value") - val version: Int = 1 + @get:AliasFor("value") val version: Int = 1 ) diff --git a/src/main/kotlin/top/fatweb/api/controller/permission/AuthenticationController.kt b/src/main/kotlin/top/fatweb/api/controller/permission/AuthenticationController.kt index c36bdac..2d26a12 100644 --- a/src/main/kotlin/top/fatweb/api/controller/permission/AuthenticationController.kt +++ b/src/main/kotlin/top/fatweb/api/controller/permission/AuthenticationController.kt @@ -2,20 +2,30 @@ package top.fatweb.api.controller.permission import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.tags.Tag -import org.springframework.web.bind.annotation.* +import jakarta.validation.Valid +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController import top.fatweb.api.annotation.ApiVersion import top.fatweb.api.entity.common.ResponseCode import top.fatweb.api.entity.common.ResponseResult -import top.fatweb.api.entity.permission.User +import top.fatweb.api.entity.converter.UserConverter +import top.fatweb.api.entity.param.LoginParam import top.fatweb.api.service.permission.IAuthenticationService @Tag(name = "身份认证", description = "身份认证相关接口") -@RestController +@Suppress("MVCPathVariableInspection") @RequestMapping("/api/{apiVersion}") @ApiVersion(2) -class AuthenticationController(val loginService: IAuthenticationService) { +@RestController +class AuthenticationController(val loginService: IAuthenticationService, val userConverter: UserConverter) { @Operation(summary = "登录") @PostMapping("/login") - fun login(@PathVariable apiVersion: String, @RequestBody user: User) = - ResponseResult.success(ResponseCode.SYSTEM_LOGIN_SUCCESS, "Login success", loginService.login(user)) + fun login(@Valid @RequestBody loginParam: LoginParam) = + ResponseResult.success( + ResponseCode.SYSTEM_LOGIN_SUCCESS, + "Login success", + loginService.login(userConverter.loginParamToUser(loginParam)) + ) } \ No newline at end of file diff --git a/src/main/kotlin/top/fatweb/api/entity/common/ResponseCode.kt b/src/main/kotlin/top/fatweb/api/entity/common/ResponseCode.kt index 8749e68..f6095bb 100644 --- a/src/main/kotlin/top/fatweb/api/entity/common/ResponseCode.kt +++ b/src/main/kotlin/top/fatweb/api/entity/common/ResponseCode.kt @@ -7,14 +7,16 @@ enum class ResponseCode(val code: Int) { SYSTEM_LOGOUT_SUCCESS(BusinessCode.SYSTEM, 22), SYSTEM_TOKEN_RENEW_SUCCESS(BusinessCode.SYSTEM, 23), SYSTEM_UNAUTHORIZED(BusinessCode.SYSTEM, 30), - SYSTEM_ACCESS_DENIED(BusinessCode.SYSTEM, 31), - SYSTEM_USER_DISABLE(BusinessCode.SYSTEM, 32), - SYSTEM_LOGIN_USERNAME_PASSWORD_ERROR(BusinessCode.SYSTEM, 33), - SYSTEM_OLD_PASSWORD_NOT_MATCH(BusinessCode.SYSTEM, 34), - SYSTEM_LOGOUT_FAILED(BusinessCode.SYSTEM, 35), - SYSTEM_TOKEN_ILLEGAL(BusinessCode.SYSTEM, 36), - SYSTEM_TOKEN_HAS_EXPIRED(BusinessCode.SYSTEM, 37), + SYSTEM_USERNAME_NOT_FOUND(BusinessCode.SYSTEM, 31), + SYSTEM_ACCESS_DENIED(BusinessCode.SYSTEM, 32), + SYSTEM_USER_DISABLE(BusinessCode.SYSTEM, 33), + SYSTEM_LOGIN_USERNAME_PASSWORD_ERROR(BusinessCode.SYSTEM, 34), + SYSTEM_OLD_PASSWORD_NOT_MATCH(BusinessCode.SYSTEM, 35), + SYSTEM_LOGOUT_FAILED(BusinessCode.SYSTEM, 36), + SYSTEM_TOKEN_ILLEGAL(BusinessCode.SYSTEM, 37), + SYSTEM_TOKEN_HAS_EXPIRED(BusinessCode.SYSTEM, 38), SYSTEM_REQUEST_ILLEGAL(BusinessCode.SYSTEM, 40), + SYSTEM_ARGUMENT_NOT_VALID(BusinessCode.SYSTEM, 41), SYSTEM_ERROR(BusinessCode.SYSTEM, 50), SYSTEM_TIMEOUT(BusinessCode.SYSTEM, 51); diff --git a/src/main/kotlin/top/fatweb/api/entity/converter/UserConverter.kt b/src/main/kotlin/top/fatweb/api/entity/converter/UserConverter.kt new file mode 100644 index 0000000..8d24463 --- /dev/null +++ b/src/main/kotlin/top/fatweb/api/entity/converter/UserConverter.kt @@ -0,0 +1,14 @@ +package top.fatweb.api.entity.converter + +import org.mapstruct.Mapper +import org.mapstruct.Mapping +import org.mapstruct.Mappings +import top.fatweb.api.entity.param.LoginParam +import top.fatweb.api.entity.permission.User + +@Mapper(componentModel = "spring") +interface UserConverter { + @Mappings(Mapping(source = "username", target = "username"), Mapping(source = "password", target = "password")) + fun loginParamToUser(loginParam: LoginParam): User + +} \ No newline at end of file diff --git a/src/main/kotlin/top/fatweb/api/entity/param/LoginParam.kt b/src/main/kotlin/top/fatweb/api/entity/param/LoginParam.kt new file mode 100644 index 0000000..4ed8d0c --- /dev/null +++ b/src/main/kotlin/top/fatweb/api/entity/param/LoginParam.kt @@ -0,0 +1,16 @@ +package top.fatweb.api.entity.param + +import io.swagger.v3.oas.annotations.media.Schema +import jakarta.validation.constraints.NotBlank +import java.io.Serializable + +class LoginParam : Serializable { + + @Schema(description = "用户名", example = "test", required = true) + @NotBlank(message = "Username can not be blank") + val username: String? = null + + @Schema(description = "密码", example = "test123456", required = true) + @NotBlank(message = "Password can not be blank") + val password: String? = null +} \ No newline at end of file diff --git a/src/main/kotlin/top/fatweb/api/entity/permission/User.kt b/src/main/kotlin/top/fatweb/api/entity/permission/User.kt index 3af3d08..bff63ab 100644 --- a/src/main/kotlin/top/fatweb/api/entity/permission/User.kt +++ b/src/main/kotlin/top/fatweb/api/entity/permission/User.kt @@ -12,7 +12,12 @@ import java.io.Serializable * @since 2023-10-04 */ @TableName("t_user") -class User : Serializable { +class User() : Serializable { + constructor(username: String, password: String, enable: Boolean = true) : this() { + this.username = username + this.password = password + this.enable = if (enable) 1 else 0 + } @TableId("id") var id: Long? = null @@ -44,13 +49,6 @@ class User : Serializable { var version: Int? = null override fun toString(): String { - return "User{" + - "id=" + id + - ", username=" + username + - ", password=" + password + - ", enable=" + enable + - ", deleted=" + deleted + - ", version=" + version + - "}" + return "User{id=$id, username=$username, password=$password, enable=$enable, deleted=$deleted, version=$version}" } } diff --git a/src/main/kotlin/top/fatweb/api/handler/ExceptionHandler.kt b/src/main/kotlin/top/fatweb/api/handler/ExceptionHandler.kt index 4d982d0..412c401 100644 --- a/src/main/kotlin/top/fatweb/api/handler/ExceptionHandler.kt +++ b/src/main/kotlin/top/fatweb/api/handler/ExceptionHandler.kt @@ -3,7 +3,10 @@ package top.fatweb.api.handler import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.http.converter.HttpMessageNotReadableException +import org.springframework.security.authentication.BadCredentialsException import org.springframework.security.authentication.InsufficientAuthenticationException +import org.springframework.security.authentication.InternalAuthenticationServiceException +import org.springframework.web.bind.MethodArgumentNotValidException import org.springframework.web.bind.annotation.ExceptionHandler import org.springframework.web.bind.annotation.RestControllerAdvice import top.fatweb.api.entity.common.ResponseCode @@ -26,6 +29,22 @@ class ExceptionHandler { ResponseResult.fail(ResponseCode.SYSTEM_REQUEST_ILLEGAL, e.localizedMessage.split(":")[0], null) } + is MethodArgumentNotValidException -> { + log.debug(e.localizedMessage, e) + val errorMessage = e.allErrors.map { error -> error.defaultMessage }.joinToString(". ") + ResponseResult.fail(ResponseCode.SYSTEM_ARGUMENT_NOT_VALID, errorMessage, null) + } + + is InternalAuthenticationServiceException -> { + log.debug(e.localizedMessage, e) + ResponseResult.fail(ResponseCode.SYSTEM_USERNAME_NOT_FOUND, e.localizedMessage, null) + } + + is BadCredentialsException -> { + log.debug(e.localizedMessage, e) + ResponseResult.fail(ResponseCode.SYSTEM_LOGIN_USERNAME_PASSWORD_ERROR, e.localizedMessage, null) + } + else -> { log.error(e.localizedMessage, e) ResponseResult.fail(ResponseCode.SYSTEM_ERROR, data = null) diff --git a/src/main/kotlin/top/fatweb/api/service/permission/impl/UserDetailsServiceImpl.kt b/src/main/kotlin/top/fatweb/api/service/permission/impl/UserDetailsServiceImpl.kt new file mode 100644 index 0000000..6b78e76 --- /dev/null +++ b/src/main/kotlin/top/fatweb/api/service/permission/impl/UserDetailsServiceImpl.kt @@ -0,0 +1,19 @@ +package top.fatweb.api.service.permission.impl + +import com.baomidou.mybatisplus.extension.kotlin.KtQueryWrapper +import org.springframework.security.core.userdetails.UserDetails +import org.springframework.security.core.userdetails.UserDetailsService +import org.springframework.stereotype.Service +import top.fatweb.api.entity.permission.LoginUser +import top.fatweb.api.entity.permission.User +import top.fatweb.api.service.IUserService + +@Service +class UserDetailsServiceImpl(val userService: IUserService) : UserDetailsService { + override fun loadUserByUsername(username: String?): UserDetails { + val user = userService.getOne(KtQueryWrapper(User()).eq(User::username, username)) + user ?: let { throw Exception("Username not found") } + + return LoginUser(user) + } +} \ No newline at end of file diff --git a/src/main/resources/application-config-template.yml b/src/main/resources/application-config-template.yml index 1e91da0..5280697 100644 --- a/src/main/resources/application-config-template.yml +++ b/src/main/resources/application-config-template.yml @@ -4,7 +4,7 @@ app: # token-prefix: "Bearer " # Token prefix # jwt-ttl: 2 # The life of token # jwt-ttl-unit: hours # Unit of life of token - jwt-key: $uuid$ # Key to generate token + jwt-key: $uuid$ # Key to generate token (Only numbers and letters allow) # jwt-issuer: FatWeb # Token issuer server: diff --git a/src/test/kotlin/top/fatweb/api/FatWebApiApplicationTests.kt b/src/test/kotlin/top/fatweb/api/FatWebApiApplicationTests.kt index 9eb700d..9dc0d31 100644 --- a/src/test/kotlin/top/fatweb/api/FatWebApiApplicationTests.kt +++ b/src/test/kotlin/top/fatweb/api/FatWebApiApplicationTests.kt @@ -16,4 +16,15 @@ class FatWebApiApplicationTests { fun removePrefixTest() { assertEquals("12312", "Bearer 12312".removePrefix(SecurityConstants.tokenPrefix)) } + + /* + @Test + fun addUser(@Autowired userService: IUserService, @Autowired passwordEncoder: PasswordEncoder) { + val username = "admin" + val rawPassword = "admin" + val encodedPassword = passwordEncoder.encode(rawPassword) + val user = User(username, encodedPassword) + userService.save(user) + } + */ }