美文网首页Java后台
Spring Boot 使用Filter进行token鉴权

Spring Boot 使用Filter进行token鉴权

作者: wesker8080 | 来源:发表于2018-08-30 14:50 被阅读421次

最近在做鉴权相关,采用JWT(JAVA WEB TOKEN)方案进行token验证。
于是想创建一个过滤器拦截token进行鉴权,如果鉴权失败则不再进行业务处理。
首先想到的就是 Filter

1. 如何使用基本Filter

  1. 定义一个类实现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");
    }
}
  1. 还需要在启动类中加上@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-Typeapplication/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接口中无法获取流的问题

相关文章

网友评论

  • yemoumou:俱怀逸兴壮思飞,欲上青天览明月。-简书朋友你好,我是币圈一老友,我的写作方向是区块链和数字货币,初到简书,望多多关照。互粉互赞,已赞,期待您的回赞哦。-뒩

本文标题:Spring Boot 使用Filter进行token鉴权

本文链接:https://www.haomeiwen.com/subject/cvirwftx.html