当前位置:网站首页>sprintboot项目通过interceptor和filter实现接入授权控制
sprintboot项目通过interceptor和filter实现接入授权控制
2022-08-10 11:59:00 【程序猿(攻城狮)】
接口的接入授权一般都有一套固定的模式,请求方通过对相关参数进行加密签名,接收方对接收到的参数信息进行同样的签名,并判断两个签名是否相同,以此来判断请求的合法性。
与授权有关的参数(一般包括请求时间,请求序号,请求接入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可以专注于业务处理,而不需要处理签名验签相关的内容。
边栏推荐
- You have a Doubaqiong thesaurus, please check it
- 47Haproxy Cluster
- 动态规划之最长回文子串
- How to do foreign media publicity to grasp the key points
- LeetCode 61. Rotating linked list
- The author of open source also has a life problem
- LT8911EXB MIPI CSI/DSI转EDP信号转换
- 22年BATJ大厂必问面试题(复盘):JVM+微服务+多线程+锁+高并发
- 多线程下自旋锁设计基本思想
- search--01
猜你喜欢

可视化服务编排在金融APP中的实践

自定义过滤器和拦截器实现ThreadLocal线程封闭

7. Instant-ngp

Chapter 5 virtual memory

Solve the idea that unit tests cannot use Scanner

【论文+代码】PEBAL/Pixel-wise Energy-biased Abstention Learning for Anomaly Segmentation on Complex Urban Driving Scenes(复杂城市驾驶场景异常分割的像素级能量偏置弃权学习)

一文读懂NFT数字藏品为何风靡全球?

如何让别人看不懂你的 JS 代码?把你当大佬!

LT8911EXB MIPI CSI/DSI转EDP信号转换

制品库是什么?
随机推荐
IM即时通讯开发WebSocket从入门到精通
自定义过滤器和拦截器实现ThreadLocal线程封闭
Data Analysis of Time Series (5): Simple Prediction Method
7. Instant-ngp
LeetCode 21. Merge two ordered linked lists
一文读懂NFT数字藏品为何风靡全球?
Cannot find symbol log because lombok is not found
How to do foreign media publicity to grasp the key points
ASP.NET Core依赖注入系统学习教程:ServiceDescriptor(服务注册描述类型)
CV复习:空洞卷积
What are some useful performance testing tools recommended? Performance testing report charging standards
CURRENT_TIMESTAMP(6) 函数是否存在问题?
The author of open source also has a life problem
16、Pytorch Lightning入门
讯飞创意组别 全国选拔赛成绩公布说明
An enhanced dynamic packet buffer management.论文核心部分
技术人必看!数据治理是什么?它对数据中台建设重要吗?
Highways「建议收藏」
多线程下自旋锁设计基本思想
LT8911EXB MIPI CSI/DSI to EDP signal conversion