Compare commits
4 Commits
5e5fb9a07d
...
3b598b0e81
Author | SHA1 | Date |
---|---|---|
|
3b598b0e81 | 1 year ago |
|
077751021e | 1 year ago |
|
7e63ad937d | 1 year ago |
|
e044f02d32 | 1 year ago |
19 changed files with 887 additions and 112 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 { |
||||||
|
|
||||||
|
} |
@ -1,28 +0,0 @@ |
|||||||
package cn.soul2.jyjc.admin.config; |
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Value; |
|
||||||
import org.springframework.context.annotation.Configuration; |
|
||||||
import org.springframework.web.servlet.config.annotation.CorsRegistry; |
|
||||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; |
|
||||||
|
|
||||||
/** |
|
||||||
* @author Soul2 |
|
||||||
* @date 2024-03-25 |
|
||||||
*/ |
|
||||||
@Configuration |
|
||||||
public class CorsConfig implements WebMvcConfigurer { |
|
||||||
|
|
||||||
@Value("${cors.allow-origin}") |
|
||||||
private String[] allowOrigin; |
|
||||||
|
|
||||||
@Override |
|
||||||
public void addCorsMappings(CorsRegistry registry) { |
|
||||||
registry.addMapping("/**") |
|
||||||
.allowedHeaders("*") |
|
||||||
.allowedOrigins(allowOrigin) |
|
||||||
.allowCredentials(true) |
|
||||||
.allowedMethods("POST") |
|
||||||
.maxAge(3600); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -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,60 @@ |
|||||||
|
package cn.soul2.jyjc.admin.filter; |
||||||
|
|
||||||
|
import cn.soul2.jyjc.admin.utils.EncryptUtils; |
||||||
|
import lombok.extern.slf4j.Slf4j; |
||||||
|
import org.apache.tomcat.util.http.fileupload.servlet.ServletFileUpload; |
||||||
|
|
||||||
|
import javax.servlet.*; |
||||||
|
import javax.servlet.http.HttpServletRequest; |
||||||
|
import javax.servlet.http.HttpServletResponse; |
||||||
|
import java.io.IOException; |
||||||
|
import java.io.PrintWriter; |
||||||
|
|
||||||
|
/** |
||||||
|
* @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); |
||||||
|
} else if ("OPTIONS".equalsIgnoreCase(((HttpServletRequest) request).getMethod())) { |
||||||
|
chain.doFilter(request, response); |
||||||
|
} else { |
||||||
|
ServletRequest requestWrapper = new ShaoduoRequestWrapper((HttpServletRequest) request); |
||||||
|
// System.out.printf("ReplaceStreamFilter触发, Method: %s, URI: %s%n", ((HttpServletRequest) request).getMethod(), ((HttpServletRequest) request).getRequestURI());
|
||||||
|
HttpServletResponse httpResponse = (HttpServletResponse) response; |
||||||
|
Soul2ResponseWrapper soul2ResponseWrapper = new Soul2ResponseWrapper(httpResponse); |
||||||
|
chain.doFilter(requestWrapper, soul2ResponseWrapper); |
||||||
|
try { |
||||||
|
byte[] data = soul2ResponseWrapper.getResponseData(); |
||||||
|
log.debug("原始返回 -> " + new String(data)); |
||||||
|
String encryptBody = EncryptUtils.encrypt(new String(data)); |
||||||
|
log.debug("加密返回 -> " + encryptBody); |
||||||
|
soul2ResponseWrapper.setHeader("encrypt", "1"); |
||||||
|
soul2ResponseWrapper.setHeader("Access-Control-Expose-Headers", "encrypt"); |
||||||
|
PrintWriter out = response.getWriter(); |
||||||
|
out.print(encryptBody); |
||||||
|
out.flush(); |
||||||
|
out.close(); |
||||||
|
} catch (Exception e) { |
||||||
|
e.printStackTrace(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void destroy() { |
||||||
|
log.info("StreamFilter销毁..."); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,130 @@ |
|||||||
|
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 <a href="https://blog.csdn.net/shaoduo/article/details/122322578">shaoduo</a> |
||||||
|
**/ |
||||||
|
@Slf4j |
||||||
|
public class ShaoduoRequestWrapper extends HttpServletRequestWrapper { |
||||||
|
/** |
||||||
|
* 存储body数据的容器 |
||||||
|
*/ |
||||||
|
private byte[] body; |
||||||
|
|
||||||
|
public ShaoduoRequestWrapper(HttpServletRequest request) { |
||||||
|
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() throws IOException { |
||||||
|
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) throws IOException { |
||||||
|
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() { |
||||||
|
|
||||||
|
final ByteArrayInputStream inputStream = new ByteArrayInputStream(body); |
||||||
|
|
||||||
|
return new ServletInputStream() { |
||||||
|
@Override |
||||||
|
public int read() { |
||||||
|
return inputStream.read(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean isFinished() { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean isReady() { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void setReadListener(ReadListener readListener) { |
||||||
|
} |
||||||
|
}; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,120 @@ |
|||||||
|
package cn.soul2.jyjc.admin.filter; |
||||||
|
|
||||||
|
import lombok.SneakyThrows; |
||||||
|
|
||||||
|
import javax.servlet.ServletOutputStream; |
||||||
|
import javax.servlet.WriteListener; |
||||||
|
import javax.servlet.http.HttpServletResponse; |
||||||
|
import javax.servlet.http.HttpServletResponseWrapper; |
||||||
|
import java.io.*; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Soul2 |
||||||
|
* @description 包装ServletResponse, 照抄自 <a href="https://blog.csdn.net/temp_44/article/details/107762290">temp_44</a> |
||||||
|
* @date 2024-04-15 |
||||||
|
*/ |
||||||
|
public class Soul2ResponseWrapper extends HttpServletResponseWrapper { |
||||||
|
private ByteArrayOutputStream buffer = null; |
||||||
|
private ServletOutputStream out = null; |
||||||
|
private PrintWriter writer = null; |
||||||
|
|
||||||
|
public Soul2ResponseWrapper(HttpServletResponse resp) throws IOException { |
||||||
|
super(resp); |
||||||
|
// 真正存储数据的流
|
||||||
|
buffer = new ByteArrayOutputStream(); |
||||||
|
out = new WapperedOutputStream(buffer); |
||||||
|
writer = new PrintWriter(new OutputStreamWriter(buffer, |
||||||
|
this.getCharacterEncoding())); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 重载父类获取outputstream的方法 |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public ServletOutputStream getOutputStream() throws IOException { |
||||||
|
return out; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 重载父类获取writer的方法 |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public PrintWriter getWriter() throws UnsupportedEncodingException { |
||||||
|
return writer; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* The default behavior of this method is to call |
||||||
|
* setCharacterEncoding(String charset) on the wrapped response object. |
||||||
|
* |
||||||
|
* @param charset |
||||||
|
* @since 2.4 |
||||||
|
*/ |
||||||
|
@SneakyThrows |
||||||
|
@Override |
||||||
|
public void setCharacterEncoding(String charset) { |
||||||
|
super.setCharacterEncoding(charset); |
||||||
|
writer = new PrintWriter(new OutputStreamWriter(buffer, |
||||||
|
this.getCharacterEncoding())); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 重载父类获取flushBuffer的方法 |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void flushBuffer() throws IOException { |
||||||
|
if (out != null) { |
||||||
|
out.flush(); |
||||||
|
} |
||||||
|
if (writer != null) { |
||||||
|
writer.flush(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void reset() { |
||||||
|
buffer.reset(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 将out、writer中的数据强制输出到WapperedResponse的buffer里面,否则取不到数据 |
||||||
|
*/ |
||||||
|
public byte[] getResponseData() throws IOException { |
||||||
|
flushBuffer(); |
||||||
|
return buffer.toByteArray(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 内部类,对ServletOutputStream进行包装 |
||||||
|
*/ |
||||||
|
private class WapperedOutputStream extends ServletOutputStream { |
||||||
|
private ByteArrayOutputStream bos = null; |
||||||
|
|
||||||
|
public WapperedOutputStream(ByteArrayOutputStream stream) |
||||||
|
throws IOException { |
||||||
|
bos = stream; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void write(int b) throws IOException { |
||||||
|
bos.write(b); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void write(byte[] b) throws IOException { |
||||||
|
bos.write(b, 0, b.length); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean isReady() { |
||||||
|
// TODO Auto-generated method stub
|
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void setWriteListener(WriteListener writeListener) { |
||||||
|
// TODO Auto-generated method stub
|
||||||
|
|
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,140 @@ |
|||||||
|
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.bean.UserLoginStatusBean; |
||||||
|
import cn.soul2.jyjc.admin.filter.ShaoduoRequestWrapper; |
||||||
|
import cn.soul2.jyjc.admin.service.IUserService; |
||||||
|
import cn.soul2.jyjc.admin.utils.EncryptUtils; |
||||||
|
import cn.soul2.jyjc.admin.vo.base.Back; |
||||||
|
import com.alibaba.fastjson.JSON; |
||||||
|
import com.alibaba.fastjson.JSONObject; |
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper; |
||||||
|
import lombok.extern.slf4j.Slf4j; |
||||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||||
|
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.io.PrintWriter; |
||||||
|
import java.lang.reflect.Method; |
||||||
|
|
||||||
|
/** |
||||||
|
* Token拦截器 |
||||||
|
* |
||||||
|
* @author Soul2 |
||||||
|
* @date 2024-04-02 15:31 |
||||||
|
*/ |
||||||
|
@Slf4j |
||||||
|
public class FinallyInterceptor implements HandlerInterceptor { |
||||||
|
|
||||||
|
@Resource |
||||||
|
private UserLoginStatusBean userLoginStatusBean; |
||||||
|
|
||||||
|
@Autowired |
||||||
|
private IUserService userService; |
||||||
|
|
||||||
|
private boolean load = true; |
||||||
|
|
||||||
|
private void loadUserLoginStatus() { |
||||||
|
userLoginStatusBean.loadUserLoginStatus(userService.getLoginedUser()); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { |
||||||
|
if (load) { |
||||||
|
loadUserLoginStatus(); |
||||||
|
load = false; |
||||||
|
} |
||||||
|
boolean pass = false; |
||||||
|
// 允许OPTIONS请求通过
|
||||||
|
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) { |
||||||
|
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; |
||||||
|
} |
||||||
|
} |
||||||
|
// 跳过使用 @SkinEncrypt 的情况
|
||||||
|
if (skinEncrypt != null) { |
||||||
|
pass = true; |
||||||
|
} |
||||||
|
|
||||||
|
if (!pass) { |
||||||
|
ShaoduoRequestWrapper shaoduoRequestWrapper; |
||||||
|
try { |
||||||
|
shaoduoRequestWrapper = (ShaoduoRequestWrapper) request; |
||||||
|
} catch (Exception e) { |
||||||
|
System.out.printf("request.ClassTypeError: %s%n", request.getClass().getName()); |
||||||
|
return true; |
||||||
|
} |
||||||
|
String sourceParamBody = shaoduoRequestWrapper.getBodyString(); |
||||||
|
JSONObject obj = JSON.parseObject(EncryptUtils.decrypt(sourceParamBody)); |
||||||
|
String afterBody = JSONObject.toJSONString(obj); |
||||||
|
shaoduoRequestWrapper.setBody(afterBody); |
||||||
|
log.debug(String.format("解密: %s -> %s", sourceParamBody, afterBody)); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
/* token验证 */ |
||||||
|
if (hasSkinLogin) { |
||||||
|
// 跳过使用 @SkinLogin 的情况
|
||||||
|
pass = true; |
||||||
|
} else { |
||||||
|
// 验证token
|
||||||
|
// 检查 token 是否存在并且有效
|
||||||
|
if (token == null) { |
||||||
|
// 没有Token,拒绝请求
|
||||||
|
response.setStatus(40401); |
||||||
|
pass = false; |
||||||
|
} else if (userLoginStatusBean != null) { |
||||||
|
if (!userLoginStatusBean.containsToken(token)) { |
||||||
|
// Token 无效,拒绝请求
|
||||||
|
response.setContentType("application/json"); |
||||||
|
Back<String> back = new Back<String>().setCode(40401).setMessage("Token invalid!"); |
||||||
|
|
||||||
|
// 转换为 JSON 字符串
|
||||||
|
ObjectMapper objectMapper = new ObjectMapper(); |
||||||
|
String responseBody = objectMapper.writeValueAsString(back); |
||||||
|
|
||||||
|
// 输出错误信息到响应中
|
||||||
|
PrintWriter writer = response.getWriter(); |
||||||
|
writer.print(responseBody); |
||||||
|
writer.flush(); |
||||||
|
pass = false; |
||||||
|
} else { |
||||||
|
// token存在, 通过拦截器
|
||||||
|
pass = true; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} 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' |
||||||
|
}; |
||||||
|
|
||||||
|
} |
@ -0,0 +1,127 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<configuration scan="true" scanPeriod="10 seconds"> |
||||||
|
|
||||||
|
<!--<include resource="org/springframework/boot/logging/logback/base.xml" />--> |
||||||
|
|
||||||
|
<contextName>logback</contextName> |
||||||
|
<!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义变量后,可以使“${}”来使用变量。 --> |
||||||
|
<!-- <property name="log.path" value=".logs/"/>--> |
||||||
|
<springProperty scope="context" name="log.path" source="logging.path" defaultValue="./logs/"/> |
||||||
|
<property name="log.pattern" |
||||||
|
value="%cyan(%d{HH:mm:ss}) %green(%-5level) %blue(%logger{50}) - %boldMagenta(%msg) \t\t ---- %yellow([%thread] %d{yyyy-MM-dd HH:mm:ss.SSS}) %n" |
||||||
|
/> |
||||||
|
<property name="debug.path" value="${log.path}/%d{yyyyMM,aux}/%d{dd,aux}/debug-%d{yyyyMMdd}.%i.log.gz"/> |
||||||
|
<property name="info.path" value="${log.path}/%d{yyyyMM,aux}/%d{dd,aux}/info-%d{yyyyMMdd}.%i.log.gz"/> |
||||||
|
<property name="error.path" value="${log.path}/%d{yyyyMM,aux}/%d{dd,aux}/error-%d{yyyyMMdd}.%i.log.gz"/> |
||||||
|
|
||||||
|
<!--输出到控制台--> |
||||||
|
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> |
||||||
|
<!-- 控制台输出的日志级别是大于或等于此级别的日志信息 --> |
||||||
|
<filter class="ch.qos.logback.classic.filter.ThresholdFilter"> |
||||||
|
<level>DEBUG</level> |
||||||
|
</filter> |
||||||
|
<!--日志文件输出格式与字符集--> |
||||||
|
<encoder> |
||||||
|
<Pattern>${log.pattern}</Pattern> |
||||||
|
<charset>UTF-8</charset> |
||||||
|
</encoder> |
||||||
|
</appender> |
||||||
|
|
||||||
|
<!--输出到文件--> |
||||||
|
<!-- 时间滚动输出 level为 DEBUG 日志 --> |
||||||
|
<appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> |
||||||
|
<!-- 正在记录的日志文件的路径及文件名 --> |
||||||
|
<file>${log.path}/debug.log</file> |
||||||
|
<!--日志文件输出格式与字符集--> |
||||||
|
<encoder> |
||||||
|
<pattern>${log.pattern}</pattern> |
||||||
|
<charset>UTF-8</charset> |
||||||
|
</encoder> |
||||||
|
<!-- 日志记录器的滚动策略,按日期,按大小记录 --> |
||||||
|
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> |
||||||
|
<!-- 日志归档 --> |
||||||
|
<fileNamePattern>${debug.path}</fileNamePattern> |
||||||
|
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> |
||||||
|
<maxFileSize>10MB</maxFileSize> |
||||||
|
</timeBasedFileNamingAndTriggeringPolicy> |
||||||
|
<!--日志文件保留天数--> |
||||||
|
<maxHistory>7</maxHistory> |
||||||
|
</rollingPolicy> |
||||||
|
<!-- 此日志文件记录debug级别以上的 --> |
||||||
|
<filter class="ch.qos.logback.classic.filter.ThresholdFilter"> |
||||||
|
<level>DEBUG</level> |
||||||
|
</filter> |
||||||
|
</appender> |
||||||
|
|
||||||
|
<!-- 时间滚动输出 level为 INFO 日志 --> |
||||||
|
<appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> |
||||||
|
<!-- 正在记录的日志文件的路径及文件名 --> |
||||||
|
<file>${log.path}/info.log</file> |
||||||
|
<!--日志文件输出格式--> |
||||||
|
<encoder> |
||||||
|
<pattern>${log.pattern}</pattern> |
||||||
|
<charset>UTF-8</charset> |
||||||
|
</encoder> |
||||||
|
<!-- 日志记录器的滚动策略,按日期,按大小记录 --> |
||||||
|
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> |
||||||
|
<!-- 每天日志归档路径以及格式 --> |
||||||
|
<fileNamePattern>${info.path}</fileNamePattern> |
||||||
|
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> |
||||||
|
<maxFileSize>100MB</maxFileSize> |
||||||
|
</timeBasedFileNamingAndTriggeringPolicy> |
||||||
|
<!--日志文件保留天数--> |
||||||
|
<maxHistory>60</maxHistory> |
||||||
|
</rollingPolicy> |
||||||
|
<!-- 此日志文件记录info级别以上的 --> |
||||||
|
<filter class="ch.qos.logback.classic.filter.ThresholdFilter"> |
||||||
|
<level>INFO</level> |
||||||
|
</filter> |
||||||
|
</appender> |
||||||
|
|
||||||
|
<!-- 时间滚动输出 level为 ERROR 日志 --> |
||||||
|
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> |
||||||
|
<!-- 正在记录的日志文件的路径及文件名 --> |
||||||
|
<file>${log.path}/error.log</file> |
||||||
|
<!--日志文件输出格式--> |
||||||
|
<encoder> |
||||||
|
<pattern>${log.pattern}</pattern> |
||||||
|
<charset>UTF-8</charset> <!-- 此处设置字符集 --> |
||||||
|
</encoder> |
||||||
|
<!-- 日志记录器的滚动策略,按日期,按大小记录 --> |
||||||
|
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> |
||||||
|
<fileNamePattern>${error.path}</fileNamePattern> |
||||||
|
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> |
||||||
|
<maxFileSize>10MB</maxFileSize> |
||||||
|
</timeBasedFileNamingAndTriggeringPolicy> |
||||||
|
<!--日志文件保留天数--> |
||||||
|
<maxHistory>60</maxHistory> |
||||||
|
</rollingPolicy> |
||||||
|
<!-- 此日志文件记录error级别以上的 --> |
||||||
|
<filter class="ch.qos.logback.classic.filter.ThresholdFilter"> |
||||||
|
<level>ERROR</level> |
||||||
|
</filter> |
||||||
|
</appender> |
||||||
|
|
||||||
|
<!--开发环境:打印控制台--> |
||||||
|
<springProfile name="local,dev,test"> |
||||||
|
<logger level="DEBUG" additivity="false" name="cn.soul2"> |
||||||
|
<appender-ref ref="CONSOLE"/> |
||||||
|
<appender-ref ref="DEBUG_FILE"/> |
||||||
|
<appender-ref ref="INFO_FILE"/> |
||||||
|
<appender-ref ref="ERROR_FILE"/> |
||||||
|
</logger> |
||||||
|
<root level="INFO"> |
||||||
|
<appender-ref ref="CONSOLE"/> |
||||||
|
<appender-ref ref="INFO_FILE"/> |
||||||
|
<appender-ref ref="ERROR_FILE"/> |
||||||
|
</root> |
||||||
|
</springProfile> |
||||||
|
|
||||||
|
<!--生产环境:输出到文件--> |
||||||
|
<springProfile name="pre,prod,prod-yun"> |
||||||
|
<root level="INFO"> |
||||||
|
<appender-ref ref="INFO_FILE"/> |
||||||
|
<appender-ref ref="ERROR_FILE"/> |
||||||
|
</root> |
||||||
|
</springProfile> |
||||||
|
</configuration> |
Loading…
Reference in new issue