最近在做鉴权相关,采用JWT(JAVA WEB TOKEN)方案进行token验证。
于是想创建一个过滤器拦截token进行鉴权,如果鉴权失败则不再进行业务处理。
首先想到的就是 Filter
1. 如何使用基本Filter
- 定义一个类实现Filter接口,
@Component
@WebFilter(urlPatterns = "/api/v1.1/app/*", filterName = "authFilter")
//urlPatterns 是url拦截规则
public class AuthFilter implements Filter {
private static Logger logger = LoggerFactory.getLogger(AuthFilter.class);
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("wesker---------------------init");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) {
System.out.println("wesker------------------->doFilter");
}
@Override
public void destroy() {
System.out.println("wesker------------------->destroy");
}
}
- 还需要在启动类中加上@ServletComponentScan注解,否则会拦截规则会不生效,导致拦截所以
2. 拦截token获取post json类型请求参数
这里为什么指明获取post参数,是因为
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
String param = httpServletRequest.getParameter("param");
这种方式只能获取 POST 提交方式中的 Content-Type: application/x-www-form-urlencoded;
无法获取常用的application/json类型的参数
当然我们可以通过httpServletRequest.getInputStream();
来获取流,然后通过readLine形式取出Content-Type 为 application/x-www-form-urlencoded ,application/json , text/xml
等格式的数据。具体操作如下
StringBuffer sb = new StringBuffer() ;
BufferedReader reader = httpServletRequest.getReader();
String s = "" ;
while((s=reader.readLine())!=null){
sb.append(s) ;
}
String str =sb.toString();
问题来了
我们执行完这个操作后继续放行执行filterChain.doFilter(servletRequest, servletResponse);
会使应用崩溃,大致的错误是说miss request body啥的。原因就是:
当从请求中获取流然后readLine的时候,这个流就被“消耗”了,这会导致,chain.doFilter(request, res)这个链在传递 request对象的时候,里面的请求流为空,导致责任链模式下,其他下游的链无法获取请求的body,从而导致程序无法正常运行,这也使得我们的这个filter虽然可以获取请求信息,但是它会导致整个应用程序不可用,那么它也就失去了意义
解决方案
将取出来的字符串,再次转换成流,然后把它放入到新request 对象中,在chain.doFiler方法中 传递新的request对象
实现思路
继承HttpServletRequestWrapper,然后重写public BufferedReader getReader()方法,public ServletInputStream getInputStream()方法;(这两个方法的重写实现逻辑如下:getInputStream()方法中将body体中的字符串转换为字节流(它实质上返回的是一个ServletInputStream 对象);然后通过getReader()调用---->getInputStream()方法;),继承实现重写逻辑以后,在自定义分filter(VersionCheckFilter)中,使用自定义的HttpServletRequestWrapper(BodyReaderHttpServletRequestWrapper)将原始的HttpServletRequest对象进行再次封装;再通过BodyReaderHttpServletRequestWrapper对象去做dofilter(req,res)的req对象;
下面就直接放代码了
package com.eliteai.et8080.intercept.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.InputStreamReader;
import java.nio.charset.Charset;
import java.util.Enumeration;
/**
* 从Filter获取post参数时,当请求格式和application/json时,
* 当从请求中获取流以后,流被filter中的这个 inputStreamToString(InputStream in) 这个方法处理后就被“消耗”了,
* 这会导致,chain.doFilter(request, res)这个链在传递 request对象的时候,
* 里面的请求流为空,导致责任链模式下,其他下游的链无法获取请求的body,
* 从而导致程序无法正常运行,这也使得我们的这个filter虽然可以获取请求信息,
* 但是它会导致整个应用程序不可用,那么它也就失去了意义
* 所以需要将取出来的字符串,再次转换成流,然后把它放入到新request 对象中,
* 在chain.doFiler方法中 传递新的request对象;要实现这种思路,需要自定义一个类
* AuthHttpServletResquestWrapper
*
* @author MR.ZHANG
* @create 2018-08-30 10:49
*/
public class AuthHttpServletResquestWrapper extends HttpServletRequestWrapper {
private byte[] body = null;
public AuthHttpServletResquestWrapper(HttpServletRequest request) {
super(request);
System.out.println("-------------------------------------------------");
Enumeration<String> e = request.getHeaderNames();
while(e.hasMoreElements()){
String name = (String) e.nextElement();
String value = request.getHeader(name);
System.out.println(name+" = "+value);
}
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 readListener) {
}
@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);
}
}
HttpHelper 里面只有一个静态方法 用来获取body内容
package com.eliteai.et8080.intercept.filter;
import javax.servlet.ServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
/**
* @author MR.ZHANG
* @create 2018-08-30 10:54
*/
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();
}
}
AuthFilter 在doFilter里进行你的业务处理
package com.eliteai.et8080.intercept.filter;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.eliteai.et8080.BaseResult;
import com.eliteai.et8080.C;
import com.eliteai.et8080.JwtToken;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLDecoder;
import java.util.Map;
/**
* token filter
*
* @author MR.ZHANG
* @create 2018-08-29 13:39
*/
@Component
@WebFilter(urlPatterns = "/api/v1.1/app/*", filterName = "authFilter")
public class AuthFilter implements Filter {
private static Logger logger = LoggerFactory.getLogger(AuthFilter.class);
private static final String POST = "POST";
private static final String GET = "GET";
private static final String LOGIN = "login";
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("wesker--------------------->init");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) {
System.out.println("wesker------------------->doFilter");
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
String uri = httpServletRequest.getRequestURI();
BaseResult baseResult = new BaseResult();
if (uri.contains(LOGIN)) {
//登陆放行
try {
filterChain.doFilter(servletRequest, servletResponse);
} catch (Exception e) {
logger.error("filter 登陆放行doFilter失败" + e.getMessage());
}
} else {
String reqMethod = httpServletRequest.getMethod();
if (POST.equals(reqMethod)) {
PrintWriter out = null;
HttpServletResponse response = (HttpServletResponse) servletResponse;
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
ServletRequest requestWrapper = new AuthHttpServletResquestWrapper(httpServletRequest);
String body = HttpHelper.getBodyString(requestWrapper);
//如果是POST请求则需要获取 param 参数
String param = null;
try {
param = URLDecoder.decode(body, "utf-8");
} catch (UnsupportedEncodingException e) {
logger.error("fliter 解析POST请求时发生不支持的解码格式" + e.getMessage());
}
//json串 转换为Map
Map<String,Object> map = (Map)JSONObject.parse(param);
String token = (String) map.get("token");
if(JwtToken.verifyToken(token) != null) {
try {
filterChain.doFilter(requestWrapper, servletResponse);
} catch (Exception e) {
logger.error("filter token放行doFilter失败" + e.getMessage());
}
} else {
baseResult.setresultCode(C.ERR_APP_INVALID_TOKEN);
PrintWriter writer = null;
OutputStreamWriter osw = null;
try {
osw = new OutputStreamWriter(response.getOutputStream(),
"UTF-8");
} catch (IOException e) {
logger.error("获取OutputStreamm失败" + e.getMessage());
}
writer = new PrintWriter(osw, true);
String jsonStr = JSON.toJSONString(baseResult);
writer.write(jsonStr);
writer.flush();
writer.close();
try {
osw.close();
} catch (IOException e) {
logger.error("OutputStreamWriter 关闭失败 : " + e.getMessage());
}
}
} else {
//get请求直接放行
try {
filterChain.doFilter(servletRequest, servletResponse);
} catch (Exception e) {
logger.error("filter 放行doFilter失败 " + e.getMessage());
}
}
}
}
@Override
public void destroy() {
}
}
就这样吧,如有纰漏,还请指出!
参考文章 解决在Filter中读取Request中的流后,后续controller或restful接口中无法获取流的问题
网友评论