Browse Source

fix: 积木报表 API 数据集解析时 token 未正确解析的问题

gaibu 2 years ago
parent
commit
ee454ace9d

+ 5 - 3
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/enums/WebFilterOrderEnum.java

@@ -2,8 +2,8 @@ package cn.iocoder.yudao.framework.common.enums;
 
 /**
  * Web 过滤器顺序的枚举类,保证过滤器按照符合我们的预期
- *
- *  考虑到每个 starter 都需要用到该工具类,所以放到 common 模块下的 enums 包下
+ * <p>
+ * 考虑到每个 starter 都需要用到该工具类,所以放到 common 模块下的 enums 包下
  *
  * @author 芋道源码
  */
@@ -17,7 +17,9 @@ public interface WebFilterOrderEnum {
 
     // OrderedRequestContextFilter 默认为 -105,用于国际化上下文等等
 
-    int TENANT_CONTEXT_FILTER = - 104; // 需要保证在 ApiAccessLogFilter 前面
+    int JM_TOKEN_FILTER = -105; // 需要保证在  Spring Security 过滤前面
+
+    int TENANT_CONTEXT_FILTER = -104; // 需要保证在 ApiAccessLogFilter 前面
 
     int API_ACCESS_LOG_FILTER = -103; // 需要保证在 RequestBodyCacheFilter 后面
 

+ 0 - 2
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/SecurityProperties.java

@@ -20,8 +20,6 @@ public class SecurityProperties {
     @NotEmpty(message = "Token Header 不能为空")
     private String tokenHeader = "Authorization";
 
-    private String jmTokenHeader = "X-Access-Token";
-
     /**
      * mock 模式的开关
      */

+ 3 - 23
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/filter/TokenAuthenticationFilter.java

@@ -21,7 +21,6 @@ import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
-import java.util.Optional;
 
 /**
  * Token 过滤器,验证 token 的有效性
@@ -63,25 +62,6 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
                 return;
             }
         }
-        // 积木请求头
-        String jmTokenHeader = request.getHeader(securityProperties.getJmTokenHeader());
-        if (StrUtil.isNotEmpty(jmTokenHeader)) {
-            try {
-                OAuth2AccessTokenCheckRespDTO accessToken = oauth2TokenApi.checkAccessToken(jmTokenHeader);
-                Optional<LoginUser> optUser = Optional.ofNullable(accessToken)
-                        .map(
-                                t -> new LoginUser().setId(t.getUserId())
-                                        .setUserType(t.getUserType())
-                                        .setTenantId(t.getTenantId())
-                                        .setScopes(t.getScopes())
-                        );
-                if (optUser.isPresent()) {
-                    SecurityFrameworkUtils.setLoginUser(optUser.get(), request);
-                }
-            } catch (ServiceException ignored) {
-                // do nothing:如果报错,说明认证失败,忽略即可
-            }
-        }
 
         // 继续过滤链
         chain.doFilter(request, response);
@@ -108,11 +88,11 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
 
     /**
      * 模拟登录用户,方便日常开发调试
-     * <p>
+     *
      * 注意,在线上环境下,一定要关闭该功能!!!
      *
-     * @param request  请求
-     * @param token    模拟的 token,格式为 {@link SecurityProperties#getMockSecret()} + 用户编号
+     * @param request 请求
+     * @param token 模拟的 token,格式为 {@link SecurityProperties#getMockSecret()} + 用户编号
      * @param userType 用户类型
      * @return 模拟的 LoginUser
      */

+ 11 - 0
yudao-module-visualization/yudao-module-visualization-biz/src/main/java/cn/iocoder/yudao/module/visualization/framework/jmreport/config/JmReportConfiguration.java

@@ -1,8 +1,11 @@
 package cn.iocoder.yudao.module.visualization.framework.jmreport.config;
 
+import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum;
 import cn.iocoder.yudao.module.system.api.oauth2.OAuth2TokenApi;
 import cn.iocoder.yudao.module.visualization.framework.jmreport.core.service.JmReportTokenServiceImpl;
+import cn.iocoder.yudao.module.visualization.framework.jmreport.core.web.JmReportTokenFilter;
 import org.jeecg.modules.jmreport.api.JmReportTokenServiceI;
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.ComponentScan;
 import org.springframework.context.annotation.Configuration;
@@ -22,4 +25,12 @@ public class JmReportConfiguration {
         return new JmReportTokenServiceImpl(oAuth2TokenApi);
     }
 
+    @Bean
+    @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
+    public FilterRegistrationBean<JmReportTokenFilter> registerMyAnotherFilter(OAuth2TokenApi oAuth2TokenApi){
+        FilterRegistrationBean<JmReportTokenFilter> bean = new FilterRegistrationBean<>();
+        bean.setOrder(WebFilterOrderEnum.JM_TOKEN_FILTER);
+        bean.setFilter(new JmReportTokenFilter(oAuth2TokenApi));
+        return bean;
+    }
 }

+ 132 - 0
yudao-module-visualization/yudao-module-visualization-biz/src/main/java/cn/iocoder/yudao/module/visualization/framework/jmreport/core/web/JmReportTokenFilter.java

@@ -0,0 +1,132 @@
+package cn.iocoder.yudao.module.visualization.framework.jmreport.core.web;
+
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.security.core.LoginUser;
+import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
+import cn.iocoder.yudao.module.system.api.oauth2.OAuth2TokenApi;
+import cn.iocoder.yudao.module.system.api.oauth2.dto.OAuth2AccessTokenCheckRespDTO;
+import lombok.RequiredArgsConstructor;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * 积木报表 token 处理,将积木报表请求头中的 token 转换成 spring security 的 auth head
+ */
+@RequiredArgsConstructor
+public class JmReportTokenFilter implements Filter {
+    /**
+     * 积木 token 请求头
+     */
+    private static final String JM_TOKEN_HEADER = "X-Access-Token";
+    /**
+     * 系统内置请求头
+     */
+    private static final String TOKEN_HEADER = "Authorization";
+    /**
+     * auth 相关格式
+     */
+    private static final String AUTHORIZATION_FORMAT = "Bearer %s";
+
+    private final OAuth2TokenApi oauth2TokenApi;
+
+    @Override
+    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
+        // 积木请求头
+        HttpServletRequest req = (HttpServletRequest) servletRequest;
+        String token = req.getHeader(JM_TOKEN_HEADER);
+        if (StrUtil.isNotEmpty(token)) {
+            // 1. 增加请求头
+            HeaderMapRequestWrapper requestWrapper = new HeaderMapRequestWrapper(req);
+            requestWrapper.addHeader(TOKEN_HEADER, String.format(AUTHORIZATION_FORMAT, token));
+
+            OAuth2AccessTokenCheckRespDTO resp = oauth2TokenApi.checkAccessToken(token);
+            Optional<LoginUser> optUser = Optional.ofNullable(resp)
+                    .map(
+                            t -> new LoginUser().setId(t.getUserId())
+                                    .setUserType(t.getUserType())
+                                    .setTenantId(t.getTenantId())
+                                    .setScopes(t.getScopes())
+                    );
+            if (optUser.isPresent()) {
+                // 2. 设置登录用户类型
+                WebFrameworkUtils.setLoginUserType(servletRequest, optUser.get().getUserType());
+                filterChain.doFilter(requestWrapper, servletResponse);
+                return;
+            }
+        }
+        filterChain.doFilter(servletRequest, servletResponse);
+    }
+
+    /**
+     * request 包装类,用于修改 head
+     *
+     * <a href="https://stackoverflow.com/questions/2811769/adding-an-http-header-to-the-request-in-a-servlet-filter">add request head</a>
+     */
+    public class HeaderMapRequestWrapper extends HttpServletRequestWrapper {
+        /**
+         * construct a wrapper for this request
+         *
+         * @param request
+         */
+        public HeaderMapRequestWrapper(HttpServletRequest request) {
+            super(request);
+        }
+
+        private Map<String, String> headerMap = new HashMap<String, String>();
+
+        /**
+         * add a header with given name and value
+         *
+         * @param name
+         * @param value
+         */
+        public void addHeader(String name, String value) {
+            headerMap.put(name, value);
+        }
+
+        @Override
+        public String getHeader(String name) {
+            String headerValue = super.getHeader(name);
+            if (headerMap.containsKey(name)) {
+                headerValue = headerMap.get(name);
+            }
+            return headerValue;
+        }
+
+        /**
+         * get the Header names
+         */
+        @Override
+        public Enumeration<String> getHeaderNames() {
+            List<String> names = Collections.list(super.getHeaderNames());
+            for (String name : headerMap.keySet()) {
+                names.add(name);
+            }
+            return Collections.enumeration(names);
+        }
+
+        @Override
+        public Enumeration<String> getHeaders(String name) {
+            List<String> values = Collections.list(super.getHeaders(name));
+            if (headerMap.containsKey(name)) {
+                values.add(headerMap.get(name));
+            }
+            return Collections.enumeration(values);
+        }
+
+    }
+
+}