番外:OpenFeign权限处理

2023-11-22 SpringSecurity

首先是内部处理时也许携带请求头,尤其是Token,毕竟其他服务可不知道你来自外部还是内部,除了Token外还可传递一些其他信息:

@Component
public class FeignRequestInterceptor implements RequestInterceptor {

    @Override
    public void apply(RequestTemplate requestTemplate) {
        HttpServletRequest request = SecurityUtils.getRequest();
        if (ObjUtil.isNotEmpty(request)) {
            // 传递用户信息请求头,防止丢失
            String userId = request.getHeader(TokenConstants.DETAILS_USER_ID);
            if (StrUtil.isNotEmpty(userId)) {
                requestTemplate.header(TokenConstants.DETAILS_USER_ID, userId);
            }
            String userName = request.getHeader(TokenConstants.DETAILS_USERNAME);
            if (StrUtil.isNotEmpty(userName)) {
                requestTemplate.header(TokenConstants.DETAILS_USERNAME, userName);
            }
            String authentication = request.getHeader(TokenConstants.AUTHENTICATION);
            if (StrUtil.isNotEmpty(authentication)) {
                requestTemplate.header(TokenConstants.AUTHENTICATION, authentication);
            } else {
                // 如果没有token,设置请求来源为内部请求,即匿名访问
                requestTemplate.header(TokenConstants.FROM_SOURCE, TokenConstants.INNER);
            }
            // 配置客户端IP
            requestTemplate.header("X-Forwarded-For", IpUtils.getIpAddr(request));
        }
    }
}

创建配置类使其生效:

@Configuration
public class FeignAutoConfiguration {
    @Bean
    public RequestInterceptor requestInterceptor() {
        return new FeignRequestInterceptor();
    }
}

一些全局变量:

public class TokenConstants {
    /** 令牌自定义标识  */
    public static final String AUTHENTICATION = "token";
    
    /** 请求来源 */
    public static final String FROM_SOURCE = "from-source";

    /** 内部请求 */
    public static final String INNER = "inner";
}

# 解决:内部调用不可匿名访问

首先,内部调用且未携带Token时都需要携带请求头,如上述代码高亮部分。

然后,Token过滤器需要新增逻辑,如果是内部请求并且补携带Token则直接放行。

@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // 0.如果请求是内部请求并且,则直接放行
        String source = request.getHeader(TokenConstants.FROM_SOURCE);
        if (StrUtil.isNotBlank(source) && source.equals(TokenConstants.INNER)) {
            // 设置一个空用户,绕过SpringSecurity权限
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(new LoginUser(new SysUser("root"), new ArrayList<>()), null, null);
            SecurityContextHolder.getContext().setAuthentication(authenticationToken);
            // 放行
            filterChain.doFilter(request, response);
            return;
        }
        
        // 此处省略,和之前相同
    }
}

# gateway设置拦截器

设置来源为内部,那么如果黑客将请求都设置为内部请求,岂不是带来安全隐患?因此在网管层设置过滤器,所有请求的请求来源均从头部删除。

@Component
public class AuthFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
         return chain.filter(exchange).doFirst(() -> {
                exchange.getResponse().beforeCommit(() -> Mono.fromRunnable(() -> {
                    HttpHeaders headers = exchange.getResponse().getHeaders();
                    headers.remove(TokenConstants.FROM_SOURCE);
                }));
        });
    }

    @Override
    public int getOrder() {
        return -200;
    }
}