完成请求和响应的加解密

soul2/encrypt
soul2 1 year ago
parent 077751021e
commit 3b598b0e81
  1. 7
      src/main/java/cn/soul2/jyjc/admin/bean/UserLoginStatusBean.java
  2. 25
      src/main/java/cn/soul2/jyjc/admin/filter/ReplaceStreamFilter.java
  3. 120
      src/main/java/cn/soul2/jyjc/admin/filter/Soul2ResponseWrapper.java
  4. 20
      src/main/java/cn/soul2/jyjc/admin/interceptor/FinallyInterceptor.java
  5. 9
      src/main/java/cn/soul2/jyjc/admin/repository/IUserLoginOutRepository.java
  6. 27
      src/main/java/cn/soul2/jyjc/admin/repository/impl/UserLoginOutRepositoryImpl.java
  7. 10
      src/main/java/cn/soul2/jyjc/admin/service/IUserService.java
  8. 8
      src/main/java/cn/soul2/jyjc/admin/service/impl/UserServiceImpl.java
  9. 127
      src/main/resources/logback-spring.xml

@ -5,6 +5,7 @@ import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.concurrent.ConcurrentHashMap;
@Component
@ -12,6 +13,12 @@ public class UserLoginStatusBean {
private ConcurrentHashMap<String, UserLoginOutDO> loginStatusMap = new ConcurrentHashMap<>();
public void loadUserLoginStatus(Collection<UserLoginOutDO> caches) {
for (UserLoginOutDO cache : caches) {
loginStatusMap.put(cache.getId(), cache);
}
}
/**
* 缓存登录信息
*

@ -1,11 +1,14 @@
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
@ -24,12 +27,28 @@ public class ReplaceStreamFilter implements Filter {
//如果是文件上传则会报错可以判断是否是文件上传不读取流即可
if (ServletFileUpload.isMultipartContent((HttpServletRequest) request)) {
chain.doFilter(request, response);
return;
} 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());
chain.doFilter(requestWrapper, response);
return;
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();
}
}
}

@ -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();
}
/**
* 将outwriter中的数据强制输出到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
}
}
}

@ -4,12 +4,14 @@ 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;
@ -31,8 +33,21 @@ 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())) {
@ -81,7 +96,7 @@ public class FinallyInterceptor implements HandlerInterceptor {
JSONObject obj = JSON.parseObject(EncryptUtils.decrypt(sourceParamBody));
String afterBody = JSONObject.toJSONString(obj);
shaoduoRequestWrapper.setBody(afterBody);
log.info(String.format("解密: %s -> %s", sourceParamBody, afterBody));
log.debug(String.format("解密: %s -> %s", sourceParamBody, afterBody));
}
@ -99,7 +114,7 @@ public class FinallyInterceptor implements HandlerInterceptor {
} else if (userLoginStatusBean != null) {
if (!userLoginStatusBean.containsToken(token)) {
// Token 无效,拒绝请求
response.setStatus(0);
response.setContentType("application/json");
Back<String> back = new Back<String>().setCode(40401).setMessage("Token invalid!");
// 转换为 JSON 字符串
@ -122,5 +137,4 @@ public class FinallyInterceptor implements HandlerInterceptor {
}
return pass;
}
}

@ -8,6 +8,8 @@ import cn.soul2.jyjc.admin.entity.UserLoginOutDO;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
/**
* <p>
* 用户登入登出表 服务类
@ -59,4 +61,11 @@ public interface IUserLoginOutRepository extends IService<UserLoginOutDO> {
*/
Boolean removeByUserId(String userId);
/**
* 重建当前登录的用户
*
* @return {@link List}<{@link UserLoginOutDO}>
*/
List<UserLoginOutDO> getLoginedUser();
}

@ -12,6 +12,7 @@ import cn.soul2.jyjc.admin.utils.base.PageUtils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
@ -19,6 +20,12 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* <p>
@ -96,4 +103,24 @@ public class UserLoginOutRepositoryImpl extends ServiceImpl<UserLoginOutMapper,
update.eq(UserLoginOutDO::getUserId, userId);
return super.remove(update);
}
@Override
public List<UserLoginOutDO> getLoginedUser() {
LocalDateTime now = LocalDateTime.now();
LambdaQueryWrapper<UserLoginOutDO> query = Wrappers.lambdaQuery();
query
.isNull(UserLoginOutDO::getLogoutTime)
.gt(UserLoginOutDO::getLapseTime, now);
List<UserLoginOutDO> list = super.list(query);
if (CollectionUtils.isEmpty(list)) {
return new ArrayList<>();
}
return new ArrayList<>(list
.stream()
.collect(Collectors.toMap(UserLoginOutDO::getUserId,
Function.identity(),
BinaryOperator.maxBy(Comparator.comparing(UserLoginOutDO::getLoginTime))))
.values());
}
}

@ -2,8 +2,11 @@ package cn.soul2.jyjc.admin.service;
import cn.soul2.jyjc.admin.dto.UserLoginDTO;
import cn.soul2.jyjc.admin.dto.UserLogoutDTO;
import cn.soul2.jyjc.admin.entity.UserLoginOutDO;
import cn.soul2.jyjc.admin.vo.UserVO;
import java.util.List;
/**
* @author Soul2
* @date 2024-03-29 20:47
@ -43,4 +46,11 @@ public interface IUserService {
*/
Boolean remove(String userId);
/**
* 重建当前登录的用户
*
* @return {@link List}<{@link UserLoginOutDO}>
*/
List<UserLoginOutDO> getLoginedUser();
}

@ -3,6 +3,7 @@ package cn.soul2.jyjc.admin.service.impl;
import cn.soul2.jyjc.admin.dto.UserLoginDTO;
import cn.soul2.jyjc.admin.dto.UserLogoutDTO;
import cn.soul2.jyjc.admin.entity.UserDO;
import cn.soul2.jyjc.admin.entity.UserLoginOutDO;
import cn.soul2.jyjc.admin.repository.IUserLoginOutRepository;
import cn.soul2.jyjc.admin.repository.IUserRepository;
import cn.soul2.jyjc.admin.service.IUserService;
@ -12,6 +13,8 @@ import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* @author Soul2
* @date 2024-03-29 20:47
@ -84,4 +87,9 @@ public class UserServiceImpl implements IUserService {
loginOutRepository.removeByUserId(userId);
return userRepository.removeById(userId);
}
@Override
public List<UserLoginOutDO> getLoginedUser() {
return loginOutRepository.getLoginedUser();
}
}

@ -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…
Cancel
Save