更新文档

This commit is contained in:
Eric
2026-02-10 17:24:47 +08:00
parent 2c8995653a
commit 6b6a728cf9
12 changed files with 1827 additions and 586 deletions

View File

@@ -0,0 +1,105 @@
# 一、使用说明
1.引入依赖
```xml
<dependency>
<groupId>org.lingniu</groupId>
<artifactId>oauth2-login-sdk</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
```
2.添加配置
```yaml
spring:
security:
oauth2:
resourceserver:
jwt:
# 资源服务器 认证公钥地址
jwk-set-uri: http://localhost:8000/oauth2/jwks
client:
registration:
portal:
# 统一登录颁发的client_id
client-id: xxx
# 统一登录颁发的秘钥
client-secret: xxx
# 当前对接客户端名称 随便填
client-name: xxx
# 认证类型 使用授权码类型
authorization-grant-type: authorization_code
# 认证地址
redirect-uri: http://106.14.217.120/portal-ui/callback
# 权限范围
scope:
- openid
- profile
# 返回权限
- perms
provider: idp
provider:
idp:
# sso登录地址
authorization-uri: http://106.14.217.120/idp-ui/sso
# token 获取接口
token-uri: http://localhost:8082/oauth2/token
# 用户信息接口
user-info-uri: http://localhost:8082/userinfo
# 认证公钥地址
jwk-set-uri: http://localhost:8082/oauth2/jwks
# 用户信息属性
user-name-attribute: sub
```
3. 启动项目
# 二 、 权限配置
如果不做额外配置,接入成功后默认所有接口都是登录成功后即可访问,如果需要对接口进行更精确精细化的权限控制,提供了如下注解
- @PreAuthorize:方法执行前进行权限检查
- @PostAuthorize:方法执行后进行权限检查
- @Secured:类似于 @PreAuthorize
- security提供了许多默认表达式
![img.png](img.png)
结合SpEl表达是进行复杂配置
```java
@Service
public class HelloService {
@PreAuthorize("principal.username.equals('admin')")
public String hello() {
return "hello";
}
@PreAuthorize("principal.username.equals(#abc)")
public String admin(String abc) {
return "admin";
}
@Secured({"ROLE_user"})
public String user() {
return "user";
}
@PreAuthorize("#age>98")
public String getAge(Integer age) {
return String.valueOf(age);
}
@PostAuthorize("returnObject == null || returnObject.id%2==0")
public User findUserById(Long id) {
// 根据id查找用户无论用户是否存在id是偶数的用户才能获取到结果
// 实现根据id查找用户的逻辑...
return userRepository.findById(id).orElse(null);
}
@GetMapping("/testPermission1")
@PreAuthorize("@ss.hasPermission('def')")
public String testPermission1() {
return "testPermission1 有权访问";
}
@GetMapping("/testPermission2")
@PreAuthorize("@ss.hasPermission(#code)")
public String testPermission2(String code) {
return "testPermission2 有权访问";
}
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

View File

@@ -3,8 +3,6 @@ package org.lingniu.sdk.config;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.lingniu.sdk.common.redis.RedisCache;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -60,9 +58,4 @@ public class SdkRedisConfig {
template.afterPropertiesSet();
return template;
}
@Bean
@ConditionalOnMissingBean(name="redisCache")
public RedisCache redisCache(@Qualifier("redisTemplate")RedisTemplate redisTemplate){
return new RedisCache(redisTemplate);
}
}

View File

@@ -0,0 +1,27 @@
package org.lingniu.sdk.context;
import org.lingniu.sdk.common.convert.Convert;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
/**
* 权限信息
*
* @author ruoyi
*/
public class PermissionContextHolder
{
private static final String PERMISSION_CONTEXT_ATTRIBUTES = "PERMISSION_CONTEXT";
public static void setContext(String permission)
{
RequestContextHolder.currentRequestAttributes().setAttribute(PERMISSION_CONTEXT_ATTRIBUTES, permission,
RequestAttributes.SCOPE_REQUEST);
}
public static String getContext()
{
return Convert.toStr(RequestContextHolder.currentRequestAttributes().getAttribute(PERMISSION_CONTEXT_ATTRIBUTES,
RequestAttributes.SCOPE_REQUEST));
}
}

View File

@@ -0,0 +1,158 @@
package org.lingniu.sdk.service;//package com.ruoyi.framework.web.service;
import org.lingniu.sdk.constant.Constants;
import org.lingniu.sdk.context.PermissionContextHolder;
import org.lingniu.sdk.model.user.UserInfo;
import org.lingniu.sdk.utils.UserUtil;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.util.Set;
/**
* RuoYi首创 自定义权限实现ss取自SpringSecurity首字母
*
* @author ruoyi
*/
@Service("ss")
public class PermissionService
{
/**
* 验证用户是否具备某权限
*
* @param permission 权限字符串
* @return 用户是否具备某权限
*/
public boolean hasPermi(String permission)
{
if (!StringUtils.hasText(permission))
{
return false;
}
UserInfo userInfo = UserUtil.getUserInfo();
if (userInfo==null || CollectionUtils.isEmpty(userInfo.getPermissions()))
{
return false;
}
PermissionContextHolder.setContext(permission);
return hasPermissions(userInfo.getPermissions(), permission);
}
/**
* 验证用户是否不具备某权限,与 hasPermi逻辑相反
*
* @param permission 权限字符串
* @return 用户是否不具备某权限
*/
public boolean lacksPermi(String permission)
{
return hasPermi(permission) != true;
}
/**
* 验证用户是否具有以下任意一个权限
*
* @param permissions 以 PERMISSION_DELIMITER 为分隔符的权限列表
* @return 用户是否具有以下任意一个权限
*/
public boolean hasAnyPermi(String permissions)
{
if (StringUtils.isEmpty(permissions))
{
return false;
}
UserInfo userInfo = UserUtil.getUserInfo();
if (userInfo==null || CollectionUtils.isEmpty(userInfo.getPermissions()))
{
return false;
}
PermissionContextHolder.setContext(permissions);
Set<String> authorities = userInfo.getPermissions();
for (String permission : permissions.split(Constants.PERMISSION_DELIMITER))
{
if (hasPermissions(authorities, permission))
{
return true;
}
}
return false;
}
/**
* 判断用户是否拥有某个角色
*
* @param role 角色字符串
* @return 用户是否具备某角色
*/
public boolean hasRole(String role)
{
if (StringUtils.hasLength(role))
{
return false;
}
UserInfo userInfo = UserUtil.getUserInfo();
if (userInfo==null || CollectionUtils.isEmpty(userInfo.getRoles()))
{
return false;
}
for (String sysRole : userInfo.getRoles())
{
if (Constants.SUPER_ADMIN.equals(sysRole) || sysRole.equals(StringUtils.trimAllWhitespace(role)))
{
return true;
}
}
return false;
}
/**
* 验证用户是否不具备某角色,与 isRole逻辑相反。
*
* @param role 角色名称
* @return 用户是否不具备某角色
*/
public boolean lacksRole(String role)
{
return hasRole(role) != true;
}
/**
* 验证用户是否具有以下任意一个角色
*
* @param roles 以 ROLE_DELIMITER 为分隔符的角色列表
* @return 用户是否具有以下任意一个角色
*/
public boolean hasAnyRoles(String roles)
{
if (StringUtils.isEmpty(roles))
{
return false;
}
UserInfo userInfo = UserUtil.getUserInfo();
if (userInfo==null|| CollectionUtils.isEmpty(userInfo.getRoles()))
{
return false;
}
for (String role : roles.split(Constants.ROLE_DELIMITER))
{
if (hasRole(role))
{
return true;
}
}
return false;
}
/**
* 判断是否包含权限
*
* @param permissions 权限列表
* @param permission 权限字符串
* @return 用户是否具备某权限
*/
private boolean hasPermissions(Set<String> permissions, String permission)
{
return permissions.contains(Constants.ALL_PERMISSION) || permissions.contains(StringUtils.trimAllWhitespace(permission));
}
}

View File

@@ -1,89 +1,225 @@
package org.lingniu.sdk.service;
import lombok.extern.slf4j.Slf4j;
import org.lingniu.sdk.common.redis.RedisCache;
import org.lingniu.sdk.constant.CacheConstants;
import org.lingniu.sdk.model.token.AccessTokenInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.time.Instant;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@Component
@Slf4j
public class RedisAccessTokenService {
@Autowired
private RedisCache redisCache;
private final long ACCESS_TOKEN_EXPIRE = 3600; // 1小时
private final RedisTemplate<String, Object> redisTemplate;
public RedisAccessTokenService(RedisTemplate<String, Object> sdkRedisTemplate) {
this.redisTemplate = sdkRedisTemplate;
}
/**
* 存储Access Token到Redis
*/
public void storeAccessToken(AccessTokenInfo tokenInfo) {
if (tokenInfo == null || tokenInfo.getTokenValue() == null) {
log.error("tokenInfo或tokenValue为空");
return;
}
String key = String.format(CacheConstants.ACCESS_TOKEN_KEY, tokenInfo.getTokenValue());
try {
redisCache.setCacheMap(key,tokenInfo.toMap());
Map<String, Object> tokenMap = tokenInfo.toMap();
// 使用Hash类型存储
redisTemplate.opsForHash().putAll(key, tokenMap);
// 设置过期时间
Instant expiresAt = tokenInfo.getExpiresAt();
long expire = ACCESS_TOKEN_EXPIRE;
if(expiresAt!=null){
expire = expiresAt.getEpochSecond() - Instant.now().getEpochSecond();
// 1小时
long expireSeconds = 3600;
if (expiresAt != null) {
expireSeconds = expiresAt.getEpochSecond() - Instant.now().getEpochSecond();
if (expireSeconds <= 0) {
log.warn("token已过期不再存储: {}", tokenInfo.getTokenValue());
redisTemplate.delete(key); // 删除可能已存在的key
return;
}
}
redisCache.expire(key,expire);
redisTemplate.expire(key, expireSeconds, TimeUnit.SECONDS);
log.debug("存储Access Token成功: key={}, expireSeconds={}", key, expireSeconds);
} catch (Exception e) {
log.error("存储Access Token失败", e);
log.error("存储Access Token失败: token={}", tokenInfo.getTokenValue(), e);
}
}
/**
* 验证Access Token
*/
public boolean validateAccessToken(String token) {
String key = String.format(CacheConstants.ACCESS_TOKEN_KEY, token);
if(!redisCache.hasKey(key)){
if (token == null || token.trim().isEmpty()) {
return false;
}
AccessTokenInfo accessTokenInfo = getAccessTokenInfo(token);
if(accessTokenInfo==null){
return false;
}
return accessTokenInfo.isValid();
}
String key = String.format(CacheConstants.ACCESS_TOKEN_KEY, token);
// 检查key是否存在
Boolean exists = redisTemplate.hasKey(key);
if (!exists) {
return false;
}
// 获取token信息并验证
AccessTokenInfo accessTokenInfo = getAccessTokenInfo(token);
return accessTokenInfo != null && accessTokenInfo.isValid();
}
/**
* 删除Access Token
*/
public boolean removeAccessToken(String token) {
if (token == null || token.trim().isEmpty()) {
return false;
}
String key = String.format(CacheConstants.ACCESS_TOKEN_KEY, token);
return redisCache.deleteObject(key);
try {
Boolean deleted = redisTemplate.delete(key);
log.debug("删除Access Token: key={}, 结果={}", key, deleted);
return deleted;
} catch (Exception e) {
log.error("删除Access Token失败: token={}", token, e);
return false;
}
}
/**
* 获取Access Token信息
*/
public AccessTokenInfo getAccessTokenInfo(String token) {
String key = String.format(CacheConstants.ACCESS_TOKEN_KEY, token);
Map<String, Object> cacheMap = redisCache.getCacheMap(key);
if(cacheMap!=null){
return AccessTokenInfo.fromMap(cacheMap);
if (token == null || token.trim().isEmpty()) {
return null;
}
String key = String.format(CacheConstants.ACCESS_TOKEN_KEY, token);
try {
// 获取所有hash字段
Map<Object, Object> entries = redisTemplate.opsForHash().entries(key);
if (entries.isEmpty()) {
return null;
}
// 转换为Map<String, Object>
Map<String, Object> cacheMap = convertMap(entries);
return AccessTokenInfo.fromMap(cacheMap);
} catch (Exception e) {
log.error("获取Access Token信息失败: token={}", token, e);
return null;
}
return null;
}
/**
* 作废 删除
* @param tokenInfo
*/
public void revokeAccessToken(AccessTokenInfo tokenInfo) {
if(tokenInfo==null){
if (tokenInfo == null || tokenInfo.getTokenValue() == null) {
return;
}
String key = String.format(CacheConstants.ACCESS_TOKEN_KEY, tokenInfo.getTokenValue());
redisCache.deleteObject(key);
try {
Boolean deleted = redisTemplate.delete(key);
log.debug("作废Access Token: key={}, 结果={}", key, deleted);
} catch (Exception e) {
log.error("作废Access Token失败: token={}", tokenInfo.getTokenValue(), e);
}
}
/**
* 刷新Access Token过期时间
*/
public boolean refreshTokenExpire(String token, long expireSeconds) {
if (token == null || token.trim().isEmpty() || expireSeconds <= 0) {
return false;
}
String key = String.format(CacheConstants.ACCESS_TOKEN_KEY, token);
try {
Boolean exists = redisTemplate.hasKey(key);
if (exists) {
Boolean refreshed = redisTemplate.expire(key, expireSeconds, TimeUnit.SECONDS);
log.debug("刷新Token过期时间: key={}, expireSeconds={}, 结果={}",
key, expireSeconds, refreshed);
return refreshed;
}
return false;
} catch (Exception e) {
log.error("刷新Token过期时间失败: token={}", token, e);
return false;
}
}
/**
* 获取Token剩余生存时间
*/
public Long getTokenTtl(String token) {
if (token == null || token.trim().isEmpty()) {
return null;
}
String key = String.format(CacheConstants.ACCESS_TOKEN_KEY, token);
try {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
} catch (Exception e) {
log.error("获取Token剩余生存时间失败: token={}", token, e);
return null;
}
}
/**
* 辅助方法转换Map类型
*/
@SuppressWarnings("unchecked")
private Map<String, Object> convertMap(Map<Object, Object> originalMap) {
// 根据实际情况转换,这里假设键值都是可序列化的对象
// 如果键都是String值都是可序列化的对象可以直接转换
return (Map<String, Object>) (Map<?, ?>) originalMap;
}
/**
* 另一种实现方式使用StringRedisTemplate如果需要明确的字符串类型
*
* @Autowired
* private StringRedisTemplate stringRedisTemplate;
*
* 注意使用StringRedisTemplate时存储和获取Map需要额外的序列化处理
*/
/**
* 批量删除包含特定模式的token可选功能
*/
public Long batchRemoveTokens(String pattern) {
if (pattern == null || pattern.trim().isEmpty()) {
return 0L;
}
try {
// 使用scan命令避免阻塞
var keys = redisTemplate.keys(pattern);
if (!keys.isEmpty()) {
Long count = redisTemplate.delete(keys);
log.debug("批量删除Token: pattern={}, 删除数量={}", pattern, count);
return count;
}
return 0L;
} catch (Exception e) {
log.error("批量删除Token失败: pattern={}", pattern, e);
return 0L;
}
}
}

View File

@@ -1,81 +1,287 @@
package org.lingniu.sdk.service;
import lombok.extern.slf4j.Slf4j;
import org.lingniu.sdk.common.redis.RedisCache;
import org.lingniu.sdk.constant.CacheConstants;
import org.lingniu.sdk.model.token.RefreshTokenInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.time.Instant;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@Component
@Slf4j
public class RedisRefreshTokenService {
@Autowired
private RedisCache redisCache;
private final long REFRESH_TOKEN_EXPIRE = 30 * 24 * 3600L; // 30天
private final RedisTemplate<String, Object> redisTemplate;
public RedisRefreshTokenService(RedisTemplate<String, Object> sdkRedisTemplate) {
this.redisTemplate = sdkRedisTemplate;
}
/**
* 存储Refresh Token到Redis Hash
*/
public void storeRefreshToken(RefreshTokenInfo tokenInfo) {
if(tokenInfo==null){
if (tokenInfo == null || tokenInfo.getTokenValue() == null) {
log.error("tokenInfo或tokenValue为空");
return;
}
String key = String.format(CacheConstants.REFRESH_TOKEN_KEY, tokenInfo.getTokenValue());
redisCache.setCacheMap(key,tokenInfo.toMap());
Instant expiresAt = tokenInfo.getExpiresAt();
long expire = REFRESH_TOKEN_EXPIRE;
if(expiresAt!=null){
expire = expiresAt.getEpochSecond() - Instant.now().getEpochSecond();
}
redisCache.expire(key,expire);
// 维护用户会话列表
String userSessionsKey = String.format(CacheConstants.USER_SESSIONS, tokenInfo.getUsername());
redisCache.setCacheSet(userSessionsKey,tokenInfo.getTokenValue());
redisCache.expire(userSessionsKey,expire);
try {
String key = String.format(CacheConstants.REFRESH_TOKEN_KEY, tokenInfo.getTokenValue());
// 使用Hash存储token信息
Map<String, String> tokenMap = tokenInfo.toMap();
redisTemplate.opsForHash().putAll(key, tokenMap);
// 设置过期时间
Instant expiresAt = tokenInfo.getExpiresAt();
// 30天
long expireSeconds = 30 * 24 * 3600L;
if (expiresAt != null) {
expireSeconds = expiresAt.getEpochSecond() - Instant.now().getEpochSecond();
if (expireSeconds <= 0) {
log.warn("Refresh Token已过期不再存储: {}", tokenInfo.getTokenValue());
redisTemplate.delete(key);
return;
}
}
redisTemplate.expire(key, expireSeconds, TimeUnit.SECONDS);
log.debug("存储Refresh Token成功: key={}, expireSeconds={}", key, expireSeconds);
// 维护用户会话列表 - 使用Set存储用户的refresh token
String userSessionsKey = String.format(CacheConstants.USER_SESSIONS, tokenInfo.getUsername());
redisTemplate.opsForSet().add(userSessionsKey, tokenInfo.getTokenValue());
redisTemplate.expire(userSessionsKey, expireSeconds, TimeUnit.SECONDS);
} catch (Exception e) {
log.error("存储Refresh Token失败: token={}", tokenInfo.getTokenValue(), e);
}
}
/**
* 获取Refresh Token信息
*/
public RefreshTokenInfo getRefreshTokenInfo(String token) {
String key = String.format(CacheConstants.REFRESH_TOKEN_KEY, token);
Map<String, Object> cacheMap = redisCache.getCacheMap(key);
if(cacheMap!=null){
return RefreshTokenInfo.fromMap(cacheMap);
if (token == null || token.trim().isEmpty()) {
return null;
}
String key = String.format(CacheConstants.REFRESH_TOKEN_KEY, token);
try {
// 获取所有hash字段
Map<Object, Object> entries = redisTemplate.opsForHash().entries(key);
if (entries.isEmpty()) {
return null;
}
// 转换为Map<String, Object>
Map<String, Object> cacheMap = convertMap(entries);
return RefreshTokenInfo.fromMap(cacheMap);
} catch (Exception e) {
log.error("获取Refresh Token信息失败: token={}", token, e);
return null;
}
return null;
}
/**
* 更新Refresh Token最后使用时间
*/
public void updateRefreshToken(RefreshTokenInfo tokenInfo) {
if(tokenInfo==null){
if (tokenInfo == null || tokenInfo.getTokenValue() == null) {
log.error("tokenInfo或tokenValue为空");
return;
}
String key = String.format(CacheConstants.REFRESH_TOKEN_KEY, tokenInfo.getTokenValue());
redisCache.setCacheMap(key,tokenInfo.toUpdateMap());
try {
String key = String.format(CacheConstants.REFRESH_TOKEN_KEY, tokenInfo.getTokenValue());
Map<String, String> updateMap = tokenInfo.toUpdateMap();
// 更新指定字段
for (Map.Entry<String, String> entry : updateMap.entrySet()) {
redisTemplate.opsForHash().put(key, entry.getKey(), entry.getValue());
}
log.debug("更新Refresh Token成功: token={}", tokenInfo.getTokenValue());
} catch (Exception e) {
log.error("更新Refresh Token失败: token={}", tokenInfo.getTokenValue(), e);
}
}
/**
* 作废 删除
* @param tokenInfo
* 作废/删除Refresh Token
*/
public void revokeRefreshToken(RefreshTokenInfo tokenInfo) {
if(tokenInfo==null){
if (tokenInfo == null) {
log.warn("tokenInfo为空");
return;
}
String key = String.format(CacheConstants.REFRESH_TOKEN_KEY, tokenInfo.getTokenValue());
String userSessionsKey = String.format(CacheConstants.USER_SESSIONS, tokenInfo.getUsername());
redisCache.deleteCacheSetValue(userSessionsKey,tokenInfo.getTokenValue());
redisCache.deleteObject(key);
try {
String key = String.format(CacheConstants.REFRESH_TOKEN_KEY, tokenInfo.getTokenValue());
Boolean deleted = redisTemplate.delete(key);
if (tokenInfo.getUsername() != null) {
// 从用户会话列表中移除
String userSessionsKey = String.format(CacheConstants.USER_SESSIONS, tokenInfo.getUsername());
Long removed = redisTemplate.opsForSet().remove(userSessionsKey, tokenInfo.getTokenValue());
log.debug("从用户会话列表移除token: user={}, token={}, 移除数量={}",
tokenInfo.getUsername(), tokenInfo.getTokenValue(), removed);
}
log.debug("作废Refresh Token: token={}, 删除结果={}", tokenInfo.getTokenValue(), deleted);
} catch (Exception e) {
log.error("作废Refresh Token失败: token={}", tokenInfo.getTokenValue(), e);
}
}
/**
* 验证Refresh Token是否存在
*/
public boolean validateRefreshToken(String token) {
if (token == null || token.trim().isEmpty()) {
return false;
}
String key = String.format(CacheConstants.REFRESH_TOKEN_KEY, token);
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
log.error("验证Refresh Token失败: token={}", token, e);
return false;
}
}
/**
* 获取用户的所有Refresh Tokens
*/
public Long getUserRefreshTokensCount(String username) {
if (username == null || username.trim().isEmpty()) {
return 0L;
}
String userSessionsKey = String.format(CacheConstants.USER_SESSIONS, username);
try {
Long size = redisTemplate.opsForSet().size(userSessionsKey);
return size != null ? size : 0L;
} catch (Exception e) {
log.error("获取用户Refresh Tokens数量失败: user={}", username, e);
return 0L;
}
}
/**
* 清理用户的所有Refresh Tokens登出所有设备
*/
public Long revokeAllUserRefreshTokens(String username) {
if (username == null || username.trim().isEmpty()) {
return 0L;
}
try {
String userSessionsKey = String.format(CacheConstants.USER_SESSIONS, username);
// 获取用户的所有token
Set<Object> tokens = redisTemplate.opsForSet().members(userSessionsKey);
if (tokens == null || tokens.isEmpty()) {
return 0L;
}
long deletedCount = 0;
for (Object tokenObj : tokens) {
if (tokenObj instanceof String token) {
String tokenKey = String.format(CacheConstants.REFRESH_TOKEN_KEY, token);
Boolean deleted = redisTemplate.delete(tokenKey);
if (deleted) {
deletedCount++;
}
}
}
// 删除用户会话列表
redisTemplate.delete(userSessionsKey);
log.debug("清理用户所有Refresh Tokens: user={}, 清理数量={}", username, deletedCount);
return deletedCount;
} catch (Exception e) {
log.error("清理用户所有Refresh Tokens失败: user={}", username, e);
return 0L;
}
}
/**
* 获取Refresh Token剩余生存时间
*/
public Long getRefreshTokenTtl(String token) {
if (token == null || token.trim().isEmpty()) {
return null;
}
String key = String.format(CacheConstants.REFRESH_TOKEN_KEY, token);
try {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
} catch (Exception e) {
log.error("获取Refresh Token剩余生存时间失败: token={}", token, e);
return null;
}
}
/**
* 刷新Refresh Token过期时间
*/
public boolean refreshTokenExpire(String token, long expireSeconds) {
if (token == null || token.trim().isEmpty() || expireSeconds <= 0) {
return false;
}
String key = String.format(CacheConstants.REFRESH_TOKEN_KEY, token);
try {
Boolean exists = redisTemplate.hasKey(key);
if (exists) {
Boolean refreshed = redisTemplate.expire(key, expireSeconds, TimeUnit.SECONDS);
log.debug("刷新Refresh Token过期时间: token={}, expireSeconds={}, 结果={}",
token, expireSeconds, refreshed);
return refreshed;
}
return false;
} catch (Exception e) {
log.error("刷新Refresh Token过期时间失败: token={}", token, e);
return false;
}
}
/**
* 辅助方法转换Map类型
*/
@SuppressWarnings("unchecked")
private Map<String, Object> convertMap(Map<Object, Object> originalMap) {
// 根据实际情况转换,这里假设键值都是可序列化的对象
return (Map<String, Object>) (Map<?, ?>) originalMap;
}
/**
* 检查用户会话列表中是否包含指定token
*/
public boolean isTokenInUserSessions(String username, String token) {
if (username == null || token == null) {
return false;
}
String userSessionsKey = String.format(CacheConstants.USER_SESSIONS, username);
try {
Boolean isMember = redisTemplate.opsForSet().isMember(userSessionsKey, token);
return Boolean.TRUE.equals(isMember);
} catch (Exception e) {
log.error("检查用户会话列表失败: user={}, token={}", username, token, e);
return false;
}
}
}

View File

@@ -0,0 +1,17 @@
package org.lingniu.sdk.utils;
import org.lingniu.sdk.model.user.UserInfo;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
public class UserUtil {
public static UserInfo getUserInfo(){
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if(authentication instanceof OAuth2AuthenticationToken oAuth2AuthenticationToken){
return (UserInfo) oAuth2AuthenticationToken.getPrincipal();
}
return null;
}
}

View File

@@ -28,6 +28,10 @@ public class UserController {
this.oAuth2ClientProperties = oAuth2ClientProperties;
this.objectMapper = objectMapper;
}
@GetMapping("/getUserInfo")
public CommonResult<UserInfo> getUserInfo(@AuthenticationPrincipal UserInfo userInfo) throws Exception {
return CommonResult.success(userInfo);
}
@GetMapping("/routes")
public CommonResult<Object> getUserMenu(@RegisteredOAuth2AuthorizedClient OAuth2AuthorizedClient oAuth2AuthorizedClient) throws Exception {