做api项目,各个接口都有对应的权限。所以使用过滤器对各个请求进行过滤。以下是学习的简单总结。
springboot中使用filter
在springboot项目中使用filter,需要注入bean:
@SpringBootApplication //等同于 @Configuration @EnableAutoConfiguration @ComponentScan
public class SpringbootFilterDemoApp {
public static void main(String[] args) {
SpringApplication.run(SpringbootFilterDemoApp.class, args);
}
@Bean
public FilterRegistrationBean filterRegistrationBean() {
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(new AuthorizationFilter()); // 自己的filter
List<String> urlPatterns = new ArrayList<>();
urlPatterns.add("/openapi/*");
registrationBean.setUrlPatterns(urlPatterns);
return registrationBean;
}
}
过滤器filter
以下是自己的filter类。继承Filter接口,对于满足条件的请求,使用filterChain.doFilter(request, response);将连接转发到目的地,不满足,则直接通过response写入错误信息。
/**
* 过滤器:校验接口访问权限
*/
public class AuthorizationFilter implements Filter{
private static final Logger logger = LoggerFactory.getLogger(AuthorizationFilter.class);
private CheckAuth checkAuth;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
// 将请求转换成HttpServletRequest 请求
HttpServletRequest req = (HttpServletRequest) servletRequest;
HttpServletResponse rsp = (HttpServletResponse) servletResponse;
// 取得接口URI
String currentURI = req.getRequestURI();
// 注入checkAuth对象
。。。
// 读取请求参数
。。。
if (requestWrapper == null) {
strError = "请求方式错误!";
} else {
if ("".equals(accessToken)) {
strError = "缺少必要的参数";
} else {
// 校验接口权限
if (checkAuth.checkAuth(accessToken, currentURI)) {
// Filter 只是链式处理,请求依然转发到目的地址。
filterChain.doFilter(requestWrapper, rsp);
return;
} else {
strError = "权限不足";
}
}
}
rsp.setCharacterEncoding("UTF-8");
rsp.setContentType("application/json; charset=utf-8");
String rspValue = ""; // 返回错误json信息
logger.info("返回:{}", rspValue);
rsp.getWriter().write(rspValue);
}
@Override
public void destroy() {
}
}
注意事项
filter中注入对象
filter的加载在servlet之前。所以在Filter中使用@Autowired注入对象CheckAuth肯定会失败。我们可以使用WebApplicationContextUtils来获取对象。(可参考:web.xml 中的listener、 filter、servlet 加载顺序及其详解)
// 注入checkAuth对象
ServletContext sc = req.getSession().getServletContext();
WebApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(sc);
if (context != null && context.getBean("checkAuth") != null && checkAuth == null) {
checkAuth = context.getBean(CheckAuth.class);
}
流的读取
读取请求参数:对于post请求,我们通过stream流进行读取,但是会发现,直接读取response流中的数据后,使用filterChain.doFilter(requestWrapper, rsp);请求,会报错:没有请求内容。
因为:
stream流的意思就是说当你读取之后就无法回到上一次读取的数据。所以说流是单向的。当第一个filter中读取流之后流已经被读完。所以无法继续读取。所以第二个filter中读不到数据。
为了防止流读取一次后就没有了, 所以需要将流继续写出去。
下面是对Request的封装。
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.Enumeration;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
/**
* httpServletRequst封装类
*/
public class HttpServletRequestWrapper extends
javax.servlet.http.HttpServletRequestWrapper {
private final byte[] body;
public HttpServletRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
body = HttpHelper.getBodyString(request).getBytes(Charset.forName("UTF-8"));
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream bais = new ByteArrayInputStream(body);
return new ServletInputStream() {
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener listener) {
}
@Override
public int read() throws IOException {
return bais.read();
}
};
}
@Override
public String getHeader(String name) {
return super.getHeader(name);
}
@Override
public Enumeration<String> getHeaderNames() {
return super.getHeaderNames();
}
@Override
public Enumeration<String> getHeaders(String name) {
return super.getHeaders(name);
}
}
request读取数据工具类
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import javax.servlet.ServletRequest;
public class HttpHelper {
/**
* 获取请求Body
*
* @param request
* @return
*/
public static String getBodyString(ServletRequest request) {
StringBuilder sb = new StringBuilder();
InputStream inputStream = null;
BufferedReader reader = null;
try {
inputStream = request.getInputStream();
reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return sb.toString();
}
}
获取请求参数
get、post获取请求参数的方式是不一样的。
if (req.getMethod().equals("POST")) {
requestWrapper = new HttpServletRequestWrapper(req);
String reqParamsStr = HttpHelper.getBodyString(requestWrapper);
if(reqParamsStr.length() >0) {
// 从json字符串获取参数
accessToken = "";
}
} else if (req.getMethod().equals("GET")) {
requestWrapper = req;
String[] tokens = req.getParameterValues("accessToken");
if (tokens != null && tokens.length > 0) {
accessToken = tokens[0];
}
}