Add login IP record
This commit is contained in:
@@ -2,11 +2,20 @@ drop table if exists t_user;
|
|||||||
|
|
||||||
create table if not exists t_user
|
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 '密码',
|
||||||
enable int not null comment '启用',
|
locking int not null comment '锁定',
|
||||||
deleted bigint not null default 0,
|
expiration datetime comment '过期时间',
|
||||||
version int not null default 0,
|
credentials_expiration datetime 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,
|
||||||
|
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)
|
||||||
@@ -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 = "登出")
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
@@ -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)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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"))
|
||||||
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user