完善权限认证
This commit is contained in:
parent
156955c448
commit
e9f8c2fae7
|
|
@ -1,4 +1,3 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||||
<component name="MavenProjectsManager">
|
<component name="MavenProjectsManager">
|
||||||
|
|
|
||||||
|
|
@ -119,6 +119,17 @@
|
||||||
<artifactId>jjwt</artifactId>
|
<artifactId>jjwt</artifactId>
|
||||||
<version>0.9.1</version>
|
<version>0.9.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- 解析客户端操作系统、浏览器等 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>eu.bitwalker</groupId>
|
||||||
|
<artifactId>UserAgentUtils</artifactId>
|
||||||
|
<version>1.21</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.security</groupId>
|
||||||
|
<artifactId>spring-security-config</artifactId>
|
||||||
|
<version>6.0.1</version>
|
||||||
|
</dependency>
|
||||||
<!-- <dependency>-->
|
<!-- <dependency>-->
|
||||||
<!-- <groupId>com.qihang</groupId>-->
|
<!-- <groupId>com.qihang</groupId>-->
|
||||||
<!-- <artifactId>oms-core</artifactId>-->
|
<!-- <artifactId>oms-core</artifactId>-->
|
||||||
|
|
|
||||||
|
|
@ -1,90 +0,0 @@
|
||||||
package com.qihang.oms.api.config;
|
|
||||||
|
|
||||||
import io.jsonwebtoken.Claims;
|
|
||||||
import io.jsonwebtoken.JwtBuilder;
|
|
||||||
import io.jsonwebtoken.Jwts;
|
|
||||||
import io.jsonwebtoken.SignatureAlgorithm;
|
|
||||||
|
|
||||||
import javax.crypto.SecretKey;
|
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
|
||||||
import java.util.Base64;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
public class JwtUtil {
|
|
||||||
/**
|
|
||||||
* 需要拦截的请求头信息
|
|
||||||
*/
|
|
||||||
public static final String TOKEN_HEADER = "token";
|
|
||||||
/**
|
|
||||||
* 有效期
|
|
||||||
*/
|
|
||||||
public static final Long JWT_EXPIRE_TIME = 60 * 60 * 1000L; // 1h
|
|
||||||
/**
|
|
||||||
* 加密算法
|
|
||||||
*/
|
|
||||||
public static final String SIGN_ALGORITHMS = "AES";
|
|
||||||
/**
|
|
||||||
* jwt key
|
|
||||||
*/
|
|
||||||
public static final String JWT_KEY = "security";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取token
|
|
||||||
* @param id 唯一标识(盐值)
|
|
||||||
* @param subject
|
|
||||||
* @param expire
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public static String createToken(String id, String subject, Long expire) {
|
|
||||||
JwtBuilder builder = getJwtBuilder(subject, expire, id);
|
|
||||||
return builder.compact();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 解析token
|
|
||||||
* @param token
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public static Claims parseToken(String token) {
|
|
||||||
SecretKey secretKey = generalKey();
|
|
||||||
Claims body = Jwts.parser()
|
|
||||||
.setSigningKey(secretKey)
|
|
||||||
.parseClaimsJws(token)
|
|
||||||
.getBody();
|
|
||||||
return body;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static JwtBuilder getJwtBuilder(String subject, Long expire, String uuid) {
|
|
||||||
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
|
|
||||||
SecretKey secretKey = generalKey();
|
|
||||||
Date date = new Date();
|
|
||||||
if (expire == null) {
|
|
||||||
expire = JWT_EXPIRE_TIME;
|
|
||||||
}
|
|
||||||
if (uuid == null) {
|
|
||||||
uuid = getUUID();
|
|
||||||
}
|
|
||||||
Long expireTime = date.getTime() + expire;
|
|
||||||
Date expireDate = new Date(expireTime);
|
|
||||||
JwtBuilder builder = Jwts.builder()
|
|
||||||
.setId(uuid) // 唯一标识
|
|
||||||
.setSubject(subject) // 签名数据/主题
|
|
||||||
.setIssuer(JWT_KEY) // 签发者
|
|
||||||
.setIssuedAt(date) // 签发时间
|
|
||||||
.signWith(signatureAlgorithm, secretKey) // 签名算法 + 秘钥
|
|
||||||
.setExpiration(expireDate); // 过期时间
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getUUID() {
|
|
||||||
return UUID.randomUUID().toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 生成秘钥
|
|
||||||
public static SecretKey generalKey() {
|
|
||||||
byte[] encodeKey = Base64.getDecoder().decode(JWT_KEY);
|
|
||||||
SecretKey secretKey = new SecretKeySpec(encodeKey, 0, encodeKey.length, SIGN_ALGORITHMS);
|
|
||||||
return secretKey;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,265 @@
|
||||||
|
package com.qihang.oms.api.config;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
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 org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* spring redis 工具类
|
||||||
|
*
|
||||||
|
* @author qihang
|
||||||
|
**/
|
||||||
|
@SuppressWarnings(value = { "unchecked", "rawtypes" })
|
||||||
|
@Component
|
||||||
|
public class RedisCache
|
||||||
|
{
|
||||||
|
@Autowired
|
||||||
|
public 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得缓存的基本对象列表
|
||||||
|
*
|
||||||
|
* @param pattern 字符串前缀
|
||||||
|
* @return 对象列表
|
||||||
|
*/
|
||||||
|
public Collection<String> keys(final String pattern)
|
||||||
|
{
|
||||||
|
return redisTemplate.keys(pattern);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package com.qihang.oms.api.config;
|
package com.qihang.oms.api.config;
|
||||||
|
|
||||||
|
|
||||||
|
import com.qihang.oms.api.utils.FastJson2JsonRedisSerializer;
|
||||||
import org.springframework.cache.annotation.CachingConfigurerSupport;
|
import org.springframework.cache.annotation.CachingConfigurerSupport;
|
||||||
import org.springframework.cache.annotation.EnableCaching;
|
import org.springframework.cache.annotation.EnableCaching;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
|
|
|
||||||
|
|
@ -1,88 +0,0 @@
|
||||||
package com.qihang.oms.api.config;
|
|
||||||
|
|
||||||
import com.alibaba.nacos.client.naming.utils.CollectionUtils;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.boot.autoconfigure.security.reactive.PathRequest;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.http.HttpMethod;
|
|
||||||
import org.springframework.security.authentication.AuthenticationManager;
|
|
||||||
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
|
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
|
||||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
|
||||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
|
||||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
@EnableWebSecurity
|
|
||||||
public class SecurityConfig {
|
|
||||||
@Autowired
|
|
||||||
private TokenFilter tokenFilter;
|
|
||||||
@Autowired
|
|
||||||
private AuthenticationExceptionHandler authenticationExceptionHandler;
|
|
||||||
@Bean
|
|
||||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
|
||||||
// List<String> permitAllPaths = new ArrayList<>();//security.getPermitAllPath();
|
|
||||||
// permitAllPaths.add("/login");
|
|
||||||
// // 配置不需要认证的请求(这里所有的路径可以写在配置文件上修改时就不用改代码)
|
|
||||||
// if (!CollectionUtils.isEmpty(permitAllPaths)) {
|
|
||||||
// permitAllPaths.forEach(path -> {
|
|
||||||
// try {
|
|
||||||
// http.authorizeHttpRequests().requestMatchers(path).permitAll();
|
|
||||||
// } catch (Exception e) {
|
|
||||||
// e.printStackTrace();
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// http.csrf().disable().authorizeHttpRequests().requestMatchers(HttpMethod.GET,"/home").permitAll();
|
|
||||||
// 关闭csrf因为不使用session
|
|
||||||
http.csrf().disable()
|
|
||||||
// 不通过Session获取SecurityContext
|
|
||||||
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
|
|
||||||
.and()
|
|
||||||
.authorizeHttpRequests(authorizeHttpRequests -> authorizeHttpRequests
|
|
||||||
|
|
||||||
.requestMatchers( "/home").permitAll()
|
|
||||||
.requestMatchers("/login").permitAll()
|
|
||||||
// 允许 SpringMVC 的默认错误地址匿名访问
|
|
||||||
.requestMatchers("/error").permitAll()
|
|
||||||
// 其他所有接口必须有Authority信息,Authority在登录成功后的UserDetailsImpl对象中默认设置“ROLE_USER”
|
|
||||||
//.requestMatchers("/**").hasAnyAuthority("ROLE_USER")
|
|
||||||
// 允许任意请求被已登录用户访问,不检查Authority
|
|
||||||
.anyRequest().authenticated()
|
|
||||||
)
|
|
||||||
// 除了上面那些请求都需要认证 https://blog.csdn.net/xieshaohu/article/details/129780439
|
|
||||||
// .anyRequest().authenticated()
|
|
||||||
// .and()
|
|
||||||
// 配置异常处理
|
|
||||||
// 如果是认证过程中出现的异常会被封装成AuthenticationException然后调用AuthenticationEntryPoint对象的方法去进行异常处理。
|
|
||||||
// 如果是授权过程中出现的异常会被封装成AccessDeniedException然后调用AccessDeniedHandler对象的方法去进行异常处理。
|
|
||||||
.exceptionHandling()
|
|
||||||
.authenticationEntryPoint(authenticationExceptionHandler);
|
|
||||||
// 配置token拦截过滤器
|
|
||||||
http.addFilterBefore(tokenFilter, UsernamePasswordAuthenticationFilter.class);
|
|
||||||
return http.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 身份认证管理器,调用authenticate()方法完成认证
|
|
||||||
*/
|
|
||||||
@Bean
|
|
||||||
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
|
|
||||||
return authenticationConfiguration.getAuthenticationManager();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 密码加密器
|
|
||||||
*/
|
|
||||||
@Bean
|
|
||||||
public PasswordEncoder passwordEncoder() {
|
|
||||||
return new BCryptPasswordEncoder();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,119 +0,0 @@
|
||||||
package com.qihang.oms.api.config;
|
|
||||||
|
|
||||||
|
|
||||||
import com.alibaba.fastjson2.JSON;
|
|
||||||
import com.qihang.oms.api.common.R;
|
|
||||||
import io.jsonwebtoken.Claims;
|
|
||||||
import jakarta.servlet.FilterChain;
|
|
||||||
import jakarta.servlet.ServletException;
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.data.redis.core.RedisTemplate;
|
|
||||||
import org.springframework.http.MediaType;
|
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
import org.springframework.util.AntPathMatcher;
|
|
||||||
import org.springframework.util.CollectionUtils;
|
|
||||||
import org.springframework.web.filter.OncePerRequestFilter;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.PrintWriter;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @Description
|
|
||||||
* @Author
|
|
||||||
* @Date
|
|
||||||
**/
|
|
||||||
@Component
|
|
||||||
public class TokenFilter extends OncePerRequestFilter {
|
|
||||||
private Logger log = LoggerFactory.getLogger(getClass());
|
|
||||||
private AntPathMatcher pathMatcher = new AntPathMatcher();
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private RedisTemplate<String, String> redisTemplate;
|
|
||||||
|
|
||||||
// @Autowired
|
|
||||||
// private DemoConfiguration.Security security;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
|
|
||||||
String token = request.getHeader("token");
|
|
||||||
log.info("intercept " + request.getRequestURI());
|
|
||||||
|
|
||||||
// token=1用于swagger页面调用API
|
|
||||||
/*if (!StringUtils.hasText(token) || "1".equals(token)) {
|
|
||||||
filterChain.doFilter(request, response);
|
|
||||||
return;
|
|
||||||
}*/
|
|
||||||
// 判断是否是放行请求
|
|
||||||
if (isFilterRequest(request)) {
|
|
||||||
filterChain.doFilter(request, response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Claims claims = null;
|
|
||||||
try {
|
|
||||||
claims = JwtUtil.parseToken(token);
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error(e.getMessage());
|
|
||||||
fallback("token解析失败(非法token)!", response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
String username = claims.getSubject();
|
|
||||||
String cache = (String) redisTemplate.opsForValue().get(String.format("login_tokens:", username));
|
|
||||||
if (cache == null) {
|
|
||||||
fallback("token失效,请重新登录!", response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// SecurityUser user = JSON.parseObject(cache, SecurityUser.class);
|
|
||||||
// log.info(JSON.toJSONString(user));
|
|
||||||
// UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user, null, null);
|
|
||||||
// SecurityContextHolder.getContext().setAuthentication(authenticationToken);
|
|
||||||
// 放行
|
|
||||||
filterChain.doFilter(request, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void fallback(String message, HttpServletResponse response) {
|
|
||||||
response.setCharacterEncoding("UTF-8");
|
|
||||||
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
|
||||||
PrintWriter writer = null;
|
|
||||||
try {
|
|
||||||
if (message == null) {
|
|
||||||
message = "403 Forbidden";
|
|
||||||
}
|
|
||||||
R res = R.error(403, message);
|
|
||||||
writer = response.getWriter();
|
|
||||||
writer.append(JSON.toJSONString(res));
|
|
||||||
} catch (IOException e) {
|
|
||||||
log.error(e.getMessage());
|
|
||||||
} finally {
|
|
||||||
if (writer != null) {
|
|
||||||
writer.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isFilterRequest(HttpServletRequest request) {
|
|
||||||
String contextPath = request.getContextPath();
|
|
||||||
String filterPath = request.getRequestURI();
|
|
||||||
List<String> permitAllPathList = new ArrayList<>();//security.getPermitAllPath();
|
|
||||||
if (CollectionUtils.isEmpty(permitAllPathList)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for (String path : permitAllPathList) {
|
|
||||||
String pattern = contextPath + path;
|
|
||||||
pattern = pattern.replaceAll("/+", "/");
|
|
||||||
if (pathMatcher.match(pattern, filterPath)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -27,4 +27,9 @@ public class HomeController {
|
||||||
public String feign() {
|
public String feign() {
|
||||||
return echoService.echo();
|
return echoService.echo();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping(value = "/home")
|
||||||
|
public String home(){
|
||||||
|
return "home";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
package com.qihang.oms.api.controller;
|
||||||
|
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
public class LoginController {
|
||||||
|
@RequestMapping(value = "/login")
|
||||||
|
public String home(){
|
||||||
|
return "login";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,249 @@
|
||||||
|
package com.qihang.oms.api.domain;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户对象 sys_user
|
||||||
|
*
|
||||||
|
* @author qihang
|
||||||
|
*/
|
||||||
|
public class SysUser
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/** 用户ID */
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
/** 部门ID */
|
||||||
|
private Long deptId;
|
||||||
|
|
||||||
|
/** 用户账号 */
|
||||||
|
|
||||||
|
private String userName;
|
||||||
|
|
||||||
|
/** 用户昵称 */
|
||||||
|
private String nickName;
|
||||||
|
|
||||||
|
/** 用户邮箱 */
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
/** 手机号码 */
|
||||||
|
private String phonenumber;
|
||||||
|
|
||||||
|
/** 用户性别 */
|
||||||
|
private String sex;
|
||||||
|
|
||||||
|
/** 用户头像 */
|
||||||
|
private String avatar;
|
||||||
|
|
||||||
|
/** 密码 */
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
/** 帐号状态(0正常 1停用) */
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
/** 删除标志(0代表存在 2代表删除) */
|
||||||
|
private String delFlag;
|
||||||
|
|
||||||
|
/** 最后登录IP */
|
||||||
|
private String loginIp;
|
||||||
|
|
||||||
|
/** 最后登录时间 */
|
||||||
|
private Date loginDate;
|
||||||
|
|
||||||
|
/** 角色组 */
|
||||||
|
private Long[] roleIds;
|
||||||
|
|
||||||
|
/** 岗位组 */
|
||||||
|
private Long[] postIds;
|
||||||
|
|
||||||
|
/** 角色ID */
|
||||||
|
private Long roleId;
|
||||||
|
|
||||||
|
public SysUser()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public SysUser(Long userId)
|
||||||
|
{
|
||||||
|
this.userId = userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getUserId()
|
||||||
|
{
|
||||||
|
return userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserId(Long userId)
|
||||||
|
{
|
||||||
|
this.userId = userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAdmin()
|
||||||
|
{
|
||||||
|
return isAdmin(this.userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isAdmin(Long userId)
|
||||||
|
{
|
||||||
|
return userId != null && 1L == userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getDeptId()
|
||||||
|
{
|
||||||
|
return deptId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDeptId(Long deptId)
|
||||||
|
{
|
||||||
|
this.deptId = deptId;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String getNickName()
|
||||||
|
{
|
||||||
|
return nickName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNickName(String nickName)
|
||||||
|
{
|
||||||
|
this.nickName = nickName;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String getUserName()
|
||||||
|
{
|
||||||
|
return userName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserName(String userName)
|
||||||
|
{
|
||||||
|
this.userName = userName;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String getEmail()
|
||||||
|
{
|
||||||
|
return email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEmail(String email)
|
||||||
|
{
|
||||||
|
this.email = email;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String getPhonenumber()
|
||||||
|
{
|
||||||
|
return phonenumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPhonenumber(String phonenumber)
|
||||||
|
{
|
||||||
|
this.phonenumber = phonenumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSex()
|
||||||
|
{
|
||||||
|
return sex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSex(String sex)
|
||||||
|
{
|
||||||
|
this.sex = sex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAvatar()
|
||||||
|
{
|
||||||
|
return avatar;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAvatar(String avatar)
|
||||||
|
{
|
||||||
|
this.avatar = avatar;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPassword()
|
||||||
|
{
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPassword(String password)
|
||||||
|
{
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStatus()
|
||||||
|
{
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatus(String status)
|
||||||
|
{
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDelFlag()
|
||||||
|
{
|
||||||
|
return delFlag;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDelFlag(String delFlag)
|
||||||
|
{
|
||||||
|
this.delFlag = delFlag;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLoginIp()
|
||||||
|
{
|
||||||
|
return loginIp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLoginIp(String loginIp)
|
||||||
|
{
|
||||||
|
this.loginIp = loginIp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getLoginDate()
|
||||||
|
{
|
||||||
|
return loginDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLoginDate(Date loginDate)
|
||||||
|
{
|
||||||
|
this.loginDate = loginDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Long[] getRoleIds()
|
||||||
|
{
|
||||||
|
return roleIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRoleIds(Long[] roleIds)
|
||||||
|
{
|
||||||
|
this.roleIds = roleIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long[] getPostIds()
|
||||||
|
{
|
||||||
|
return postIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPostIds(Long[] postIds)
|
||||||
|
{
|
||||||
|
this.postIds = postIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getRoleId()
|
||||||
|
{
|
||||||
|
return roleId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRoleId(Long roleId)
|
||||||
|
{
|
||||||
|
this.roleId = roleId;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.qihang.oms.api.config;
|
package com.qihang.oms.api.security;
|
||||||
|
|
||||||
import com.alibaba.fastjson2.JSON;
|
import com.alibaba.fastjson2.JSON;
|
||||||
import jakarta.servlet.ServletException;
|
import jakarta.servlet.ServletException;
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
package com.qihang.oms.api.security;
|
||||||
|
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSON;
|
||||||
|
import com.qihang.oms.api.common.R;
|
||||||
|
import jakarta.servlet.FilterChain;
|
||||||
|
import jakarta.servlet.ServletException;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* token过滤器 验证token有效性
|
||||||
|
*
|
||||||
|
* @author qihang
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter
|
||||||
|
{
|
||||||
|
@Autowired
|
||||||
|
private TokenService tokenService;
|
||||||
|
/**
|
||||||
|
* 需要拦截的请求头信息
|
||||||
|
*/
|
||||||
|
@Value("${token.header:'Authorization'}")
|
||||||
|
public String TOKEN_HEADER = "Authorization";
|
||||||
|
private Logger log = LoggerFactory.getLogger(getClass());
|
||||||
|
@Override
|
||||||
|
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
|
||||||
|
throws ServletException, IOException
|
||||||
|
{
|
||||||
|
String token = request.getHeader(TOKEN_HEADER);
|
||||||
|
log.info("intercept " + request.getRequestURI());
|
||||||
|
log.info("token: " + token);
|
||||||
|
LoginUser loginUser = tokenService.getLoginUser(request);
|
||||||
|
if (loginUser !=null )
|
||||||
|
{
|
||||||
|
tokenService.verifyToken(loginUser);
|
||||||
|
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
|
||||||
|
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
|
||||||
|
}
|
||||||
|
chain.doFilter(request, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fallback(String message, HttpServletResponse response) {
|
||||||
|
response.setCharacterEncoding("UTF-8");
|
||||||
|
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||||
|
PrintWriter writer = null;
|
||||||
|
try {
|
||||||
|
if (message == null) {
|
||||||
|
message = "403 Forbidden";
|
||||||
|
}
|
||||||
|
R res = R.error(403, message);
|
||||||
|
writer = response.getWriter();
|
||||||
|
writer.append(JSON.toJSONString(res));
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error(e.getMessage());
|
||||||
|
} finally {
|
||||||
|
if (writer != null) {
|
||||||
|
writer.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,94 @@
|
||||||
|
//package com.qihang.oms.api.security;
|
||||||
|
//
|
||||||
|
//import io.jsonwebtoken.Claims;
|
||||||
|
//import io.jsonwebtoken.JwtBuilder;
|
||||||
|
//import io.jsonwebtoken.Jwts;
|
||||||
|
//import io.jsonwebtoken.SignatureAlgorithm;
|
||||||
|
//import org.springframework.beans.factory.annotation.Value;
|
||||||
|
//
|
||||||
|
//import javax.crypto.SecretKey;
|
||||||
|
//import javax.crypto.spec.SecretKeySpec;
|
||||||
|
//import java.util.Base64;
|
||||||
|
//import java.util.Date;
|
||||||
|
//import java.util.UUID;
|
||||||
|
//
|
||||||
|
//public class JwtUtil {
|
||||||
|
// /**
|
||||||
|
// * 需要拦截的请求头信息
|
||||||
|
// */
|
||||||
|
// @Value("${token.header:'Authorization'}")
|
||||||
|
// public static final String TOKEN_HEADER = "Authorization";
|
||||||
|
// /**
|
||||||
|
// * 有效期
|
||||||
|
// */
|
||||||
|
// @Value("${token.expireTime:1800000}")
|
||||||
|
// public static final Long JWT_EXPIRE_TIME = 30 * 60 * 1000L; // 30分钟
|
||||||
|
// /**
|
||||||
|
// * 加密算法
|
||||||
|
// */
|
||||||
|
// public static final String SIGN_ALGORITHMS = "AES";
|
||||||
|
// /**
|
||||||
|
// * jwt key
|
||||||
|
// */
|
||||||
|
// @Value("${token.secret:'abcdefghijklmnopqrstuvwxyz'}")
|
||||||
|
// public static final String JWT_KEY = "abcdefghijklmnopqrstuvwxyz";
|
||||||
|
//
|
||||||
|
// /**
|
||||||
|
// * 获取token
|
||||||
|
// * @param id 唯一标识(盐值)
|
||||||
|
// * @param subject
|
||||||
|
// * @param expire
|
||||||
|
// * @return
|
||||||
|
// */
|
||||||
|
// public static String createToken(String id, String subject, Long expire) {
|
||||||
|
// JwtBuilder builder = getJwtBuilder(subject, expire, id);
|
||||||
|
// return builder.compact();
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// /**
|
||||||
|
// * 解析token
|
||||||
|
// * @param token
|
||||||
|
// * @return
|
||||||
|
// */
|
||||||
|
// public static Claims parseToken(String token) {
|
||||||
|
// SecretKey secretKey = generalKey();
|
||||||
|
// Claims body = Jwts.parser()
|
||||||
|
// .setSigningKey(secretKey)
|
||||||
|
// .parseClaimsJws(token)
|
||||||
|
// .getBody();
|
||||||
|
// return body;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// private static JwtBuilder getJwtBuilder(String subject, Long expire, String uuid) {
|
||||||
|
// SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
|
||||||
|
// SecretKey secretKey = generalKey();
|
||||||
|
// Date date = new Date();
|
||||||
|
// if (expire == null) {
|
||||||
|
// expire = JWT_EXPIRE_TIME;
|
||||||
|
// }
|
||||||
|
// if (uuid == null) {
|
||||||
|
// uuid = getUUID();
|
||||||
|
// }
|
||||||
|
// Long expireTime = date.getTime() + expire;
|
||||||
|
// Date expireDate = new Date(expireTime);
|
||||||
|
// JwtBuilder builder = Jwts.builder()
|
||||||
|
// .setId(uuid) // 唯一标识
|
||||||
|
// .setSubject(subject) // 签名数据/主题
|
||||||
|
// .setIssuer(JWT_KEY) // 签发者
|
||||||
|
// .setIssuedAt(date) // 签发时间
|
||||||
|
// .signWith(signatureAlgorithm, secretKey) // 签名算法 + 秘钥
|
||||||
|
// .setExpiration(expireDate); // 过期时间
|
||||||
|
// return builder;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// public static String getUUID() {
|
||||||
|
// return UUID.randomUUID().toString();
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // 生成秘钥
|
||||||
|
// public static SecretKey generalKey() {
|
||||||
|
// byte[] encodeKey = Base64.getDecoder().decode(JWT_KEY);
|
||||||
|
// SecretKey secretKey = new SecretKeySpec(encodeKey, 0, encodeKey.length, SIGN_ALGORITHMS);
|
||||||
|
// return secretKey;
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
@ -0,0 +1,267 @@
|
||||||
|
package com.qihang.oms.api.security;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.annotation.JSONField;
|
||||||
|
import com.qihang.oms.api.domain.SysUser;
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 登录用户身份权限
|
||||||
|
*
|
||||||
|
* @author qihang
|
||||||
|
*/
|
||||||
|
public class LoginUser implements UserDetails
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户ID
|
||||||
|
*/
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 部门ID
|
||||||
|
*/
|
||||||
|
private Long deptId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户唯一标识
|
||||||
|
*/
|
||||||
|
private String token;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 登录时间
|
||||||
|
*/
|
||||||
|
private Long loginTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 过期时间
|
||||||
|
*/
|
||||||
|
private Long expireTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 登录IP地址
|
||||||
|
*/
|
||||||
|
private String ipaddr;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 登录地点
|
||||||
|
*/
|
||||||
|
private String loginLocation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 浏览器类型
|
||||||
|
*/
|
||||||
|
private String browser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 操作系统
|
||||||
|
*/
|
||||||
|
private String os;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 权限列表
|
||||||
|
*/
|
||||||
|
private Set<String> permissions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户信息
|
||||||
|
*/
|
||||||
|
private SysUser user;
|
||||||
|
|
||||||
|
public LoginUser()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public LoginUser(SysUser user, Set<String> permissions)
|
||||||
|
{
|
||||||
|
this.user = user;
|
||||||
|
this.permissions = permissions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LoginUser(Long userId, Long deptId, SysUser user, Set<String> permissions)
|
||||||
|
{
|
||||||
|
this.userId = userId;
|
||||||
|
this.deptId = deptId;
|
||||||
|
this.user = user;
|
||||||
|
this.permissions = permissions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getUserId()
|
||||||
|
{
|
||||||
|
return userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserId(Long userId)
|
||||||
|
{
|
||||||
|
this.userId = userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getDeptId()
|
||||||
|
{
|
||||||
|
return deptId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDeptId(Long deptId)
|
||||||
|
{
|
||||||
|
this.deptId = deptId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getToken()
|
||||||
|
{
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setToken(String token)
|
||||||
|
{
|
||||||
|
this.token = token;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JSONField(serialize = false)
|
||||||
|
@Override
|
||||||
|
public String getPassword()
|
||||||
|
{
|
||||||
|
return user.getPassword();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUsername()
|
||||||
|
{
|
||||||
|
return user.getUserName();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 账户是否未过期,过期无法验证
|
||||||
|
*/
|
||||||
|
@JSONField(serialize = false)
|
||||||
|
@Override
|
||||||
|
public boolean isAccountNonExpired()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 指定用户是否解锁,锁定的用户无法进行身份验证
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@JSONField(serialize = false)
|
||||||
|
@Override
|
||||||
|
public boolean isAccountNonLocked()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 指示是否已过期的用户的凭据(密码),过期的凭据防止认证
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@JSONField(serialize = false)
|
||||||
|
@Override
|
||||||
|
public boolean isCredentialsNonExpired()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否可用 ,禁用的用户不能身份验证
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@JSONField(serialize = false)
|
||||||
|
@Override
|
||||||
|
public boolean isEnabled()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getLoginTime()
|
||||||
|
{
|
||||||
|
return loginTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLoginTime(Long loginTime)
|
||||||
|
{
|
||||||
|
this.loginTime = loginTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getIpaddr()
|
||||||
|
{
|
||||||
|
return ipaddr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIpaddr(String ipaddr)
|
||||||
|
{
|
||||||
|
this.ipaddr = ipaddr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLoginLocation()
|
||||||
|
{
|
||||||
|
return loginLocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLoginLocation(String loginLocation)
|
||||||
|
{
|
||||||
|
this.loginLocation = loginLocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBrowser()
|
||||||
|
{
|
||||||
|
return browser;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBrowser(String browser)
|
||||||
|
{
|
||||||
|
this.browser = browser;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getOs()
|
||||||
|
{
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOs(String os)
|
||||||
|
{
|
||||||
|
this.os = os;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getExpireTime()
|
||||||
|
{
|
||||||
|
return expireTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExpireTime(Long expireTime)
|
||||||
|
{
|
||||||
|
this.expireTime = expireTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<String> getPermissions()
|
||||||
|
{
|
||||||
|
return permissions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPermissions(Set<String> permissions)
|
||||||
|
{
|
||||||
|
this.permissions = permissions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SysUser getUser()
|
||||||
|
{
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUser(SysUser user)
|
||||||
|
{
|
||||||
|
this.user = user;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<? extends GrantedAuthority> getAuthorities()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,109 @@
|
||||||
|
package com.qihang.oms.api.security;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.http.HttpMethod;
|
||||||
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
|
import org.springframework.security.authentication.AuthenticationProvider;
|
||||||
|
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
|
||||||
|
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
|
||||||
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSecurity
|
||||||
|
public class SecurityConfig {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SecurityUserDetailsService userDetailsService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private AuthenticationExceptionHandler invalidAuthenticationEntryPoint;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public PasswordEncoder passwordEncoder() {
|
||||||
|
return new BCryptPasswordEncoder();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public JwtAuthenticationTokenFilter authenticationJwtTokenFilter() {
|
||||||
|
return new JwtAuthenticationTokenFilter();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||||
|
|
||||||
|
http
|
||||||
|
// 禁用basic明文验证
|
||||||
|
.httpBasic().disable()
|
||||||
|
// 前后端分离架构不需要csrf保护
|
||||||
|
.csrf().disable()
|
||||||
|
// 禁用默认登录页
|
||||||
|
.formLogin().disable()
|
||||||
|
// 禁用默认登出页
|
||||||
|
.logout().disable()
|
||||||
|
// 设置异常的EntryPoint,如果不设置,默认使用Http403ForbiddenEntryPoint
|
||||||
|
.exceptionHandling(exceptions -> exceptions.authenticationEntryPoint(invalidAuthenticationEntryPoint))
|
||||||
|
// 前后端分离是无状态的,不需要session了,直接禁用。
|
||||||
|
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||||
|
.authorizeHttpRequests(authorizeHttpRequests -> authorizeHttpRequests
|
||||||
|
// 允许所有OPTIONS请求
|
||||||
|
.requestMatchers("/home").permitAll()
|
||||||
|
.requestMatchers(HttpMethod.GET, "/favicon.ico").permitAll()
|
||||||
|
// 允许直接访问授权登录接口
|
||||||
|
.requestMatchers(HttpMethod.POST, "/login").permitAll()
|
||||||
|
// 允许 SpringMVC 的默认错误地址匿名访问
|
||||||
|
.requestMatchers("/error").permitAll()
|
||||||
|
// 其他所有接口必须有Authority信息,Authority在登录成功后的UserDetailsImpl对象中默认设置“ROLE_USER”
|
||||||
|
//.requestMatchers("/**").hasAnyAuthority("ROLE_USER")
|
||||||
|
// 允许任意请求被已登录用户访问,不检查Authority
|
||||||
|
.anyRequest().authenticated())
|
||||||
|
.authenticationProvider(authenticationProvider())
|
||||||
|
// 加我们自定义的过滤器,替代UsernamePasswordAuthenticationFilter
|
||||||
|
.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
|
||||||
|
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public UserDetailsService userDetailsService() {
|
||||||
|
// 调用 JwtUserDetailService实例执行实际校验
|
||||||
|
return username -> userDetailsService.loadUserByUsername(username);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调用loadUserByUsername获得UserDetail信息,在AbstractUserDetailsAuthenticationProvider里执行用户状态检查
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public AuthenticationProvider authenticationProvider() {
|
||||||
|
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
|
||||||
|
// DaoAuthenticationProvider 从自定义的 userDetailsService.loadUserByUsername 方法获取UserDetails
|
||||||
|
authProvider.setUserDetailsService(userDetailsService());
|
||||||
|
// 设置密码编辑器
|
||||||
|
authProvider.setPasswordEncoder(passwordEncoder());
|
||||||
|
return authProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 登录时需要调用AuthenticationManager.authenticate执行一次校验
|
||||||
|
*
|
||||||
|
* @param config
|
||||||
|
* @return
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
|
||||||
|
return config.getAuthenticationManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
package com.qihang.oms.api.security;
|
||||||
|
|
||||||
|
import com.qihang.oms.api.domain.SysUser;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
|
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class SecurityUserDetailsService implements UserDetailsService {
|
||||||
|
@Override
|
||||||
|
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||||
|
|
||||||
|
SysUser user = new SysUser();
|
||||||
|
user.setUserName("admin");
|
||||||
|
Set<String> p = new HashSet<>();
|
||||||
|
p.add("all");
|
||||||
|
UserDetails u = new LoginUser(1L,1L,user,p);
|
||||||
|
return u;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,221 @@
|
||||||
|
package com.qihang.oms.api.security;
|
||||||
|
|
||||||
|
import com.alibaba.cloud.commons.lang.StringUtils;
|
||||||
|
import com.qihang.oms.api.config.RedisCache;
|
||||||
|
import com.qihang.oms.api.utils.IdUtils;
|
||||||
|
import io.jsonwebtoken.Claims;
|
||||||
|
import io.jsonwebtoken.Jwts;
|
||||||
|
import io.jsonwebtoken.SignatureAlgorithm;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* token验证处理
|
||||||
|
*
|
||||||
|
* @author qihang
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class TokenService
|
||||||
|
{
|
||||||
|
// 令牌自定义标识
|
||||||
|
@Value("${token.header:'Authorization'}")
|
||||||
|
private String header;
|
||||||
|
|
||||||
|
// 令牌秘钥
|
||||||
|
@Value("${token.secret:'abcdefghijklmnopqrstuvwxyz'}")
|
||||||
|
private String secret;
|
||||||
|
|
||||||
|
// 令牌有效期(默认30分钟)
|
||||||
|
@Value("${token.expireTime:30}")
|
||||||
|
private int expireTime;
|
||||||
|
|
||||||
|
protected static final long MILLIS_SECOND = 1000;
|
||||||
|
|
||||||
|
protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND;
|
||||||
|
|
||||||
|
private static final Long MILLIS_MINUTE_TEN = 20 * 60 * 1000L;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RedisCache redisCache;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户身份信息
|
||||||
|
*
|
||||||
|
* @return 用户信息
|
||||||
|
*/
|
||||||
|
public LoginUser getLoginUser(HttpServletRequest request)
|
||||||
|
{
|
||||||
|
// 获取请求携带的令牌
|
||||||
|
String token = getToken(request);
|
||||||
|
if (StringUtils.isNotEmpty(token))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Claims claims = parseToken(token);
|
||||||
|
// 解析对应的权限以及用户信息
|
||||||
|
String uuid = (String) claims.get("login_user_key");
|
||||||
|
String userKey = getTokenKey(uuid);
|
||||||
|
LoginUser user = redisCache.getCacheObject(userKey);
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置用户身份信息
|
||||||
|
*/
|
||||||
|
public void setLoginUser(LoginUser loginUser)
|
||||||
|
{
|
||||||
|
if (loginUser!= null && StringUtils.isNotEmpty(loginUser.getToken()))
|
||||||
|
{
|
||||||
|
refreshToken(loginUser);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除用户身份信息
|
||||||
|
*/
|
||||||
|
public void delLoginUser(String token)
|
||||||
|
{
|
||||||
|
if (StringUtils.isNotEmpty(token))
|
||||||
|
{
|
||||||
|
String userKey = getTokenKey(token);
|
||||||
|
redisCache.deleteObject(userKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建令牌
|
||||||
|
*
|
||||||
|
* @param loginUser 用户信息
|
||||||
|
* @return 令牌
|
||||||
|
*/
|
||||||
|
public String createToken(LoginUser loginUser)
|
||||||
|
{
|
||||||
|
String token = IdUtils.fastUUID();
|
||||||
|
loginUser.setToken(token);
|
||||||
|
setUserAgent(loginUser);
|
||||||
|
refreshToken(loginUser);
|
||||||
|
|
||||||
|
Map<String, Object> claims = new HashMap<>();
|
||||||
|
claims.put("login_user_key", token);
|
||||||
|
return createToken(claims);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证令牌有效期,相差不足20分钟,自动刷新缓存
|
||||||
|
*
|
||||||
|
* @param loginUser
|
||||||
|
* @return 令牌
|
||||||
|
*/
|
||||||
|
public void verifyToken(LoginUser loginUser)
|
||||||
|
{
|
||||||
|
long expireTime = loginUser.getExpireTime();
|
||||||
|
long currentTime = System.currentTimeMillis();
|
||||||
|
if (expireTime - currentTime <= MILLIS_MINUTE_TEN)
|
||||||
|
{
|
||||||
|
refreshToken(loginUser);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新令牌有效期
|
||||||
|
*
|
||||||
|
* @param loginUser 登录信息
|
||||||
|
*/
|
||||||
|
public void refreshToken(LoginUser loginUser)
|
||||||
|
{
|
||||||
|
loginUser.setLoginTime(System.currentTimeMillis());
|
||||||
|
loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE);
|
||||||
|
// 根据uuid将loginUser缓存
|
||||||
|
String userKey = getTokenKey(loginUser.getToken());
|
||||||
|
redisCache.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置用户代理信息
|
||||||
|
*
|
||||||
|
* @param loginUser 登录信息
|
||||||
|
*/
|
||||||
|
public void setUserAgent(LoginUser loginUser)
|
||||||
|
{
|
||||||
|
// UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent"));
|
||||||
|
// String ip = IpUtils.getIpAddr();
|
||||||
|
// loginUser.setIpaddr(ip);
|
||||||
|
// loginUser.setLoginLocation(AddressUtils.getRealAddressByIP(ip));
|
||||||
|
// loginUser.setBrowser(userAgent.getBrowser().getName());
|
||||||
|
// loginUser.setOs(userAgent.getOperatingSystem().getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从数据声明生成令牌
|
||||||
|
*
|
||||||
|
* @param claims 数据声明
|
||||||
|
* @return 令牌
|
||||||
|
*/
|
||||||
|
private String createToken(Map<String, Object> claims)
|
||||||
|
{
|
||||||
|
String token = Jwts.builder()
|
||||||
|
.setClaims(claims)
|
||||||
|
.signWith(SignatureAlgorithm.HS512, secret).compact();
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从令牌中获取数据声明
|
||||||
|
*
|
||||||
|
* @param token 令牌
|
||||||
|
* @return 数据声明
|
||||||
|
*/
|
||||||
|
private Claims parseToken(String token)
|
||||||
|
{
|
||||||
|
return Jwts.parser()
|
||||||
|
.setSigningKey(secret)
|
||||||
|
.parseClaimsJws(token)
|
||||||
|
.getBody();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从令牌中获取用户名
|
||||||
|
*
|
||||||
|
* @param token 令牌
|
||||||
|
* @return 用户名
|
||||||
|
*/
|
||||||
|
public String getUsernameFromToken(String token)
|
||||||
|
{
|
||||||
|
Claims claims = parseToken(token);
|
||||||
|
return claims.getSubject();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取请求token
|
||||||
|
*
|
||||||
|
* @param request
|
||||||
|
* @return token
|
||||||
|
*/
|
||||||
|
private String getToken(HttpServletRequest request)
|
||||||
|
{
|
||||||
|
String token = request.getHeader(header);
|
||||||
|
// if (StringUtils.isNotEmpty(token) && token.startsWith(Constants.TOKEN_PREFIX))
|
||||||
|
// {
|
||||||
|
// token = token.replace(Constants.TOKEN_PREFIX, "");
|
||||||
|
// }
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getTokenKey(String uuid)
|
||||||
|
{
|
||||||
|
// return CacheConstants.LOGIN_TOKEN_KEY + uuid;
|
||||||
|
return uuid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.qihang.oms.api.config;
|
package com.qihang.oms.api.utils;
|
||||||
|
|
||||||
import com.alibaba.fastjson2.JSON;
|
import com.alibaba.fastjson2.JSON;
|
||||||
import com.alibaba.fastjson2.JSONReader;
|
import com.alibaba.fastjson2.JSONReader;
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
package com.qihang.oms.api.utils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ID生成器工具类
|
||||||
|
*
|
||||||
|
* @author qihang
|
||||||
|
*/
|
||||||
|
public class IdUtils
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 获取随机UUID
|
||||||
|
*
|
||||||
|
* @return 随机UUID
|
||||||
|
*/
|
||||||
|
public static String randomUUID()
|
||||||
|
{
|
||||||
|
return UUID.randomUUID().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 简化的UUID,去掉了横线
|
||||||
|
*
|
||||||
|
* @return 简化的UUID,去掉了横线
|
||||||
|
*/
|
||||||
|
public static String simpleUUID()
|
||||||
|
{
|
||||||
|
return UUID.randomUUID().toString(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取随机UUID,使用性能更好的ThreadLocalRandom生成UUID
|
||||||
|
*
|
||||||
|
* @return 随机UUID
|
||||||
|
*/
|
||||||
|
public static String fastUUID()
|
||||||
|
{
|
||||||
|
return UUID.fastUUID().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 简化的UUID,去掉了横线,使用性能更好的ThreadLocalRandom生成UUID
|
||||||
|
*
|
||||||
|
* @return 简化的UUID,去掉了横线
|
||||||
|
*/
|
||||||
|
public static String fastSimpleUUID()
|
||||||
|
{
|
||||||
|
return UUID.fastUUID().toString(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,492 @@
|
||||||
|
package com.qihang.oms.api.utils;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提供通用唯一识别码(universally unique identifier)(UUID)实现
|
||||||
|
*
|
||||||
|
* @author qihang
|
||||||
|
*/
|
||||||
|
public final class UUID implements java.io.Serializable, Comparable<UUID>
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = -1185015143654744140L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SecureRandom 的单例
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private static class Holder
|
||||||
|
{
|
||||||
|
static final SecureRandom numberGenerator;
|
||||||
|
|
||||||
|
static {
|
||||||
|
try {
|
||||||
|
numberGenerator = getSecureRandom();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 此UUID的最高64有效位 */
|
||||||
|
private final long mostSigBits;
|
||||||
|
|
||||||
|
/** 此UUID的最低64有效位 */
|
||||||
|
private final long leastSigBits;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 私有构造
|
||||||
|
*
|
||||||
|
* @param data 数据
|
||||||
|
*/
|
||||||
|
private UUID(byte[] data)
|
||||||
|
{
|
||||||
|
long msb = 0;
|
||||||
|
long lsb = 0;
|
||||||
|
assert data.length == 16 : "data must be 16 bytes in length";
|
||||||
|
for (int i = 0; i < 8; i++)
|
||||||
|
{
|
||||||
|
msb = (msb << 8) | (data[i] & 0xff);
|
||||||
|
}
|
||||||
|
for (int i = 8; i < 16; i++)
|
||||||
|
{
|
||||||
|
lsb = (lsb << 8) | (data[i] & 0xff);
|
||||||
|
}
|
||||||
|
this.mostSigBits = msb;
|
||||||
|
this.leastSigBits = lsb;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用指定的数据构造新的 UUID。
|
||||||
|
*
|
||||||
|
* @param mostSigBits 用于 {@code UUID} 的最高有效 64 位
|
||||||
|
* @param leastSigBits 用于 {@code UUID} 的最低有效 64 位
|
||||||
|
*/
|
||||||
|
public UUID(long mostSigBits, long leastSigBits)
|
||||||
|
{
|
||||||
|
this.mostSigBits = mostSigBits;
|
||||||
|
this.leastSigBits = leastSigBits;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取类型 4(伪随机生成的)UUID 的静态工厂。
|
||||||
|
*
|
||||||
|
* @return 随机生成的 {@code UUID}
|
||||||
|
*/
|
||||||
|
public static UUID fastUUID()
|
||||||
|
{
|
||||||
|
return randomUUID(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取类型 4(伪随机生成的)UUID 的静态工厂。 使用加密的强伪随机数生成器生成该 UUID。
|
||||||
|
*
|
||||||
|
* @return 随机生成的 {@code UUID}
|
||||||
|
*/
|
||||||
|
public static UUID randomUUID()
|
||||||
|
{
|
||||||
|
return randomUUID(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取类型 4(伪随机生成的)UUID 的静态工厂。 使用加密的强伪随机数生成器生成该 UUID。
|
||||||
|
*
|
||||||
|
* @param isSecure 是否使用{@link SecureRandom}如果是可以获得更安全的随机码,否则可以得到更好的性能
|
||||||
|
* @return 随机生成的 {@code UUID}
|
||||||
|
*/
|
||||||
|
public static UUID randomUUID(boolean isSecure)
|
||||||
|
{
|
||||||
|
final Random ng = isSecure ? Holder.numberGenerator : getRandom();
|
||||||
|
|
||||||
|
byte[] randomBytes = new byte[16];
|
||||||
|
ng.nextBytes(randomBytes);
|
||||||
|
randomBytes[6] &= 0x0f; /* clear version */
|
||||||
|
randomBytes[6] |= 0x40; /* set to version 4 */
|
||||||
|
randomBytes[8] &= 0x3f; /* clear variant */
|
||||||
|
randomBytes[8] |= 0x80; /* set to IETF variant */
|
||||||
|
return new UUID(randomBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据指定的字节数组获取类型 3(基于名称的)UUID 的静态工厂。
|
||||||
|
*
|
||||||
|
* @param name 用于构造 UUID 的字节数组。
|
||||||
|
*
|
||||||
|
* @return 根据指定数组生成的 {@code UUID}
|
||||||
|
*/
|
||||||
|
public static UUID nameUUIDFromBytes(byte[] name)
|
||||||
|
{
|
||||||
|
MessageDigest md;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
md = MessageDigest.getInstance("MD5");
|
||||||
|
}
|
||||||
|
catch (NoSuchAlgorithmException nsae)
|
||||||
|
{
|
||||||
|
throw new InternalError("MD5 not supported");
|
||||||
|
}
|
||||||
|
byte[] md5Bytes = md.digest(name);
|
||||||
|
md5Bytes[6] &= 0x0f; /* clear version */
|
||||||
|
md5Bytes[6] |= 0x30; /* set to version 3 */
|
||||||
|
md5Bytes[8] &= 0x3f; /* clear variant */
|
||||||
|
md5Bytes[8] |= 0x80; /* set to IETF variant */
|
||||||
|
return new UUID(md5Bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据 {@link #toString()} 方法中描述的字符串标准表示形式创建{@code UUID}。
|
||||||
|
*
|
||||||
|
* @param name 指定 {@code UUID} 字符串
|
||||||
|
* @return 具有指定值的 {@code UUID}
|
||||||
|
* @throws IllegalArgumentException 如果 name 与 {@link #toString} 中描述的字符串表示形式不符抛出此异常
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public static UUID fromString(String name)
|
||||||
|
{
|
||||||
|
String[] components = name.split("-");
|
||||||
|
if (components.length != 5)
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("Invalid UUID string: " + name);
|
||||||
|
}
|
||||||
|
for (int i = 0; i < 5; i++)
|
||||||
|
{
|
||||||
|
components[i] = "0x" + components[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
long mostSigBits = Long.decode(components[0]).longValue();
|
||||||
|
mostSigBits <<= 16;
|
||||||
|
mostSigBits |= Long.decode(components[1]).longValue();
|
||||||
|
mostSigBits <<= 16;
|
||||||
|
mostSigBits |= Long.decode(components[2]).longValue();
|
||||||
|
|
||||||
|
long leastSigBits = Long.decode(components[3]).longValue();
|
||||||
|
leastSigBits <<= 48;
|
||||||
|
leastSigBits |= Long.decode(components[4]).longValue();
|
||||||
|
|
||||||
|
return new UUID(mostSigBits, leastSigBits);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回此 UUID 的 128 位值中的最低有效 64 位。
|
||||||
|
*
|
||||||
|
* @return 此 UUID 的 128 位值中的最低有效 64 位。
|
||||||
|
*/
|
||||||
|
public long getLeastSignificantBits()
|
||||||
|
{
|
||||||
|
return leastSigBits;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回此 UUID 的 128 位值中的最高有效 64 位。
|
||||||
|
*
|
||||||
|
* @return 此 UUID 的 128 位值中最高有效 64 位。
|
||||||
|
*/
|
||||||
|
public long getMostSignificantBits()
|
||||||
|
{
|
||||||
|
return mostSigBits;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 与此 {@code UUID} 相关联的版本号. 版本号描述此 {@code UUID} 是如何生成的。
|
||||||
|
* <p>
|
||||||
|
* 版本号具有以下含意:
|
||||||
|
* <ul>
|
||||||
|
* <li>1 基于时间的 UUID
|
||||||
|
* <li>2 DCE 安全 UUID
|
||||||
|
* <li>3 基于名称的 UUID
|
||||||
|
* <li>4 随机生成的 UUID
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @return 此 {@code UUID} 的版本号
|
||||||
|
*/
|
||||||
|
public int version()
|
||||||
|
{
|
||||||
|
// Version is bits masked by 0x000000000000F000 in MS long
|
||||||
|
return (int) ((mostSigBits >> 12) & 0x0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 与此 {@code UUID} 相关联的变体号。变体号描述 {@code UUID} 的布局。
|
||||||
|
* <p>
|
||||||
|
* 变体号具有以下含意:
|
||||||
|
* <ul>
|
||||||
|
* <li>0 为 NCS 向后兼容保留
|
||||||
|
* <li>2 <a href="http://www.ietf.org/rfc/rfc4122.txt">IETF RFC 4122</a>(Leach-Salz), 用于此类
|
||||||
|
* <li>6 保留,微软向后兼容
|
||||||
|
* <li>7 保留供以后定义使用
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @return 此 {@code UUID} 相关联的变体号
|
||||||
|
*/
|
||||||
|
public int variant()
|
||||||
|
{
|
||||||
|
// This field is composed of a varying number of bits.
|
||||||
|
// 0 - - Reserved for NCS backward compatibility
|
||||||
|
// 1 0 - The IETF aka Leach-Salz variant (used by this class)
|
||||||
|
// 1 1 0 Reserved, Microsoft backward compatibility
|
||||||
|
// 1 1 1 Reserved for future definition.
|
||||||
|
return (int) ((leastSigBits >>> (64 - (leastSigBits >>> 62))) & (leastSigBits >> 63));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 与此 UUID 相关联的时间戳值。
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* 60 位的时间戳值根据此 {@code UUID} 的 time_low、time_mid 和 time_hi 字段构造。<br>
|
||||||
|
* 所得到的时间戳以 100 毫微秒为单位,从 UTC(通用协调时间) 1582 年 10 月 15 日零时开始。
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* 时间戳值仅在在基于时间的 UUID(其 version 类型为 1)中才有意义。<br>
|
||||||
|
* 如果此 {@code UUID} 不是基于时间的 UUID,则此方法抛出 UnsupportedOperationException。
|
||||||
|
*
|
||||||
|
* @throws UnsupportedOperationException 如果此 {@code UUID} 不是 version 为 1 的 UUID。
|
||||||
|
*/
|
||||||
|
public long timestamp() throws UnsupportedOperationException
|
||||||
|
{
|
||||||
|
checkTimeBase();
|
||||||
|
return (mostSigBits & 0x0FFFL) << 48//
|
||||||
|
| ((mostSigBits >> 16) & 0x0FFFFL) << 32//
|
||||||
|
| mostSigBits >>> 32;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 与此 UUID 相关联的时钟序列值。
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* 14 位的时钟序列值根据此 UUID 的 clock_seq 字段构造。clock_seq 字段用于保证在基于时间的 UUID 中的时间唯一性。
|
||||||
|
* <p>
|
||||||
|
* {@code clockSequence} 值仅在基于时间的 UUID(其 version 类型为 1)中才有意义。 如果此 UUID 不是基于时间的 UUID,则此方法抛出
|
||||||
|
* UnsupportedOperationException。
|
||||||
|
*
|
||||||
|
* @return 此 {@code UUID} 的时钟序列
|
||||||
|
*
|
||||||
|
* @throws UnsupportedOperationException 如果此 UUID 的 version 不为 1
|
||||||
|
*/
|
||||||
|
public int clockSequence() throws UnsupportedOperationException
|
||||||
|
{
|
||||||
|
checkTimeBase();
|
||||||
|
return (int) ((leastSigBits & 0x3FFF000000000000L) >>> 48);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 与此 UUID 相关的节点值。
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* 48 位的节点值根据此 UUID 的 node 字段构造。此字段旨在用于保存机器的 IEEE 802 地址,该地址用于生成此 UUID 以保证空间唯一性。
|
||||||
|
* <p>
|
||||||
|
* 节点值仅在基于时间的 UUID(其 version 类型为 1)中才有意义。<br>
|
||||||
|
* 如果此 UUID 不是基于时间的 UUID,则此方法抛出 UnsupportedOperationException。
|
||||||
|
*
|
||||||
|
* @return 此 {@code UUID} 的节点值
|
||||||
|
*
|
||||||
|
* @throws UnsupportedOperationException 如果此 UUID 的 version 不为 1
|
||||||
|
*/
|
||||||
|
public long node() throws UnsupportedOperationException
|
||||||
|
{
|
||||||
|
checkTimeBase();
|
||||||
|
return leastSigBits & 0x0000FFFFFFFFFFFFL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回此{@code UUID} 的字符串表现形式。
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* UUID 的字符串表示形式由此 BNF 描述:
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* {@code
|
||||||
|
* UUID = <time_low>-<time_mid>-<time_high_and_version>-<variant_and_sequence>-<node>
|
||||||
|
* time_low = 4*<hexOctet>
|
||||||
|
* time_mid = 2*<hexOctet>
|
||||||
|
* time_high_and_version = 2*<hexOctet>
|
||||||
|
* variant_and_sequence = 2*<hexOctet>
|
||||||
|
* node = 6*<hexOctet>
|
||||||
|
* hexOctet = <hexDigit><hexDigit>
|
||||||
|
* hexDigit = [0-9a-fA-F]
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* </blockquote>
|
||||||
|
*
|
||||||
|
* @return 此{@code UUID} 的字符串表现形式
|
||||||
|
* @see #toString(boolean)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return toString(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回此{@code UUID} 的字符串表现形式。
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* UUID 的字符串表示形式由此 BNF 描述:
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* {@code
|
||||||
|
* UUID = <time_low>-<time_mid>-<time_high_and_version>-<variant_and_sequence>-<node>
|
||||||
|
* time_low = 4*<hexOctet>
|
||||||
|
* time_mid = 2*<hexOctet>
|
||||||
|
* time_high_and_version = 2*<hexOctet>
|
||||||
|
* variant_and_sequence = 2*<hexOctet>
|
||||||
|
* node = 6*<hexOctet>
|
||||||
|
* hexOctet = <hexDigit><hexDigit>
|
||||||
|
* hexDigit = [0-9a-fA-F]
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* </blockquote>
|
||||||
|
*
|
||||||
|
* @param isSimple 是否简单模式,简单模式为不带'-'的UUID字符串
|
||||||
|
* @return 此{@code UUID} 的字符串表现形式
|
||||||
|
*/
|
||||||
|
public String toString(boolean isSimple)
|
||||||
|
{
|
||||||
|
final StringBuilder builder = new StringBuilder(isSimple ? 32 : 36);
|
||||||
|
// time_low
|
||||||
|
builder.append(digits(mostSigBits >> 32, 8));
|
||||||
|
if (!isSimple)
|
||||||
|
{
|
||||||
|
builder.append('-');
|
||||||
|
}
|
||||||
|
// time_mid
|
||||||
|
builder.append(digits(mostSigBits >> 16, 4));
|
||||||
|
if (!isSimple)
|
||||||
|
{
|
||||||
|
builder.append('-');
|
||||||
|
}
|
||||||
|
// time_high_and_version
|
||||||
|
builder.append(digits(mostSigBits, 4));
|
||||||
|
if (!isSimple)
|
||||||
|
{
|
||||||
|
builder.append('-');
|
||||||
|
}
|
||||||
|
// variant_and_sequence
|
||||||
|
builder.append(digits(leastSigBits >> 48, 4));
|
||||||
|
if (!isSimple)
|
||||||
|
{
|
||||||
|
builder.append('-');
|
||||||
|
}
|
||||||
|
// node
|
||||||
|
builder.append(digits(leastSigBits, 12));
|
||||||
|
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回此 UUID 的哈希码。
|
||||||
|
*
|
||||||
|
* @return UUID 的哈希码值。
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int hashCode()
|
||||||
|
{
|
||||||
|
long hilo = mostSigBits ^ leastSigBits;
|
||||||
|
return ((int) (hilo >> 32)) ^ (int) hilo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将此对象与指定对象比较。
|
||||||
|
* <p>
|
||||||
|
* 当且仅当参数不为 {@code null}、而是一个 UUID 对象、具有与此 UUID 相同的 varriant、包含相同的值(每一位均相同)时,结果才为 {@code true}。
|
||||||
|
*
|
||||||
|
* @param obj 要与之比较的对象
|
||||||
|
*
|
||||||
|
* @return 如果对象相同,则返回 {@code true};否则返回 {@code false}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj)
|
||||||
|
{
|
||||||
|
if ((null == obj) || (obj.getClass() != UUID.class))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
UUID id = (UUID) obj;
|
||||||
|
return (mostSigBits == id.mostSigBits && leastSigBits == id.leastSigBits);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Comparison Operations
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将此 UUID 与指定的 UUID 比较。
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* 如果两个 UUID 不同,且第一个 UUID 的最高有效字段大于第二个 UUID 的对应字段,则第一个 UUID 大于第二个 UUID。
|
||||||
|
*
|
||||||
|
* @param val 与此 UUID 比较的 UUID
|
||||||
|
*
|
||||||
|
* @return 在此 UUID 小于、等于或大于 val 时,分别返回 -1、0 或 1。
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int compareTo(UUID val)
|
||||||
|
{
|
||||||
|
// The ordering is intentionally set up so that the UUIDs
|
||||||
|
// can simply be numerically compared as two numbers
|
||||||
|
return (this.mostSigBits < val.mostSigBits ? -1 : //
|
||||||
|
(this.mostSigBits > val.mostSigBits ? 1 : //
|
||||||
|
(this.leastSigBits < val.leastSigBits ? -1 : //
|
||||||
|
(this.leastSigBits > val.leastSigBits ? 1 : //
|
||||||
|
0))));
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------------------------------------------------
|
||||||
|
// Private method start
|
||||||
|
/**
|
||||||
|
* 返回指定数字对应的hex值
|
||||||
|
*
|
||||||
|
* @param val 值
|
||||||
|
* @param digits 位
|
||||||
|
* @return 值
|
||||||
|
*/
|
||||||
|
private static String digits(long val, int digits)
|
||||||
|
{
|
||||||
|
long hi = 1L << (digits * 4);
|
||||||
|
return Long.toHexString(hi | (val & (hi - 1))).substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查是否为time-based版本UUID
|
||||||
|
*/
|
||||||
|
private void checkTimeBase()
|
||||||
|
{
|
||||||
|
if (version() != 1)
|
||||||
|
{
|
||||||
|
throw new UnsupportedOperationException("Not a time-based UUID");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取{@link SecureRandom},类提供加密的强随机数生成器 (RNG)
|
||||||
|
*
|
||||||
|
* @return {@link SecureRandom}
|
||||||
|
*/
|
||||||
|
public static SecureRandom getSecureRandom() throws Exception {
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return SecureRandom.getInstance("SHA1PRNG");
|
||||||
|
}
|
||||||
|
catch (NoSuchAlgorithmException e)
|
||||||
|
{
|
||||||
|
throw new Exception(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取随机数生成器对象<br>
|
||||||
|
* ThreadLocalRandom是JDK 7之后提供并发产生随机数,能够解决多个线程发生的竞争争夺。
|
||||||
|
*
|
||||||
|
* @return {@link ThreadLocalRandom}
|
||||||
|
*/
|
||||||
|
public static ThreadLocalRandom getRandom()
|
||||||
|
{
|
||||||
|
return ThreadLocalRandom.current();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -47,9 +47,7 @@
|
||||||
// // 基于token,所以不需要session
|
// // 基于token,所以不需要session
|
||||||
// .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
|
// .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
|
||||||
// // 过滤请求
|
// // 过滤请求
|
||||||
// .authorizeRequests(authz -> authz
|
// .authorizeRequests()
|
||||||
// .requestMatchers(URL_WHITELIST).permitAll() // 允许访问无需认证的路径
|
|
||||||
// .anyRequest().authenticated())
|
|
||||||
// .formLogin(form -> form.
|
// .formLogin(form -> form.
|
||||||
// loginProcessingUrl("/login")
|
// loginProcessingUrl("/login")
|
||||||
// .usernameParameter("username")
|
// .usernameParameter("username")
|
||||||
|
|
|
||||||
|
|
@ -1,84 +1,84 @@
|
||||||
package com.qihang.core.config;
|
//package com.qihang.core.config;
|
||||||
|
//
|
||||||
import com.qihang.core.security.AuthenticationExceptionHandler;
|
//import com.qihang.core.security.AuthenticationExceptionHandler;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
//import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
//import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.context.annotation.Bean;
|
//import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
//import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.security.authentication.AuthenticationManager;
|
//import org.springframework.security.authentication.AuthenticationManager;
|
||||||
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
|
//import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
//import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
//import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
//import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
//import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
//import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
//import org.springframework.security.web.SecurityFilterChain;
|
||||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
//import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||||
import org.springframework.util.CollectionUtils;
|
//import org.springframework.util.CollectionUtils;
|
||||||
import java.util.ArrayList;
|
//import java.util.ArrayList;
|
||||||
import java.util.List;
|
//import java.util.List;
|
||||||
|
//
|
||||||
/**
|
///**
|
||||||
*
|
// *
|
||||||
* @Description SecurityConfiguration
|
// * @Description SecurityConfiguration
|
||||||
* @Author
|
// * @Author
|
||||||
* @Date
|
// * @Date
|
||||||
**/
|
// **/
|
||||||
@Configuration
|
//@Configuration
|
||||||
@EnableWebSecurity
|
//@EnableWebSecurity
|
||||||
public class SecurityConfiguration {
|
//public class SecurityConfiguration {
|
||||||
@Autowired
|
// @Autowired
|
||||||
private AuthenticationExceptionHandler authenticationExceptionHandler;
|
// private AuthenticationExceptionHandler authenticationExceptionHandler;
|
||||||
@Autowired
|
// @Autowired
|
||||||
private TokenFilter tokenFilter;
|
// private TokenFilter tokenFilter;
|
||||||
|
//
|
||||||
|
//
|
||||||
@Bean
|
// @Bean
|
||||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
// public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||||
List<String> permitAllPaths = new ArrayList<>();//security.getPermitAllPath();
|
// List<String> permitAllPaths = new ArrayList<>();//security.getPermitAllPath();
|
||||||
// 配置不需要认证的请求(这里所有的路径可以写在配置文件上修改时就不用改代码)
|
// // 配置不需要认证的请求(这里所有的路径可以写在配置文件上修改时就不用改代码)
|
||||||
if (!CollectionUtils.isEmpty(permitAllPaths)) {
|
// if (!CollectionUtils.isEmpty(permitAllPaths)) {
|
||||||
permitAllPaths.forEach(path -> {
|
// permitAllPaths.forEach(path -> {
|
||||||
try {
|
// try {
|
||||||
http.authorizeHttpRequests().requestMatchers(path).permitAll();
|
// http.authorizeHttpRequests().requestMatchers(path).permitAll();
|
||||||
} catch (Exception e) {
|
// } catch (Exception e) {
|
||||||
e.printStackTrace();
|
// e.printStackTrace();
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
// 关闭csrf因为不使用session
|
// // 关闭csrf因为不使用session
|
||||||
http.csrf().disable()
|
// http.csrf().disable()
|
||||||
// 不通过Session获取SecurityContext
|
// // 不通过Session获取SecurityContext
|
||||||
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
|
// .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
|
||||||
.and()
|
// .and()
|
||||||
.authorizeHttpRequests()
|
// .authorizeHttpRequests()
|
||||||
// 除了上面那些请求都需要认证
|
// // 除了上面那些请求都需要认证
|
||||||
.anyRequest().authenticated()
|
// .anyRequest().authenticated()
|
||||||
.and()
|
// .and()
|
||||||
// 配置异常处理
|
// // 配置异常处理
|
||||||
// 如果是认证过程中出现的异常会被封装成AuthenticationException然后调用AuthenticationEntryPoint对象的方法去进行异常处理。
|
// // 如果是认证过程中出现的异常会被封装成AuthenticationException然后调用AuthenticationEntryPoint对象的方法去进行异常处理。
|
||||||
// 如果是授权过程中出现的异常会被封装成AccessDeniedException然后调用AccessDeniedHandler对象的方法去进行异常处理。
|
// // 如果是授权过程中出现的异常会被封装成AccessDeniedException然后调用AccessDeniedHandler对象的方法去进行异常处理。
|
||||||
.exceptionHandling()
|
// .exceptionHandling()
|
||||||
.authenticationEntryPoint(authenticationExceptionHandler);
|
// .authenticationEntryPoint(authenticationExceptionHandler);
|
||||||
// 配置token拦截过滤器
|
// // 配置token拦截过滤器
|
||||||
http.addFilterBefore(tokenFilter, UsernamePasswordAuthenticationFilter.class);
|
// http.addFilterBefore(tokenFilter, UsernamePasswordAuthenticationFilter.class);
|
||||||
return http.build();
|
// return http.build();
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
/**
|
// /**
|
||||||
* 身份认证管理器,调用authenticate()方法完成认证
|
// * 身份认证管理器,调用authenticate()方法完成认证
|
||||||
*/
|
// */
|
||||||
@Bean
|
// @Bean
|
||||||
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
|
// public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
|
||||||
return authenticationConfiguration.getAuthenticationManager();
|
// return authenticationConfiguration.getAuthenticationManager();
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
/**
|
// /**
|
||||||
* 密码加密器
|
// * 密码加密器
|
||||||
*/
|
// */
|
||||||
@Bean
|
// @Bean
|
||||||
public PasswordEncoder passwordEncoder() {
|
// public PasswordEncoder passwordEncoder() {
|
||||||
return new BCryptPasswordEncoder();
|
// return new BCryptPasswordEncoder();
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
}
|
//}
|
||||||
|
|
@ -1,122 +1,122 @@
|
||||||
package com.qihang.core.config;
|
//package com.qihang.core.config;
|
||||||
|
//
|
||||||
|
//
|
||||||
import com.alibaba.fastjson2.JSON;
|
//import com.alibaba.fastjson2.JSON;
|
||||||
import com.qihang.core.constant.CacheConstants;
|
//import com.qihang.core.constant.CacheConstants;
|
||||||
import com.qihang.core.security.SecurityUser;
|
//import com.qihang.core.security.SecurityUser;
|
||||||
import com.qihang.core.utils.JwtUtil;
|
//import com.qihang.core.utils.JwtUtil;
|
||||||
import com.qihang.core.utils.R;
|
//import com.qihang.core.utils.R;
|
||||||
import io.jsonwebtoken.Claims;
|
//import io.jsonwebtoken.Claims;
|
||||||
import jakarta.servlet.FilterChain;
|
//import jakarta.servlet.FilterChain;
|
||||||
import jakarta.servlet.ServletException;
|
//import jakarta.servlet.ServletException;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
//import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
//import jakarta.servlet.http.HttpServletResponse;
|
||||||
import org.slf4j.Logger;
|
//import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
//import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
//import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.data.redis.core.RedisTemplate;
|
//import org.springframework.data.redis.core.RedisTemplate;
|
||||||
import org.springframework.http.MediaType;
|
//import org.springframework.http.MediaType;
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
//import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
//import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.stereotype.Component;
|
//import org.springframework.stereotype.Component;
|
||||||
import org.springframework.util.AntPathMatcher;
|
//import org.springframework.util.AntPathMatcher;
|
||||||
import org.springframework.util.CollectionUtils;
|
//import org.springframework.util.CollectionUtils;
|
||||||
import org.springframework.web.filter.OncePerRequestFilter;
|
//import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
//
|
||||||
import java.io.IOException;
|
//import java.io.IOException;
|
||||||
import java.io.PrintWriter;
|
//import java.io.PrintWriter;
|
||||||
import java.util.ArrayList;
|
//import java.util.ArrayList;
|
||||||
import java.util.List;
|
//import java.util.List;
|
||||||
|
//
|
||||||
/**
|
///**
|
||||||
*
|
// *
|
||||||
*
|
// *
|
||||||
* @Description
|
// * @Description
|
||||||
* @Author
|
// * @Author
|
||||||
* @Date
|
// * @Date
|
||||||
**/
|
// **/
|
||||||
@Component
|
//@Component
|
||||||
public class TokenFilter extends OncePerRequestFilter {
|
//public class TokenFilter extends OncePerRequestFilter {
|
||||||
private Logger log = LoggerFactory.getLogger(getClass());
|
// private Logger log = LoggerFactory.getLogger(getClass());
|
||||||
private AntPathMatcher pathMatcher = new AntPathMatcher();
|
// private AntPathMatcher pathMatcher = new AntPathMatcher();
|
||||||
|
//
|
||||||
@Autowired
|
|
||||||
private RedisTemplate<String, Object> redisTemplate;
|
|
||||||
|
|
||||||
// @Autowired
|
// @Autowired
|
||||||
// private DemoConfiguration.Security security;
|
// private RedisTemplate<String, Object> redisTemplate;
|
||||||
|
//
|
||||||
@Override
|
//// @Autowired
|
||||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
|
//// private DemoConfiguration.Security security;
|
||||||
String token = request.getHeader(JwtUtil.TOKEN_HEADER);
|
//
|
||||||
log.info("intercept " + request.getRequestURI());
|
// @Override
|
||||||
|
// protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
|
||||||
// token=1用于swagger页面调用API
|
// String token = request.getHeader(JwtUtil.TOKEN_HEADER);
|
||||||
/*if (!StringUtils.hasText(token) || "1".equals(token)) {
|
// log.info("intercept " + request.getRequestURI());
|
||||||
filterChain.doFilter(request, response);
|
//
|
||||||
return;
|
// // token=1用于swagger页面调用API
|
||||||
}*/
|
// /*if (!StringUtils.hasText(token) || "1".equals(token)) {
|
||||||
// 判断是否是放行请求
|
// filterChain.doFilter(request, response);
|
||||||
if (isFilterRequest(request)) {
|
// return;
|
||||||
filterChain.doFilter(request, response);
|
// }*/
|
||||||
return;
|
// // 判断是否是放行请求
|
||||||
}
|
// if (isFilterRequest(request)) {
|
||||||
Claims claims = null;
|
// filterChain.doFilter(request, response);
|
||||||
try {
|
// return;
|
||||||
claims = JwtUtil.parseToken(token);
|
// }
|
||||||
} catch (Exception e) {
|
// Claims claims = null;
|
||||||
log.error(e.getMessage());
|
// try {
|
||||||
fallback("token解析失败(非法token)!", response);
|
// claims = JwtUtil.parseToken(token);
|
||||||
return;
|
// } catch (Exception e) {
|
||||||
}
|
// log.error(e.getMessage());
|
||||||
String username = claims.getSubject();
|
// fallback("token解析失败(非法token)!", response);
|
||||||
String cache = (String) redisTemplate.opsForValue().get(String.format(CacheConstants.LOGIN_TOKEN_KEY, username));
|
// return;
|
||||||
if (cache == null) {
|
// }
|
||||||
fallback("token失效,请重新登录!", response);
|
// String username = claims.getSubject();
|
||||||
return;
|
// String cache = (String) redisTemplate.opsForValue().get(String.format(CacheConstants.LOGIN_TOKEN_KEY, username));
|
||||||
}
|
// if (cache == null) {
|
||||||
SecurityUser user = JSON.parseObject(cache, SecurityUser.class);
|
// fallback("token失效,请重新登录!", response);
|
||||||
log.info(JSON.toJSONString(user));
|
// return;
|
||||||
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user, null, null);
|
// }
|
||||||
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
|
// SecurityUser user = JSON.parseObject(cache, SecurityUser.class);
|
||||||
// 放行
|
// log.info(JSON.toJSONString(user));
|
||||||
filterChain.doFilter(request, response);
|
// UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user, null, null);
|
||||||
}
|
// SecurityContextHolder.getContext().setAuthentication(authenticationToken);
|
||||||
|
// // 放行
|
||||||
private void fallback(String message, HttpServletResponse response) {
|
// filterChain.doFilter(request, response);
|
||||||
response.setCharacterEncoding("UTF-8");
|
// }
|
||||||
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
//
|
||||||
PrintWriter writer = null;
|
// private void fallback(String message, HttpServletResponse response) {
|
||||||
try {
|
// response.setCharacterEncoding("UTF-8");
|
||||||
if (message == null) {
|
// response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||||
message = "403 Forbidden";
|
// PrintWriter writer = null;
|
||||||
}
|
// try {
|
||||||
R res = R.error(403, message);
|
// if (message == null) {
|
||||||
writer = response.getWriter();
|
// message = "403 Forbidden";
|
||||||
writer.append(JSON.toJSONString(res));
|
// }
|
||||||
} catch (IOException e) {
|
// R res = R.error(403, message);
|
||||||
log.error(e.getMessage());
|
// writer = response.getWriter();
|
||||||
} finally {
|
// writer.append(JSON.toJSONString(res));
|
||||||
if (writer != null) {
|
// } catch (IOException e) {
|
||||||
writer.close();
|
// log.error(e.getMessage());
|
||||||
}
|
// } finally {
|
||||||
}
|
// if (writer != null) {
|
||||||
}
|
// writer.close();
|
||||||
|
// }
|
||||||
private boolean isFilterRequest(HttpServletRequest request) {
|
// }
|
||||||
String contextPath = request.getContextPath();
|
// }
|
||||||
String filterPath = request.getRequestURI();
|
//
|
||||||
List<String> permitAllPathList = new ArrayList<>();//security.getPermitAllPath();
|
// private boolean isFilterRequest(HttpServletRequest request) {
|
||||||
if (CollectionUtils.isEmpty(permitAllPathList)) {
|
// String contextPath = request.getContextPath();
|
||||||
return false;
|
// String filterPath = request.getRequestURI();
|
||||||
}
|
// List<String> permitAllPathList = new ArrayList<>();//security.getPermitAllPath();
|
||||||
for (String path : permitAllPathList) {
|
// if (CollectionUtils.isEmpty(permitAllPathList)) {
|
||||||
String pattern = contextPath + path;
|
// return false;
|
||||||
pattern = pattern.replaceAll("/+", "/");
|
// }
|
||||||
if (pathMatcher.match(pattern, filterPath)) {
|
// for (String path : permitAllPathList) {
|
||||||
return true;
|
// String pattern = contextPath + path;
|
||||||
}
|
// pattern = pattern.replaceAll("/+", "/");
|
||||||
}
|
// if (pathMatcher.match(pattern, filterPath)) {
|
||||||
return false;
|
// return true;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue