Add login IP record

This commit is contained in:
2023-10-16 17:35:34 +08:00
parent 1fcd468581
commit 8e3ceb16c1
7 changed files with 110 additions and 22 deletions

View File

@@ -5,8 +5,17 @@ create table if not exists t_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 '密码',
locking int not null comment '锁定',
expiration datetime comment '过期时间',
credentials_expiration datetime comment '认证过期时间',
enable int not null comment '启用', enable int not null comment '启用',
last_login_time datetime comment '上次登录时间',
last_login_ip varchar(128) comment '上次登录 IP',
create_time datetime not null default (utc_timestamp()) comment '创建时间',
update_time datetime not null default (utc_timestamp()) comment '修改时间',
deleted bigint not null default 0, deleted bigint not null default 0,
version int not null default 0, version int not null default 0,
constraint t_user_unique unique (username, deleted) constraint t_user_unique unique (username, deleted)
) comment '用户'; ) comment '用户';
insert into t_user (id, username, password, locking, enable) value (0, 'admin', '$2a$10$3wDGdzTZlC..7eY6u2XM5u78xUQo0z5Sj5yOpneD4QJ0q/TA5TY0S', 0, 1)

View File

@@ -4,7 +4,10 @@ import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.tags.Tag import io.swagger.v3.oas.annotations.tags.Tag
import jakarta.servlet.http.HttpServletRequest import jakarta.servlet.http.HttpServletRequest
import jakarta.validation.Valid import jakarta.validation.Valid
import org.springframework.web.bind.annotation.* import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RestController
import top.fatweb.api.annotation.ApiVersion import top.fatweb.api.annotation.ApiVersion
import top.fatweb.api.converter.UserConverter import top.fatweb.api.converter.UserConverter
import top.fatweb.api.entity.common.ResponseCode import top.fatweb.api.entity.common.ResponseCode
@@ -19,11 +22,11 @@ import top.fatweb.api.util.WebUtil
class AuthenticationController(val authenticationService: IAuthenticationService, val userConverter: UserConverter) { class AuthenticationController(val authenticationService: IAuthenticationService, val userConverter: UserConverter) {
@Operation(summary = "登录") @Operation(summary = "登录")
@PostMapping("/login") @PostMapping("/login")
fun login(@Valid @RequestBody loginParam: LoginParam) = fun login(request: HttpServletRequest, @Valid @RequestBody loginParam: LoginParam) =
ResponseResult.success( ResponseResult.success(
ResponseCode.SYSTEM_LOGIN_SUCCESS, ResponseCode.SYSTEM_LOGIN_SUCCESS,
"Login success", "Login success",
authenticationService.login(userConverter.loginParamToUser(loginParam)) authenticationService.login(request, userConverter.loginParamToUser(loginParam))
) )
@Operation(summary = "登出") @Operation(summary = "登出")

View File

@@ -4,6 +4,8 @@ import com.fasterxml.jackson.annotation.JsonIgnore
import com.fasterxml.jackson.annotation.JsonTypeInfo import com.fasterxml.jackson.annotation.JsonTypeInfo
import org.springframework.security.core.GrantedAuthority import org.springframework.security.core.GrantedAuthority
import org.springframework.security.core.userdetails.UserDetails import org.springframework.security.core.userdetails.UserDetails
import java.time.LocalDateTime
import java.time.ZoneOffset
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
class LoginUser() : UserDetails { class LoginUser() : UserDetails {
@@ -25,20 +27,22 @@ class LoginUser() : UserDetails {
} }
@JsonIgnore @JsonIgnore
override fun getPassword(): String? = user.password override fun getPassword() = user.password
@JsonIgnore @JsonIgnore
override fun getUsername(): String? = user.username override fun getUsername() = user.username
@JsonIgnore @JsonIgnore
override fun isAccountNonExpired(): Boolean = true override fun isAccountNonExpired() =
user.expiration == null || user.expiration!!.isAfter(LocalDateTime.now(ZoneOffset.UTC))
@JsonIgnore @JsonIgnore
override fun isAccountNonLocked(): Boolean = true override fun isAccountNonLocked() = user.locking == 0
@JsonIgnore @JsonIgnore
override fun isCredentialsNonExpired(): Boolean = true override fun isCredentialsNonExpired() =
user.credentialsExpiration == null || user.credentialsExpiration!!.isAfter(LocalDateTime.now(ZoneOffset.UTC))
@JsonIgnore @JsonIgnore
override fun isEnabled(): Boolean = user.enable == 1 override fun isEnabled() = user.enable == 1
} }

View File

@@ -2,10 +2,11 @@ package top.fatweb.api.entity.permission
import com.baomidou.mybatisplus.annotation.* import com.baomidou.mybatisplus.annotation.*
import java.io.Serializable import java.io.Serializable
import java.time.LocalDateTime
/** /**
* <p> * <p>
* 用户 * 用户
* </p> * </p>
* *
* @author FatttSnake * @author FatttSnake
@@ -13,7 +14,8 @@ import java.io.Serializable
*/ */
@TableName("t_user") @TableName("t_user")
class User() : Serializable { class User() : Serializable {
constructor(username: String, password: String, enable: Boolean = true) : this() { constructor(id: Long?, username: String, password: String, enable: Boolean = true) : this() {
this.id = id
this.username = username this.username = username
this.password = password this.password = password
this.enable = if (enable) 1 else 0 this.enable = if (enable) 1 else 0
@@ -34,12 +36,54 @@ class User() : Serializable {
@TableField("password") @TableField("password")
var password: String? = null var password: String? = null
/**
* 锁定
*/
@TableField("locking")
var locking: Int? = null
/**
* 过期时间
*/
@TableField("expiration")
var expiration: LocalDateTime? = null
/**
* 认证过期时间
*/
@TableField("credentials_expiration")
var credentialsExpiration: LocalDateTime? = null
/** /**
* 启用 * 启用
*/ */
@TableField("enable") @TableField("enable")
var enable: Int? = null var enable: Int? = null
/**
* 上次登录时间
*/
@TableField("last_login_time")
var lastLoginTime: LocalDateTime? = null
/**
* 上次登录 IP
*/
@TableField("last_login_ip")
var lastLoginIp: String? = null
/**
* 创建时间
*/
@TableField("create_time")
var createTime: LocalDateTime? = null
/**
* 修改时间
*/
@TableField("update_time")
var updateTime: LocalDateTime? = null
@TableField("deleted") @TableField("deleted")
@TableLogic @TableLogic
var deleted: Long? = null var deleted: Long? = null
@@ -49,6 +93,6 @@ class User() : Serializable {
var version: Int? = null var version: Int? = null
override fun toString(): String { 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, locking=$locking, expiration=$expiration, credentialsExpiration=$credentialsExpiration, enable=$enable, lastLoginTime=$lastLoginTime, lastLoginIp=$lastLoginIp, createTime=$createTime, updateTime=$updateTime, deleted=$deleted, version=$version)"
} }
} }

View File

@@ -1,11 +1,12 @@
package top.fatweb.api.service.permission package top.fatweb.api.service.permission
import jakarta.servlet.http.HttpServletRequest
import top.fatweb.api.entity.permission.User import top.fatweb.api.entity.permission.User
import top.fatweb.api.vo.LoginVo import top.fatweb.api.vo.LoginVo
import top.fatweb.api.vo.TokenVo import top.fatweb.api.vo.TokenVo
interface IAuthenticationService { interface IAuthenticationService {
fun login(user: User): LoginVo fun login(request: HttpServletRequest, user: User): LoginVo
fun logout(token: String): Boolean fun logout(token: String): Boolean

View File

@@ -1,30 +1,46 @@
package top.fatweb.api.service.permission.impl package top.fatweb.api.service.permission.impl
import com.baomidou.mybatisplus.extension.kotlin.KtUpdateWrapper
import jakarta.servlet.http.HttpServletRequest
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.security.authentication.AuthenticationManager import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
import top.fatweb.api.constant.SecurityConstants import top.fatweb.api.constant.SecurityConstants
import top.fatweb.api.entity.permission.LoginUser import top.fatweb.api.entity.permission.LoginUser
import top.fatweb.api.entity.permission.User import top.fatweb.api.entity.permission.User
import top.fatweb.api.service.IUserService
import top.fatweb.api.service.permission.IAuthenticationService import top.fatweb.api.service.permission.IAuthenticationService
import top.fatweb.api.util.JwtUtil import top.fatweb.api.util.JwtUtil
import top.fatweb.api.util.RedisUtil import top.fatweb.api.util.RedisUtil
import top.fatweb.api.util.WebUtil import top.fatweb.api.util.WebUtil
import top.fatweb.api.vo.LoginVo import top.fatweb.api.vo.LoginVo
import top.fatweb.api.vo.TokenVo import top.fatweb.api.vo.TokenVo
import java.time.LocalDateTime
import java.time.ZoneOffset
@Service @Service
class AuthenticationServiceImpl( class AuthenticationServiceImpl(
private val authenticationManager: AuthenticationManager, private val authenticationManager: AuthenticationManager,
private val redisUtil: RedisUtil private val redisUtil: RedisUtil,
private val userService: IUserService
) : IAuthenticationService { ) : IAuthenticationService {
override fun login(user: User): LoginVo { private val logger: Logger = LoggerFactory.getLogger(this::class.java)
override fun login(request: HttpServletRequest, user: User): LoginVo {
val usernamePasswordAuthenticationToken = UsernamePasswordAuthenticationToken(user.username, user.password) val usernamePasswordAuthenticationToken = UsernamePasswordAuthenticationToken(user.username, user.password)
val authentication = authenticationManager.authenticate(usernamePasswordAuthenticationToken) val authentication = authenticationManager.authenticate(usernamePasswordAuthenticationToken)
authentication ?: let { authentication ?: let {
throw RuntimeException("Login failed") throw RuntimeException("Login failed")
} }
logger.info("用户登录 [用户名: '{}', IP: '{}']", user.username, request.remoteAddr)
userService.update(User().apply {
lastLoginIp = request.remoteAddr
lastLoginTime = LocalDateTime.now(ZoneOffset.UTC)
}, KtUpdateWrapper(User()).eq(User::username, user.username))
val loginUser = authentication.principal as LoginUser val loginUser = authentication.principal as LoginUser
loginUser.user.password = "" loginUser.user.password = ""
val userId = loginUser.user.id.toString() val userId = loginUser.user.id.toString()

View File

@@ -3,12 +3,15 @@ package top.fatweb.api
import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith import org.junit.jupiter.api.extension.ExtendWith
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.test.context.junit.jupiter.SpringExtension import org.springframework.test.context.junit.jupiter.SpringExtension
import top.fatweb.api.constant.SecurityConstants import top.fatweb.api.constant.SecurityConstants
import top.fatweb.api.util.JwtUtil import top.fatweb.api.util.JwtUtil
@ExtendWith(SpringExtension::class) @ExtendWith(SpringExtension::class)
class FatWebApiApplicationTests { class FatWebApiApplicationTests {
private val logger: Logger = LoggerFactory.getLogger(this::class.java)
@Test @Test
fun removePrefixTest() { fun removePrefixTest() {
@@ -20,4 +23,12 @@ class FatWebApiApplicationTests {
val jwt = JwtUtil.createJwt("User") val jwt = JwtUtil.createJwt("User")
assertEquals("User", jwt?.let { JwtUtil.parseJwt(it).subject }) assertEquals("User", jwt?.let { JwtUtil.parseJwt(it).subject })
} }
/*
@Test
fun generatePassword() {
val passwordEncoder = BCryptPasswordEncoder()
logger.info(passwordEncoder.encode("admin@dev"))
}
*/
} }