Add authentication #1

Merged
FatttSnake merged 24 commits from FatttSnake into dev 2023-10-09 10:38:31 +08:00
12 changed files with 141 additions and 41 deletions
Showing only changes of commit 79e65f0785 - Show all commits

26
pom.xml
View File

@@ -74,6 +74,10 @@
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId> <artifactId>spring-boot-starter-web</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency> <dependency>
<groupId>com.fasterxml.jackson.module</groupId> <groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-kotlin</artifactId> <artifactId>jackson-module-kotlin</artifactId>
@@ -112,12 +116,12 @@
<dependency> <dependency>
<groupId>com.baomidou</groupId> <groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId> <artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version> <version>3.5.3.2</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.baomidou</groupId> <groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter-test</artifactId> <artifactId>mybatis-plus-boot-starter-test</artifactId>
<version>3.5.3.1</version> <version>3.5.3.2</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
@@ -125,11 +129,13 @@
<artifactId>druid-spring-boot-starter</artifactId> <artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.19</version> <version>1.2.19</version>
</dependency> </dependency>
<!--
<dependency> <dependency>
<groupId>com.baomidou</groupId> <groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId> <artifactId>mybatis-plus-generator</artifactId>
<version>3.5.3.1</version> <version>3.5.3.2</version>
</dependency> </dependency>
-->
<dependency> <dependency>
<groupId>org.apache.velocity</groupId> <groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId> <artifactId>velocity-engine-core</artifactId>
@@ -142,7 +148,7 @@
<dependency> <dependency>
<groupId>com.auth0</groupId> <groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId> <artifactId>java-jwt</artifactId>
<version>4.3.0</version> <version>4.4.0</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
@@ -152,6 +158,16 @@
<groupId>com.fasterxml.jackson.datatype</groupId> <groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId> <artifactId>jackson-datatype-jsr310</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.5.5.Final</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.5.5.Final</version>
</dependency>
</dependencies> </dependencies>
<build> <build>
@@ -180,7 +196,7 @@
<executions> <executions>
<execution> <execution>
<id>compile</id> <id>compile</id>
<phase>compile</phase> <phase>process-sources</phase>
<goals> <goals>
<goal>compile</goal> <goal>compile</goal>
</goals> </goals>

View File

@@ -18,12 +18,10 @@ fun main(args: Array<String>) {
runApplication<FatWebApiApplication>(*args) runApplication<FatWebApiApplication>(*args)
} else { } 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.") 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() FatWebApiApplication::class.java.getResource("/application-config-template.yml")?.readText()?.let {
?.let {
File("application-config.example.yml").writeText( File("application-config.example.yml").writeText(
it.replace( it.replace(
"\$uuid\$", "\$uuid\$", UUID.randomUUID().toString().replace("-", "")
UUID.randomUUID().toString()
) )
) )
} }

View File

@@ -2,13 +2,10 @@ package top.fatweb.api.annotation
import org.springframework.core.annotation.AliasFor import org.springframework.core.annotation.AliasFor
@Target(AnnotationTarget.CLASS) @Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME) @Retention(AnnotationRetention.RUNTIME)
annotation class ApiVersion( annotation class ApiVersion(
@get:AliasFor("version") @get:AliasFor("version") val value: Int = 1,
val value: Int = 1,
@get:AliasFor("value") @get:AliasFor("value") val version: Int = 1
val version: Int = 1
) )

View File

@@ -2,20 +2,30 @@ package top.fatweb.api.controller.permission
import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.tags.Tag 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.annotation.ApiVersion
import top.fatweb.api.entity.common.ResponseCode import top.fatweb.api.entity.common.ResponseCode
import top.fatweb.api.entity.common.ResponseResult 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 import top.fatweb.api.service.permission.IAuthenticationService
@Tag(name = "身份认证", description = "身份认证相关接口") @Tag(name = "身份认证", description = "身份认证相关接口")
@RestController @Suppress("MVCPathVariableInspection")
@RequestMapping("/api/{apiVersion}") @RequestMapping("/api/{apiVersion}")
@ApiVersion(2) @ApiVersion(2)
class AuthenticationController(val loginService: IAuthenticationService) { @RestController
class AuthenticationController(val loginService: IAuthenticationService, val userConverter: UserConverter) {
@Operation(summary = "登录") @Operation(summary = "登录")
@PostMapping("/login") @PostMapping("/login")
fun login(@PathVariable apiVersion: String, @RequestBody user: User) = fun login(@Valid @RequestBody loginParam: LoginParam) =
ResponseResult.success(ResponseCode.SYSTEM_LOGIN_SUCCESS, "Login success", loginService.login(user)) ResponseResult.success(
ResponseCode.SYSTEM_LOGIN_SUCCESS,
"Login success",
loginService.login(userConverter.loginParamToUser(loginParam))
)
} }

View File

@@ -7,14 +7,16 @@ enum class ResponseCode(val code: Int) {
SYSTEM_LOGOUT_SUCCESS(BusinessCode.SYSTEM, 22), SYSTEM_LOGOUT_SUCCESS(BusinessCode.SYSTEM, 22),
SYSTEM_TOKEN_RENEW_SUCCESS(BusinessCode.SYSTEM, 23), SYSTEM_TOKEN_RENEW_SUCCESS(BusinessCode.SYSTEM, 23),
SYSTEM_UNAUTHORIZED(BusinessCode.SYSTEM, 30), SYSTEM_UNAUTHORIZED(BusinessCode.SYSTEM, 30),
SYSTEM_ACCESS_DENIED(BusinessCode.SYSTEM, 31), SYSTEM_USERNAME_NOT_FOUND(BusinessCode.SYSTEM, 31),
SYSTEM_USER_DISABLE(BusinessCode.SYSTEM, 32), SYSTEM_ACCESS_DENIED(BusinessCode.SYSTEM, 32),
SYSTEM_LOGIN_USERNAME_PASSWORD_ERROR(BusinessCode.SYSTEM, 33), SYSTEM_USER_DISABLE(BusinessCode.SYSTEM, 33),
SYSTEM_OLD_PASSWORD_NOT_MATCH(BusinessCode.SYSTEM, 34), SYSTEM_LOGIN_USERNAME_PASSWORD_ERROR(BusinessCode.SYSTEM, 34),
SYSTEM_LOGOUT_FAILED(BusinessCode.SYSTEM, 35), SYSTEM_OLD_PASSWORD_NOT_MATCH(BusinessCode.SYSTEM, 35),
SYSTEM_TOKEN_ILLEGAL(BusinessCode.SYSTEM, 36), SYSTEM_LOGOUT_FAILED(BusinessCode.SYSTEM, 36),
SYSTEM_TOKEN_HAS_EXPIRED(BusinessCode.SYSTEM, 37), SYSTEM_TOKEN_ILLEGAL(BusinessCode.SYSTEM, 37),
SYSTEM_TOKEN_HAS_EXPIRED(BusinessCode.SYSTEM, 38),
SYSTEM_REQUEST_ILLEGAL(BusinessCode.SYSTEM, 40), SYSTEM_REQUEST_ILLEGAL(BusinessCode.SYSTEM, 40),
SYSTEM_ARGUMENT_NOT_VALID(BusinessCode.SYSTEM, 41),
SYSTEM_ERROR(BusinessCode.SYSTEM, 50), SYSTEM_ERROR(BusinessCode.SYSTEM, 50),
SYSTEM_TIMEOUT(BusinessCode.SYSTEM, 51); SYSTEM_TIMEOUT(BusinessCode.SYSTEM, 51);

View File

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

View File

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

View File

@@ -12,7 +12,12 @@ import java.io.Serializable
* @since 2023-10-04 * @since 2023-10-04
*/ */
@TableName("t_user") @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") @TableId("id")
var id: Long? = null var id: Long? = null
@@ -44,13 +49,6 @@ class User : Serializable {
var version: Int? = null var version: Int? = null
override fun toString(): String { override fun toString(): String {
return "User{" + return "User{id=$id, username=$username, password=$password, enable=$enable, deleted=$deleted, version=$version}"
"id=" + id +
", username=" + username +
", password=" + password +
", enable=" + enable +
", deleted=" + deleted +
", version=" + version +
"}"
} }
} }

View File

@@ -3,7 +3,10 @@ package top.fatweb.api.handler
import org.slf4j.Logger import org.slf4j.Logger
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.springframework.http.converter.HttpMessageNotReadableException import org.springframework.http.converter.HttpMessageNotReadableException
import org.springframework.security.authentication.BadCredentialsException
import org.springframework.security.authentication.InsufficientAuthenticationException 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.ExceptionHandler
import org.springframework.web.bind.annotation.RestControllerAdvice import org.springframework.web.bind.annotation.RestControllerAdvice
import top.fatweb.api.entity.common.ResponseCode import top.fatweb.api.entity.common.ResponseCode
@@ -26,6 +29,22 @@ class ExceptionHandler {
ResponseResult.fail(ResponseCode.SYSTEM_REQUEST_ILLEGAL, e.localizedMessage.split(":")[0], null) 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 -> { else -> {
log.error(e.localizedMessage, e) log.error(e.localizedMessage, e)
ResponseResult.fail(ResponseCode.SYSTEM_ERROR, data = null) ResponseResult.fail(ResponseCode.SYSTEM_ERROR, data = null)

View File

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

View File

@@ -4,7 +4,7 @@ app:
# token-prefix: "Bearer " # Token prefix # token-prefix: "Bearer " # Token prefix
# jwt-ttl: 2 # The life of token # jwt-ttl: 2 # The life of token
# jwt-ttl-unit: hours # Unit of 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 # jwt-issuer: FatWeb # Token issuer
server: server:

View File

@@ -16,4 +16,15 @@ class FatWebApiApplicationTests {
fun removePrefixTest() { fun removePrefixTest() {
assertEquals("12312", "Bearer 12312".removePrefix(SecurityConstants.tokenPrefix)) 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)
}
*/
} }