接口的接入授权一般都有一套固定的模式,请求方通过对相关参数进行加密签名,接收方对接收到的参数信息进行同样的签名,并判断两个签名是否相同,以此来判断请求的合法性。
与授权有关的参数(一般包括请求时间,请求序号,请求接入id,请求签名等)可以和业务参数一起传递,也可以将授权相关参数通过请求头的方式传递。将授权相关参数通过请求头进行传递,并且通过interceptor和filter技术,在controller接收请求以前进行授权判断,这样controller就只需要处理正常的业务请求,使得业务处理更加简洁,不会和授权处理混在一起。
签名信息也包含业务信息,所以在进行签名验证的时候,需要读取request的请求体,但是对于post请求,请求体只能读取一次,第二次读取会出现异常,导致controller无法进行业务处理,错误信息类似如下:
getReader() has already been called for this request
此时需要在filter层,对请求信息进行自定义扩展处理,经过处理后的请求,才支持多次读取请求体信息。
- 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;
- }
- };
- }
- }
在包装类中,首先读取请求体的内容,并将内容保存到自定义的属性中,这样后续读取的时候,就从自定义的属性中读取,自定义的属性读取是没有次数限制的。
- 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;
- }
- }
进行过滤器注册,这样程序启动后,过滤器就会对请求进行处理。
- 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
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
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可以专注于业务处理,而不需要处理签名验签相关的内容。