parent
5e5fb9a07d
commit
e044f02d32
11 changed files with 506 additions and 80 deletions
@ -0,0 +1,18 @@ |
||||
package cn.soul2.jyjc.admin.annotation; |
||||
|
||||
import java.lang.annotation.ElementType; |
||||
import java.lang.annotation.Retention; |
||||
import java.lang.annotation.RetentionPolicy; |
||||
import java.lang.annotation.Target; |
||||
|
||||
/** |
||||
* 跳过加解密 |
||||
* |
||||
* @author Soul2 |
||||
* @date 2024-04-08 |
||||
*/ |
||||
@Target({ElementType.METHOD, ElementType.TYPE}) |
||||
@Retention(RetentionPolicy.RUNTIME) |
||||
public @interface SkinEncrypt { |
||||
|
||||
} |
@ -0,0 +1,41 @@ |
||||
package cn.soul2.jyjc.admin.config; |
||||
|
||||
import cn.soul2.jyjc.admin.filter.ReplaceStreamFilter; |
||||
import org.springframework.boot.web.servlet.FilterRegistrationBean; |
||||
import org.springframework.context.annotation.Bean; |
||||
import org.springframework.context.annotation.Configuration; |
||||
|
||||
import javax.servlet.Filter; |
||||
|
||||
/** |
||||
* @author Soul2 |
||||
* @description 过滤器配置类 |
||||
* @date 2024-04-08 |
||||
* <p>照抄自 <a href="https://blog.csdn.net/shaoduo/article/details/122322578">Shao duo</a></p> |
||||
*/ |
||||
@Configuration |
||||
public class FilterConfig { |
||||
/** |
||||
* 注册过滤器 |
||||
* |
||||
* @return FilterRegistrationBean |
||||
*/ |
||||
@Bean |
||||
public FilterRegistrationBean someFilterRegistration() { |
||||
FilterRegistrationBean registration = new FilterRegistrationBean(); |
||||
registration.setFilter(replaceStreamFilter()); |
||||
registration.addUrlPatterns("/*"); |
||||
registration.setName("streamFilter"); |
||||
return registration; |
||||
} |
||||
|
||||
/** |
||||
* 实例化StreamFilter |
||||
* |
||||
* @return Filter |
||||
*/ |
||||
@Bean(name = "replaceStreamFilter") |
||||
public Filter replaceStreamFilter() { |
||||
return new ReplaceStreamFilter(); |
||||
} |
||||
} |
@ -0,0 +1,40 @@ |
||||
package cn.soul2.jyjc.admin.filter; |
||||
|
||||
import lombok.extern.slf4j.Slf4j; |
||||
import org.apache.tomcat.util.http.fileupload.servlet.ServletFileUpload; |
||||
|
||||
import javax.servlet.*; |
||||
import javax.servlet.http.HttpServletRequest; |
||||
import java.io.IOException; |
||||
|
||||
/** |
||||
* @author shaoduo |
||||
* @description 替换HttpServletRequest |
||||
* @since 1.0 |
||||
**/ |
||||
@Slf4j |
||||
public class ReplaceStreamFilter implements Filter { |
||||
@Override |
||||
public void init(FilterConfig filterConfig) throws ServletException { |
||||
log.info("StreamFilter初始化..."); |
||||
} |
||||
|
||||
@Override |
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { |
||||
//如果是文件上传则会报错可以判断是否是文件上传不读取流即可
|
||||
if (ServletFileUpload.isMultipartContent((HttpServletRequest) request)) { |
||||
chain.doFilter(request, response); |
||||
return; |
||||
} else { |
||||
ServletRequest requestWrapper = new ShaoduoRequestWrapper((HttpServletRequest) request); |
||||
chain.doFilter(requestWrapper, response); |
||||
return; |
||||
} |
||||
|
||||
} |
||||
|
||||
@Override |
||||
public void destroy() { |
||||
log.info("StreamFilter销毁..."); |
||||
} |
||||
} |
@ -0,0 +1,134 @@ |
||||
package cn.soul2.jyjc.admin.filter; |
||||
|
||||
import lombok.extern.slf4j.Slf4j; |
||||
|
||||
import javax.servlet.ReadListener; |
||||
import javax.servlet.ServletInputStream; |
||||
import javax.servlet.ServletRequest; |
||||
import javax.servlet.http.HttpServletRequest; |
||||
import javax.servlet.http.HttpServletRequestWrapper; |
||||
import java.io.*; |
||||
import java.nio.charset.Charset; |
||||
import java.nio.charset.StandardCharsets; |
||||
|
||||
/** |
||||
* @author shaoduo |
||||
* @description 包装HttpServletRequest,目的是让其输入流可重复读 |
||||
* @since 1.0 |
||||
**/ |
||||
@Slf4j |
||||
public class ShaoduoRequestWrapper extends HttpServletRequestWrapper { |
||||
/** |
||||
* 存储body数据的容器 |
||||
*/ |
||||
private byte[] body; |
||||
|
||||
public ShaoduoRequestWrapper(HttpServletRequest request) throws IOException { |
||||
super(request); |
||||
|
||||
// 将body数据存储起来
|
||||
String bodyStr = getBodyString(request); |
||||
body = bodyStr.getBytes(Charset.defaultCharset()); |
||||
} |
||||
|
||||
/** |
||||
* 获取请求Body |
||||
* |
||||
* @param request request |
||||
* @return String |
||||
*/ |
||||
public String getBodyString(final ServletRequest request) { |
||||
try { |
||||
return inputStream2String(request.getInputStream()); |
||||
} catch (IOException e) { |
||||
log.error("", e); |
||||
throw new RuntimeException(e); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 获取请求Body |
||||
* |
||||
* @return String |
||||
*/ |
||||
public String getBodyString() { |
||||
InputStream inputStream = new ByteArrayInputStream(body); |
||||
|
||||
return inputStream2String(inputStream); |
||||
} |
||||
|
||||
/** |
||||
* 修改body 将json 重新设置成body |
||||
* |
||||
* @param val |
||||
*/ |
||||
public void setBody(String val) { |
||||
|
||||
body = val.getBytes(StandardCharsets.UTF_8); |
||||
} |
||||
|
||||
/** |
||||
* 将inputStream里的数据读取出来并转换成字符串 |
||||
* |
||||
* @param inputStream inputStream |
||||
* @return String |
||||
*/ |
||||
private String inputStream2String(InputStream inputStream) { |
||||
StringBuilder sb = new StringBuilder(); |
||||
BufferedReader reader = null; |
||||
|
||||
try { |
||||
reader = new BufferedReader(new InputStreamReader(inputStream, Charset.defaultCharset())); |
||||
String line; |
||||
while ((line = reader.readLine()) != null) { |
||||
sb.append(line); |
||||
} |
||||
} catch (IOException e) { |
||||
log.error("", e); |
||||
throw new RuntimeException(e); |
||||
} finally { |
||||
if (reader != null) { |
||||
try { |
||||
reader.close(); |
||||
} catch (IOException e) { |
||||
log.error("", e); |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
return sb.toString(); |
||||
} |
||||
|
||||
@Override |
||||
public BufferedReader getReader() throws IOException { |
||||
return new BufferedReader(new InputStreamReader(getInputStream())); |
||||
} |
||||
|
||||
@Override |
||||
public ServletInputStream getInputStream() throws IOException { |
||||
|
||||
final ByteArrayInputStream inputStream = new ByteArrayInputStream(body); |
||||
|
||||
return new ServletInputStream() { |
||||
@Override |
||||
public int read() throws IOException { |
||||
return inputStream.read(); |
||||
} |
||||
|
||||
@Override |
||||
public boolean isFinished() { |
||||
return false; |
||||
} |
||||
|
||||
@Override |
||||
public boolean isReady() { |
||||
return false; |
||||
} |
||||
|
||||
@Override |
||||
public void setReadListener(ReadListener readListener) { |
||||
} |
||||
}; |
||||
} |
||||
} |
@ -0,0 +1,128 @@ |
||||
package cn.soul2.jyjc.admin.interceptor; |
||||
|
||||
import cn.soul2.jyjc.admin.annotation.SkinEncrypt; |
||||
import cn.soul2.jyjc.admin.annotation.SkinLogin; |
||||
import cn.soul2.jyjc.admin.config.UserLoginStatusBean; |
||||
import cn.soul2.jyjc.admin.filter.ShaoduoRequestWrapper; |
||||
import cn.soul2.jyjc.admin.utils.EncryptUtils; |
||||
import com.alibaba.fastjson.JSON; |
||||
import com.alibaba.fastjson.JSONObject; |
||||
import lombok.extern.slf4j.Slf4j; |
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.context.ApplicationContext; |
||||
import org.springframework.context.annotation.Lazy; |
||||
import org.springframework.http.MediaType; |
||||
import org.springframework.web.method.HandlerMethod; |
||||
import org.springframework.web.servlet.HandlerInterceptor; |
||||
|
||||
import javax.annotation.Resource; |
||||
import javax.servlet.http.HttpServletRequest; |
||||
import javax.servlet.http.HttpServletResponse; |
||||
import java.lang.reflect.Method; |
||||
|
||||
/** |
||||
* Token拦截器 |
||||
* |
||||
* @author Soul2 |
||||
* @date 2024-04-02 15:31 |
||||
*/ |
||||
@Slf4j |
||||
public class FinallyInterceptor implements HandlerInterceptor { |
||||
|
||||
@Autowired |
||||
@Lazy |
||||
private ApplicationContext context; |
||||
@Resource |
||||
@Lazy |
||||
private UserLoginStatusBean userLoginStatusBean; |
||||
|
||||
@Override |
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { |
||||
boolean pass = false; |
||||
// 允许OPTIONS请求通过
|
||||
if ("OPTIONS".equals(request.getMethod()) && request.getHeader("Origin") != null) { |
||||
return true; |
||||
} |
||||
// 如果不是映射到方法直接通过
|
||||
if (!(handler instanceof HandlerMethod)) { |
||||
return true; |
||||
} |
||||
HandlerMethod handlerMethod = (HandlerMethod) handler; |
||||
Method method = handlerMethod.getMethod(); |
||||
String httpMethod = request.getMethod(); |
||||
SkinEncrypt skinEncrypt = handlerMethod.getMethodAnnotation(SkinEncrypt.class); |
||||
// 检查方法上是否存在SkinLogin注解
|
||||
boolean hasSkinLogin = handlerMethod.getMethodAnnotation(SkinLogin.class) != null; |
||||
// 从请求头中获取 token
|
||||
String token = request.getHeader("jyjc-Token"); |
||||
try { |
||||
/* |
||||
加解密 |
||||
*/ |
||||
// 不拦截get请求
|
||||
if ("GET".equals(httpMethod)) { |
||||
pass = true; |
||||
// 如果是post请求且是json
|
||||
} else if ("POST".equals(httpMethod)) { |
||||
// 如果类型为空就放行
|
||||
if (request.getContentType() == null) { |
||||
pass = true; |
||||
} |
||||
// 如果类型不是json 就放行
|
||||
if (!(request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE))) { |
||||
pass = true; |
||||
} |
||||
} |
||||
|
||||
// 跳过使用SkinEncrypt注解的情况
|
||||
if (skinEncrypt != null) { |
||||
pass = true; |
||||
} |
||||
ShaoduoRequestWrapper shaoduoRequestWrapper; |
||||
if (request instanceof ShaoduoRequestWrapper) { |
||||
shaoduoRequestWrapper = (ShaoduoRequestWrapper) request; |
||||
} else { |
||||
shaoduoRequestWrapper = new ShaoduoRequestWrapper(request); |
||||
request = shaoduoRequestWrapper; |
||||
} |
||||
String jsonParamBody = shaoduoRequestWrapper.getBodyString(); |
||||
JSONObject obj = JSON.parseObject(EncryptUtils.decrypt(jsonParamBody)); |
||||
String afterBody = JSONObject.toJSONString(obj); |
||||
|
||||
System.out.println("加密前 " + jsonParamBody); |
||||
System.out.println("解密后 " + afterBody); |
||||
shaoduoRequestWrapper.setBody(afterBody); |
||||
String temp = new ShaoduoRequestWrapper(shaoduoRequestWrapper).getBodyString(); |
||||
System.out.println("过滤器中缓存 " + temp); |
||||
|
||||
/* |
||||
token验证 |
||||
*/ |
||||
|
||||
if (hasSkinLogin) { |
||||
// 如果存在,绕过拦截器
|
||||
pass = true; |
||||
} else { |
||||
// 验证token
|
||||
if (userLoginStatusBean == null) { |
||||
userLoginStatusBean = context.getBean(UserLoginStatusBean.class); |
||||
} |
||||
// 检查 token 是否存在并且有效
|
||||
if (token == null) { |
||||
// 没有Token,拒绝请求
|
||||
response.setStatus(40401); |
||||
pass = false; |
||||
} else if (userLoginStatusBean != null && !userLoginStatusBean.containsToken(token)) { |
||||
// Token 无效,拒绝请求,可以返回特定的响应状态码,例如 401 Unauthorized
|
||||
response.setStatus(40401); |
||||
pass = false; |
||||
} |
||||
} |
||||
} catch (Exception e) { |
||||
throw new RuntimeException(e); |
||||
} |
||||
|
||||
return pass; |
||||
} |
||||
|
||||
} |
@ -1,63 +0,0 @@ |
||||
package cn.soul2.jyjc.admin.interceptor; |
||||
|
||||
import cn.soul2.jyjc.admin.annotation.SkinLogin; |
||||
import cn.soul2.jyjc.admin.config.UserLoginStatusBean; |
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.web.method.HandlerMethod; |
||||
import org.springframework.web.servlet.HandlerInterceptor; |
||||
|
||||
import javax.servlet.http.HttpServletRequest; |
||||
import javax.servlet.http.HttpServletResponse; |
||||
|
||||
/** |
||||
* @author Soul2 |
||||
* @date 2024-04-02 15:31 |
||||
*/ |
||||
|
||||
public class TokenInterceptor implements HandlerInterceptor { |
||||
|
||||
@Autowired |
||||
private UserLoginStatusBean userLoginStatusBean; |
||||
|
||||
@Override |
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { |
||||
// 允许OPTIONS请求通过
|
||||
if (isCorsRequest(request)) { |
||||
return true; |
||||
} |
||||
|
||||
// 跳过登录
|
||||
// 如果处理器是一个方法处理器
|
||||
if (handler instanceof HandlerMethod) { |
||||
HandlerMethod handlerMethod = (HandlerMethod) handler; |
||||
// 检查方法上是否存在SkinLogin注解
|
||||
if (handlerMethod.getMethod().isAnnotationPresent(SkinLogin.class)) { |
||||
// 如果存在,绕过拦截器
|
||||
return true; |
||||
} |
||||
} |
||||
// 验证token
|
||||
// 从请求头中获取 token
|
||||
String token = request.getHeader("jyjc-Token"); |
||||
// 检查 token 是否存在并且有效,这里可以根据实际情况自行实现验证逻辑
|
||||
if (token != null && isValidToken(token)) { |
||||
// Token 有效,允许请求通过
|
||||
return true; |
||||
} else { |
||||
// Token 无效,拒绝请求,可以返回特定的响应状态码,例如 401 Unauthorized
|
||||
response.setStatus(40401); |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
private boolean isValidToken(String token) { |
||||
// 实现 token 验证逻辑,例如验证 token 的签名或者在数据库中验证 token 的有效性
|
||||
return userLoginStatusBean.containsToken(token); |
||||
// 返回 true 表示 token 有效,返回 false 表示 token 无效
|
||||
} |
||||
|
||||
private boolean isCorsRequest(HttpServletRequest request) { |
||||
// 检查请求头中是否包含 Origin 字段,如果存在,则认为是跨域请求
|
||||
return request.getHeader("Origin") != null; |
||||
} |
||||
} |
@ -0,0 +1,132 @@ |
||||
package cn.soul2.jyjc.admin.utils; |
||||
|
||||
import org.apache.tomcat.util.codec.binary.Base64; |
||||
|
||||
import javax.crypto.Cipher; |
||||
import javax.crypto.KeyGenerator; |
||||
import javax.crypto.spec.SecretKeySpec; |
||||
|
||||
/** |
||||
* @author Soul2 |
||||
* @date 2024-04-08 15:00 |
||||
* <p>照抄自 <a href="https://blog.csdn.net/shaoduo/article/details/122322578">Shao duo</a></p> |
||||
*/ |
||||
|
||||
public class EncryptUtils { |
||||
|
||||
private static final String KEY = "620b8d3c3be1e725"; |
||||
|
||||
//参数分别代表 算法名称/加密模式/数据填充方式
|
||||
private static final String ALGORITHMSTR = "AES/ECB/PKCS5Padding"; |
||||
|
||||
/** |
||||
* 加密 |
||||
* |
||||
* @param content 加密的字符串 |
||||
* @param encryptKey key值 |
||||
* @return {@link String} |
||||
*/ |
||||
public static String encrypt(String content, String encryptKey) throws Exception { |
||||
KeyGenerator kgen = KeyGenerator.getInstance("AES"); |
||||
kgen.init(128); |
||||
Cipher cipher = Cipher.getInstance(ALGORITHMSTR); |
||||
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(encryptKey.getBytes(), "AES")); |
||||
byte[] b = cipher.doFinal(content.getBytes("utf-8")); |
||||
// 采用base64算法进行转码,避免出现中文乱码
|
||||
return Base64.encodeBase64String(b); |
||||
} |
||||
|
||||
/** |
||||
* 解密 |
||||
* |
||||
* @param encryptStr 解密的字符串 |
||||
* @param decryptKey 解密的key值 |
||||
* @return {@link String} |
||||
*/ |
||||
public static String decrypt(String encryptStr, String decryptKey) throws Exception { |
||||
KeyGenerator kgen = KeyGenerator.getInstance("AES"); |
||||
kgen.init(128); |
||||
Cipher cipher = Cipher.getInstance(ALGORITHMSTR); |
||||
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(decryptKey.getBytes("utf-8"), "AES")); |
||||
// 采用base64算法进行转码,避免出现中文乱码
|
||||
|
||||
//byte[] b = hex2Bytes(encryptStr) ;
|
||||
byte[] encryptBytes = Base64.decodeBase64(encryptStr); |
||||
byte[] decryptBytes = cipher.doFinal(encryptBytes); |
||||
return new String(decryptBytes); |
||||
} |
||||
|
||||
/** |
||||
* @param content 加密的字符串 |
||||
* @return {@link String} |
||||
*/ |
||||
public static String encrypt(String content) throws Exception { |
||||
return encrypt(content, KEY); |
||||
} |
||||
|
||||
/** |
||||
* @param encryptStr 解密的字符串 |
||||
* @return {@link String} |
||||
*/ |
||||
public static String decrypt(String encryptStr) throws Exception { |
||||
return decrypt(encryptStr, KEY); |
||||
} |
||||
|
||||
|
||||
/* public static void main(String[] args) throws Exception { |
||||
Map map=new HashMap<String,String>(); |
||||
map.put("key","value"); |
||||
map.put("中文","汉字"); |
||||
String content = JSONObject.toJSONString(map); |
||||
System.out.println("加密前:" + content); |
||||
String encrypt = encrypt(content, KEY); |
||||
System.out.println("加密后:" + encrypt); |
||||
String decrypt = decrypt(encrypt, KEY); |
||||
System.out.println("解密后:" + decrypt); |
||||
}*/ |
||||
|
||||
/** |
||||
* byte数组 转换成 16进制小写字符串 |
||||
*/ |
||||
public static String bytes2Hex(byte[] bytes) { |
||||
if (bytes == null || bytes.length == 0) { |
||||
return null; |
||||
} |
||||
|
||||
StringBuilder hex = new StringBuilder(); |
||||
|
||||
for (byte b : bytes) { |
||||
hex.append(HEXES[(b >> 4) & 0x0F]); |
||||
hex.append(HEXES[b & 0x0F]); |
||||
} |
||||
|
||||
return hex.toString(); |
||||
} |
||||
|
||||
/** |
||||
* 16进制字符串 转换为对应的 byte数组 |
||||
*/ |
||||
public static byte[] hex2Bytes(String hex) { |
||||
if (hex == null || hex.length() == 0) { |
||||
return null; |
||||
} |
||||
|
||||
char[] hexChars = hex.toCharArray(); |
||||
// 如果 hex 中的字符不是偶数个, 则忽略最后一个
|
||||
byte[] bytes = new byte[hexChars.length / 2]; |
||||
|
||||
for (int i = 0; i < bytes.length; i++) { |
||||
bytes[i] = (byte) Integer.parseInt("" + hexChars[i * 2] + hexChars[i * 2 + 1], 16); |
||||
} |
||||
|
||||
return bytes; |
||||
} |
||||
|
||||
private static final char[] HEXES = { |
||||
'0', '1', '2', '3', |
||||
'4', '5', '6', '7', |
||||
'8', '9', 'a', 'b', |
||||
'c', 'd', 'e', 'f' |
||||
}; |
||||
|
||||
} |
Loading…
Reference in new issue