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 2  
3 3 /**
4 4 * Web 过滤器顺序的枚举类,保证过滤器按照符合我们的预期
5   - *
6   - * 考虑到每个 starter 都需要用到该工具类,所以放到 common 模块下的 enums 包下
  5 + * <p>
  6 + * 考虑到每个 starter 都需要用到该工具类,所以放到 common 模块下的 enums 包下
7 7 *
8 8 * @author 芋道源码
9 9 */
... ... @@ -17,7 +17,9 @@ public interface WebFilterOrderEnum {
17 17  
18 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 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 20 @NotEmpty(message = "Token Header 不能为空")
21 21 private String tokenHeader = "Authorization";
22 22  
23   - private String jmTokenHeader = "X-Access-Token";
24   -
25 23 /**
26 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 21 import javax.servlet.http.HttpServletRequest;
22 22 import javax.servlet.http.HttpServletResponse;
23 23 import java.io.IOException;
24   -import java.util.Optional;
25 24  
26 25 /**
27 26 * Token 过滤器,验证 token 的有效性
... ... @@ -63,25 +62,6 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
63 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 67 chain.doFilter(request, response);
... ... @@ -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 96 * @param userType 用户类型
117 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 1 package cn.iocoder.yudao.module.visualization.framework.jmreport.config;
2 2  
  3 +import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum;
3 4 import cn.iocoder.yudao.module.system.api.oauth2.OAuth2TokenApi;
4 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 7 import org.jeecg.modules.jmreport.api.JmReportTokenServiceI;
  8 +import org.springframework.boot.web.servlet.FilterRegistrationBean;
6 9 import org.springframework.context.annotation.Bean;
7 10 import org.springframework.context.annotation.ComponentScan;
8 11 import org.springframework.context.annotation.Configuration;
... ... @@ -22,4 +25,12 @@ public class JmReportConfiguration {
22 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 +}
... ...