Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
有来技术
youlai-boot
提交
95fdbc2c
Y
youlai-boot
项目概览
有来技术
/
youlai-boot
通知
2
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
DevOps
流水线
流水线任务
计划
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
Y
youlai-boot
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
DevOps
DevOps
流水线
流水线任务
计划
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
流水线任务
提交
Issue看板
前往新版Gitcode,体验更适合开发者的 AI 搜索 >>
提交
95fdbc2c
编写于
4月 18, 2024
作者:
R
Ray.Hao
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
refactor: 优化 `JWT` 解析和验证代码和修复用户名密码错误的异常提示
上级
69274645
变更
16
隐藏空白更改
内联
并排
Showing
16 changed file
with
211 addition
and
153 deletion
+211
-153
src/main/java/com/youlai/system/SystemApplication.java
src/main/java/com/youlai/system/SystemApplication.java
+2
-0
src/main/java/com/youlai/system/common/constant/JwtClaimConstants.java
.../com/youlai/system/common/constant/JwtClaimConstants.java
+1
-1
src/main/java/com/youlai/system/common/constant/SecurityConstants.java
.../com/youlai/system/common/constant/SecurityConstants.java
+12
-0
src/main/java/com/youlai/system/common/util/ResponseUtils.java
...ain/java/com/youlai/system/common/util/ResponseUtils.java
+26
-24
src/main/java/com/youlai/system/config/SecurityConfig.java
src/main/java/com/youlai/system/config/SecurityConfig.java
+17
-17
src/main/java/com/youlai/system/config/WebSocketConfig.java
src/main/java/com/youlai/system/config/WebSocketConfig.java
+2
-2
src/main/java/com/youlai/system/config/property/SecurityProperties.java
...com/youlai/system/config/property/SecurityProperties.java
+44
-0
src/main/java/com/youlai/system/filter/CaptchaValidationFilter.java
...ava/com/youlai/system/filter/CaptchaValidationFilter.java
+1
-1
src/main/java/com/youlai/system/filter/JwtValidationFilter.java
...in/java/com/youlai/system/filter/JwtValidationFilter.java
+32
-17
src/main/java/com/youlai/system/plugin/dupsubmit/aspect/DuplicateSubmitAspect.java
...system/plugin/dupsubmit/aspect/DuplicateSubmitAspect.java
+9
-6
src/main/java/com/youlai/system/security/constant/SecurityConstants.java
...om/youlai/system/security/constant/SecurityConstants.java
+0
-17
src/main/java/com/youlai/system/security/model/SysUserDetails.java
...java/com/youlai/system/security/model/SysUserDetails.java
+2
-4
src/main/java/com/youlai/system/security/util/JwtUtils.java
src/main/java/com/youlai/system/security/util/JwtUtils.java
+15
-43
src/main/java/com/youlai/system/service/impl/AuthServiceImpl.java
.../java/com/youlai/system/service/impl/AuthServiceImpl.java
+18
-9
src/main/resources/application-dev.yml
src/main/resources/application-dev.yml
+15
-6
src/main/resources/application-prod.yml
src/main/resources/application-prod.yml
+15
-6
未找到文件。
src/main/java/com/youlai/system/SystemApplication.java
浏览文件 @
95fdbc2c
...
...
@@ -2,8 +2,10 @@ package com.youlai.system;
import
org.springframework.boot.SpringApplication
;
import
org.springframework.boot.autoconfigure.SpringBootApplication
;
import
org.springframework.boot.context.properties.ConfigurationPropertiesScan
;
@SpringBootApplication
@ConfigurationPropertiesScan
public
class
SystemApplication
{
public
static
void
main
(
String
[]
args
)
{
SpringApplication
.
run
(
SystemApplication
.
class
,
args
);
...
...
src/main/java/com/youlai/system/
security
/constant/JwtClaimConstants.java
→
src/main/java/com/youlai/system/
common
/constant/JwtClaimConstants.java
浏览文件 @
95fdbc2c
package
com.youlai.system.
security
.constant
;
package
com.youlai.system.
common
.constant
;
/**
* JWT Claims声明常量
...
...
src/main/java/com/youlai/system/common/constant/SecurityConstants.java
浏览文件 @
95fdbc2c
...
...
@@ -24,4 +24,16 @@ public interface SecurityConstants {
String
BLACKLIST_TOKEN_PREFIX
=
"token:blacklist:"
;
/**
* 登录路径
*/
String
LOGIN_PATH
=
"/api/v1/auth/login"
;
/**
* JWT Token 前缀
*/
String
JWT_TOKEN_PREFIX
=
"Bearer "
;
}
src/main/java/com/youlai/system/common/util/ResponseUtils.java
浏览文件 @
95fdbc2c
package
com.youlai.system.common.util
;
import
cn.hutool.json.JSONUtil
;
import
com.youlai.system.common.result.IResultCode
;
import
com.youlai.system.common.result.Result
;
import
com.youlai.system.common.result.ResultCode
;
import
jakarta.servlet.http.HttpServletResponse
;
import
lombok.extern.slf4j.Slf4j
;
import
org.springframework.http.HttpStatus
;
import
org.springframework.http.MediaType
;
import
jakarta.servlet.http.HttpServletResponse
;
import
java.io.IOException
;
import
static
com
.
youlai
.
system
.
common
.
result
.
ResultCode
.*
;
import
java.io.PrintWriter
;
import
java.nio.charset.StandardCharsets
;
/**
* 响应工具类
*
* @author
haoxr
* @author
Ray Hao
* @since 2.0.0
*/
@Slf4j
public
class
ResponseUtils
{
/**
* 异常消息返回(适用过滤器中处理异常响应)
*
* @param response
* @param resultCode
* @param response
HttpServletResponse
* @param resultCode
响应结果码
*/
public
static
void
writeErrMsg
(
HttpServletResponse
response
,
ResultCode
resultCode
)
throws
IOException
{
switch
(
resultCode
)
{
case
ACCESS_UNAUTHORIZED:
case
TOKEN_INVALID:
response
.
setStatus
(
HttpStatus
.
UNAUTHORIZED
.
value
());
break
;
case
TOKEN_ACCESS_FORBIDDEN:
response
.
setStatus
(
HttpStatus
.
FORBIDDEN
.
value
());
break
;
default
:
response
.
setStatus
(
HttpStatus
.
BAD_REQUEST
.
value
());
break
;
}
public
static
void
writeErrMsg
(
HttpServletResponse
response
,
ResultCode
resultCode
)
{
// 根据不同的结果码设置HTTP状态
int
status
=
switch
(
resultCode
)
{
case
ACCESS_UNAUTHORIZED
,
TOKEN_INVALID
->
HttpStatus
.
UNAUTHORIZED
.
value
();
case
TOKEN_ACCESS_FORBIDDEN
->
HttpStatus
.
FORBIDDEN
.
value
();
default
->
HttpStatus
.
BAD_REQUEST
.
value
();
};
response
.
setStatus
(
status
);
response
.
setContentType
(
MediaType
.
APPLICATION_JSON_VALUE
);
response
.
setCharacterEncoding
(
"UTF-8"
);
response
.
getWriter
().
print
(
JSONUtil
.
toJsonStr
(
Result
.
failed
(
resultCode
)));
response
.
setCharacterEncoding
(
StandardCharsets
.
UTF_8
.
name
());
try
(
PrintWriter
writer
=
response
.
getWriter
())
{
String
jsonResponse
=
JSONUtil
.
toJsonStr
(
Result
.
failed
(
resultCode
));
writer
.
print
(
jsonResponse
);
writer
.
flush
();
// 确保将响应内容写入到输出流
}
catch
(
IOException
e
)
{
log
.
error
(
"响应异常处理失败"
,
e
);
}
}
}
src/main/java/com/youlai/system/config/SecurityConfig.java
浏览文件 @
95fdbc2c
package
com.youlai.system.config
;
import
cn.hutool.captcha.generator.CodeGenerator
;
import
com.youlai.system.security.constant.SecurityConstants
;
import
cn.hutool.core.collection.CollectionUtil
;
import
com.youlai.system.common.constant.SecurityConstants
;
import
com.youlai.system.config.property.SecurityProperties
;
import
com.youlai.system.security.exception.MyAccessDeniedHandler
;
import
com.youlai.system.security.exception.MyAuthenticationEntryPoint
;
import
com.youlai.system.filter.JwtValidationFilter
;
...
...
@@ -17,6 +19,7 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import
org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
;
import
org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer
;
import
org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer
;
import
org.springframework.security.config.annotation.web.configurers.HeadersConfigurer
;
import
org.springframework.security.config.http.SessionCreationPolicy
;
import
org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
;
import
org.springframework.security.crypto.password.PasswordEncoder
;
...
...
@@ -26,7 +29,7 @@ import org.springframework.security.web.authentication.UsernamePasswordAuthentic
/**
* Spring Security 权限配置
*
* @author
haoxr
* @author
Ray Hao
* @since 2023/2/17
*/
@Configuration
...
...
@@ -39,29 +42,33 @@ public class SecurityConfig {
private
final
MyAccessDeniedHandler
accessDeniedHandler
;
private
final
RedisTemplate
<
String
,
Object
>
redisTemplate
;
private
final
CodeGenerator
codeGenerator
;
private
final
SecurityProperties
securityProperties
;
@Bean
public
SecurityFilterChain
securityFilterChain
(
HttpSecurity
http
)
throws
Exception
{
http
.
authorizeHttpRequests
(
requestMatcherRegistry
->
requestMatcherRegistry
.
requestMatchers
(
SecurityConstants
.
LOGIN_PATH
).
permitAll
()
.
anyRequest
().
authenticated
()
)
.
sessionManagement
(
configurer
->
configurer
.
sessionCreationPolicy
(
SessionCreationPolicy
.
STATELESS
))
.
exceptionHandling
(
httpSecurityExceptionHandlingConfigurer
->
httpSecurityExceptionHandlingConfigurer
.
authenticationEntryPoint
(
authenticationEntryPoint
)
.
accessDeniedHandler
(
accessDeniedHandler
)
)
.
sessionManagement
(
configurer
->
configurer
.
sessionCreationPolicy
(
SessionCreationPolicy
.
STATELESS
))
.
csrf
(
AbstractHttpConfigurer:
:
disable
)
.
headers
(
headers
->
headers
.
frameOptions
(
HeadersConfigurer
.
FrameOptionsConfig
::
disable
))
;
// 验证码校验过滤器
http
.
addFilterBefore
(
new
CaptchaValidationFilter
(
redisTemplate
,
codeGenerator
),
UsernamePasswordAuthenticationFilter
.
class
);
http
.
addFilterBefore
(
new
CaptchaValidationFilter
(
redisTemplate
,
codeGenerator
),
UsernamePasswordAuthenticationFilter
.
class
);
// JWT 校验过滤器
http
.
addFilterBefore
(
new
JwtValidationFilter
(
redisTemplate
),
UsernamePasswordAuthenticationFilter
.
class
);
http
.
addFilterBefore
(
new
JwtValidationFilter
(
redisTemplate
,
securityProperties
.
getJwt
().
getKey
()
),
UsernamePasswordAuthenticationFilter
.
class
);
return
http
.
build
();
}
...
...
@@ -71,18 +78,11 @@ public class SecurityConfig {
*/
@Bean
public
WebSecurityCustomizer
webSecurityCustomizer
()
{
return
(
web
)
->
web
.
ignoring
()
.
requestMatchers
(
"/api/v1/auth/captcha"
,
"/webjars/**"
,
"/doc.html"
,
"/swagger-resources/**"
,
"/v3/api-docs/**"
,
"/swagger-ui/**"
,
"/swagger-ui.html"
,
"/ws/**"
,
"/ws-app/**"
);
return
(
web
)
->
{
if
(
CollectionUtil
.
isNotEmpty
(
securityProperties
.
getIgnoreUrls
()))
{
web
.
ignoring
().
requestMatchers
(
securityProperties
.
getIgnoreUrls
().
toArray
(
new
String
[
0
]));
}
};
}
/**
...
...
src/main/java/com/youlai/system/config/WebSocketConfig.java
浏览文件 @
95fdbc2c
...
...
@@ -2,7 +2,7 @@ package com.youlai.system.config;
import
cn.hutool.core.util.StrUtil
;
import
cn.hutool.jwt.JWTPayload
;
import
c
om.youlai.system.security.util.JwtUtils
;
import
c
n.hutool.jwt.JWTUtil
;
import
lombok.extern.slf4j.Slf4j
;
import
org.springframework.context.annotation.Configuration
;
import
org.springframework.http.HttpHeaders
;
...
...
@@ -83,7 +83,7 @@ public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
// 这里不应该用"name"
// String username = JwtUtils.parseToken(bearerToken).get("name").toString();
String
username
=
J
wtUtils
.
parseToken
(
bearerToken
).
get
(
JWTPayload
.
SUBJECT
).
toString
(
);
String
username
=
J
WTUtil
.
parseToken
(
bearerToken
).
getPayloads
().
getStr
(
JWTPayload
.
SUBJECT
);
if
(
StrUtil
.
isNotBlank
(
username
))
{
accessor
.
setUser
(()
->
username
);
...
...
src/main/java/com/youlai/system/config/property/SecurityProperties.java
0 → 100644
浏览文件 @
95fdbc2c
package
com.youlai.system.config.property
;
import
lombok.Data
;
import
org.springframework.boot.context.properties.ConfigurationProperties
;
import
java.util.List
;
/**
* @author haoxr
* @since 2024/4/18
*/
@Data
@ConfigurationProperties
(
prefix
=
"security"
)
public
class
SecurityProperties
{
/**
* 白名单 URL 集合
*/
private
List
<
String
>
ignoreUrls
;
/**
* JWT 配置
*/
private
JwtProperty
jwt
;
/**
* JWT 配置
*/
@Data
public
static
class
JwtProperty
{
/**
* JWT 秘钥
*/
private
String
key
;
/**
* JWT 过期时间
*/
private
Long
ttl
;
}
}
src/main/java/com/youlai/system/filter/CaptchaValidationFilter.java
浏览文件 @
95fdbc2c
...
...
@@ -24,7 +24,7 @@ import java.io.IOException;
*/
public
class
CaptchaValidationFilter
extends
OncePerRequestFilter
{
private
static
final
AntPathRequestMatcher
LOGIN_PATH_REQUEST_MATCHER
=
new
AntPathRequestMatcher
(
com
.
youlai
.
system
.
security
.
constant
.
SecurityConstants
.
LOGIN_PATH
,
"POST"
);
private
static
final
AntPathRequestMatcher
LOGIN_PATH_REQUEST_MATCHER
=
new
AntPathRequestMatcher
(
SecurityConstants
.
LOGIN_PATH
,
"POST"
);
public
static
final
String
CAPTCHA_CODE_PARAM_NAME
=
"captchaCode"
;
public
static
final
String
CAPTCHA_KEY_PARAM_NAME
=
"captchaKey"
;
...
...
src/main/java/com/youlai/system/filter/JwtValidationFilter.java
浏览文件 @
95fdbc2c
package
com.youlai.system.filter
;
import
cn.hutool.core.convert.Convert
;
import
cn.hutool.core.util.StrUtil
;
import
cn.hutool.json.JSONObject
;
import
cn.hutool.jwt.JWT
;
import
cn.hutool.jwt.JWTPayload
;
import
cn.hutool.jwt.JWTUtil
;
import
com.youlai.system.common.constant.SecurityConstants
;
import
com.youlai.system.common.result.ResultCode
;
import
com.youlai.system.security.util.JwtUtils
;
import
com.youlai.system.common.util.ResponseUtils
;
import
com.youlai.system.common.exception.BusinessException
;
import
jakarta.servlet.FilterChain
;
import
jakarta.servlet.ServletException
;
import
jakarta.servlet.http.HttpServletRequest
;
...
...
@@ -19,22 +20,25 @@ import org.springframework.security.core.context.SecurityContextHolder;
import
org.springframework.web.filter.OncePerRequestFilter
;
import
java.io.IOException
;
import
java.util.Map
;
/**
* JWT token 校验过滤器
*
* @author
haoxr
* @author
Ray Hao
* @since 2023/9/13
*/
public
class
JwtValidationFilter
extends
OncePerRequestFilter
{
private
final
RedisTemplate
<
String
,
Object
>
redisTemplate
;
public
JwtValidationFilter
(
RedisTemplate
<
String
,
Object
>
redisTemplate
)
{
private
final
byte
[]
secretKey
;
public
JwtValidationFilter
(
RedisTemplate
<
String
,
Object
>
redisTemplate
,
String
secretKey
)
{
this
.
redisTemplate
=
redisTemplate
;
this
.
secretKey
=
secretKey
.
getBytes
();
}
/**
* 从请求中获取 JWT Token,校验 JWT Token 是否合法
* <p>
...
...
@@ -44,27 +48,38 @@ public class JwtValidationFilter extends OncePerRequestFilter {
@Override
protected
void
doFilterInternal
(
HttpServletRequest
request
,
HttpServletResponse
response
,
FilterChain
filterChain
)
throws
ServletException
,
IOException
{
String
token
=
request
.
getHeader
(
HttpHeaders
.
AUTHORIZATION
);
try
{
if
(
StrUtil
.
isNotBlank
(
token
))
{
Map
<
String
,
Object
>
payload
=
JwtUtils
.
parseToken
(
token
);
if
(
StrUtil
.
isNotBlank
(
token
)
&&
token
.
startsWith
(
SecurityConstants
.
JWT_TOKEN_PREFIX
))
{
token
=
token
.
substring
(
SecurityConstants
.
JWT_TOKEN_PREFIX
.
length
());
// 去除 Bearer 前缀
// 校验 Token 是否有效
if
(
JWTUtil
.
verify
(
token
,
secretKey
))
{
// 解析 Token 获取有效载荷
JWT
jwt
=
JWTUtil
.
parseToken
(
token
);
JSONObject
payloads
=
jwt
.
getPayloads
();
String
jti
=
Convert
.
toStr
(
payload
.
get
(
JWTPayload
.
JWT_ID
));
Boolean
isTokenBlacklisted
=
redisTemplate
.
hasKey
(
SecurityConstants
.
BLACKLIST_TOKEN_PREFIX
+
jti
);
if
(
Boolean
.
TRUE
.
equals
(
isTokenBlacklisted
))
{
// 检查 Token 是否已被加入黑名单
String
jti
=
payloads
.
getStr
(
JWTPayload
.
JWT_ID
);
boolean
isTokenBlacklisted
=
Boolean
.
TRUE
.
equals
(
redisTemplate
.
hasKey
(
SecurityConstants
.
BLACKLIST_TOKEN_PREFIX
+
jti
));
if
(
isTokenBlacklisted
)
{
ResponseUtils
.
writeErrMsg
(
response
,
ResultCode
.
TOKEN_INVALID
);
return
;
}
// Token 有效将其解析为 Authentication 对象,并设置到 Spring Security 上下文中
Authentication
authentication
=
JwtUtils
.
getAuthentication
(
payloads
);
SecurityContextHolder
.
getContext
().
setAuthentication
(
authentication
);
}
else
{
// Token 无效,直接返回响应
ResponseUtils
.
writeErrMsg
(
response
,
ResultCode
.
TOKEN_INVALID
);
return
;
}
Authentication
authentication
=
JwtUtils
.
getAuthentication
(
payload
);
SecurityContextHolder
.
getContext
().
setAuthentication
(
authentication
);
}
}
catch
(
BusinessException
ex
)
{
//this is very important, since it guarantees the user is not authenticated at all
}
catch
(
Exception
e
)
{
SecurityContextHolder
.
clearContext
();
ResponseUtils
.
writeErrMsg
(
response
,
(
ResultCode
)
ex
.
getResultCode
()
);
ResponseUtils
.
writeErrMsg
(
response
,
ResultCode
.
TOKEN_INVALID
);
return
;
}
// Token有效或无Token时继续执行过滤链
filterChain
.
doFilter
(
request
,
response
);
}
}
src/main/java/com/youlai/system/plugin/dupsubmit/aspect/DuplicateSubmitAspect.java
浏览文件 @
95fdbc2c
package
com.youlai.system.plugin.dupsubmit.aspect
;
import
cn.hutool.core.convert.Convert
;
import
cn.hutool.core.util.StrUtil
;
import
com.youlai.system.plugin.dupsubmit.annotation.PreventDuplicateSubmit
;
import
com.youlai.system.common.result.ResultCode
;
import
cn.hutool.jwt.JWTUtil
;
import
cn.hutool.jwt.RegisteredPayload
;
import
com.youlai.system.common.constant.SecurityConstants
;
import
com.youlai.system.common.exception.BusinessException
;
import
com.youlai.system.security.util.JwtUtils
;
import
com.youlai.system.common.result.ResultCode
;
import
com.youlai.system.plugin.dupsubmit.annotation.PreventDuplicateSubmit
;
import
jakarta.servlet.http.HttpServletRequest
;
import
lombok.RequiredArgsConstructor
;
import
lombok.extern.slf4j.Slf4j
;
...
...
@@ -69,8 +70,10 @@ public class DuplicateSubmitAspect {
HttpServletRequest
request
=
((
ServletRequestAttributes
)
RequestContextHolder
.
getRequestAttributes
()).
getRequest
();
String
token
=
request
.
getHeader
(
HttpHeaders
.
AUTHORIZATION
);
if
(
StrUtil
.
isNotBlank
(
token
))
{
String
jti
=
Convert
.
toStr
(
JwtUtils
.
parseToken
(
token
).
get
(
"jti"
),
null
);
if
(
StrUtil
.
isNotBlank
(
token
)
&&
token
.
startsWith
(
SecurityConstants
.
JWT_TOKEN_PREFIX
))
{
token
=
token
.
substring
(
SecurityConstants
.
JWT_TOKEN_PREFIX
.
length
());
// 从 JWT Token 中获取 jti
String
jti
=
(
String
)
JWTUtil
.
parseToken
(
token
).
getPayload
(
RegisteredPayload
.
JWT_ID
);
resubmitLockKey
=
RESUBMIT_LOCK_PREFIX
+
jti
+
":"
+
request
.
getMethod
()
+
"-"
+
request
.
getRequestURI
();
}
return
resubmitLockKey
;
...
...
src/main/java/com/youlai/system/security/constant/SecurityConstants.java
已删除
100644 → 0
浏览文件 @
69274645
package
com.youlai.system.security.constant
;
/**
* Security 常量
*
* @author haoxr
* @since 2.0.0
*/
public
interface
SecurityConstants
{
/**
* 登录接口路径
*/
String
LOGIN_PATH
=
"/api/v1/auth/login"
;
}
src/main/java/com/youlai/system/security/model/SysUserDetails.java
浏览文件 @
95fdbc2c
...
...
@@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollectionUtil;
import
cn.hutool.core.util.ObjectUtil
;
import
com.youlai.system.model.dto.UserAuthInfo
;
import
lombok.Data
;
import
lombok.Getter
;
import
lombok.NoArgsConstructor
;
import
org.springframework.security.core.GrantedAuthority
;
import
org.springframework.security.core.authority.SimpleGrantedAuthority
;
...
...
@@ -24,6 +25,7 @@ import java.util.stream.Collectors;
@NoArgsConstructor
public
class
SysUserDetails
implements
UserDetails
{
@Getter
private
Long
userId
;
private
String
username
;
...
...
@@ -60,10 +62,6 @@ public class SysUserDetails implements UserDetails {
this
.
dataScope
=
user
.
getDataScope
();
}
public
Long
getUserId
()
{
return
this
.
userId
;
}
@Override
public
Collection
<?
extends
GrantedAuthority
>
getAuthorities
()
{
...
...
src/main/java/com/youlai/system/security/util/JwtUtils.java
浏览文件 @
95fdbc2c
...
...
@@ -3,12 +3,10 @@ package com.youlai.system.security.util;
import
cn.hutool.core.convert.Convert
;
import
cn.hutool.core.date.DateUtil
;
import
cn.hutool.core.util.IdUtil
;
import
cn.hutool.core.util.StrUtil
;
import
cn.hutool.json.JSONArray
;
import
cn.hutool.jwt.JWT
;
import
cn.hutool.json.JSONObject
;
import
cn.hutool.jwt.JWTPayload
;
import
cn.hutool.jwt.JWTUtil
;
import
com.youlai.system.
security
.constant.JwtClaimConstants
;
import
com.youlai.system.
common
.constant.JwtClaimConstants
;
import
com.youlai.system.security.model.SysUserDetails
;
import
org.springframework.beans.factory.annotation.Value
;
import
org.springframework.security.authentication.UsernamePasswordAuthenticationToken
;
...
...
@@ -21,9 +19,9 @@ import java.util.*;
import
java.util.stream.Collectors
;
/**
* JWT 工具类
* JWT
Token
工具类
*
* @author
haoxr
* @author
Ray Hao
* @since 2.6.0
*/
@Component
...
...
@@ -41,12 +39,12 @@ public class JwtUtils {
private
static
int
ttl
;
@Value
(
"${jwt.key}"
)
@Value
(
"${
security.
jwt.key}"
)
public
void
setKey
(
String
key
)
{
JwtUtils
.
key
=
key
.
getBytes
();
}
@Value
(
"${jwt.ttl}"
)
@Value
(
"${
security.
jwt.ttl}"
)
public
void
setTtl
(
Integer
ttl
)
{
JwtUtils
.
ttl
=
ttl
;
}
...
...
@@ -57,7 +55,7 @@ public class JwtUtils {
* @param authentication 用户认证信息
* @return Token 字符串
*/
public
static
String
gener
ateToken
(
Authentication
authentication
)
{
public
static
String
cre
ateToken
(
Authentication
authentication
)
{
SysUserDetails
userDetails
=
(
SysUserDetails
)
authentication
.
getPrincipal
();
...
...
@@ -80,25 +78,25 @@ public class JwtUtils {
payload
.
put
(
JWTPayload
.
SUBJECT
,
authentication
.
getName
());
payload
.
put
(
JWTPayload
.
JWT_ID
,
IdUtil
.
simpleUUID
());
return
JWTUtil
.
createToken
(
payload
,
JwtUtils
.
key
);
return
JWTUtil
.
createToken
(
payload
,
key
);
}
/**
* 从 JWT Token 中解析 Authentication 用户认证信息
*
* @param payload JWT 载体
* @param payload
s
JWT 载体
* @return 用户认证信息
*/
public
static
UsernamePasswordAuthenticationToken
getAuthentication
(
Map
<
String
,
Object
>
payload
)
{
public
static
UsernamePasswordAuthenticationToken
getAuthentication
(
JSONObject
payloads
)
{
SysUserDetails
userDetails
=
new
SysUserDetails
();
userDetails
.
setUserId
(
Convert
.
toLong
(
payload
.
get
(
JwtClaimConstants
.
USER_ID
)
));
// 用户ID
userDetails
.
setDeptId
(
Convert
.
toLong
(
payload
.
get
(
JwtClaimConstants
.
DEPT_ID
)
));
// 部门ID
userDetails
.
setDataScope
(
Convert
.
toInt
(
payload
.
get
(
JwtClaimConstants
.
DATA_SCOPE
)
));
// 数据权限范围
userDetails
.
setUserId
(
payloads
.
getLong
(
JwtClaimConstants
.
USER_ID
));
// 用户ID
userDetails
.
setDeptId
(
payloads
.
getLong
(
JwtClaimConstants
.
DEPT_ID
));
// 部门ID
userDetails
.
setDataScope
(
payloads
.
getInt
(
JwtClaimConstants
.
DATA_SCOPE
));
// 数据权限范围
userDetails
.
setUsername
(
Convert
.
toStr
(
payload
.
get
(
JWTPayload
.
SUBJECT
)
));
// 用户名
userDetails
.
setUsername
(
payloads
.
getStr
(
JWTPayload
.
SUBJECT
));
// 用户名
// 角色集合
Set
<
SimpleGrantedAuthority
>
authorities
=
((
JSONArray
)
payload
.
get
(
JwtClaimConstants
.
AUTHORITIES
)
)
Set
<
SimpleGrantedAuthority
>
authorities
=
payloads
.
getJSONArray
(
JwtClaimConstants
.
AUTHORITIES
)
.
stream
()
.
map
(
authority
->
new
SimpleGrantedAuthority
(
Convert
.
toStr
(
authority
)))
.
collect
(
Collectors
.
toSet
());
...
...
@@ -107,30 +105,4 @@ public class JwtUtils {
}
/**
* 解析 JWT Token 获取载体信息
*
* @param token JWT Token
* @return 载体信息
*/
public
static
Map
<
String
,
Object
>
parseToken
(
String
token
)
{
try
{
if
(
StrUtil
.
isBlank
(
token
))
{
return
null
;
}
if
(
token
.
startsWith
(
"Bearer "
))
{
token
=
token
.
substring
(
7
);
}
JWT
jwt
=
JWTUtil
.
parseToken
(
token
);
if
(
jwt
.
setKey
(
JwtUtils
.
key
).
validate
(
0
))
{
return
jwt
.
getPayloads
();
}
}
catch
(
Exception
ignored
)
{
}
return
null
;
}
}
src/main/java/com/youlai/system/service/impl/AuthServiceImpl.java
浏览文件 @
95fdbc2c
...
...
@@ -3,10 +3,11 @@ package com.youlai.system.service.impl;
import
cn.hutool.captcha.AbstractCaptcha
;
import
cn.hutool.captcha.CaptchaUtil
;
import
cn.hutool.captcha.generator.CodeGenerator
;
import
cn.hutool.core.convert.Convert
;
import
cn.hutool.core.util.IdUtil
;
import
cn.hutool.core.util.StrUtil
;
import
cn.hutool.json.JSONObject
;
import
cn.hutool.jwt.JWTPayload
;
import
cn.hutool.jwt.JWTUtil
;
import
com.youlai.system.common.constant.SecurityConstants
;
import
com.youlai.system.common.enums.CaptchaTypeEnum
;
import
com.youlai.system.model.dto.CaptchaResult
;
...
...
@@ -28,7 +29,6 @@ import org.springframework.web.context.request.RequestContextHolder;
import
org.springframework.web.context.request.ServletRequestAttributes
;
import
java.awt.*
;
import
java.util.Map
;
import
java.util.concurrent.TimeUnit
;
/**
...
...
@@ -43,7 +43,7 @@ import java.util.concurrent.TimeUnit;
public
class
AuthServiceImpl
implements
AuthService
{
private
final
AuthenticationManager
authenticationManager
;
private
final
RedisTemplate
<
String
,
Object
>
redisTemplate
;
private
final
RedisTemplate
<
String
,
Object
>
redisTemplate
;
private
final
CodeGenerator
codeGenerator
;
private
final
Font
captchaFont
;
private
final
CaptchaProperties
captchaProperties
;
...
...
@@ -57,10 +57,13 @@ public class AuthServiceImpl implements AuthService {
*/
@Override
public
LoginResult
login
(
String
username
,
String
password
)
{
// 认证用户信息
UsernamePasswordAuthenticationToken
authenticationToken
=
new
UsernamePasswordAuthenticationToken
(
username
.
toLowerCase
().
trim
(),
password
);
// 认证
Authentication
authentication
=
authenticationManager
.
authenticate
(
authenticationToken
);
String
accessToken
=
JwtUtils
.
generateToken
(
authentication
);
// 认证成功,生成Token
String
accessToken
=
JwtUtils
.
createToken
(
authentication
);
return
LoginResult
.
builder
()
.
tokenType
(
"Bearer"
)
.
accessToken
(
accessToken
)
...
...
@@ -74,18 +77,24 @@ public class AuthServiceImpl implements AuthService {
public
void
logout
()
{
HttpServletRequest
request
=
((
ServletRequestAttributes
)
RequestContextHolder
.
getRequestAttributes
()).
getRequest
();
String
token
=
request
.
getHeader
(
HttpHeaders
.
AUTHORIZATION
);
if
(
StrUtil
.
isNotBlank
(
token
))
{
Map
<
String
,
Object
>
claims
=
JwtUtils
.
parseToken
(
token
);
String
jti
=
Convert
.
toStr
(
claims
.
get
(
JWTPayload
.
JWT_ID
));
Long
expiration
=
Convert
.
toLong
(
claims
.
get
(
JWTPayload
.
EXPIRES_AT
));
if
(
StrUtil
.
isNotBlank
(
token
)
&&
token
.
startsWith
(
SecurityConstants
.
JWT_TOKEN_PREFIX
))
{
token
=
token
.
substring
(
SecurityConstants
.
JWT_TOKEN_PREFIX
.
length
());
// 解析Token以获取有效载荷(payload)
JSONObject
payloads
=
JWTUtil
.
parseToken
(
token
).
getPayloads
();
// 解析 Token 获取 jti(JWT ID) 和 exp(过期时间)
String
jti
=
payloads
.
getStr
(
JWTPayload
.
JWT_ID
);
Long
expiration
=
payloads
.
getLong
(
JWTPayload
.
EXPIRES_AT
);
// 如果exp存在,则计算Token剩余有效时间
if
(
expiration
!=
null
)
{
// 将Token的jti加入黑名单,并设置剩余有效时间,使其在过期后自动从黑名单移除
long
ttl
=
expiration
-
System
.
currentTimeMillis
()
/
1000
;
redisTemplate
.
opsForValue
().
set
(
SecurityConstants
.
BLACKLIST_TOKEN_PREFIX
+
jti
,
null
,
ttl
,
TimeUnit
.
SECONDS
);
}
else
{
// 如果exp不存在,说明Token永不过期,则永久加入黑名单
redisTemplate
.
opsForValue
().
set
(
SecurityConstants
.
BLACKLIST_TOKEN_PREFIX
+
jti
,
null
);
}
}
// 清空Spring Security上下文
SecurityContextHolder
.
clearContext
();
}
...
...
src/main/resources/application-dev.yml
浏览文件 @
95fdbc2c
...
...
@@ -57,12 +57,21 @@ mybatis-plus:
log-impl
:
org.apache.ibatis.logging.stdout.StdOutImpl
# 认证配置
jwt
:
# 密钥
key
:
SecretKey012345678901234567890123456789012345678901234567890123456789
# token 过期时间(单位:秒)
ttl
:
7200
# 安全配置
security
:
jwt
:
key
:
SecretKey012345678901234567890123456789012345678901234567890123456789
ttl
:
7200
ignore-urls
:
-
/v3/api-docs/**
-
/doc.html
-
/swagger-resources/**
-
/webjars/**
-
/doc.html
-
/swagger-ui/**
-
/swagger-ui.html
-
/api/v1/auth/captcha
oss
:
# OSS 类型 (目前支持aliyun、minio)
...
...
src/main/resources/application-prod.yml
浏览文件 @
95fdbc2c
...
...
@@ -57,13 +57,22 @@ mybatis-plus:
log-impl
:
org.apache.ibatis.logging.stdout.StdOutImpl
# 认证配置
jwt
:
# 密钥
key
:
SecretKey012345678901234567890123456789012345678901234567890123456789
# token 过期时间(单位:秒)
ttl
:
7200
# 安全配置
security
:
jwt
:
key
:
SecretKey012345678901234567890123456789012345678901234567890123456789
ttl
:
7200
ignore-urls
:
-
/v3/api-docs/**
-
/doc.html
-
/swagger-resources/**
-
/webjars/**
-
/doc.html
-
/swagger-ui/**
-
/swagger-ui.html
-
/api/v1/auth/captcha
# 文件上传配置
oss
:
# OSS 类型 (目前支持aliyun、minio)
type
:
minio
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录