Add authentication #1
26
pom.xml
26
pom.xml
@@ -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>
|
||||||
|
|||||||
@@ -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()
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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))
|
||||||
|
)
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
}
|
||||||
16
src/main/kotlin/top/fatweb/api/entity/param/LoginParam.kt
Normal file
16
src/main/kotlin/top/fatweb/api/entity/param/LoginParam.kt
Normal 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
|
||||||
|
}
|
||||||
@@ -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 +
|
|
||||||
"}"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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:
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user