From c90255f04f5adf48aa4e2edf86e4596996d30bc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=98=BF=E6=98=8E?= <1763113879@qq.com> Date: Thu, 16 Nov 2023 16:20:13 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E9=AA=8C=E8=AF=81=E7=A0=81?= =?UTF-8?q?=E6=A0=A1=E9=AA=8C=E9=97=AE=E9=A2=98=E5=92=8C=20filter=E5=BC=82?= =?UTF-8?q?=E5=B8=B8=E5=A4=84=E7=90=86=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ContentCachingRequestWrapperNew.java | 98 +++++++++++++++++++ .../filter/JwtAuthenticationTokenFilter.java | 73 ++++++++++++-- .../GlobalAdminWebExceptionHandler.java | 35 +++++++ 3 files changed, 198 insertions(+), 8 deletions(-) create mode 100644 ruoyi-web/ruoyi-web-admin/src/main/java/com/ruoyi/web/admin/filter/ContentCachingRequestWrapperNew.java create mode 100644 ruoyi-web/ruoyi-web-admin/src/main/java/com/ruoyi/web/admin/handler/GlobalAdminWebExceptionHandler.java diff --git a/ruoyi-web/ruoyi-web-admin/src/main/java/com/ruoyi/web/admin/filter/ContentCachingRequestWrapperNew.java b/ruoyi-web/ruoyi-web-admin/src/main/java/com/ruoyi/web/admin/filter/ContentCachingRequestWrapperNew.java new file mode 100644 index 00000000..088871d6 --- /dev/null +++ b/ruoyi-web/ruoyi-web-admin/src/main/java/com/ruoyi/web/admin/filter/ContentCachingRequestWrapperNew.java @@ -0,0 +1,98 @@ +package com.ruoyi.web.admin.filter; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.servlet.ReadListener; +import javax.servlet.ServletInputStream; +import javax.servlet.http.HttpServletRequest; + +import org.springframework.web.util.ContentCachingRequestWrapper; + +/** + * @author 1763113879@qq.com + * @version V2.1 + * @since 2.1.0 2023/11/16 14:59 + */ +//继承ContentCachingRequestWrapper +public class ContentCachingRequestWrapperNew extends ContentCachingRequestWrapper { + + //原子变量,用来区分首次读取还是非首次 + private AtomicBoolean isFirst = new AtomicBoolean(true); + + public ContentCachingRequestWrapperNew(HttpServletRequest request) { + super(request); + } + + public ContentCachingRequestWrapperNew(HttpServletRequest request, int contentCacheLimit) { + super(request, contentCacheLimit); + } + + @Override + public ServletInputStream getInputStream() throws IOException { + + if(isFirst.get()){ + //首次读取直接调父类的方法,这一次执行完之后 缓存流中有数据了 + //后续读取就读缓存流里的。 + isFirst.set(false); + return super.getInputStream(); + } + + //用缓存流构建一个新的输入流 + return new ServletInputStreamNew(super.getContentAsByteArray()); + } + + //参考自 DelegatingServletInputStream + class ServletInputStreamNew extends ServletInputStream{ + + private InputStream sourceStream; + + private boolean finished = false; + + + + public ServletInputStreamNew(byte [] bytes) { + //构建一个普通的输入流 + this.sourceStream = new ByteArrayInputStream(bytes); + } + + + @Override + public int read() throws IOException { + int data = this.sourceStream.read(); + if (data == -1) { + this.finished = true; + } + return data; + } + + @Override + public int available() throws IOException { + return this.sourceStream.available(); + } + + @Override + public void close() throws IOException { + super.close(); + this.sourceStream.close(); + } + + @Override + public boolean isFinished() { + return this.finished; + } + + @Override + public boolean isReady() { + return true; + } + + @Override + public void setReadListener(ReadListener readListener) { + throw new UnsupportedOperationException(); + } + } + +} \ No newline at end of file diff --git a/ruoyi-web/ruoyi-web-admin/src/main/java/com/ruoyi/web/admin/filter/JwtAuthenticationTokenFilter.java b/ruoyi-web/ruoyi-web-admin/src/main/java/com/ruoyi/web/admin/filter/JwtAuthenticationTokenFilter.java index fa16c1dc..2ea878b0 100644 --- a/ruoyi-web/ruoyi-web-admin/src/main/java/com/ruoyi/web/admin/filter/JwtAuthenticationTokenFilter.java +++ b/ruoyi-web/ruoyi-web-admin/src/main/java/com/ruoyi/web/admin/filter/JwtAuthenticationTokenFilter.java @@ -1,5 +1,6 @@ package com.ruoyi.web.admin.filter; +import java.io.BufferedReader; import java.io.IOException; import javax.servlet.FilterChain; @@ -10,16 +11,18 @@ import javax.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.core.annotation.Order; -import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; +import org.springframework.web.servlet.HandlerExceptionResolver; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; import com.ruoyi.common.core.constant.CacheConstants; import com.ruoyi.common.core.constant.SecurityConstants; import com.ruoyi.common.core.constant.TokenConstants; import com.ruoyi.common.core.context.SecurityContextHolder; -import com.ruoyi.common.core.exception.InnerAuthException; import com.ruoyi.common.core.exception.ServiceException; import com.ruoyi.common.core.utils.JwtUtils; import com.ruoyi.common.core.utils.ServletUtils; @@ -29,7 +32,9 @@ import com.ruoyi.common.security.auth.AuthUtil; import com.ruoyi.common.security.service.TokenService; import com.ruoyi.system.api.model.LoginUser; import com.ruoyi.web.admin.config.CustomHttpServletRequest; +import com.ruoyi.web.admin.config.properties.CaptchaProperties; import com.ruoyi.web.admin.config.properties.IgnoreWhiteProperties; +import com.ruoyi.web.admin.service.ValidateCodeService; import io.jsonwebtoken.Claims; @@ -54,6 +59,22 @@ public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { @Autowired private TokenService tokenService; + @Autowired + private ValidateCodeService validateCodeService; + + @Autowired + private CaptchaProperties captchaProperties; + + private static final String CODE = "code"; + + private static final String UUID = "uuid"; + + private final static String[] VALIDATE_URL = new String[] {"/auth/login", "/auth/register"}; + + @Autowired + @Qualifier("handlerExceptionResolver") + private HandlerExceptionResolver resolver; + @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { @@ -66,6 +87,27 @@ public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { } String requestURI = request.getRequestURI(); + + // 非登录/注册请求或验证码关闭,不处理 + if (StringUtils.equalsAnyIgnoreCase(requestURI, VALIDATE_URL) && captchaProperties.getEnabled()) { + + //,HttpServletRequst中的body内容只会读取一次,但是可能某些情境下可能会读取多次,替换request + ContentCachingRequestWrapperNew wrappedRequest = new ContentCachingRequestWrapperNew(request); + try { + String rspStr = resolveBodyFromRequest(wrappedRequest); + JSONObject obj = JSON.parseObject(rspStr); + validateCodeService.checkCaptcha(obj.getString(CODE), obj.getString(UUID)); + } catch (Exception e) { + // throw new ServiceException(e.getMessage()); + resolver.resolveException(request, response, null, e); + return; + + } + + + request = wrappedRequest; + } + // 跳过不需要验证的路径 if (!StringUtils.matches(requestURI, ignoreWhite.getWhites())) { @@ -83,7 +125,6 @@ public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { boolean islogin = redisService.hasKey(getTokenKey(userkey)); if (!islogin) { throw new ServiceException("登录状态已过期"); - } String userid = JwtUtils.getUserId(claims); String username = JwtUtils.getUserName(claims); @@ -96,7 +137,7 @@ public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { SecurityContextHolder.setUserName(username); SecurityContextHolder.setUserKey(userkey); - CustomHttpServletRequest customHttpServletRequest = new CustomHttpServletRequest(request); + CustomHttpServletRequest customHttpServletRequest = new CustomHttpServletRequest(request); // 设置用户信息到请求 addHeader(customHttpServletRequest, SecurityConstants.USER_KEY, userkey); addHeader(customHttpServletRequest, SecurityConstants.DETAILS_USER_ID, userid); @@ -136,10 +177,8 @@ public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { return token; } - private void addHeader(CustomHttpServletRequest customHttpServletRequest, String name, Object value) - { - if (value == null) - { + private void addHeader(CustomHttpServletRequest customHttpServletRequest, String name, Object value) { + if (value == null) { return; } String valueStr = value.toString(); @@ -147,6 +186,24 @@ public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { customHttpServletRequest.addHeader(name, valueEncode); } + private String resolveBodyFromRequest(HttpServletRequest request) throws IOException { + + // 直接从HttpServletRequest的Reader流中获取RequestBody + BufferedReader reader = request.getReader(); + StringBuilder builder = new StringBuilder(); + String line = reader.readLine(); + while (line != null) { + builder.append(line); + line = reader.readLine(); + } + reader.close(); + String reqBody = builder.toString(); + return reqBody; + + // String requestBody = new String(request.getContentAsByteArray()); + // return requestBody; + + } } diff --git a/ruoyi-web/ruoyi-web-admin/src/main/java/com/ruoyi/web/admin/handler/GlobalAdminWebExceptionHandler.java b/ruoyi-web/ruoyi-web-admin/src/main/java/com/ruoyi/web/admin/handler/GlobalAdminWebExceptionHandler.java new file mode 100644 index 00000000..ccdc165f --- /dev/null +++ b/ruoyi-web/ruoyi-web-admin/src/main/java/com/ruoyi/web/admin/handler/GlobalAdminWebExceptionHandler.java @@ -0,0 +1,35 @@ +package com.ruoyi.web.admin.handler; + +import javax.servlet.http.HttpServletRequest; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import com.ruoyi.common.core.exception.CaptchaException; +import com.ruoyi.common.core.web.domain.AjaxResult; + +/** + * 全局异常处理器 + * + * @author ruoyi + */ +@RestControllerAdvice +public class GlobalAdminWebExceptionHandler +{ + private static final Logger log = LoggerFactory.getLogger(GlobalAdminWebExceptionHandler.class); + + + /** + * 验证码异常 + */ + @ExceptionHandler(CaptchaException.class) + public AjaxResult handleCaptchaException(CaptchaException e, HttpServletRequest request) + { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}'", requestURI, e.getMessage()); + return AjaxResult.error(e.getMessage()); + } + +}