sprintboot项目通过interceptor和filter实现接入授权控制

接口的接入授权一般都有一套固定的模式,请求方通过对相关参数进行加密签名,接收方对接收到的参数信息进行同样的签名,并判断两个签名是否相同,以此来判断请求的合法性。

与授权有关的参数(一般包括请求时间,请求序号,请求接入id,请求签名等)可以和业务参数一起传递,也可以将授权相关参数通过请求头的方式传递。将授权相关参数通过请求头进行传递,并且通过interceptor和filter技术,在controller接收请求以前进行授权判断,这样controller就只需要处理正常的业务请求,使得业务处理更加简洁,不会和授权处理混在一起。

签名信息也包含业务信息,所以在进行签名验证的时候,需要读取request的请求体,但是对于post请求,请求体只能读取一次,第二次读取会出现异常,导致controller无法进行业务处理,错误信息类似如下:

getReader() has already been called for this request

此时需要在filter层,对请求信息进行自定义扩展处理,经过处理后的请求,才支持多次读取请求体信息。

1. 自定义request的包装类

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;

public class MyRequestWrapper extends HttpServletRequestWrapper {

    private byte[] body;

    public MyRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);

        BufferedReader reader = request.getReader();
        try (StringWriter writer = new StringWriter()) {
            int read;
            char[] buf = new char[1024 * 8];
            while ((read = reader.read(buf)) != -1) {
                writer.write(buf, 0, read);
            }
            this.body = writer.getBuffer().toString().getBytes();
        }
    }

    public byte[] getBody() {
        return body;
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body);
        return new ServletInputStream() {

            @Override
            public int read() throws IOException {
                return byteArrayInputStream.read();
            }

            @Override
            public void setReadListener(ReadListener listener) {
            }

            @Override
            public boolean isReady() {
                return false;
            }

            @Override
            public boolean isFinished() {
                return false;
            }
        };
    }
}

在包装类中,首先读取请求体的内容,并将内容保存到自定义的属性中,这样后续读取的时候,就从自定义的属性中读取,自定义的属性读取是没有次数限制的。

2. 定义filter类,并注册filter

import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.framework.interceptor.MyRequestWrapper;
import org.springframework.http.HttpMethod;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;


public class AccessFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        //如果是POST走自己的继承的HttpServletRequestWrapper类请求,否则走正常的请求
        if(StringUtils.equalsIgnoreCase(HttpMethod.POST.name(), request.getMethod())){
            //一定要在判断中new对象,否则还会出现Stream closed问题
            filterChain.doFilter(new MyRequestWrapper(request),servletResponse);
        }else{
            filterChain.doFilter(servletRequest,servletResponse);
        }
    }
    @Override
    public void destroy() {
    }

}

可以看到,在filter中,将原始的ServletRequest 采用自定义的包装类进行包装之后,再传递到后续进行处理。

@Configuration
public class ResourcesConfig implements WebMvcConfigurer
{

    @Bean
    public FilterRegistrationBean httpServletRequestReplacedFilter() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(new AccessFilter());
        // /* 是全部的请求拦截,和Interceptor的拦截地址/**区别开
        registration.addUrlPatterns("/*");
        registration.setName("accessRequestFilter");
        registration.setOrder(1);
        return registration;
    }
}

进行过滤器注册,这样程序启动后,过滤器就会对请求进行处理。

3. 定义拦截器,并注册interceptor

import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.SignUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.operate.domain.PlatAccessPerm;
import com.ruoyi.operate.service.IPlatAccessPermService;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import org.apache.shiro.authz.AuthorizationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

@Component
public class AccessPermAuthInterceptor implements HandlerInterceptor {

    protected static Logger logger = LoggerFactory.getLogger(AccessPermAuthInterceptor.class);

    @Autowired
    private IPlatAccessPermService permService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        String clientSign = request.getHeader(SignUtils.SIGN_HEADER_KEY);
        String key = request.getHeader(SignUtils.ACCESS_KEY_HEADER_KEY);
        String id = request.getHeader(SignUtils.REQUEST_ID_HEADER_KEY);
        String time = request.getHeader(SignUtils.REQUEST_TIME_HEADER_KEY);

        if(StringUtils.isEmpty(clientSign) || StringUtils.isEmpty(key)
                || StringUtils.isEmpty(id) || StringUtils.isEmpty(time))
        {
            throw new AuthorizationException("请求信息无效");
        }

        PlatAccessPerm platAccessPerm = new PlatAccessPerm();

        platAccessPerm.setAccessKey(key);

        List<PlatAccessPerm> platAccessPerms = permService.selectPlatAccessPermList(platAccessPerm);

        if(platAccessPerms != null && platAccessPerms.size() > 0)
        {
            platAccessPerm = platAccessPerms.get(0);
        }
        else
        {
            response.addHeader(SignUtils.REQUEST_TIME_HEADER_KEY, DateUtils.getTime());
            response.addHeader(SignUtils.REQUEST_ID_HEADER_KEY, UUID.randomUUID().toString());

            throw new AuthorizationException("授权信息无效");

        }

        String accessSecret = platAccessPerm.getAccessSecret();
        
        String path = "/gateway/test/testdemo";

        Map<String, String> reqHeaders = new HashMap<>();
        reqHeaders.put(SignUtils.ACCESS_KEY_HEADER_KEY, key);
        reqHeaders.put(SignUtils.REQUEST_TIME_HEADER_KEY, time);
        reqHeaders.put(SignUtils.REQUEST_ID_HEADER_KEY, id);

        MyRequestWrapper myrequest = (MyRequestWrapper)request;

        byte[] reqBody = myrequest.getBody();


        String reqJson = "{\"userName\":\"test\"}";

        reqJson = new String(reqBody);


        Gson gson = new Gson();

        JsonObject userObject = gson.fromJson(reqJson, JsonObject.class);

        String sign = SignUtils.sign(key, accessSecret, path, userObject, reqHeaders);
        System.out.println("sign:" + sign);

        response.addHeader(SignUtils.REQUEST_TIME_HEADER_KEY, DateUtils.getTime());
        response.addHeader(SignUtils.REQUEST_ID_HEADER_KEY, UUID.randomUUID().toString());

        if(!sign.equals(clientSign))
        {
            throw new AuthorizationException("签名验证失败!");
        }

        return HandlerInterceptor.super.preHandle(request, response, handler);
    }
}

可以看到这里通过以下两行代码进行的请求体的读取:

MyRequestWrapper myrequest = (MyRequestWrapper)request;
byte[] reqBody = myrequest.getBody();

之所以能够读取,就是因为过滤器对请求进行了包装处理。

import com.ruoyi.framework.interceptor.AccessPermAuthInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import com.ruoyi.common.config.RuoYiConfig;
import com.ruoyi.common.constant.Constants;


@Configuration
public class ResourcesConfig implements WebMvcConfigurer
{
    @Autowired
    private AccessPermAuthInterceptor permAuthInterceptor;

    /**
     * 自定义拦截规则
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry)
    {
        registry.addInterceptor(permAuthInterceptor).addPathPatterns("/**");
    }
}

以上是拦截器注册代码。

经过以上的准备工作,可以实现在interceptor中对请求的签名进行验签,而在controller层,只会接收到签名验签通过的请求,所以controller可以专注于业务处理,而不需要处理签名验签相关的内容。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值