jdk 17
This commit is contained in:
135
sdk/backend/oauth2-login-sdk/pom.xml
Normal file
135
sdk/backend/oauth2-login-sdk/pom.xml
Normal file
@@ -0,0 +1,135 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>oauth2-login-sdk</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<groupId>org.lingniu</groupId>
|
||||
<packaging>jar</packaging>
|
||||
<name>OAuth2 Login SDK</name>
|
||||
<description>OAuth2登录SDK后端Java版本</description>
|
||||
<properties>
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<spring-security.version>6.5.7</spring-security.version>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-config</artifactId>
|
||||
<version>${spring-security.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-oauth2-client</artifactId>
|
||||
<version>${spring-security.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-oauth2-resource-server</artifactId>
|
||||
<version>${spring-security.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-oauth2-jose</artifactId>
|
||||
<version>${spring-security.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot</artifactId>
|
||||
<version>3.5.10</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-autoconfigure</artifactId>
|
||||
<version>3.5.10</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.tomcat.embed</groupId>
|
||||
<artifactId>tomcat-embed-core</artifactId>
|
||||
<version>11.0.15</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.42</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||
<version>3.5.10</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
<version>2.20.2</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>2.20.2</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.fastjson2</groupId>
|
||||
<artifactId>fastjson2</artifactId>
|
||||
<version>2.0.60</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||
<version>2.19.4</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.nimbusds</groupId>
|
||||
<artifactId>nimbus-jose-jwt</artifactId>
|
||||
<version>10.0.2</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-codec</artifactId>
|
||||
<version>4.1.130.Final</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.11.0</version>
|
||||
<configuration>
|
||||
<source>17</source>
|
||||
<target>17</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<licenses>
|
||||
<license>
|
||||
<name>MIT License</name>
|
||||
<url>https://opensource.org/licenses/MIT</url>
|
||||
<distribution>repo</distribution>
|
||||
</license>
|
||||
</licenses>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,271 @@
|
||||
package org.lingniu.sdk.common.redis;
|
||||
|
||||
import org.springframework.data.redis.core.BoundSetOperations;
|
||||
import org.springframework.data.redis.core.HashOperations;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.core.ValueOperations;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* spring redis 工具类
|
||||
*
|
||||
* @author portal
|
||||
**/
|
||||
public class RedisCache
|
||||
{
|
||||
public final RedisTemplate redisTemplate;
|
||||
public RedisCache(RedisTemplate redisTemplate){
|
||||
this.redisTemplate = redisTemplate;
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存基本的对象,Integer、String、实体类等
|
||||
*
|
||||
* @param key 缓存的键值
|
||||
* @param value 缓存的值
|
||||
*/
|
||||
public <T> void setCacheObject(final String key, final T value)
|
||||
{
|
||||
redisTemplate.opsForValue().set(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存基本的对象,Integer、String、实体类等
|
||||
*
|
||||
* @param key 缓存的键值
|
||||
* @param value 缓存的值
|
||||
* @param timeout 时间
|
||||
* @param timeUnit 时间颗粒度
|
||||
*/
|
||||
public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit)
|
||||
{
|
||||
redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置有效时间
|
||||
*
|
||||
* @param key Redis键
|
||||
* @param timeout 超时时间
|
||||
* @return true=设置成功;false=设置失败
|
||||
*/
|
||||
public boolean expire(final String key, final long timeout)
|
||||
{
|
||||
return expire(key, timeout, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置有效时间
|
||||
*
|
||||
* @param key Redis键
|
||||
* @param timeout 超时时间
|
||||
* @param unit 时间单位
|
||||
* @return true=设置成功;false=设置失败
|
||||
*/
|
||||
public boolean expire(final String key, final long timeout, final TimeUnit unit)
|
||||
{
|
||||
return redisTemplate.expire(key, timeout, unit);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取有效时间
|
||||
*
|
||||
* @param key Redis键
|
||||
* @return 有效时间
|
||||
*/
|
||||
public long getExpire(final String key)
|
||||
{
|
||||
return redisTemplate.getExpire(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断 key是否存在
|
||||
*
|
||||
* @param key 键
|
||||
* @return true 存在 false不存在
|
||||
*/
|
||||
public Boolean hasKey(String key)
|
||||
{
|
||||
return redisTemplate.hasKey(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得缓存的基本对象。
|
||||
*
|
||||
* @param key 缓存键值
|
||||
* @return 缓存键值对应的数据
|
||||
*/
|
||||
public <T> T getCacheObject(final String key)
|
||||
{
|
||||
ValueOperations<String, T> operation = redisTemplate.opsForValue();
|
||||
return operation.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除单个对象
|
||||
*
|
||||
* @param key
|
||||
*/
|
||||
public boolean deleteObject(final String key)
|
||||
{
|
||||
return redisTemplate.delete(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除集合对象
|
||||
*
|
||||
* @param collection 多个对象
|
||||
* @return
|
||||
*/
|
||||
public boolean deleteObject(final Collection collection)
|
||||
{
|
||||
return redisTemplate.delete(collection) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存List数据
|
||||
*
|
||||
* @param key 缓存的键值
|
||||
* @param dataList 待缓存的List数据
|
||||
* @return 缓存的对象
|
||||
*/
|
||||
public <T> long setCacheList(final String key, final List<T> dataList)
|
||||
{
|
||||
Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
|
||||
return count == null ? 0 : count;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得缓存的list对象
|
||||
*
|
||||
* @param key 缓存的键值
|
||||
* @return 缓存键值对应的数据
|
||||
*/
|
||||
public <T> List<T> getCacheList(final String key)
|
||||
{
|
||||
return redisTemplate.opsForList().range(key, 0, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存Set
|
||||
*
|
||||
* @param key 缓存键值
|
||||
* @param dataSet 缓存的数据
|
||||
* @return 缓存数据的对象
|
||||
*/
|
||||
public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet)
|
||||
{
|
||||
BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
|
||||
Iterator<T> it = dataSet.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
setOperation.add(it.next());
|
||||
}
|
||||
return setOperation;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得缓存的set
|
||||
*
|
||||
* @param key
|
||||
* @return
|
||||
*/
|
||||
public <T> Set<T> getCacheSet(final String key)
|
||||
{
|
||||
return redisTemplate.opsForSet().members(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存Map
|
||||
*
|
||||
* @param key
|
||||
* @param dataMap
|
||||
*/
|
||||
public <T> void setCacheMap(final String key, final Map<String, T> dataMap)
|
||||
{
|
||||
if (dataMap != null) {
|
||||
redisTemplate.opsForHash().putAll(key, dataMap);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得缓存的Map
|
||||
*
|
||||
* @param key
|
||||
* @return
|
||||
*/
|
||||
public <T> Map<String, T> getCacheMap(final String key)
|
||||
{
|
||||
return redisTemplate.opsForHash().entries(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 往Hash中存入数据
|
||||
*
|
||||
* @param key Redis键
|
||||
* @param hKey Hash键
|
||||
* @param value 值
|
||||
*/
|
||||
public <T> void setCacheMapValue(final String key, final String hKey, final T value)
|
||||
{
|
||||
redisTemplate.opsForHash().put(key, hKey, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Hash中的数据
|
||||
*
|
||||
* @param key Redis键
|
||||
* @param hKey Hash键
|
||||
* @return Hash中的对象
|
||||
*/
|
||||
public <T> T getCacheMapValue(final String key, final String hKey)
|
||||
{
|
||||
HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
|
||||
return opsForHash.get(key, hKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取多个Hash中的数据
|
||||
*
|
||||
* @param key Redis键
|
||||
* @param hKeys Hash键集合
|
||||
* @return Hash对象集合
|
||||
*/
|
||||
public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys)
|
||||
{
|
||||
return redisTemplate.opsForHash().multiGet(key, hKeys);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除Hash中的某条数据
|
||||
*
|
||||
* @param key Redis键
|
||||
* @param hKey Hash键
|
||||
* @return 是否成功
|
||||
*/
|
||||
public boolean deleteCacheMapValue(final String key, final String hKey)
|
||||
{
|
||||
return redisTemplate.opsForHash().delete(key, hKey) > 0;
|
||||
}
|
||||
public boolean deleteCacheSetValue(final String key, final String hKey)
|
||||
{
|
||||
return redisTemplate.opsForSet().remove(key, hKey) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得缓存的基本对象列表
|
||||
*
|
||||
* @param pattern 字符串前缀
|
||||
* @return 对象列表
|
||||
*/
|
||||
public Collection<String> keys(final String pattern)
|
||||
{
|
||||
return redisTemplate.keys(pattern);
|
||||
}
|
||||
|
||||
public void setCacheSet(String key, String value) {
|
||||
redisTemplate.opsForSet().add(key,value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package org.lingniu.sdk.common.serializer;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONReader;
|
||||
import com.alibaba.fastjson2.JSONWriter;
|
||||
import com.alibaba.fastjson2.filter.Filter;
|
||||
import org.lingniu.sdk.constant.Constants;
|
||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
import org.springframework.data.redis.serializer.SerializationException;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
/**
|
||||
* Redis使用FastJson序列化
|
||||
*
|
||||
* @author portal
|
||||
*/
|
||||
public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
|
||||
{
|
||||
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
|
||||
|
||||
static final Filter AUTO_TYPE_FILTER = JSONReader.autoTypeFilter(Constants.JSON_WHITELIST_STR);
|
||||
|
||||
private Class<T> clazz;
|
||||
|
||||
public FastJson2JsonRedisSerializer(Class<T> clazz)
|
||||
{
|
||||
super();
|
||||
this.clazz = clazz;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] serialize(T t) throws SerializationException
|
||||
{
|
||||
if (t == null)
|
||||
{
|
||||
return new byte[0];
|
||||
}
|
||||
return JSON.toJSONString(t, JSONWriter.Feature.WriteClassName).getBytes(DEFAULT_CHARSET);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T deserialize(byte[] bytes) throws SerializationException
|
||||
{
|
||||
if (bytes == null || bytes.length <= 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
String str = new String(bytes, DEFAULT_CHARSET);
|
||||
|
||||
return JSON.parseObject(str, clazz, AUTO_TYPE_FILTER);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package org.lingniu.sdk.config;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
|
||||
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.TimeZone;
|
||||
|
||||
@Configuration
|
||||
public class JacksonConfiguration {
|
||||
|
||||
/**
|
||||
* 默认日期时间格式
|
||||
*/
|
||||
public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
|
||||
|
||||
/**
|
||||
* 默认日期格式
|
||||
*/
|
||||
public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
|
||||
|
||||
/**
|
||||
* 默认时区
|
||||
*/
|
||||
public static final String DEFAULT_TIME_ZONE = "Asia/Shanghai";
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) {
|
||||
ObjectMapper objectMapper = builder.createXmlMapper(false).build();
|
||||
|
||||
// 配置序列化
|
||||
objectMapper.setDefaultPropertyInclusion(JsonInclude.Include.NON_NULL);
|
||||
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
|
||||
objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
|
||||
|
||||
// 配置反序列化
|
||||
objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
|
||||
objectMapper.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
|
||||
|
||||
// 配置时区和日期格式
|
||||
objectMapper.setTimeZone(TimeZone.getTimeZone(DEFAULT_TIME_ZONE));
|
||||
objectMapper.setDateFormat(new SimpleDateFormat(DEFAULT_DATE_TIME_FORMAT));
|
||||
|
||||
// 注册JavaTimeModule
|
||||
JavaTimeModule javaTimeModule = new JavaTimeModule();
|
||||
javaTimeModule.addSerializer(LocalDateTime.class,
|
||||
new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)));
|
||||
javaTimeModule.addDeserializer(LocalDateTime.class,
|
||||
new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)));
|
||||
javaTimeModule.addSerializer(LocalDate.class,
|
||||
new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)));
|
||||
javaTimeModule.addDeserializer(LocalDate.class,
|
||||
new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)));
|
||||
|
||||
objectMapper.registerModule(javaTimeModule);
|
||||
|
||||
return objectMapper;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 定制器方式(Spring Boot推荐)
|
||||
*/
|
||||
@Bean
|
||||
public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
|
||||
return builder -> {
|
||||
// 序列化配置
|
||||
builder.serializationInclusion(JsonInclude.Include.NON_NULL);
|
||||
builder.failOnEmptyBeans(false);
|
||||
builder.failOnUnknownProperties(false);
|
||||
|
||||
// 日期格式配置
|
||||
builder.timeZone(TimeZone.getTimeZone(DEFAULT_TIME_ZONE));
|
||||
builder.simpleDateFormat(DEFAULT_DATE_TIME_FORMAT);
|
||||
|
||||
// 特性配置
|
||||
builder.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
|
||||
builder.featuresToEnable(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL);
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
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;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
import org.springframework.security.jackson2.SecurityJackson2Modules;
|
||||
import org.springframework.security.oauth2.client.jackson2.OAuth2ClientJackson2Module;
|
||||
|
||||
@Configuration("SdkRedisConfig")
|
||||
public class SdkRedisConfig {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(name = "sdkRedisTemplate")
|
||||
@Lazy
|
||||
public RedisTemplate<String, Object> sdkRedisTemplate(
|
||||
RedisConnectionFactory connectionFactory,
|
||||
ObjectMapper objectMapper) {
|
||||
|
||||
// 配置 ObjectMapper 以支持多态类型
|
||||
ObjectMapper redisObjectMapper = objectMapper.copy();
|
||||
|
||||
// 启用默认类型信息,用于反序列化
|
||||
redisObjectMapper.activateDefaultTyping(
|
||||
LaissezFaireSubTypeValidator.instance,
|
||||
ObjectMapper.DefaultTyping.NON_FINAL,
|
||||
JsonTypeInfo.As.PROPERTY
|
||||
);
|
||||
|
||||
// 注册 Spring Security 和 OAuth2 模块
|
||||
redisObjectMapper.registerModules(
|
||||
SecurityJackson2Modules.getModules(getClass().getClassLoader())
|
||||
);
|
||||
redisObjectMapper.registerModule(new OAuth2ClientJackson2Module());
|
||||
|
||||
// 创建 RedisTemplate
|
||||
RedisTemplate<String, Object> template = new RedisTemplate<>();
|
||||
template.setConnectionFactory(connectionFactory);
|
||||
|
||||
// 使用 StringRedisSerializer 来序列化和反序列化redis的key值
|
||||
template.setKeySerializer(new StringRedisSerializer());
|
||||
template.setHashKeySerializer(new StringRedisSerializer());
|
||||
|
||||
// 使用 GenericJackson2JsonRedisSerializer 来序列化和反序列化redis的value值
|
||||
GenericJackson2JsonRedisSerializer serializer =
|
||||
new GenericJackson2JsonRedisSerializer(redisObjectMapper);
|
||||
|
||||
template.setValueSerializer(serializer);
|
||||
template.setHashValueSerializer(serializer);
|
||||
|
||||
template.afterPropertiesSet();
|
||||
return template;
|
||||
}
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(name="redisCache")
|
||||
public RedisCache redisCache(@Qualifier("redisTemplate")RedisTemplate redisTemplate){
|
||||
return new RedisCache(redisTemplate);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright 2020-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.lingniu.sdk.config;
|
||||
|
||||
import org.lingniu.sdk.filter.IdpAuthenticationFilter;
|
||||
import org.lingniu.sdk.handler.LoginSuccessHandler;
|
||||
import org.lingniu.sdk.handler.LogoutIdpSuccessHandler;
|
||||
import org.lingniu.sdk.handler.RedirectHandler;
|
||||
import org.lingniu.sdk.service.RedisOAuth2AuthorizedClientService;
|
||||
import org.lingniu.sdk.service.TokenService;
|
||||
import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||
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.configurers.AbstractHttpConfigurer;
|
||||
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
|
||||
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
||||
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
import org.springframework.web.client.RestOperations;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import static org.springframework.security.config.Customizer.withDefaults;
|
||||
|
||||
/**
|
||||
* @author Joe Grandja
|
||||
* @author Dmitriy Dubson
|
||||
* @author Steve Riesenberg
|
||||
* @since 0.0.1
|
||||
*/
|
||||
@EnableWebSecurity
|
||||
@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true)
|
||||
@EnableConfigurationProperties({OAuth2ClientProperties.class})
|
||||
@Configuration
|
||||
public class SecurityConfig {
|
||||
private final LoginSuccessHandler loginSuccessHandler;
|
||||
private final OAuth2AuthorizedClientRepository oAuth2AuthorizedClientRepository;
|
||||
private final RedisOAuth2AuthorizedClientService redisOAuth2AuthorizedClientService;
|
||||
private final TokenService tokenService;
|
||||
private final LogoutIdpSuccessHandler logoutIdpSuccessHandler;
|
||||
private final RedirectHandler redirectHandler;
|
||||
|
||||
public SecurityConfig(LoginSuccessHandler loginSuccessHandler, OAuth2AuthorizedClientRepository oAuth2AuthorizedClientRepository, RedisOAuth2AuthorizedClientService redisOAuth2AuthorizedClientService, TokenService tokenService, LogoutIdpSuccessHandler logoutIdpSuccessHandler, RedirectHandler redirectHandler) {
|
||||
this.loginSuccessHandler = loginSuccessHandler;
|
||||
this.oAuth2AuthorizedClientRepository = oAuth2AuthorizedClientRepository;
|
||||
this.redisOAuth2AuthorizedClientService = redisOAuth2AuthorizedClientService;
|
||||
this.tokenService = tokenService;
|
||||
this.logoutIdpSuccessHandler = logoutIdpSuccessHandler;
|
||||
this.redirectHandler = redirectHandler;
|
||||
}
|
||||
|
||||
// @formatter:off
|
||||
@Bean
|
||||
public SecurityFilterChain securityFilterChain(HttpSecurity http, JwtDecoder jwtDecoder) throws Exception {
|
||||
http
|
||||
.csrf(AbstractHttpConfigurer::disable)
|
||||
.authorizeHttpRequests(authorize ->
|
||||
authorize
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.oauth2Login(oauth2->
|
||||
oauth2.authorizationEndpoint(authorization ->authorization.authorizationRedirectStrategy(redirectHandler))
|
||||
.successHandler(loginSuccessHandler)
|
||||
.authorizedClientRepository(oAuth2AuthorizedClientRepository)
|
||||
.authorizedClientService(redisOAuth2AuthorizedClientService)
|
||||
)
|
||||
.exceptionHandling(withDefaults())
|
||||
.addFilterBefore(new IdpAuthenticationFilter(tokenService),UsernamePasswordAuthenticationFilter.class)
|
||||
.oauth2ResourceServer(resource ->resource.jwt(withDefaults()))
|
||||
.logout(httpSecurityLogoutConfigurer ->
|
||||
httpSecurityLogoutConfigurer
|
||||
.logoutSuccessHandler(logoutIdpSuccessHandler)
|
||||
);
|
||||
return http.build();
|
||||
}
|
||||
// @formatter:on
|
||||
|
||||
@Bean
|
||||
public JwtDecoder jwtDecoder(OAuth2ClientProperties oAuth2ClientProperties) {
|
||||
RestOperations rest = new RestTemplate();
|
||||
|
||||
return NimbusJwtDecoder
|
||||
.withJwkSetUri(oAuth2ClientProperties.getProvider().get("idp").getJwkSetUri())
|
||||
.restOperations(rest)
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package org.lingniu.sdk.constant;
|
||||
|
||||
/**
|
||||
* 缓存的key 常量
|
||||
*
|
||||
* @author portal
|
||||
*/
|
||||
public class CacheConstants
|
||||
{
|
||||
// Access Token存储: String结构
|
||||
// 格式: access_token:{token}
|
||||
public static final String ACCESS_TOKEN_KEY = "app_access_token:%s";
|
||||
public static final String ACCESS_TOKEN_USER_KEY = "app_access_token_user:%s";
|
||||
|
||||
// Refresh Token存储: Hash结构
|
||||
// 格式: refresh_token:{token}
|
||||
public static final String REFRESH_TOKEN_KEY = "app_refresh_token:%s";
|
||||
|
||||
// 用户会话管理
|
||||
public static final String USER_SESSIONS = "app_user_sessions:%s"; // userId -> session列表
|
||||
|
||||
public static final String OAUTH2_CLIENT_KEY_PREFIX = "oauth2:client:";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
package org.lingniu.sdk.constant;
|
||||
|
||||
import com.nimbusds.openid.connect.sdk.claims.CommonClaimsSet;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* 通用常量信息
|
||||
*
|
||||
* @author portal
|
||||
*/
|
||||
public class Constants
|
||||
{
|
||||
/**
|
||||
* UTF-8 字符集
|
||||
*/
|
||||
public static final String UTF8 = "UTF-8";
|
||||
|
||||
/**
|
||||
* GBK 字符集
|
||||
*/
|
||||
public static final String GBK = "GBK";
|
||||
|
||||
/**
|
||||
* 系统语言
|
||||
*/
|
||||
public static final Locale DEFAULT_LOCALE = Locale.SIMPLIFIED_CHINESE;
|
||||
|
||||
/**
|
||||
* www主域
|
||||
*/
|
||||
public static final String WWW = "www.";
|
||||
|
||||
/**
|
||||
* http请求
|
||||
*/
|
||||
public static final String HTTP = "http://";
|
||||
|
||||
/**
|
||||
* https请求
|
||||
*/
|
||||
public static final String HTTPS = "https://";
|
||||
|
||||
/**
|
||||
* 通用成功标识
|
||||
*/
|
||||
public static final String SUCCESS = "0";
|
||||
|
||||
/**
|
||||
* 通用失败标识
|
||||
*/
|
||||
public static final String FAIL = "1";
|
||||
|
||||
/**
|
||||
* 登录成功
|
||||
*/
|
||||
public static final String LOGIN_SUCCESS = "Success";
|
||||
|
||||
/**
|
||||
* 注销
|
||||
*/
|
||||
public static final String LOGOUT = "Logout";
|
||||
|
||||
/**
|
||||
* 注册
|
||||
*/
|
||||
public static final String REGISTER = "Register";
|
||||
|
||||
/**
|
||||
* 登录失败
|
||||
*/
|
||||
public static final String LOGIN_FAIL = "Error";
|
||||
|
||||
/**
|
||||
* 所有权限标识
|
||||
*/
|
||||
public static final String ALL_PERMISSION = "*:*:*";
|
||||
|
||||
/**
|
||||
* 管理员角色权限标识
|
||||
*/
|
||||
public static final String SUPER_ADMIN = "admin";
|
||||
|
||||
/**
|
||||
* 角色权限分隔符
|
||||
*/
|
||||
public static final String ROLE_DELIMITER = ",";
|
||||
|
||||
/**
|
||||
* 权限标识分隔符
|
||||
*/
|
||||
public static final String PERMISSION_DELIMITER = ",";
|
||||
|
||||
/**
|
||||
* 验证码有效期(分钟)
|
||||
*/
|
||||
public static final Integer CAPTCHA_EXPIRATION = 2;
|
||||
|
||||
/**
|
||||
* 令牌
|
||||
*/
|
||||
public static final String TOKEN = "token";
|
||||
|
||||
/**
|
||||
* 令牌前缀
|
||||
*/
|
||||
public static final String TOKEN_PREFIX = "Bearer ";
|
||||
|
||||
/**
|
||||
* 令牌前缀
|
||||
*/
|
||||
public static final String LOGIN_USER_KEY = "login_user_key";
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
public static final String JWT_USERID = "userid";
|
||||
|
||||
/**
|
||||
* 用户名称
|
||||
*/
|
||||
public static final String JWT_USERNAME = CommonClaimsSet.SUB_CLAIM_NAME;
|
||||
|
||||
/**
|
||||
* 用户头像
|
||||
*/
|
||||
public static final String JWT_AVATAR = "avatar";
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
public static final String JWT_CREATED = "created";
|
||||
|
||||
/**
|
||||
* 用户权限
|
||||
*/
|
||||
public static final String JWT_AUTHORITIES = "authorities";
|
||||
|
||||
/**
|
||||
* 资源映射路径 前缀
|
||||
*/
|
||||
public static final String RESOURCE_PREFIX = "/profile";
|
||||
|
||||
/**
|
||||
* RMI 远程方法调用
|
||||
*/
|
||||
public static final String LOOKUP_RMI = "rmi:";
|
||||
|
||||
/**
|
||||
* LDAP 远程方法调用
|
||||
*/
|
||||
public static final String LOOKUP_LDAP = "ldap:";
|
||||
|
||||
/**
|
||||
* LDAPS 远程方法调用
|
||||
*/
|
||||
public static final String LOOKUP_LDAPS = "ldaps:";
|
||||
|
||||
/**
|
||||
* 自动识别json对象白名单配置(仅允许解析的包名,范围越小越安全)
|
||||
*/
|
||||
public static final String[] JSON_WHITELIST_STR = { "com.portal" };
|
||||
|
||||
/**
|
||||
* 定时任务白名单配置(仅允许访问的包名,如其他需要可以自行添加)
|
||||
*/
|
||||
public static final String[] JOB_WHITELIST_STR = { "com.portal.quartz.task" };
|
||||
|
||||
/**
|
||||
* 定时任务违规的字符
|
||||
*/
|
||||
public static final String[] JOB_ERROR_STR = { "java.net.URL", "javax.naming.InitialContext", "org.yaml.snakeyaml",
|
||||
"org.springframework", "org.apache", "org.lingniu.idp.utils.file", "org.lingniu.idp.config", "com.portal.generator" };
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package org.lingniu.sdk.constant;
|
||||
|
||||
/**
|
||||
* 用户常量信息
|
||||
*
|
||||
* @author portal
|
||||
*/
|
||||
public class UserConstants
|
||||
{
|
||||
/**
|
||||
* 平台内系统用户的唯一标志
|
||||
*/
|
||||
public static final String SYS_USER = "SYS_USER";
|
||||
|
||||
/** 正常状态 */
|
||||
public static final String NORMAL = "0";
|
||||
|
||||
/** 异常状态 */
|
||||
public static final String EXCEPTION = "1";
|
||||
|
||||
/** 用户封禁状态 */
|
||||
public static final String USER_DISABLE = "1";
|
||||
|
||||
/** 角色正常状态 */
|
||||
public static final String ROLE_NORMAL = "0";
|
||||
|
||||
/** 角色封禁状态 */
|
||||
public static final String ROLE_DISABLE = "1";
|
||||
|
||||
/** 部门正常状态 */
|
||||
public static final String DEPT_NORMAL = "0";
|
||||
|
||||
/** 部门停用状态 */
|
||||
public static final String DEPT_DISABLE = "1";
|
||||
|
||||
/** 字典正常状态 */
|
||||
public static final String DICT_NORMAL = "0";
|
||||
|
||||
/** 是否为系统默认(是) */
|
||||
public static final String YES = "Y";
|
||||
|
||||
/** 是否菜单外链(是) */
|
||||
public static final String YES_FRAME = "0";
|
||||
|
||||
/** 是否菜单外链(否) */
|
||||
public static final String NO_FRAME = "1";
|
||||
|
||||
/** 菜单类型(目录) */
|
||||
public static final String TYPE_DIR = "M";
|
||||
|
||||
/** 菜单类型(菜单) */
|
||||
public static final String TYPE_MENU = "C";
|
||||
|
||||
/** 菜单类型(按钮) */
|
||||
public static final String TYPE_BUTTON = "F";
|
||||
|
||||
/** Layout组件标识 */
|
||||
public final static String LAYOUT = "Layout";
|
||||
|
||||
/** ParentView组件标识 */
|
||||
public final static String PARENT_VIEW = "ParentView";
|
||||
|
||||
/** InnerLink组件标识 */
|
||||
public final static String INNER_LINK = "InnerLink";
|
||||
|
||||
/** 校验是否唯一的返回标识 */
|
||||
public final static boolean UNIQUE = true;
|
||||
public final static boolean NOT_UNIQUE = false;
|
||||
|
||||
/**
|
||||
* 用户名长度限制
|
||||
*/
|
||||
public static final int USERNAME_MIN_LENGTH = 2;
|
||||
public static final int USERNAME_MAX_LENGTH = 20;
|
||||
|
||||
/**
|
||||
* 密码长度限制
|
||||
*/
|
||||
public static final int PASSWORD_MIN_LENGTH = 5;
|
||||
public static final int PASSWORD_MAX_LENGTH = 20;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package org.lingniu.sdk.filter;
|
||||
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.lingniu.sdk.model.token.AccessTokenInfo;
|
||||
import org.lingniu.sdk.service.TokenService;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class IdpAuthenticationFilter extends OncePerRequestFilter {
|
||||
private final TokenService tokenService;
|
||||
|
||||
public IdpAuthenticationFilter(TokenService tokenService) {
|
||||
this.tokenService = tokenService;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
FilterChain filterChain) throws ServletException, IOException {
|
||||
|
||||
try {
|
||||
AccessTokenInfo accessTokenInfo = null;
|
||||
// 验证令牌
|
||||
if (tokenService.validateAccessToken(request)) {
|
||||
accessTokenInfo = tokenService.getAccessTokenInfo(request);
|
||||
}else{
|
||||
accessTokenInfo = tokenService.refreshToken(request, response);
|
||||
}
|
||||
if(accessTokenInfo!=null){
|
||||
// 创建认证对象
|
||||
OAuth2AuthenticationToken authentication =
|
||||
new OAuth2AuthenticationToken(
|
||||
tokenService.convertPrincipal(accessTokenInfo),null,accessTokenInfo.getClientRegistrationId()
|
||||
);
|
||||
|
||||
// 设置认证信息到SecurityContext
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
// 令牌验证失败,记录日志但不中断请求
|
||||
logger.error("token 验证失败", e);
|
||||
}
|
||||
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package org.lingniu.sdk.handler;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.lingniu.sdk.model.base.CommonResult;
|
||||
import org.lingniu.sdk.model.token.TokenInfo;
|
||||
import org.lingniu.sdk.model.user.UserInfo;
|
||||
import org.lingniu.sdk.service.TokenService;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
|
||||
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
|
||||
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
@Component
|
||||
public class LoginSuccessHandler implements AuthenticationSuccessHandler {
|
||||
|
||||
private final TokenService tokenService;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
public LoginSuccessHandler(TokenService tokenService, ObjectMapper objectMapper) {
|
||||
this.tokenService = tokenService;
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
|
||||
OAuth2AuthenticationToken oAuth2AuthenticationToken = (OAuth2AuthenticationToken)authentication;
|
||||
DefaultOidcUser principal = (DefaultOidcUser)oAuth2AuthenticationToken.getPrincipal();
|
||||
Map<String, Object> claims = principal.getUserInfo().getClaims();
|
||||
String clientRegistrationId = oAuth2AuthenticationToken.getAuthorizedClientRegistrationId();
|
||||
String s = objectMapper.writeValueAsString(claims);
|
||||
// 生成token
|
||||
TokenInfo token = tokenService.createToken(principal.getName());
|
||||
token.getAccessTokenInfo().setAdditionalInfo(s);
|
||||
token.getAccessTokenInfo().setClientRegistrationId(clientRegistrationId);
|
||||
token.getRefreshTokenInfo().setClientRegistrationId(clientRegistrationId);
|
||||
// 保存token
|
||||
tokenService.storeTokenInfo(token);
|
||||
// 将短token放入响应头
|
||||
tokenService.setAccessTokenHeader(response,token.getAccessTokenInfo().getTokenValue());
|
||||
// 设置Refresh Token到HttpOnly Cookie
|
||||
tokenService.setRefreshTokenCookie(response, token);
|
||||
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||
response.setCharacterEncoding("UTF-8");
|
||||
response.setStatus(HttpStatus.OK.value());
|
||||
objectMapper.writeValue(response.getWriter(), CommonResult.success(CommonResult.success(claims)));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package org.lingniu.sdk.handler;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.lingniu.sdk.model.base.CommonResult;
|
||||
import org.lingniu.sdk.service.TokenService;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@Component
|
||||
public class LogoutIdpSuccessHandler implements LogoutSuccessHandler{
|
||||
private final ObjectMapper objectMapper;
|
||||
private final TokenService tokenService;
|
||||
public LogoutIdpSuccessHandler(ObjectMapper objectMapper, TokenService tokenService) {
|
||||
this.objectMapper = objectMapper;
|
||||
this.tokenService = tokenService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
|
||||
tokenService.clearToken(request);
|
||||
tokenService.clearRefreshTokenCookie(response);
|
||||
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||
response.setCharacterEncoding("UTF-8");
|
||||
response.setStatus(HttpStatus.OK.value());
|
||||
|
||||
objectMapper.writeValue(response.getWriter(), CommonResult.success("success"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package org.lingniu.sdk.handler;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.lingniu.sdk.model.base.CommonResult;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.web.RedirectStrategy;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
@Component
|
||||
public class RedirectHandler implements RedirectStrategy {
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
public RedirectHandler(ObjectMapper objectMapper) {
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendRedirect(HttpServletRequest request, HttpServletResponse response, String url) throws IOException {
|
||||
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||
response.setCharacterEncoding("UTF-8");
|
||||
response.setStatus(HttpStatus.OK.value());
|
||||
objectMapper.writeValue(response.getWriter(), CommonResult.success(Map.of("redirect_url",url)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package org.lingniu.sdk.model.base;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import lombok.Data;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 通用返回
|
||||
*
|
||||
* @param <T> 数据泛型
|
||||
*/
|
||||
@Data
|
||||
public class CommonResult<T> implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
/**
|
||||
* 错误码
|
||||
*
|
||||
*/
|
||||
private Integer code;
|
||||
/**
|
||||
* 错误提示,用户可阅读
|
||||
*
|
||||
*/
|
||||
private String msg;
|
||||
/**
|
||||
* 返回数据
|
||||
*/
|
||||
private T data;
|
||||
|
||||
/**
|
||||
* 将传入的 result 对象,转换成另外一个泛型结果的对象
|
||||
*
|
||||
* 因为 A 方法返回的 CommonResult 对象,不满足调用其的 B 方法的返回,所以需要进行转换。
|
||||
*
|
||||
* @param result 传入的 result 对象
|
||||
* @param <T> 返回的泛型
|
||||
* @return 新的 CommonResult 对象
|
||||
*/
|
||||
public static <T> CommonResult<T> error(CommonResult<?> result) {
|
||||
return error(result.getCode(), result.getMsg());
|
||||
}
|
||||
|
||||
public static <T> CommonResult<T> error(Integer code, String message) {
|
||||
CommonResult<T> result = new CommonResult<>();
|
||||
result.code = code;
|
||||
result.msg = message;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
public static <T> CommonResult<T> success(T data) {
|
||||
CommonResult<T> result = new CommonResult<>();
|
||||
result.code = HttpStatus.OK.value();
|
||||
result.data = data;
|
||||
result.msg = "";
|
||||
return result;
|
||||
}
|
||||
|
||||
public static boolean isSuccess(Integer code) {
|
||||
return Objects.equals(code, HttpStatus.OK.value());
|
||||
}
|
||||
|
||||
@JsonIgnore // 避免 jackson 序列化
|
||||
public boolean isSuccess() {
|
||||
return isSuccess(code);
|
||||
}
|
||||
|
||||
@JsonIgnore // 避免 jackson 序列化
|
||||
public boolean isError() {
|
||||
return !isSuccess();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
package org.lingniu.sdk.model.token;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Access Token 信息
|
||||
* 存储在 Redis String 结构中
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class AccessTokenInfo {
|
||||
private String tokenValue;
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
private String username;
|
||||
|
||||
|
||||
/**
|
||||
* 颁发时间
|
||||
*/
|
||||
private Instant issuedAt;
|
||||
|
||||
/**
|
||||
* 过期时间
|
||||
*/
|
||||
private Instant expiresAt;
|
||||
|
||||
/**
|
||||
* 关联的刷新Token ID
|
||||
*/
|
||||
private String refreshTokenId;
|
||||
|
||||
/**
|
||||
* JWT ID(如果是JWT token)
|
||||
*/
|
||||
private String jti;
|
||||
|
||||
/**
|
||||
* 附加数据(JSON格式)
|
||||
*/
|
||||
private String additionalInfo;
|
||||
|
||||
private String clientRegistrationId;
|
||||
|
||||
/**
|
||||
* 检查Token是否过期
|
||||
*/
|
||||
public boolean isExpired() {
|
||||
return expiresAt != null && Instant.now().isAfter(expiresAt);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查Token是否有效
|
||||
*/
|
||||
public boolean isValid() {
|
||||
return !isExpired();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取剩余有效时间(秒)
|
||||
*/
|
||||
public long getRemainingSeconds() {
|
||||
if (expiresAt == null) {
|
||||
return 0;
|
||||
}
|
||||
Instant now = Instant.now();
|
||||
if (now.isAfter(expiresAt)) {
|
||||
return 0;
|
||||
}
|
||||
return expiresAt.getEpochSecond() - now.getEpochSecond();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Token使用时长(秒)
|
||||
*/
|
||||
public long getUsedSeconds() {
|
||||
if (issuedAt == null) {
|
||||
return 0;
|
||||
}
|
||||
Instant end = Instant.now();
|
||||
return end.getEpochSecond() - issuedAt.getEpochSecond();
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为Map,便于Redis存储
|
||||
*/
|
||||
public Map<String, Object> toMap() {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("username", username);
|
||||
map.put("tokenValue",tokenValue);
|
||||
map.put("issuedAt", issuedAt != null ? issuedAt.toString() : null);
|
||||
map.put("expiresAt", expiresAt != null ? expiresAt.toString() : null);
|
||||
map.put("refreshTokenId", refreshTokenId);
|
||||
map.put("jti", jti);
|
||||
map.put("clientRegistrationId",clientRegistrationId);
|
||||
map.put("additionalInfo", additionalInfo);
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从Map创建AccessTokenInfo
|
||||
*/
|
||||
public static AccessTokenInfo fromMap(Map<String, Object> map) {
|
||||
if (map == null || map.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
AccessTokenInfo.AccessTokenInfoBuilder builder = AccessTokenInfo.builder();
|
||||
builder.username((String) map.get("username"));
|
||||
builder.tokenValue((String) map.get("tokenValue"));
|
||||
// 处理时间字段
|
||||
String issuedAtStr = (String) map.get("issuedAt");
|
||||
if (issuedAtStr != null) {
|
||||
builder.issuedAt(Instant.parse(issuedAtStr));
|
||||
}
|
||||
|
||||
String expiresAtStr = (String) map.get("expiresAt");
|
||||
if (expiresAtStr != null) {
|
||||
builder.expiresAt(Instant.parse(expiresAtStr));
|
||||
}
|
||||
|
||||
builder.refreshTokenId((String) map.get("refreshTokenId"));
|
||||
builder.jti((String) map.get("jti"));
|
||||
builder.clientRegistrationId((String) map.get("clientRegistrationId"));
|
||||
builder.additionalInfo((String) map.get("additionalInfo"));
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 简化的用户信息(用于接口返回)
|
||||
*/
|
||||
public Map<String, Object> toSimpleInfo() {
|
||||
Map<String, Object> info = new HashMap<>();
|
||||
info.put("username", username);
|
||||
info.put("expiresAt", expiresAt != null ? expiresAt.toEpochMilli() : null);
|
||||
info.put("issuedAt", issuedAt != null ? issuedAt.toEpochMilli() : null);
|
||||
info.put("valid", isValid());
|
||||
return info;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
package org.lingniu.sdk.model.token;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Refresh Token 信息
|
||||
* 存储在 Redis Hash 结构中
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class RefreshTokenInfo {
|
||||
private String tokenValue;
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
private String username;
|
||||
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private Instant createdAt;
|
||||
|
||||
/**
|
||||
* 最后使用时间
|
||||
*/
|
||||
private Instant lastUsedAt;
|
||||
|
||||
/**
|
||||
* 过期时间
|
||||
*/
|
||||
private Instant expiresAt;
|
||||
|
||||
/**
|
||||
* 对应accessToken
|
||||
*/
|
||||
private String accessToken;
|
||||
/**
|
||||
* 关联的Access Token数量(用于统计)
|
||||
*/
|
||||
private int accessTokenCount;
|
||||
|
||||
/**
|
||||
* 使用次数
|
||||
*/
|
||||
private int usageCount;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 附加数据(JSON格式)
|
||||
*/
|
||||
private String additionalInfo;
|
||||
|
||||
private String clientRegistrationId;
|
||||
|
||||
|
||||
/**
|
||||
* 检查Refresh Token是否过期
|
||||
*/
|
||||
public boolean isExpired() {
|
||||
return expiresAt != null && Instant.now().isAfter(expiresAt);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查Refresh Token是否有效
|
||||
*/
|
||||
public boolean isValid() {
|
||||
return !isExpired();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取剩余有效时间(秒)
|
||||
*/
|
||||
public long getRemainingSeconds() {
|
||||
if (expiresAt == null) {
|
||||
return 0;
|
||||
}
|
||||
Instant now = Instant.now();
|
||||
if (now.isAfter(expiresAt)) {
|
||||
return 0;
|
||||
}
|
||||
return expiresAt.getEpochSecond() - now.getEpochSecond();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取活跃天数(创建到现在)
|
||||
*/
|
||||
public long getActiveDays() {
|
||||
if (createdAt == null) {
|
||||
return 0;
|
||||
}
|
||||
Instant end = Instant.now();
|
||||
long seconds = end.getEpochSecond() - createdAt.getEpochSecond();
|
||||
return seconds / (24 * 3600);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取闲置天数(最后使用到现在)
|
||||
*/
|
||||
public long getIdleDays() {
|
||||
if (lastUsedAt == null) {
|
||||
return getActiveDays();
|
||||
}
|
||||
Instant now = Instant.now();
|
||||
long seconds = now.getEpochSecond() - lastUsedAt.getEpochSecond();
|
||||
return seconds / (24 * 3600);
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加使用计数
|
||||
*/
|
||||
public void incrementUsage() {
|
||||
this.usageCount++;
|
||||
this.lastUsedAt = Instant.now();
|
||||
}
|
||||
|
||||
public void incrementAccessTokenUsage(String accessToken) {
|
||||
this.accessTokenCount++;
|
||||
this.accessToken = accessToken;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 转换为Map,便于Redis存储
|
||||
*/
|
||||
public Map<String, String> toMap() {
|
||||
Map<String, String> hash = new HashMap<>();
|
||||
hash.put("username", username != null ? username : "");
|
||||
hash.put("tokenValue", tokenValue != null ? tokenValue : "");
|
||||
hash.put("accessToken", accessToken != null ? accessToken : "");
|
||||
hash.put("createdAt", createdAt != null ? createdAt.toString() : "");
|
||||
hash.put("lastUsedAt", lastUsedAt != null ? lastUsedAt.toString() : "");
|
||||
hash.put("expiresAt", expiresAt != null ? expiresAt.toString() : "");
|
||||
hash.put("accessTokenCount", Integer.toString(accessTokenCount));
|
||||
hash.put("usageCount", Integer.toString(usageCount));
|
||||
hash.put("clientRegistrationId", clientRegistrationId);
|
||||
hash.put("additionalInfo", additionalInfo != null ? additionalInfo : "");
|
||||
|
||||
return hash;
|
||||
}
|
||||
public Map<String, String> toUpdateMap() {
|
||||
Map<String, String> hash = new HashMap<>();
|
||||
hash.put("lastUsedAt", lastUsedAt != null ? lastUsedAt.toString() : "");
|
||||
hash.put("accessTokenCount", Integer.toString(accessTokenCount));
|
||||
hash.put("accessToken", accessToken != null ? accessToken : "");
|
||||
hash.put("usageCount", Integer.toString(usageCount));
|
||||
return hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从Redis Hash创建RefreshTokenInfo
|
||||
*/
|
||||
public static RefreshTokenInfo fromMap(Map<String, Object> hash) {
|
||||
if (hash == null || hash.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
RefreshTokenInfoBuilder builder = RefreshTokenInfo.builder();
|
||||
builder.username((String) hash.getOrDefault("username", ""));
|
||||
builder.accessToken((String) hash.getOrDefault("accessToken", ""));
|
||||
builder.tokenValue((String) hash.getOrDefault("tokenValue", ""));
|
||||
builder.clientRegistrationId((String) hash.getOrDefault("clientRegistrationId", ""));
|
||||
|
||||
// 处理时间字段
|
||||
String createdAtStr = (String)hash.get("createdAt");
|
||||
if (createdAtStr != null && !createdAtStr.isEmpty()) {
|
||||
try {
|
||||
builder.createdAt(Instant.parse(createdAtStr));
|
||||
} catch (Exception e) {
|
||||
// 解析失败,使用当前时间
|
||||
builder.createdAt(Instant.now());
|
||||
}
|
||||
}
|
||||
|
||||
String lastUsedAtStr = (String)hash.get("lastUsedAt");
|
||||
if (lastUsedAtStr != null && !lastUsedAtStr.isEmpty()) {
|
||||
try {
|
||||
builder.lastUsedAt(Instant.parse(lastUsedAtStr));
|
||||
} catch (Exception e) {
|
||||
// 解析失败,忽略
|
||||
}
|
||||
}
|
||||
|
||||
String expiresAtStr = (String)hash.get("expiresAt");
|
||||
if (expiresAtStr != null && !expiresAtStr.isEmpty()) {
|
||||
try {
|
||||
builder.expiresAt(Instant.parse(expiresAtStr));
|
||||
} catch (Exception e) {
|
||||
// 解析失败,忽略
|
||||
}
|
||||
}
|
||||
|
||||
// 处理数值字段
|
||||
String accessTokenCountStr = (String)hash.get("accessTokenCount");
|
||||
if (accessTokenCountStr != null && !accessTokenCountStr.isEmpty()) {
|
||||
try {
|
||||
builder.accessTokenCount(Integer.parseInt(accessTokenCountStr));
|
||||
} catch (NumberFormatException e) {
|
||||
builder.accessTokenCount(0);
|
||||
}
|
||||
}
|
||||
|
||||
String usageCountStr = (String)hash.get("usageCount");
|
||||
if (usageCountStr != null && !usageCountStr.isEmpty()) {
|
||||
try {
|
||||
builder.usageCount(Integer.parseInt(usageCountStr));
|
||||
} catch (NumberFormatException e) {
|
||||
builder.usageCount(0);
|
||||
}
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package org.lingniu.sdk.model.token;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
// TokenInfo.java
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class TokenInfo implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
private AccessTokenInfo accessTokenInfo;
|
||||
private RefreshTokenInfo refreshTokenInfo;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package org.lingniu.sdk.model.user;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@NoArgsConstructor
|
||||
public class DataPermission {
|
||||
/** 允许全部*/
|
||||
private boolean allowAll;
|
||||
/**仅自己*/
|
||||
private boolean onlySelf;
|
||||
/**部门列表*/
|
||||
private Set<String> deptList;
|
||||
/**地区*/
|
||||
private Set<String> areas;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package org.lingniu.sdk.model.user;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@NoArgsConstructor
|
||||
public class UserDept implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 部门ID */
|
||||
private Long deptId;
|
||||
|
||||
/** 父部门ID */
|
||||
private Long parentId;
|
||||
|
||||
/** 祖级列表 */
|
||||
private String ancestors;
|
||||
|
||||
/** 部门名称 */
|
||||
private String deptName;
|
||||
|
||||
/** 显示顺序 */
|
||||
private Integer orderNum;
|
||||
|
||||
/** 负责人 */
|
||||
private String leader;
|
||||
/** 部门状态:0正常,1停用 */
|
||||
private String status;
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package org.lingniu.sdk.model.user;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@NoArgsConstructor
|
||||
public class UserInfo implements OAuth2User, Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
/**
|
||||
* userid
|
||||
*/
|
||||
private Long userId;
|
||||
/**
|
||||
* 用户账号
|
||||
*/
|
||||
private String username;
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
private String nickName;
|
||||
/**
|
||||
* 性别
|
||||
*/
|
||||
private String sex;
|
||||
/**
|
||||
* 当前部门
|
||||
*/
|
||||
private Long currentDeptId;
|
||||
/**
|
||||
* 用户部门列表
|
||||
*/
|
||||
private List<UserDept> userDepts;
|
||||
/**
|
||||
* 用户岗位列表
|
||||
*/
|
||||
private List<UserPost> userPosts;
|
||||
/**
|
||||
* 用户数据权限
|
||||
*/
|
||||
private DataPermission dataPermission;
|
||||
/**
|
||||
* 权限列表
|
||||
*/
|
||||
private Set<String> permissions;
|
||||
/**
|
||||
* 角色列表
|
||||
*/
|
||||
private Set<String> roles;
|
||||
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return username;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getAttributes() {
|
||||
return Map.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package org.lingniu.sdk.model.user;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@NoArgsConstructor
|
||||
public class UserPost implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 岗位序号 */
|
||||
private Long postId;
|
||||
|
||||
/** 岗位编码 */
|
||||
private String postCode;
|
||||
|
||||
/** 岗位名称 */
|
||||
private String postName;
|
||||
|
||||
/** 岗位排序 */
|
||||
private Integer postSort;
|
||||
|
||||
/** 状态(0正常 1停用) */
|
||||
private String status;
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
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.stereotype.Component;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Map;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
public class RedisAccessTokenService {
|
||||
|
||||
@Autowired
|
||||
private RedisCache redisCache;
|
||||
|
||||
private final long ACCESS_TOKEN_EXPIRE = 3600; // 1小时
|
||||
|
||||
|
||||
/**
|
||||
* 存储Access Token到Redis
|
||||
*/
|
||||
public void storeAccessToken(AccessTokenInfo tokenInfo) {
|
||||
String key = String.format(CacheConstants.ACCESS_TOKEN_KEY, tokenInfo.getTokenValue());
|
||||
try {
|
||||
redisCache.setCacheMap(key,tokenInfo.toMap());
|
||||
Instant expiresAt = tokenInfo.getExpiresAt();
|
||||
long expire = ACCESS_TOKEN_EXPIRE;
|
||||
if(expiresAt!=null){
|
||||
expire = expiresAt.getEpochSecond() - Instant.now().getEpochSecond();
|
||||
}
|
||||
|
||||
redisCache.expire(key,expire);
|
||||
} catch (Exception e) {
|
||||
log.error("存储Access Token失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证Access Token
|
||||
*/
|
||||
public boolean validateAccessToken(String token) {
|
||||
String key = String.format(CacheConstants.ACCESS_TOKEN_KEY, token);
|
||||
if(!redisCache.hasKey(key)){
|
||||
return false;
|
||||
}
|
||||
AccessTokenInfo accessTokenInfo = getAccessTokenInfo(token);
|
||||
if(accessTokenInfo==null){
|
||||
return false;
|
||||
}
|
||||
return accessTokenInfo.isValid();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 删除Access Token
|
||||
*/
|
||||
public boolean removeAccessToken(String token) {
|
||||
String key = String.format(CacheConstants.ACCESS_TOKEN_KEY, token);
|
||||
return redisCache.deleteObject(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取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);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 作废 删除
|
||||
* @param tokenInfo
|
||||
*/
|
||||
public void revokeAccessToken(AccessTokenInfo tokenInfo) {
|
||||
if(tokenInfo==null){
|
||||
return;
|
||||
}
|
||||
String key = String.format(CacheConstants.ACCESS_TOKEN_KEY, tokenInfo.getTokenValue());
|
||||
redisCache.deleteObject(key);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
package org.lingniu.sdk.service;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.lingniu.sdk.constant.CacheConstants;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Component
|
||||
public class RedisOAuth2AuthorizedClientRepository implements OAuth2AuthorizedClientRepository {
|
||||
|
||||
private final RedisTemplate<String, Object> redisTemplate;
|
||||
private final ClientRegistrationRepository clientRegistrationRepository;
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
public RedisOAuth2AuthorizedClientRepository(
|
||||
RedisTemplate<String, Object> redisTemplate,
|
||||
ClientRegistrationRepository clientRegistrationRepository) {
|
||||
this.redisTemplate = redisTemplate;
|
||||
this.clientRegistrationRepository = clientRegistrationRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends OAuth2AuthorizedClient> T loadAuthorizedClient(
|
||||
String clientRegistrationId,
|
||||
Authentication principal,
|
||||
HttpServletRequest request) {
|
||||
|
||||
if (principal == null || !principal.isAuthenticated()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String principalName = principal.getName();
|
||||
|
||||
String key = buildClientKey(principalName, clientRegistrationId);
|
||||
|
||||
try {
|
||||
Object data = redisTemplate.opsForValue().get(key);
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 反序列化
|
||||
Map<String, Object> clientData = objectMapper.convertValue(data, new TypeReference<Map<String, Object>>() {});
|
||||
|
||||
// 重建 ClientRegistration
|
||||
ClientRegistration clientRegistration = clientRegistrationRepository
|
||||
.findByRegistrationId(clientRegistrationId);
|
||||
|
||||
if (clientRegistration == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 重建 OAuth2AccessToken
|
||||
OAuth2AccessToken accessToken = rebuildAccessToken(
|
||||
(Map<String, Object>) clientData.get("accessToken")
|
||||
);
|
||||
|
||||
// 重建 OAuth2RefreshToken(如果有)
|
||||
OAuth2RefreshToken refreshToken = null;
|
||||
if (clientData.containsKey("refreshToken")) {
|
||||
refreshToken = rebuildRefreshToken(
|
||||
(Map<String, Object>) clientData.get("refreshToken")
|
||||
);
|
||||
}
|
||||
|
||||
// 重建 OAuth2AuthorizedClient
|
||||
OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(
|
||||
clientRegistration,
|
||||
principalName,
|
||||
accessToken,
|
||||
refreshToken
|
||||
);
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
T result = (T) authorizedClient;
|
||||
return result;
|
||||
|
||||
} catch (Exception e) {
|
||||
// 如果反序列化失败,删除损坏的数据
|
||||
redisTemplate.delete(key);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveAuthorizedClient(
|
||||
OAuth2AuthorizedClient authorizedClient,
|
||||
Authentication principal,
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response) {
|
||||
|
||||
if (principal == null || !principal.isAuthenticated()) {
|
||||
return;
|
||||
}
|
||||
|
||||
String principalName = principal.getName();
|
||||
String clientRegistrationId = authorizedClient.getClientRegistration().getRegistrationId();
|
||||
String key = buildClientKey(principalName, clientRegistrationId);
|
||||
try {
|
||||
// 序列化 OAuth2AuthorizedClient
|
||||
Map<String, Object> clientData = new HashMap<>();
|
||||
|
||||
// 存储 ClientRegistrationId
|
||||
clientData.put("clientRegistrationId", clientRegistrationId);
|
||||
clientData.put("principalName", principalName);
|
||||
|
||||
// 序列化 AccessToken
|
||||
OAuth2AccessToken accessToken = authorizedClient.getAccessToken();
|
||||
Map<String, Object> accessTokenData = new HashMap<>();
|
||||
accessTokenData.put("tokenValue", accessToken.getTokenValue());
|
||||
accessTokenData.put("tokenType", accessToken.getTokenType().getValue());
|
||||
accessTokenData.put("issuedAt", accessToken.getIssuedAt().toString());
|
||||
accessTokenData.put("expiresAt", accessToken.getExpiresAt().toString());
|
||||
accessTokenData.put("scopes", new ArrayList<>(accessToken.getScopes()));
|
||||
clientData.put("accessToken", accessTokenData);
|
||||
|
||||
// 序列化 RefreshToken(如果有)
|
||||
OAuth2RefreshToken refreshToken = authorizedClient.getRefreshToken();
|
||||
if (refreshToken != null) {
|
||||
Map<String, Object> refreshTokenData = new HashMap<>();
|
||||
refreshTokenData.put("tokenValue", refreshToken.getTokenValue());
|
||||
if (refreshToken.getIssuedAt() != null) {
|
||||
refreshTokenData.put("issuedAt", refreshToken.getIssuedAt().toString());
|
||||
}
|
||||
if (refreshToken.getExpiresAt() != null) {
|
||||
refreshTokenData.put("expiresAt", refreshToken.getExpiresAt().toString());
|
||||
}
|
||||
clientData.put("refreshToken", refreshTokenData);
|
||||
}
|
||||
|
||||
// 存储到 Redis
|
||||
redisTemplate.opsForValue().set(key, clientData);
|
||||
|
||||
// 设置过期时间(根据 AccessToken 的过期时间)
|
||||
Duration expiresIn = Duration.between(Instant.now(), accessToken.getExpiresAt());
|
||||
if (!expiresIn.isNegative()) {
|
||||
redisTemplate.expire(key, 7, TimeUnit.DAYS);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to save OAuth2AuthorizedClient to Redis", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAuthorizedClient(
|
||||
String clientRegistrationId,
|
||||
Authentication principal,
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response) {
|
||||
|
||||
if (principal == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String principalName = principal.getName();
|
||||
String key = buildClientKey(principalName, clientRegistrationId);
|
||||
|
||||
// 删除客户端数据
|
||||
redisTemplate.delete(key);
|
||||
}
|
||||
|
||||
private String buildClientKey(String principalName, String clientRegistrationId) {
|
||||
return CacheConstants.OAUTH2_CLIENT_KEY_PREFIX + principalName + ":" + clientRegistrationId;
|
||||
}
|
||||
|
||||
|
||||
private OAuth2AccessToken rebuildAccessToken(Map<String, Object> data) {
|
||||
String tokenValue = (String) data.get("tokenValue");
|
||||
OAuth2AccessToken.TokenType tokenType = OAuth2AccessToken.TokenType.BEARER;
|
||||
if (data.containsKey("tokenType")) {
|
||||
String typeStr = (String) data.get("tokenType");
|
||||
tokenType = new OAuth2AccessToken.TokenType(typeStr);
|
||||
}
|
||||
|
||||
Instant issuedAt = Instant.parse((String) data.get("issuedAt"));
|
||||
Instant expiresAt = Instant.parse((String) data.get("expiresAt"));
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Set<String> scopes = new HashSet<>((List<String>) data.get("scopes"));
|
||||
|
||||
return new OAuth2AccessToken(tokenType, tokenValue, issuedAt, expiresAt, scopes);
|
||||
}
|
||||
|
||||
private OAuth2RefreshToken rebuildRefreshToken(Map<String, Object> data) {
|
||||
String tokenValue = (String) data.get("tokenValue");
|
||||
Instant issuedAt = data.containsKey("issuedAt") ?
|
||||
Instant.parse((String) data.get("issuedAt")) : null;
|
||||
Instant expiresAt = data.containsKey("expiresAt") ?
|
||||
Instant.parse((String) data.get("expiresAt")) : null;
|
||||
|
||||
return new OAuth2RefreshToken(tokenValue, issuedAt, expiresAt);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户的所有客户端
|
||||
*/
|
||||
public List<OAuth2AuthorizedClient> findByPrincipalName(String principalName) {
|
||||
String pattern = CacheConstants.OAUTH2_CLIENT_KEY_PREFIX + principalName + ":*";
|
||||
Set<String> keys = redisTemplate.keys(pattern);
|
||||
|
||||
List<OAuth2AuthorizedClient> clients = new ArrayList<>();
|
||||
for (String key : keys) {
|
||||
String clientRegistrationId = extractClientRegistrationId(key);
|
||||
// 这里需要 principal,简化处理
|
||||
// 实际使用时可能需要调整
|
||||
}
|
||||
|
||||
return clients;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理过期的客户端
|
||||
*/
|
||||
public void cleanupExpiredClients() {
|
||||
// 可以通过 Redis 的过期策略自动清理
|
||||
// 也可以手动扫描并删除过期的 token
|
||||
String pattern = CacheConstants.OAUTH2_CLIENT_KEY_PREFIX + "*";
|
||||
Set<String> keys = redisTemplate.keys(pattern);
|
||||
|
||||
for (String key : keys) {
|
||||
Long ttl = redisTemplate.getExpire(key, TimeUnit.SECONDS);
|
||||
if (ttl != null && ttl <= 0) {
|
||||
redisTemplate.delete(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String extractClientRegistrationId(String key) {
|
||||
return key.substring(key.lastIndexOf(":") + 1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package org.lingniu.sdk.service;
|
||||
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
@Component
|
||||
public class RedisOAuth2AuthorizedClientService implements OAuth2AuthorizedClientService {
|
||||
|
||||
private final RedisOAuth2AuthorizedClientRepository repository;
|
||||
|
||||
public RedisOAuth2AuthorizedClientService(RedisOAuth2AuthorizedClientRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends OAuth2AuthorizedClient> T loadAuthorizedClient(
|
||||
String clientRegistrationId, String principalName) {
|
||||
|
||||
// 从 Redis 加载
|
||||
Authentication authentication = createAuthentication(principalName);
|
||||
return repository.loadAuthorizedClient(clientRegistrationId, authentication, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveAuthorizedClient(
|
||||
OAuth2AuthorizedClient authorizedClient, Authentication principal) {
|
||||
|
||||
repository.saveAuthorizedClient(authorizedClient, principal, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAuthorizedClient(String clientRegistrationId, String principalName) {
|
||||
Authentication authentication = createAuthentication(principalName);
|
||||
repository.removeAuthorizedClient(clientRegistrationId, authentication, null, null);
|
||||
}
|
||||
|
||||
|
||||
private Authentication createAuthentication(String principalName) {
|
||||
return new UsernamePasswordAuthenticationToken(
|
||||
principalName,
|
||||
null,
|
||||
Collections.emptyList()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
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.stereotype.Component;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Map;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
public class RedisRefreshTokenService {
|
||||
|
||||
@Autowired
|
||||
private RedisCache redisCache;
|
||||
|
||||
private final long REFRESH_TOKEN_EXPIRE = 30 * 24 * 3600L; // 30天
|
||||
|
||||
/**
|
||||
* 存储Refresh Token到Redis Hash
|
||||
*/
|
||||
public void storeRefreshToken(RefreshTokenInfo tokenInfo) {
|
||||
if(tokenInfo==null){
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取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);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新Refresh Token最后使用时间
|
||||
*/
|
||||
public void updateRefreshToken(RefreshTokenInfo tokenInfo) {
|
||||
if(tokenInfo==null){
|
||||
return;
|
||||
}
|
||||
String key = String.format(CacheConstants.REFRESH_TOKEN_KEY, tokenInfo.getTokenValue());
|
||||
redisCache.setCacheMap(key,tokenInfo.toUpdateMap());
|
||||
}
|
||||
|
||||
/**
|
||||
* 作废 删除
|
||||
* @param tokenInfo
|
||||
*/
|
||||
public void revokeRefreshToken(RefreshTokenInfo tokenInfo) {
|
||||
if(tokenInfo==null){
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
package org.lingniu.sdk.service;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import jakarta.servlet.http.Cookie;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.lingniu.sdk.model.token.AccessTokenInfo;
|
||||
import org.lingniu.sdk.model.token.RefreshTokenInfo;
|
||||
import org.lingniu.sdk.model.token.TokenInfo;
|
||||
import org.lingniu.sdk.model.user.UserInfo;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizationContext;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
|
||||
import org.springframework.security.oauth2.client.RefreshTokenOAuth2AuthorizedClientProvider;
|
||||
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
|
||||
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
|
||||
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
|
||||
import org.springframework.security.oauth2.core.OAuth2Token;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||
import org.springframework.security.web.authentication.rememberme.InvalidCookieException;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Clock;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.*;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
public class TokenService {
|
||||
private final ObjectMapper objectMapper;
|
||||
// 令牌有效期(默认30分钟)单位分钟
|
||||
@Value("${token.accessToken.expireTime:30}")
|
||||
private int accessTokenExpireTime;
|
||||
// 刷新令牌有效期(默认24小时) 单位小时
|
||||
@Value("${token.refreshToken.expireTime:168}")
|
||||
private int refreshTokenExpireTime;
|
||||
@Value("${token.header:Authorization}")
|
||||
private String tokenHeader;
|
||||
private final RedisRefreshTokenService refreshTokenService;
|
||||
|
||||
private final RedisAccessTokenService accessTokenService;
|
||||
|
||||
|
||||
private final RedisOAuth2AuthorizedClientService redisOAuth2AuthorizedClientService;
|
||||
public TokenService(RedisRefreshTokenService refreshTokenService, RedisAccessTokenService accessTokenService, RedisOAuth2AuthorizedClientService redisOAuth2AuthorizedClientService, ObjectMapper objectMapper) {
|
||||
this.refreshTokenService = refreshTokenService;
|
||||
this.accessTokenService = accessTokenService;
|
||||
this.redisOAuth2AuthorizedClientService = redisOAuth2AuthorizedClientService;
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
public TokenInfo createToken(String username) throws JsonProcessingException {
|
||||
String accessToken = UUID.randomUUID().toString().replace("-", "");
|
||||
String refreshToken = UUID.randomUUID().toString().replace("-", "");
|
||||
Instant issuedAt = Instant.now();
|
||||
Instant accessExpiresAt = issuedAt.plusSeconds(accessTokenExpireTime * 60L);
|
||||
Instant refreshExpiresAt = issuedAt.plus(refreshTokenExpireTime, ChronoUnit.HOURS);
|
||||
AccessTokenInfo accessTokenInfo = AccessTokenInfo.builder()
|
||||
.tokenValue(accessToken)
|
||||
.username(username)
|
||||
.issuedAt(issuedAt)
|
||||
.expiresAt(accessExpiresAt)
|
||||
.refreshTokenId(refreshToken)
|
||||
.build();
|
||||
RefreshTokenInfo refreshTokenInfo = RefreshTokenInfo.builder()
|
||||
.tokenValue(refreshToken)
|
||||
.username(username)
|
||||
.accessToken(accessToken)
|
||||
.createdAt(issuedAt)
|
||||
.lastUsedAt(refreshExpiresAt)
|
||||
.accessTokenCount(1)
|
||||
.usageCount(0)
|
||||
.build();
|
||||
return new TokenInfo(accessTokenInfo,refreshTokenInfo);
|
||||
}
|
||||
public void storeTokenInfo(TokenInfo tokenInfo){
|
||||
accessTokenService.storeAccessToken(tokenInfo.getAccessTokenInfo());
|
||||
|
||||
refreshTokenService.storeRefreshToken(tokenInfo.getRefreshTokenInfo());
|
||||
}
|
||||
public void setAccessTokenHeader(HttpServletResponse response,String accessToken){
|
||||
response.addHeader(tokenHeader,accessToken);
|
||||
|
||||
}
|
||||
|
||||
public void setRefreshTokenCookie(HttpServletResponse response, TokenInfo tokenInfo) {
|
||||
String refreshToken = tokenInfo.getRefreshTokenInfo().getTokenValue();
|
||||
// Cookie cookie = new Cookie("refresh_token", refreshToken);
|
||||
// cookie.setHttpOnly(true);
|
||||
// cookie.setSecure(true); // 生产环境设为true
|
||||
// cookie.setPath("/");
|
||||
// cookie.setMaxAge(refreshTokenExpireTime * 60 * 60);
|
||||
// cookie.setDomain(".lingniu.com"); // 设置域名
|
||||
|
||||
// 添加SameSite属性
|
||||
response.addHeader("Set-Cookie",
|
||||
String.format("app_refresh_token=%s; HttpOnly; Secure; Path=/; Max-Age=%d; SameSite=Strict",
|
||||
refreshToken, refreshTokenExpireTime * 60 * 60));
|
||||
}
|
||||
public String getCookieRefreshToken(HttpServletRequest request){
|
||||
Cookie[] cookies = request.getCookies();
|
||||
return Arrays.stream(cookies)
|
||||
.filter(cookie -> "app_refresh_token".equals(cookie.getName()))
|
||||
.map(Cookie::getValue)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
public boolean validateAccessToken(HttpServletRequest request){
|
||||
String accessToken = request.getHeader(tokenHeader);
|
||||
return accessTokenService.validateAccessToken(accessToken);
|
||||
}
|
||||
|
||||
public AccessTokenInfo getAccessTokenInfo(HttpServletRequest request){
|
||||
String accessToken = request.getHeader(tokenHeader);
|
||||
return accessTokenService.getAccessTokenInfo(accessToken);
|
||||
}
|
||||
public RefreshTokenInfo getRefreshTokenInfo(HttpServletRequest request){
|
||||
String accessToken = getCookieRefreshToken(request);
|
||||
return refreshTokenService.getRefreshTokenInfo(accessToken);
|
||||
}
|
||||
|
||||
public AccessTokenInfo refreshToken(HttpServletRequest request,HttpServletResponse response) throws IOException {
|
||||
String accessToken = request.getHeader(tokenHeader);
|
||||
String cookieRefreshToken = getCookieRefreshToken(request);
|
||||
RefreshTokenInfo refreshTokenInfo = refreshTokenService.getRefreshTokenInfo(cookieRefreshToken);
|
||||
if(refreshTokenInfo == null || !refreshTokenInfo.isValid()){
|
||||
log.error("token 已刷新");
|
||||
return null;
|
||||
}
|
||||
if(refreshTokenInfo.getAccessToken()!=null && !refreshTokenInfo.getAccessToken().equals(accessToken)){
|
||||
log.error("token 已刷新");
|
||||
}
|
||||
String clientRegistrationId = refreshTokenInfo.getClientRegistrationId();
|
||||
String username = refreshTokenInfo.getUsername();
|
||||
OAuth2AuthorizedClient oAuth2AuthorizedClient = redisOAuth2AuthorizedClientService.loadAuthorizedClient(clientRegistrationId, username);
|
||||
if(oAuth2AuthorizedClient==null){
|
||||
log.error("idp client is expire");
|
||||
return null;
|
||||
}
|
||||
if(hasTokenExpired(oAuth2AuthorizedClient.getAccessToken())){
|
||||
RefreshTokenOAuth2AuthorizedClientProvider refreshTokenOAuth2AuthorizedClientProvider = new RefreshTokenOAuth2AuthorizedClientProvider();
|
||||
OAuth2AuthorizationContext oAuth2AuthorizationContext = OAuth2AuthorizationContext.withAuthorizedClient(oAuth2AuthorizedClient).principal(createAuthentication(username)).build();
|
||||
oAuth2AuthorizedClient = refreshTokenOAuth2AuthorizedClientProvider.authorize(oAuth2AuthorizationContext);
|
||||
redisOAuth2AuthorizedClientService.saveAuthorizedClient(oAuth2AuthorizedClient,createAuthentication(username));
|
||||
}
|
||||
if(oAuth2AuthorizedClient==null){
|
||||
log.error("idp client is expire");
|
||||
return null;
|
||||
}
|
||||
DefaultOAuth2UserService defaultOAuth2UserService = new DefaultOAuth2UserService();
|
||||
|
||||
OAuth2UserRequest oAuth2UserRequest = new OAuth2UserRequest(oAuth2AuthorizedClient.getClientRegistration(),oAuth2AuthorizedClient.getAccessToken());
|
||||
|
||||
OAuth2User oAuth2User = defaultOAuth2UserService.loadUser(oAuth2UserRequest);
|
||||
String s = objectMapper.writeValueAsString(oAuth2User.getAttributes());
|
||||
|
||||
String accessTokenNew = UUID.randomUUID().toString().replace("-", "");
|
||||
Instant issuedAt = Instant.now();
|
||||
Instant accessExpiresAt = issuedAt.plusSeconds(accessTokenExpireTime * 60L);
|
||||
AccessTokenInfo accessTokenInfo = AccessTokenInfo.builder()
|
||||
.tokenValue(accessTokenNew)
|
||||
.username(refreshTokenInfo.getUsername())
|
||||
.username(refreshTokenInfo.getUsername())
|
||||
.issuedAt(issuedAt)
|
||||
.expiresAt(accessExpiresAt)
|
||||
.additionalInfo(s)
|
||||
.clientRegistrationId(refreshTokenInfo.getClientRegistrationId())
|
||||
.refreshTokenId(refreshTokenInfo.getTokenValue())
|
||||
.build();
|
||||
accessTokenService.storeAccessToken(accessTokenInfo);
|
||||
|
||||
refreshTokenInfo.incrementUsage();
|
||||
refreshTokenInfo.incrementAccessTokenUsage(accessTokenNew);
|
||||
refreshTokenService.updateRefreshToken(refreshTokenInfo);
|
||||
setAccessTokenHeader(response,accessTokenNew);
|
||||
return accessTokenInfo;
|
||||
}
|
||||
|
||||
public UserInfo convertPrincipal(AccessTokenInfo accessTokenInfo) throws JsonProcessingException {
|
||||
return objectMapper.convertValue(objectMapper.readValue(accessTokenInfo.getAdditionalInfo(), Map.class), UserInfo.class);
|
||||
}
|
||||
public void revokeToken(HttpServletRequest request,HttpServletResponse response){
|
||||
String accessToken = request.getHeader(tokenHeader);
|
||||
String cookieRefreshToken = getCookieRefreshToken(request);
|
||||
AccessTokenInfo accessTokenInfo = accessTokenService.getAccessTokenInfo(accessToken);
|
||||
RefreshTokenInfo refreshTokenInfo = refreshTokenService.getRefreshTokenInfo(cookieRefreshToken);
|
||||
Instant now = Instant.now();
|
||||
if(accessTokenInfo!=null){
|
||||
accessTokenService.revokeAccessToken(accessTokenInfo);
|
||||
}
|
||||
if(refreshTokenInfo!=null){
|
||||
refreshTokenService.revokeRefreshToken(refreshTokenInfo);
|
||||
}
|
||||
clearRefreshTokenCookie(response);
|
||||
}
|
||||
public void clearToken(HttpServletRequest request){
|
||||
RefreshTokenInfo refreshTokenInfo = getRefreshTokenInfo(request);
|
||||
AccessTokenInfo accessTokenInfo = getAccessTokenInfo(request);
|
||||
accessTokenService.revokeAccessToken(accessTokenInfo);
|
||||
refreshTokenService.revokeRefreshToken(refreshTokenInfo);
|
||||
if(refreshTokenInfo!=null){
|
||||
AccessTokenInfo accessTokenInfo1 = accessTokenService.getAccessTokenInfo(refreshTokenInfo.getAccessToken());
|
||||
accessTokenService.revokeAccessToken(accessTokenInfo1);
|
||||
}
|
||||
// redisOAuth2AuthorizedClientService.removeAuthorizedClient(refreshTokenInfo.getClientRegistrationId(),refreshTokenInfo.getUsername());
|
||||
}
|
||||
|
||||
public void clearRefreshTokenCookie(HttpServletResponse response) {
|
||||
Cookie cookie = new Cookie("refresh_token", null);
|
||||
cookie.setHttpOnly(true);
|
||||
cookie.setSecure(true);
|
||||
cookie.setPath("/");
|
||||
cookie.setMaxAge(0);
|
||||
|
||||
response.addCookie(cookie);
|
||||
}
|
||||
private final Duration clockSkew = Duration.ofSeconds(60);
|
||||
|
||||
private final Clock clock = Clock.systemUTC();
|
||||
private boolean hasTokenExpired(OAuth2Token token) {
|
||||
return this.clock.instant().isAfter(Objects.requireNonNull(token.getExpiresAt()).minus(this.clockSkew));
|
||||
}
|
||||
private Authentication createAuthentication(String principalName) {
|
||||
return new UsernamePasswordAuthenticationToken(
|
||||
principalName,
|
||||
null,
|
||||
Collections.emptyList()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package org.lingniu.sdk.utils;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.time.Duration;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class HttpClientUtils {
|
||||
|
||||
private static final HttpClient DEFAULT_CLIENT = HttpClient.newBuilder()
|
||||
.connectTimeout(Duration.ofSeconds(10))
|
||||
.version(HttpClient.Version.HTTP_2)
|
||||
.build();
|
||||
|
||||
// GET请求
|
||||
public static String get(String url, Map<String, String> headers) throws Exception {
|
||||
HttpRequest.Builder builder = HttpRequest.newBuilder()
|
||||
.uri(URI.create(url))
|
||||
.GET();
|
||||
|
||||
if (headers != null) {
|
||||
headers.forEach(builder::header);
|
||||
}
|
||||
|
||||
HttpRequest request = builder.build();
|
||||
|
||||
HttpResponse<String> response = DEFAULT_CLIENT.send(
|
||||
request,
|
||||
HttpResponse.BodyHandlers.ofString()
|
||||
);
|
||||
|
||||
if (response.statusCode() >= 200 && response.statusCode() < 300) {
|
||||
return response.body();
|
||||
} else {
|
||||
throw new RuntimeException("HTTP Error: " + response.statusCode());
|
||||
}
|
||||
}
|
||||
|
||||
// POST请求
|
||||
public static String post(String url, String body, Map<String, String> headers)
|
||||
throws Exception {
|
||||
HttpRequest.Builder builder = HttpRequest.newBuilder()
|
||||
.uri(URI.create(url))
|
||||
.POST(HttpRequest.BodyPublishers.ofString(body));
|
||||
|
||||
if (headers != null) {
|
||||
headers.forEach(builder::header);
|
||||
}
|
||||
|
||||
HttpRequest request = builder.build();
|
||||
|
||||
HttpResponse<String> response = DEFAULT_CLIENT.send(
|
||||
request,
|
||||
HttpResponse.BodyHandlers.ofString()
|
||||
);
|
||||
|
||||
if (response.statusCode() >= 200 && response.statusCode() < 300) {
|
||||
return response.body();
|
||||
} else {
|
||||
throw new RuntimeException("HTTP Error: " + response.statusCode());
|
||||
}
|
||||
}
|
||||
|
||||
// POST JSON请求
|
||||
public static String postJson(String url, String json) throws Exception {
|
||||
return post(url, json, Map.of(
|
||||
"Content-Type", "application/json",
|
||||
"Accept", "application/json"
|
||||
));
|
||||
}
|
||||
|
||||
// 异步GET请求
|
||||
public static CompletableFuture<String> getAsync(String url) {
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create(url))
|
||||
.GET()
|
||||
.build();
|
||||
|
||||
return DEFAULT_CLIENT.sendAsync(request, HttpResponse.BodyHandlers.ofString())
|
||||
.thenApply(response -> {
|
||||
if (response.statusCode() >= 200 && response.statusCode() < 300) {
|
||||
return response.body();
|
||||
} else {
|
||||
throw new RuntimeException("HTTP Error: " + response.statusCode());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 测试示例
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
// 同步GET
|
||||
String response = get(
|
||||
"https://jsonplaceholder.typicode.com/posts/1",
|
||||
Map.of("User-Agent", "Java Client")
|
||||
);
|
||||
System.out.println("GET Response: " + response);
|
||||
|
||||
// 异步GET
|
||||
getAsync("https://jsonplaceholder.typicode.com/posts/2")
|
||||
.thenAccept(r -> System.out.println("Async Response: " + r))
|
||||
.join();
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package org.lingniu.sdk.web;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.lingniu.sdk.model.base.CommonResult;
|
||||
import org.lingniu.sdk.model.user.UserInfo;
|
||||
import org.lingniu.sdk.utils.HttpClientUtils;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
|
||||
import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@RequestMapping("/idp")
|
||||
@RestController
|
||||
public class UserController {
|
||||
|
||||
private final OAuth2ClientProperties oAuth2ClientProperties;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
public UserController(OAuth2ClientProperties oAuth2ClientProperties, ObjectMapper objectMapper) {
|
||||
this.oAuth2ClientProperties = oAuth2ClientProperties;
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
@GetMapping("/routes")
|
||||
public CommonResult<Object> getUserMenu(@RegisteredOAuth2AuthorizedClient OAuth2AuthorizedClient oAuth2AuthorizedClient) throws Exception {
|
||||
OAuth2AccessToken.TokenType tokenType = oAuth2AuthorizedClient.getAccessToken().getTokenType();
|
||||
String tokenValue = oAuth2AuthorizedClient.getAccessToken().getTokenValue();
|
||||
|
||||
String s = HttpClientUtils.get(oAuth2ClientProperties.getProvider().get("idp").getUserInfoUri().replace("userinfo","idp/getRouters"),
|
||||
Map.of("Authorization",tokenType.getValue() + " " + tokenValue,"Accept","application/json"));
|
||||
return CommonResult.success(objectMapper.readValue(s,Map.class).get("data"));
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,5 @@
|
||||
#Generated by Maven
|
||||
#Sun Feb 08 09:46:13 CST 2026
|
||||
groupId=org.lingniu
|
||||
artifactId=oauth2-login-sdk
|
||||
version=1.0-SNAPSHOT
|
||||
@@ -0,0 +1,30 @@
|
||||
org\lingniu\sdk\model\token\AccessTokenInfo.class
|
||||
org\lingniu\sdk\service\RedisRefreshTokenService.class
|
||||
org\lingniu\sdk\constant\UserConstants.class
|
||||
org\lingniu\sdk\model\token\TokenInfo.class
|
||||
org\lingniu\sdk\common\redis\RedisCache.class
|
||||
org\lingniu\sdk\model\base\CommonResult.class
|
||||
org\lingniu\sdk\model\user\UserPost.class
|
||||
org\lingniu\sdk\service\RedisOAuth2AuthorizedClientService.class
|
||||
org\lingniu\sdk\constant\CacheConstants.class
|
||||
org\lingniu\sdk\service\RedisOAuth2AuthorizedClientRepository$1.class
|
||||
org\lingniu\sdk\service\RedisAccessTokenService.class
|
||||
org\lingniu\sdk\model\user\DataPermission.class
|
||||
org\lingniu\sdk\service\RedisOAuth2AuthorizedClientRepository.class
|
||||
org\lingniu\sdk\web\UserController.class
|
||||
org\lingniu\sdk\model\user\UserInfo.class
|
||||
org\lingniu\sdk\utils\HttpClientUtils.class
|
||||
org\lingniu\sdk\handler\LoginSuccessHandler.class
|
||||
org\lingniu\sdk\model\token\TokenInfo$TokenInfoBuilder.class
|
||||
org\lingniu\sdk\constant\Constants.class
|
||||
org\lingniu\sdk\service\TokenService.class
|
||||
org\lingniu\sdk\model\user\UserDept.class
|
||||
org\lingniu\sdk\model\token\AccessTokenInfo$AccessTokenInfoBuilder.class
|
||||
org\lingniu\sdk\model\token\RefreshTokenInfo$RefreshTokenInfoBuilder.class
|
||||
org\lingniu\sdk\config\RedisConfig.class
|
||||
org\lingniu\sdk\config\JacksonConfiguration.class
|
||||
org\lingniu\sdk\filter\IdpAuthenticationFilter.class
|
||||
org\lingniu\sdk\common\serializer\FastJson2JsonRedisSerializer.class
|
||||
org\lingniu\sdk\handler\LogoutIdpSuccessHandler.class
|
||||
org\lingniu\sdk\model\token\RefreshTokenInfo.class
|
||||
org\lingniu\sdk\config\SecurityConfig.class
|
||||
@@ -0,0 +1,26 @@
|
||||
D:\privateProjects\lingniu-platform\backend\sdk\oauth2-login-sdk\src\main\java\org\lingniu\sdk\constant\CacheConstants.java
|
||||
D:\privateProjects\lingniu-platform\backend\sdk\oauth2-login-sdk\src\main\java\org\lingniu\sdk\model\user\DataPermission.java
|
||||
D:\privateProjects\lingniu-platform\backend\sdk\oauth2-login-sdk\src\main\java\org\lingniu\sdk\service\TokenService.java
|
||||
D:\privateProjects\lingniu-platform\backend\sdk\oauth2-login-sdk\src\main\java\org\lingniu\sdk\model\token\TokenInfo.java
|
||||
D:\privateProjects\lingniu-platform\backend\sdk\oauth2-login-sdk\src\main\java\org\lingniu\sdk\utils\HttpClientUtils.java
|
||||
D:\privateProjects\lingniu-platform\backend\sdk\oauth2-login-sdk\src\main\java\org\lingniu\sdk\model\user\UserInfo.java
|
||||
D:\privateProjects\lingniu-platform\backend\sdk\oauth2-login-sdk\src\main\java\org\lingniu\sdk\service\RedisOAuth2AuthorizedClientService.java
|
||||
D:\privateProjects\lingniu-platform\backend\sdk\oauth2-login-sdk\src\main\java\org\lingniu\sdk\constant\Constants.java
|
||||
D:\privateProjects\lingniu-platform\backend\sdk\oauth2-login-sdk\src\main\java\org\lingniu\sdk\service\RedisAccessTokenService.java
|
||||
D:\privateProjects\lingniu-platform\backend\sdk\oauth2-login-sdk\src\main\java\org\lingniu\sdk\config\JacksonConfiguration.java
|
||||
D:\privateProjects\lingniu-platform\backend\sdk\oauth2-login-sdk\src\main\java\org\lingniu\sdk\config\RedisConfig.java
|
||||
D:\privateProjects\lingniu-platform\backend\sdk\oauth2-login-sdk\src\main\java\org\lingniu\sdk\model\base\CommonResult.java
|
||||
D:\privateProjects\lingniu-platform\backend\sdk\oauth2-login-sdk\src\main\java\org\lingniu\sdk\model\user\UserDept.java
|
||||
D:\privateProjects\lingniu-platform\backend\sdk\oauth2-login-sdk\src\main\java\org\lingniu\sdk\config\SecurityConfig.java
|
||||
D:\privateProjects\lingniu-platform\backend\sdk\oauth2-login-sdk\src\main\java\org\lingniu\sdk\filter\IdpAuthenticationFilter.java
|
||||
D:\privateProjects\lingniu-platform\backend\sdk\oauth2-login-sdk\src\main\java\org\lingniu\sdk\model\user\UserPost.java
|
||||
D:\privateProjects\lingniu-platform\backend\sdk\oauth2-login-sdk\src\main\java\org\lingniu\sdk\common\redis\RedisCache.java
|
||||
D:\privateProjects\lingniu-platform\backend\sdk\oauth2-login-sdk\src\main\java\org\lingniu\sdk\model\token\RefreshTokenInfo.java
|
||||
D:\privateProjects\lingniu-platform\backend\sdk\oauth2-login-sdk\src\main\java\org\lingniu\sdk\web\UserController.java
|
||||
D:\privateProjects\lingniu-platform\backend\sdk\oauth2-login-sdk\src\main\java\org\lingniu\sdk\service\RedisOAuth2AuthorizedClientRepository.java
|
||||
D:\privateProjects\lingniu-platform\backend\sdk\oauth2-login-sdk\src\main\java\org\lingniu\sdk\handler\LogoutIdpSuccessHandler.java
|
||||
D:\privateProjects\lingniu-platform\backend\sdk\oauth2-login-sdk\src\main\java\org\lingniu\sdk\service\RedisRefreshTokenService.java
|
||||
D:\privateProjects\lingniu-platform\backend\sdk\oauth2-login-sdk\src\main\java\org\lingniu\sdk\constant\UserConstants.java
|
||||
D:\privateProjects\lingniu-platform\backend\sdk\oauth2-login-sdk\src\main\java\org\lingniu\sdk\handler\LoginSuccessHandler.java
|
||||
D:\privateProjects\lingniu-platform\backend\sdk\oauth2-login-sdk\src\main\java\org\lingniu\sdk\model\token\AccessTokenInfo.java
|
||||
D:\privateProjects\lingniu-platform\backend\sdk\oauth2-login-sdk\src\main\java\org\lingniu\sdk\common\serializer\FastJson2JsonRedisSerializer.java
|
||||
Binary file not shown.
Reference in New Issue
Block a user