From e9f8c2fae7478a59cd6c833b663c37ea1d98e060 Mon Sep 17 00:00:00 2001 From: Richie <280645618@qq.com> Date: Sun, 21 Jan 2024 13:16:58 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=E6=9D=83=E9=99=90=E8=AE=A4?= =?UTF-8?q?=E8=AF=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/misc.xml | 1 - oms-api/pom.xml | 13 +- .../com/qihang/oms/api/config/JwtUtil.java | 90 ---- .../com/qihang/oms/api/config/RedisCache.java | 265 ++++++++++ .../qihang/oms/api/config/RedisConfig.java | 1 + .../qihang/oms/api/config/SecurityConfig.java | 88 ---- .../qihang/oms/api/config/TokenFilter.java | 119 ----- .../oms/api/controller/HomeController.java | 5 + .../oms/api/controller/LoginController.java | 13 + .../com/qihang/oms/api/domain/SysUser.java | 249 +++++++++ .../AuthenticationExceptionHandler.java | 2 +- .../JwtAuthenticationTokenFilter.java | 77 +++ .../com/qihang/oms/api/security/JwtUtil.java | 94 ++++ .../qihang/oms/api/security/LoginUser.java | 267 ++++++++++ .../oms/api/security/SecurityConfig.java | 109 ++++ .../security/SecurityUserDetailsService.java | 24 + .../qihang/oms/api/security/TokenService.java | 221 ++++++++ .../FastJson2JsonRedisSerializer.java | 2 +- .../com/qihang/oms/api/utils/IdUtils.java | 49 ++ .../java/com/qihang/oms/api/utils/UUID.java | 492 ++++++++++++++++++ .../qihang/core/config/SecurityConfig.java | 4 +- .../core/config/SecurityConfiguration.java | 168 +++--- .../com/qihang/core/config/TokenFilter.java | 242 ++++----- 23 files changed, 2086 insertions(+), 509 deletions(-) delete mode 100644 oms-api/src/main/java/com/qihang/oms/api/config/JwtUtil.java create mode 100644 oms-api/src/main/java/com/qihang/oms/api/config/RedisCache.java delete mode 100644 oms-api/src/main/java/com/qihang/oms/api/config/SecurityConfig.java delete mode 100644 oms-api/src/main/java/com/qihang/oms/api/config/TokenFilter.java create mode 100644 oms-api/src/main/java/com/qihang/oms/api/controller/LoginController.java create mode 100644 oms-api/src/main/java/com/qihang/oms/api/domain/SysUser.java rename oms-api/src/main/java/com/qihang/oms/api/{config => security}/AuthenticationExceptionHandler.java (97%) create mode 100644 oms-api/src/main/java/com/qihang/oms/api/security/JwtAuthenticationTokenFilter.java create mode 100644 oms-api/src/main/java/com/qihang/oms/api/security/JwtUtil.java create mode 100644 oms-api/src/main/java/com/qihang/oms/api/security/LoginUser.java create mode 100644 oms-api/src/main/java/com/qihang/oms/api/security/SecurityConfig.java create mode 100644 oms-api/src/main/java/com/qihang/oms/api/security/SecurityUserDetailsService.java create mode 100644 oms-api/src/main/java/com/qihang/oms/api/security/TokenService.java rename oms-api/src/main/java/com/qihang/oms/api/{config => utils}/FastJson2JsonRedisSerializer.java (97%) create mode 100644 oms-api/src/main/java/com/qihang/oms/api/utils/IdUtils.java create mode 100644 oms-api/src/main/java/com/qihang/oms/api/utils/UUID.java diff --git a/.idea/misc.xml b/.idea/misc.xml index 82dbec8a..c3f3b0ab 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,3 @@ - diff --git a/oms-api/pom.xml b/oms-api/pom.xml index 4f7203a4..b88b70fc 100644 --- a/oms-api/pom.xml +++ b/oms-api/pom.xml @@ -119,7 +119,18 @@ jjwt 0.9.1 - + + + eu.bitwalker + UserAgentUtils + 1.21 + + + org.springframework.security + spring-security-config + 6.0.1 + + diff --git a/oms-api/src/main/java/com/qihang/oms/api/config/JwtUtil.java b/oms-api/src/main/java/com/qihang/oms/api/config/JwtUtil.java deleted file mode 100644 index 41378fa5..00000000 --- a/oms-api/src/main/java/com/qihang/oms/api/config/JwtUtil.java +++ /dev/null @@ -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; - } -} diff --git a/oms-api/src/main/java/com/qihang/oms/api/config/RedisCache.java b/oms-api/src/main/java/com/qihang/oms/api/config/RedisCache.java new file mode 100644 index 00000000..d2af2d24 --- /dev/null +++ b/oms-api/src/main/java/com/qihang/oms/api/config/RedisCache.java @@ -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 void setCacheObject(final String key, final T value) + { + redisTemplate.opsForValue().set(key, value); + } + + /** + * 缓存基本的对象,Integer、String、实体类等 + * + * @param key 缓存的键值 + * @param value 缓存的值 + * @param timeout 时间 + * @param timeUnit 时间颗粒度 + */ + public 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 getCacheObject(final String key) + { + ValueOperations 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 long setCacheList(final String key, final List dataList) + { + Long count = redisTemplate.opsForList().rightPushAll(key, dataList); + return count == null ? 0 : count; + } + + /** + * 获得缓存的list对象 + * + * @param key 缓存的键值 + * @return 缓存键值对应的数据 + */ + public List getCacheList(final String key) + { + return redisTemplate.opsForList().range(key, 0, -1); + } + + /** + * 缓存Set + * + * @param key 缓存键值 + * @param dataSet 缓存的数据 + * @return 缓存数据的对象 + */ + public BoundSetOperations setCacheSet(final String key, final Set dataSet) + { + BoundSetOperations setOperation = redisTemplate.boundSetOps(key); + Iterator it = dataSet.iterator(); + while (it.hasNext()) + { + setOperation.add(it.next()); + } + return setOperation; + } + + /** + * 获得缓存的set + * + * @param key + * @return + */ + public Set getCacheSet(final String key) + { + return redisTemplate.opsForSet().members(key); + } + + /** + * 缓存Map + * + * @param key + * @param dataMap + */ + public void setCacheMap(final String key, final Map dataMap) + { + if (dataMap != null) { + redisTemplate.opsForHash().putAll(key, dataMap); + } + } + + /** + * 获得缓存的Map + * + * @param key + * @return + */ + public Map getCacheMap(final String key) + { + return redisTemplate.opsForHash().entries(key); + } + + /** + * 往Hash中存入数据 + * + * @param key Redis键 + * @param hKey Hash键 + * @param value 值 + */ + public 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 getCacheMapValue(final String key, final String hKey) + { + HashOperations opsForHash = redisTemplate.opsForHash(); + return opsForHash.get(key, hKey); + } + + /** + * 获取多个Hash中的数据 + * + * @param key Redis键 + * @param hKeys Hash键集合 + * @return Hash对象集合 + */ + public List getMultiCacheMapValue(final String key, final Collection 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 keys(final String pattern) + { + return redisTemplate.keys(pattern); + } +} diff --git a/oms-api/src/main/java/com/qihang/oms/api/config/RedisConfig.java b/oms-api/src/main/java/com/qihang/oms/api/config/RedisConfig.java index 97fa63f8..f415ff66 100644 --- a/oms-api/src/main/java/com/qihang/oms/api/config/RedisConfig.java +++ b/oms-api/src/main/java/com/qihang/oms/api/config/RedisConfig.java @@ -1,6 +1,7 @@ package com.qihang.oms.api.config; +import com.qihang.oms.api.utils.FastJson2JsonRedisSerializer; import org.springframework.cache.annotation.CachingConfigurerSupport; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; diff --git a/oms-api/src/main/java/com/qihang/oms/api/config/SecurityConfig.java b/oms-api/src/main/java/com/qihang/oms/api/config/SecurityConfig.java deleted file mode 100644 index 5499cbce..00000000 --- a/oms-api/src/main/java/com/qihang/oms/api/config/SecurityConfig.java +++ /dev/null @@ -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 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(); - } -} diff --git a/oms-api/src/main/java/com/qihang/oms/api/config/TokenFilter.java b/oms-api/src/main/java/com/qihang/oms/api/config/TokenFilter.java deleted file mode 100644 index f41dcbd9..00000000 --- a/oms-api/src/main/java/com/qihang/oms/api/config/TokenFilter.java +++ /dev/null @@ -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 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 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; - } -} diff --git a/oms-api/src/main/java/com/qihang/oms/api/controller/HomeController.java b/oms-api/src/main/java/com/qihang/oms/api/controller/HomeController.java index 323d7a13..afc0bebb 100644 --- a/oms-api/src/main/java/com/qihang/oms/api/controller/HomeController.java +++ b/oms-api/src/main/java/com/qihang/oms/api/controller/HomeController.java @@ -27,4 +27,9 @@ public class HomeController { public String feign() { return echoService.echo(); } + + @GetMapping(value = "/home") + public String home(){ + return "home"; + } } diff --git a/oms-api/src/main/java/com/qihang/oms/api/controller/LoginController.java b/oms-api/src/main/java/com/qihang/oms/api/controller/LoginController.java new file mode 100644 index 00000000..0a636f7b --- /dev/null +++ b/oms-api/src/main/java/com/qihang/oms/api/controller/LoginController.java @@ -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"; + } +} diff --git a/oms-api/src/main/java/com/qihang/oms/api/domain/SysUser.java b/oms-api/src/main/java/com/qihang/oms/api/domain/SysUser.java new file mode 100644 index 00000000..2151a02c --- /dev/null +++ b/oms-api/src/main/java/com/qihang/oms/api/domain/SysUser.java @@ -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; + } + +} diff --git a/oms-api/src/main/java/com/qihang/oms/api/config/AuthenticationExceptionHandler.java b/oms-api/src/main/java/com/qihang/oms/api/security/AuthenticationExceptionHandler.java similarity index 97% rename from oms-api/src/main/java/com/qihang/oms/api/config/AuthenticationExceptionHandler.java rename to oms-api/src/main/java/com/qihang/oms/api/security/AuthenticationExceptionHandler.java index b275785d..6e3588e0 100644 --- a/oms-api/src/main/java/com/qihang/oms/api/config/AuthenticationExceptionHandler.java +++ b/oms-api/src/main/java/com/qihang/oms/api/security/AuthenticationExceptionHandler.java @@ -1,4 +1,4 @@ -package com.qihang.oms.api.config; +package com.qihang.oms.api.security; import com.alibaba.fastjson2.JSON; import jakarta.servlet.ServletException; diff --git a/oms-api/src/main/java/com/qihang/oms/api/security/JwtAuthenticationTokenFilter.java b/oms-api/src/main/java/com/qihang/oms/api/security/JwtAuthenticationTokenFilter.java new file mode 100644 index 00000000..adfba54b --- /dev/null +++ b/oms-api/src/main/java/com/qihang/oms/api/security/JwtAuthenticationTokenFilter.java @@ -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(); + } + } + } +} diff --git a/oms-api/src/main/java/com/qihang/oms/api/security/JwtUtil.java b/oms-api/src/main/java/com/qihang/oms/api/security/JwtUtil.java new file mode 100644 index 00000000..c2fa17a7 --- /dev/null +++ b/oms-api/src/main/java/com/qihang/oms/api/security/JwtUtil.java @@ -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; +// } +//} diff --git a/oms-api/src/main/java/com/qihang/oms/api/security/LoginUser.java b/oms-api/src/main/java/com/qihang/oms/api/security/LoginUser.java new file mode 100644 index 00000000..528ee2e7 --- /dev/null +++ b/oms-api/src/main/java/com/qihang/oms/api/security/LoginUser.java @@ -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 permissions; + + /** + * 用户信息 + */ + private SysUser user; + + public LoginUser() + { + } + + public LoginUser(SysUser user, Set permissions) + { + this.user = user; + this.permissions = permissions; + } + + public LoginUser(Long userId, Long deptId, SysUser user, Set 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 getPermissions() + { + return permissions; + } + + public void setPermissions(Set permissions) + { + this.permissions = permissions; + } + + public SysUser getUser() + { + return user; + } + + public void setUser(SysUser user) + { + this.user = user; + } + + @Override + public Collection getAuthorities() + { + return null; + } +} diff --git a/oms-api/src/main/java/com/qihang/oms/api/security/SecurityConfig.java b/oms-api/src/main/java/com/qihang/oms/api/security/SecurityConfig.java new file mode 100644 index 00000000..9aa1ec32 --- /dev/null +++ b/oms-api/src/main/java/com/qihang/oms/api/security/SecurityConfig.java @@ -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(); + } + + +} diff --git a/oms-api/src/main/java/com/qihang/oms/api/security/SecurityUserDetailsService.java b/oms-api/src/main/java/com/qihang/oms/api/security/SecurityUserDetailsService.java new file mode 100644 index 00000000..292ed860 --- /dev/null +++ b/oms-api/src/main/java/com/qihang/oms/api/security/SecurityUserDetailsService.java @@ -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 p = new HashSet<>(); + p.add("all"); + UserDetails u = new LoginUser(1L,1L,user,p); + return u; + } +} diff --git a/oms-api/src/main/java/com/qihang/oms/api/security/TokenService.java b/oms-api/src/main/java/com/qihang/oms/api/security/TokenService.java new file mode 100644 index 00000000..b6682690 --- /dev/null +++ b/oms-api/src/main/java/com/qihang/oms/api/security/TokenService.java @@ -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 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 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; + } +} diff --git a/oms-api/src/main/java/com/qihang/oms/api/config/FastJson2JsonRedisSerializer.java b/oms-api/src/main/java/com/qihang/oms/api/utils/FastJson2JsonRedisSerializer.java similarity index 97% rename from oms-api/src/main/java/com/qihang/oms/api/config/FastJson2JsonRedisSerializer.java rename to oms-api/src/main/java/com/qihang/oms/api/utils/FastJson2JsonRedisSerializer.java index 529d5911..c2f5e4a0 100644 --- a/oms-api/src/main/java/com/qihang/oms/api/config/FastJson2JsonRedisSerializer.java +++ b/oms-api/src/main/java/com/qihang/oms/api/utils/FastJson2JsonRedisSerializer.java @@ -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.JSONReader; diff --git a/oms-api/src/main/java/com/qihang/oms/api/utils/IdUtils.java b/oms-api/src/main/java/com/qihang/oms/api/utils/IdUtils.java new file mode 100644 index 00000000..8d3fc500 --- /dev/null +++ b/oms-api/src/main/java/com/qihang/oms/api/utils/IdUtils.java @@ -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); + } +} diff --git a/oms-api/src/main/java/com/qihang/oms/api/utils/UUID.java b/oms-api/src/main/java/com/qihang/oms/api/utils/UUID.java new file mode 100644 index 00000000..28e24e5a --- /dev/null +++ b/oms-api/src/main/java/com/qihang/oms/api/utils/UUID.java @@ -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 +{ + 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} 是如何生成的。 + *

+ * 版本号具有以下含意: + *

    + *
  • 1 基于时间的 UUID + *
  • 2 DCE 安全 UUID + *
  • 3 基于名称的 UUID + *
  • 4 随机生成的 UUID + *
+ * + * @return 此 {@code UUID} 的版本号 + */ + public int version() + { + // Version is bits masked by 0x000000000000F000 in MS long + return (int) ((mostSigBits >> 12) & 0x0f); + } + + /** + * 与此 {@code UUID} 相关联的变体号。变体号描述 {@code UUID} 的布局。 + *

+ * 变体号具有以下含意: + *

    + *
  • 0 为 NCS 向后兼容保留 + *
  • 2 IETF RFC 4122(Leach-Salz), 用于此类 + *
  • 6 保留,微软向后兼容 + *
  • 7 保留供以后定义使用 + *
+ * + * @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 相关联的时间戳值。 + * + *

+ * 60 位的时间戳值根据此 {@code UUID} 的 time_low、time_mid 和 time_hi 字段构造。
+ * 所得到的时间戳以 100 毫微秒为单位,从 UTC(通用协调时间) 1582 年 10 月 15 日零时开始。 + * + *

+ * 时间戳值仅在在基于时间的 UUID(其 version 类型为 1)中才有意义。
+ * 如果此 {@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 相关联的时钟序列值。 + * + *

+ * 14 位的时钟序列值根据此 UUID 的 clock_seq 字段构造。clock_seq 字段用于保证在基于时间的 UUID 中的时间唯一性。 + *

+ * {@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 相关的节点值。 + * + *

+ * 48 位的节点值根据此 UUID 的 node 字段构造。此字段旨在用于保存机器的 IEEE 802 地址,该地址用于生成此 UUID 以保证空间唯一性。 + *

+ * 节点值仅在基于时间的 UUID(其 version 类型为 1)中才有意义。
+ * 如果此 UUID 不是基于时间的 UUID,则此方法抛出 UnsupportedOperationException。 + * + * @return 此 {@code UUID} 的节点值 + * + * @throws UnsupportedOperationException 如果此 UUID 的 version 不为 1 + */ + public long node() throws UnsupportedOperationException + { + checkTimeBase(); + return leastSigBits & 0x0000FFFFFFFFFFFFL; + } + + /** + * 返回此{@code UUID} 的字符串表现形式。 + * + *

+ * UUID 的字符串表示形式由此 BNF 描述: + * + *

+     * {@code
+     * UUID                   = ----
+     * time_low               = 4*
+     * time_mid               = 2*
+     * time_high_and_version  = 2*
+     * variant_and_sequence   = 2*
+     * node                   = 6*
+     * hexOctet               = 
+     * hexDigit               = [0-9a-fA-F]
+     * }
+     * 
+ * + * + * + * @return 此{@code UUID} 的字符串表现形式 + * @see #toString(boolean) + */ + @Override + public String toString() + { + return toString(false); + } + + /** + * 返回此{@code UUID} 的字符串表现形式。 + * + *

+ * UUID 的字符串表示形式由此 BNF 描述: + * + *

+     * {@code
+     * UUID                   = ----
+     * time_low               = 4*
+     * time_mid               = 2*
+     * time_high_and_version  = 2*
+     * variant_and_sequence   = 2*
+     * node                   = 6*
+     * hexOctet               = 
+     * hexDigit               = [0-9a-fA-F]
+     * }
+     * 
+ * + * + * + * @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; + } + + /** + * 将此对象与指定对象比较。 + *

+ * 当且仅当参数不为 {@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 比较。 + * + *

+ * 如果两个 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); + } + } + + /** + * 获取随机数生成器对象
+ * ThreadLocalRandom是JDK 7之后提供并发产生随机数,能够解决多个线程发生的竞争争夺。 + * + * @return {@link ThreadLocalRandom} + */ + public static ThreadLocalRandom getRandom() + { + return ThreadLocalRandom.current(); + } +} diff --git a/oms-core/src/main/java/com/qihang/core/config/SecurityConfig.java b/oms-core/src/main/java/com/qihang/core/config/SecurityConfig.java index 578c7adb..cae5b87a 100644 --- a/oms-core/src/main/java/com/qihang/core/config/SecurityConfig.java +++ b/oms-core/src/main/java/com/qihang/core/config/SecurityConfig.java @@ -47,9 +47,7 @@ // // 基于token,所以不需要session // .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() // // 过滤请求 -// .authorizeRequests(authz -> authz -// .requestMatchers(URL_WHITELIST).permitAll() // 允许访问无需认证的路径 -// .anyRequest().authenticated()) +// .authorizeRequests() // .formLogin(form -> form. // loginProcessingUrl("/login") // .usernameParameter("username") diff --git a/oms-core/src/main/java/com/qihang/core/config/SecurityConfiguration.java b/oms-core/src/main/java/com/qihang/core/config/SecurityConfiguration.java index 46cbed0b..4acf4a5b 100644 --- a/oms-core/src/main/java/com/qihang/core/config/SecurityConfiguration.java +++ b/oms-core/src/main/java/com/qihang/core/config/SecurityConfiguration.java @@ -1,84 +1,84 @@ -package com.qihang.core.config; - -import com.qihang.core.security.AuthenticationExceptionHandler; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -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 org.springframework.util.CollectionUtils; -import java.util.ArrayList; -import java.util.List; - -/** - * - * @Description SecurityConfiguration - * @Author - * @Date - **/ -@Configuration -@EnableWebSecurity -public class SecurityConfiguration { - @Autowired - private AuthenticationExceptionHandler authenticationExceptionHandler; - @Autowired - private TokenFilter tokenFilter; - - - @Bean - public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { - List permitAllPaths = new ArrayList<>();//security.getPermitAllPath(); - // 配置不需要认证的请求(这里所有的路径可以写在配置文件上修改时就不用改代码) - if (!CollectionUtils.isEmpty(permitAllPaths)) { - permitAllPaths.forEach(path -> { - try { - http.authorizeHttpRequests().requestMatchers(path).permitAll(); - } catch (Exception e) { - e.printStackTrace(); - } - }); - } - // 关闭csrf因为不使用session - http.csrf().disable() - // 不通过Session获取SecurityContext - .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) - .and() - .authorizeHttpRequests() - // 除了上面那些请求都需要认证 - .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(); - } - -} \ No newline at end of file +//package com.qihang.core.config; +// +//import com.qihang.core.security.AuthenticationExceptionHandler; +//import org.springframework.beans.factory.annotation.Autowired; +//import org.springframework.beans.factory.annotation.Value; +//import org.springframework.context.annotation.Bean; +//import org.springframework.context.annotation.Configuration; +//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 org.springframework.util.CollectionUtils; +//import java.util.ArrayList; +//import java.util.List; +// +///** +// * +// * @Description SecurityConfiguration +// * @Author +// * @Date +// **/ +//@Configuration +//@EnableWebSecurity +//public class SecurityConfiguration { +// @Autowired +// private AuthenticationExceptionHandler authenticationExceptionHandler; +// @Autowired +// private TokenFilter tokenFilter; +// +// +// @Bean +// public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { +// List permitAllPaths = new ArrayList<>();//security.getPermitAllPath(); +// // 配置不需要认证的请求(这里所有的路径可以写在配置文件上修改时就不用改代码) +// if (!CollectionUtils.isEmpty(permitAllPaths)) { +// permitAllPaths.forEach(path -> { +// try { +// http.authorizeHttpRequests().requestMatchers(path).permitAll(); +// } catch (Exception e) { +// e.printStackTrace(); +// } +// }); +// } +// // 关闭csrf因为不使用session +// http.csrf().disable() +// // 不通过Session获取SecurityContext +// .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) +// .and() +// .authorizeHttpRequests() +// // 除了上面那些请求都需要认证 +// .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(); +// } +// +//} \ No newline at end of file diff --git a/oms-core/src/main/java/com/qihang/core/config/TokenFilter.java b/oms-core/src/main/java/com/qihang/core/config/TokenFilter.java index 6e434580..f19b735b 100644 --- a/oms-core/src/main/java/com/qihang/core/config/TokenFilter.java +++ b/oms-core/src/main/java/com/qihang/core/config/TokenFilter.java @@ -1,122 +1,122 @@ -package com.qihang.core.config; - - -import com.alibaba.fastjson2.JSON; -import com.qihang.core.constant.CacheConstants; -import com.qihang.core.security.SecurityUser; -import com.qihang.core.utils.JwtUtil; -import com.qihang.core.utils.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 redisTemplate; - +//package com.qihang.core.config; +// +// +//import com.alibaba.fastjson2.JSON; +//import com.qihang.core.constant.CacheConstants; +//import com.qihang.core.security.SecurityUser; +//import com.qihang.core.utils.JwtUtil; +//import com.qihang.core.utils.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 DemoConfiguration.Security security; - - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { - String token = request.getHeader(JwtUtil.TOKEN_HEADER); - 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(CacheConstants.LOGIN_TOKEN_KEY, 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 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; - } -} +// private RedisTemplate redisTemplate; +// +//// @Autowired +//// private DemoConfiguration.Security security; +// +// @Override +// protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { +// String token = request.getHeader(JwtUtil.TOKEN_HEADER); +// 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(CacheConstants.LOGIN_TOKEN_KEY, 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 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; +// } +//}