完善权限认证

This commit is contained in:
Richie 2024-01-21 13:16:58 +08:00
parent 156955c448
commit e9f8c2fae7
23 changed files with 2086 additions and 509 deletions

View File

@ -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">

View File

@ -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>-->

View File

@ -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;
}
}

View File

@ -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;
/**
* 缓存基本的对象IntegerString实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
*/
public <T> void setCacheObject(final String key, final T value)
{
redisTemplate.opsForValue().set(key, value);
}
/**
* 缓存基本的对象IntegerString实体类等
*
* @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);
}
}

View File

@ -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;

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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";
}
} }

View File

@ -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";
}
}

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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();
}
}
}
}

View File

@ -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;
// }
//}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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 identifierUUID实现
*
* @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&nbsp;RFC&nbsp;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_lowtime_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 分别返回 -10 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();
}
}

View File

@ -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")

View File

@ -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();
} // }
//
} //}

View File

@ -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;
// }
//}