美文网首页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