Commit ee454ace9d87dbad631c1c2b0fdd8a5ab2e6869a

Authored by gaibu
1 parent 717dd1ab

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

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; @@ -2,8 +2,8 @@ package cn.iocoder.yudao.framework.common.enums;
2 2
3 /** 3 /**
4 * Web 过滤器顺序的枚举类,保证过滤器按照符合我们的预期 4 * Web 过滤器顺序的枚举类,保证过滤器按照符合我们的预期
5 - *  
6 - * 考虑到每个 starter 都需要用到该工具类,所以放到 common 模块下的 enums 包下 5 + * <p>
  6 + * 考虑到每个 starter 都需要用到该工具类,所以放到 common 模块下的 enums 包下
7 * 7 *
8 * @author 芋道源码 8 * @author 芋道源码
9 */ 9 */
@@ -17,7 +17,9 @@ public interface WebFilterOrderEnum { @@ -17,7 +17,9 @@ public interface WebFilterOrderEnum {
17 17
18 // OrderedRequestContextFilter 默认为 -105,用于国际化上下文等等 18 // OrderedRequestContextFilter 默认为 -105,用于国际化上下文等等
19 19
20 - int TENANT_CONTEXT_FILTER = - 104; // 需要保证在 ApiAccessLogFilter 前面 20 + int JM_TOKEN_FILTER = -105; // 需要保证在 Spring Security 过滤前面
  21 +
  22 + int TENANT_CONTEXT_FILTER = -104; // 需要保证在 ApiAccessLogFilter 前面
21 23
22 int API_ACCESS_LOG_FILTER = -103; // 需要保证在 RequestBodyCacheFilter 后面 24 int API_ACCESS_LOG_FILTER = -103; // 需要保证在 RequestBodyCacheFilter 后面
23 25
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 { @@ -20,8 +20,6 @@ public class SecurityProperties {
20 @NotEmpty(message = "Token Header 不能为空") 20 @NotEmpty(message = "Token Header 不能为空")
21 private String tokenHeader = "Authorization"; 21 private String tokenHeader = "Authorization";
22 22
23 - private String jmTokenHeader = "X-Access-Token";  
24 -  
25 /** 23 /**
26 * mock 模式的开关 24 * mock 模式的开关
27 */ 25 */
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; @@ -21,7 +21,6 @@ import javax.servlet.ServletException;
21 import javax.servlet.http.HttpServletRequest; 21 import javax.servlet.http.HttpServletRequest;
22 import javax.servlet.http.HttpServletResponse; 22 import javax.servlet.http.HttpServletResponse;
23 import java.io.IOException; 23 import java.io.IOException;
24 -import java.util.Optional;  
25 24
26 /** 25 /**
27 * Token 过滤器,验证 token 的有效性 26 * Token 过滤器,验证 token 的有效性
@@ -63,25 +62,6 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter { @@ -63,25 +62,6 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
63 return; 62 return;
64 } 63 }
65 } 64 }
66 - // 积木请求头  
67 - String jmTokenHeader = request.getHeader(securityProperties.getJmTokenHeader());  
68 - if (StrUtil.isNotEmpty(jmTokenHeader)) {  
69 - try {  
70 - OAuth2AccessTokenCheckRespDTO accessToken = oauth2TokenApi.checkAccessToken(jmTokenHeader);  
71 - Optional<LoginUser> optUser = Optional.ofNullable(accessToken)  
72 - .map(  
73 - t -> new LoginUser().setId(t.getUserId())  
74 - .setUserType(t.getUserType())  
75 - .setTenantId(t.getTenantId())  
76 - .setScopes(t.getScopes())  
77 - );  
78 - if (optUser.isPresent()) {  
79 - SecurityFrameworkUtils.setLoginUser(optUser.get(), request);  
80 - }  
81 - } catch (ServiceException ignored) {  
82 - // do nothing:如果报错,说明认证失败,忽略即可  
83 - }  
84 - }  
85 65
86 // 继续过滤链 66 // 继续过滤链
87 chain.doFilter(request, response); 67 chain.doFilter(request, response);
@@ -108,11 +88,11 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter { @@ -108,11 +88,11 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
108 88
109 /** 89 /**
110 * 模拟登录用户,方便日常开发调试 90 * 模拟登录用户,方便日常开发调试
111 - * <p> 91 + *
112 * 注意,在线上环境下,一定要关闭该功能!!! 92 * 注意,在线上环境下,一定要关闭该功能!!!
113 * 93 *
114 - * @param request 请求  
115 - * @param token 模拟的 token,格式为 {@link SecurityProperties#getMockSecret()} + 用户编号 94 + * @param request 请求
  95 + * @param token 模拟的 token,格式为 {@link SecurityProperties#getMockSecret()} + 用户编号
116 * @param userType 用户类型 96 * @param userType 用户类型
117 * @return 模拟的 LoginUser 97 * @return 模拟的 LoginUser
118 */ 98 */
yudao-module-visualization/yudao-module-visualization-biz/src/main/java/cn/iocoder/yudao/module/visualization/framework/jmreport/config/JmReportConfiguration.java
1 package cn.iocoder.yudao.module.visualization.framework.jmreport.config; 1 package cn.iocoder.yudao.module.visualization.framework.jmreport.config;
2 2
  3 +import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum;
3 import cn.iocoder.yudao.module.system.api.oauth2.OAuth2TokenApi; 4 import cn.iocoder.yudao.module.system.api.oauth2.OAuth2TokenApi;
4 import cn.iocoder.yudao.module.visualization.framework.jmreport.core.service.JmReportTokenServiceImpl; 5 import cn.iocoder.yudao.module.visualization.framework.jmreport.core.service.JmReportTokenServiceImpl;
  6 +import cn.iocoder.yudao.module.visualization.framework.jmreport.core.web.JmReportTokenFilter;
5 import org.jeecg.modules.jmreport.api.JmReportTokenServiceI; 7 import org.jeecg.modules.jmreport.api.JmReportTokenServiceI;
  8 +import org.springframework.boot.web.servlet.FilterRegistrationBean;
6 import org.springframework.context.annotation.Bean; 9 import org.springframework.context.annotation.Bean;
7 import org.springframework.context.annotation.ComponentScan; 10 import org.springframework.context.annotation.ComponentScan;
8 import org.springframework.context.annotation.Configuration; 11 import org.springframework.context.annotation.Configuration;
@@ -22,4 +25,12 @@ public class JmReportConfiguration { @@ -22,4 +25,12 @@ public class JmReportConfiguration {
22 return new JmReportTokenServiceImpl(oAuth2TokenApi); 25 return new JmReportTokenServiceImpl(oAuth2TokenApi);
23 } 26 }
24 27
  28 + @Bean
  29 + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
  30 + public FilterRegistrationBean<JmReportTokenFilter> registerMyAnotherFilter(OAuth2TokenApi oAuth2TokenApi){
  31 + FilterRegistrationBean<JmReportTokenFilter> bean = new FilterRegistrationBean<>();
  32 + bean.setOrder(WebFilterOrderEnum.JM_TOKEN_FILTER);
  33 + bean.setFilter(new JmReportTokenFilter(oAuth2TokenApi));
  34 + return bean;
  35 + }
25 } 36 }
yudao-module-visualization/yudao-module-visualization-biz/src/main/java/cn/iocoder/yudao/module/visualization/framework/jmreport/core/web/JmReportTokenFilter.java 0 → 100644
  1 +package cn.iocoder.yudao.module.visualization.framework.jmreport.core.web;
  2 +
  3 +import cn.hutool.core.util.StrUtil;
  4 +import cn.iocoder.yudao.framework.security.core.LoginUser;
  5 +import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
  6 +import cn.iocoder.yudao.module.system.api.oauth2.OAuth2TokenApi;
  7 +import cn.iocoder.yudao.module.system.api.oauth2.dto.OAuth2AccessTokenCheckRespDTO;
  8 +import lombok.RequiredArgsConstructor;
  9 +
  10 +import javax.servlet.Filter;
  11 +import javax.servlet.FilterChain;
  12 +import javax.servlet.ServletException;
  13 +import javax.servlet.ServletRequest;
  14 +import javax.servlet.ServletResponse;
  15 +import javax.servlet.http.HttpServletRequest;
  16 +import javax.servlet.http.HttpServletRequestWrapper;
  17 +import java.io.IOException;
  18 +import java.util.Collections;
  19 +import java.util.Enumeration;
  20 +import java.util.HashMap;
  21 +import java.util.List;
  22 +import java.util.Map;
  23 +import java.util.Optional;
  24 +
  25 +/**
  26 + * 积木报表 token 处理,将积木报表请求头中的 token 转换成 spring security 的 auth head
  27 + */
  28 +@RequiredArgsConstructor
  29 +public class JmReportTokenFilter implements Filter {
  30 + /**
  31 + * 积木 token 请求头
  32 + */
  33 + private static final String JM_TOKEN_HEADER = "X-Access-Token";
  34 + /**
  35 + * 系统内置请求头
  36 + */
  37 + private static final String TOKEN_HEADER = "Authorization";
  38 + /**
  39 + * auth 相关格式
  40 + */
  41 + private static final String AUTHORIZATION_FORMAT = "Bearer %s";
  42 +
  43 + private final OAuth2TokenApi oauth2TokenApi;
  44 +
  45 + @Override
  46 + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
  47 + // 积木请求头
  48 + HttpServletRequest req = (HttpServletRequest) servletRequest;
  49 + String token = req.getHeader(JM_TOKEN_HEADER);
  50 + if (StrUtil.isNotEmpty(token)) {
  51 + // 1. 增加请求头
  52 + HeaderMapRequestWrapper requestWrapper = new HeaderMapRequestWrapper(req);
  53 + requestWrapper.addHeader(TOKEN_HEADER, String.format(AUTHORIZATION_FORMAT, token));
  54 +
  55 + OAuth2AccessTokenCheckRespDTO resp = oauth2TokenApi.checkAccessToken(token);
  56 + Optional<LoginUser> optUser = Optional.ofNullable(resp)
  57 + .map(
  58 + t -> new LoginUser().setId(t.getUserId())
  59 + .setUserType(t.getUserType())
  60 + .setTenantId(t.getTenantId())
  61 + .setScopes(t.getScopes())
  62 + );
  63 + if (optUser.isPresent()) {
  64 + // 2. 设置登录用户类型
  65 + WebFrameworkUtils.setLoginUserType(servletRequest, optUser.get().getUserType());
  66 + filterChain.doFilter(requestWrapper, servletResponse);
  67 + return;
  68 + }
  69 + }
  70 + filterChain.doFilter(servletRequest, servletResponse);
  71 + }
  72 +
  73 + /**
  74 + * request 包装类,用于修改 head
  75 + *
  76 + * <a href="https://stackoverflow.com/questions/2811769/adding-an-http-header-to-the-request-in-a-servlet-filter">add request head</a>
  77 + */
  78 + public class HeaderMapRequestWrapper extends HttpServletRequestWrapper {
  79 + /**
  80 + * construct a wrapper for this request
  81 + *
  82 + * @param request
  83 + */
  84 + public HeaderMapRequestWrapper(HttpServletRequest request) {
  85 + super(request);
  86 + }
  87 +
  88 + private Map<String, String> headerMap = new HashMap<String, String>();
  89 +
  90 + /**
  91 + * add a header with given name and value
  92 + *
  93 + * @param name
  94 + * @param value
  95 + */
  96 + public void addHeader(String name, String value) {
  97 + headerMap.put(name, value);
  98 + }
  99 +
  100 + @Override
  101 + public String getHeader(String name) {
  102 + String headerValue = super.getHeader(name);
  103 + if (headerMap.containsKey(name)) {
  104 + headerValue = headerMap.get(name);
  105 + }
  106 + return headerValue;
  107 + }
  108 +
  109 + /**
  110 + * get the Header names
  111 + */
  112 + @Override
  113 + public Enumeration<String> getHeaderNames() {
  114 + List<String> names = Collections.list(super.getHeaderNames());
  115 + for (String name : headerMap.keySet()) {
  116 + names.add(name);
  117 + }
  118 + return Collections.enumeration(names);
  119 + }
  120 +
  121 + @Override
  122 + public Enumeration<String> getHeaders(String name) {
  123 + List<String> values = Collections.list(super.getHeaders(name));
  124 + if (headerMap.containsKey(name)) {
  125 + values.add(headerMap.get(name));
  126 + }
  127 + return Collections.enumeration(values);
  128 + }
  129 +
  130 + }
  131 +
  132 +}