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


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

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

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

    getReader() has already been called for this request

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

    1. 自定义request的包装类

    1. import javax.servlet.ReadListener;
    2. import javax.servlet.ServletInputStream;
    3. import javax.servlet.http.HttpServletRequest;
    4. import javax.servlet.http.HttpServletRequestWrapper;
    5. import java.io.BufferedReader;
    6. import java.io.ByteArrayInputStream;
    7. import java.io.IOException;
    8. import java.io.StringWriter;
    9. import java.nio.charset.StandardCharsets;
    10. public class MyRequestWrapper extends HttpServletRequestWrapper {
    11. private byte[] body;
    12. public MyRequestWrapper(HttpServletRequest request) throws IOException {
    13. super(request);
    14. BufferedReader reader = request.getReader();
    15. try (StringWriter writer = new StringWriter()) {
    16. int read;
    17. char[] buf = new char[1024 * 8];
    18. while ((read = reader.read(buf)) != -1) {
    19. writer.write(buf, 0, read);
    20. }
    21. this.body = writer.getBuffer().toString().getBytes();
    22. }
    23. }
    24. public byte[] getBody() {
    25. return body;
    26. }
    27. @Override
    28. public ServletInputStream getInputStream() throws IOException {
    29. ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body);
    30. return new ServletInputStream() {
    31. @Override
    32. public int read() throws IOException {
    33. return byteArrayInputStream.read();
    34. }
    35. @Override
    36. public void setReadListener(ReadListener listener) {
    37. }
    38. @Override
    39. public boolean isReady() {
    40. return false;
    41. }
    42. @Override
    43. public boolean isFinished() {
    44. return false;
    45. }
    46. };
    47. }
    48. }

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

    2. 定义filter类,并注册filter

    1. import com.ruoyi.common.utils.StringUtils;
    2. import com.ruoyi.framework.interceptor.MyRequestWrapper;
    3. import org.springframework.http.HttpMethod;
    4. import javax.servlet.Filter;
    5. import javax.servlet.FilterChain;
    6. import javax.servlet.FilterConfig;
    7. import javax.servlet.ServletException;
    8. import javax.servlet.ServletRequest;
    9. import javax.servlet.ServletResponse;
    10. import javax.servlet.http.HttpServletRequest;
    11. import java.io.IOException;
    12. public class AccessFilter implements Filter {
    13. @Override
    14. public void init(FilterConfig filterConfig) throws ServletException {
    15. }
    16. @Override
    17. public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    18. HttpServletRequest request = (HttpServletRequest) servletRequest;
    19. //如果是POST走自己的继承的HttpServletRequestWrapper类请求,否则走正常的请求
    20. if(StringUtils.equalsIgnoreCase(HttpMethod.POST.name(), request.getMethod())){
    21. //一定要在判断中new对象,否则还会出现Stream closed问题
    22. filterChain.doFilter(new MyRequestWrapper(request),servletResponse);
    23. }else{
    24. filterChain.doFilter(servletRequest,servletResponse);
    25. }
    26. }
    27. @Override
    28. public void destroy() {
    29. }
    30. }

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

    1. @Configuration
    2. public class ResourcesConfig implements WebMvcConfigurer
    3. {
    4. @Bean
    5. public FilterRegistrationBean httpServletRequestReplacedFilter() {
    6. FilterRegistrationBean registration = new FilterRegistrationBean();
    7. registration.setFilter(new AccessFilter());
    8. // /* 是全部的请求拦截,和Interceptor的拦截地址/**区别开
    9. registration.addUrlPatterns("/*");
    10. registration.setName("accessRequestFilter");
    11. registration.setOrder(1);
    12. return registration;
    13. }
    14. }

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

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

    1. import com.ruoyi.common.utils.DateUtils;
    2. import com.ruoyi.common.utils.SignUtils;
    3. import com.ruoyi.common.utils.StringUtils;
    4. import com.ruoyi.operate.domain.PlatAccessPerm;
    5. import com.ruoyi.operate.service.IPlatAccessPermService;
    6. import com.google.gson.Gson;
    7. import com.google.gson.JsonElement;
    8. import com.google.gson.JsonObject;
    9. import org.apache.shiro.authz.AuthorizationException;
    10. import org.slf4j.Logger;
    11. import org.slf4j.LoggerFactory;
    12. import org.springframework.beans.factory.annotation.Autowired;
    13. import org.springframework.stereotype.Component;
    14. import org.springframework.web.servlet.HandlerInterceptor;
    15. import javax.servlet.http.HttpServletRequest;
    16. import javax.servlet.http.HttpServletResponse;
    17. import java.io.BufferedReader;
    18. import java.util.HashMap;
    19. import java.util.List;
    20. import java.util.Map;
    21. import java.util.Set;
    22. import java.util.UUID;
    23. @Component
    24. public class AccessPermAuthInterceptor implements HandlerInterceptor {
    25. protected static Logger logger = LoggerFactory.getLogger(AccessPermAuthInterceptor.class);
    26. @Autowired
    27. private IPlatAccessPermService permService;
    28. @Override
    29. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    30. String clientSign = request.getHeader(SignUtils.SIGN_HEADER_KEY);
    31. String key = request.getHeader(SignUtils.ACCESS_KEY_HEADER_KEY);
    32. String id = request.getHeader(SignUtils.REQUEST_ID_HEADER_KEY);
    33. String time = request.getHeader(SignUtils.REQUEST_TIME_HEADER_KEY);
    34. if(StringUtils.isEmpty(clientSign) || StringUtils.isEmpty(key)
    35. || StringUtils.isEmpty(id) || StringUtils.isEmpty(time))
    36. {
    37. throw new AuthorizationException("请求信息无效");
    38. }
    39. PlatAccessPerm platAccessPerm = new PlatAccessPerm();
    40. platAccessPerm.setAccessKey(key);
    41. List platAccessPerms = permService.selectPlatAccessPermList(platAccessPerm);
    42. if(platAccessPerms != null && platAccessPerms.size() > 0)
    43. {
    44. platAccessPerm = platAccessPerms.get(0);
    45. }
    46. else
    47. {
    48. response.addHeader(SignUtils.REQUEST_TIME_HEADER_KEY, DateUtils.getTime());
    49. response.addHeader(SignUtils.REQUEST_ID_HEADER_KEY, UUID.randomUUID().toString());
    50. throw new AuthorizationException("授权信息无效");
    51. }
    52. String accessSecret = platAccessPerm.getAccessSecret();
    53. String path = "/gateway/test/testdemo";
    54. Map reqHeaders = new HashMap<>();
    55. reqHeaders.put(SignUtils.ACCESS_KEY_HEADER_KEY, key);
    56. reqHeaders.put(SignUtils.REQUEST_TIME_HEADER_KEY, time);
    57. reqHeaders.put(SignUtils.REQUEST_ID_HEADER_KEY, id);
    58. MyRequestWrapper myrequest = (MyRequestWrapper)request;
    59. byte[] reqBody = myrequest.getBody();
    60. String reqJson = "{\"userName\":\"test\"}";
    61. reqJson = new String(reqBody);
    62. Gson gson = new Gson();
    63. JsonObject userObject = gson.fromJson(reqJson, JsonObject.class);
    64. String sign = SignUtils.sign(key, accessSecret, path, userObject, reqHeaders);
    65. System.out.println("sign:" + sign);
    66. response.addHeader(SignUtils.REQUEST_TIME_HEADER_KEY, DateUtils.getTime());
    67. response.addHeader(SignUtils.REQUEST_ID_HEADER_KEY, UUID.randomUUID().toString());
    68. if(!sign.equals(clientSign))
    69. {
    70. throw new AuthorizationException("签名验证失败!");
    71. }
    72. return HandlerInterceptor.super.preHandle(request, response, handler);
    73. }
    74. }

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

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

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

    1. import com.ruoyi.framework.interceptor.AccessPermAuthInterceptor;
    2. import org.springframework.beans.factory.annotation.Autowired;
    3. import org.springframework.beans.factory.annotation.Value;
    4. import org.springframework.context.annotation.Configuration;
    5. import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
    6. import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    7. import com.ruoyi.common.config.RuoYiConfig;
    8. import com.ruoyi.common.constant.Constants;
    9. @Configuration
    10. public class ResourcesConfig implements WebMvcConfigurer
    11. {
    12. @Autowired
    13. private AccessPermAuthInterceptor permAuthInterceptor;
    14. /**
    15. * 自定义拦截规则
    16. */
    17. @Override
    18. public void addInterceptors(InterceptorRegistry registry)
    19. {
    20. registry.addInterceptor(permAuthInterceptor).addPathPatterns("/**");
    21. }
    22. }

    以上是拦截器注册代码。

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

  • 相关阅读:
    EdgeCloudSim官方Sample运行——Windows+IntelliJ IDEA+Matlab
    慧通编程第4关 - 魔法学院第6课
    [附源码]Python计算机毕业设计Django小型银行管理系统
    typeof null 为什么是object
    自动生成代码器推荐-code-gen
    跳转打开新窗口
    linux查找命令使用的正则表达式
    IntelliJ Idea 撤回git已经push的操作
    长尾预测效果不好怎么办?试试这两种思路
    支持 equals 相等的对象(可重复对象)作为 WeakHashMap 的 Key
  • 原文地址:https://blog.csdn.net/liaomingwu/article/details/126226975