利用Filter和拦截器,将用户信息动态传入Request方法

前言:

  • 在开发当中,经常会验证用户登录状态和获取用户信息。如果每次都手动调用用户信息查询接口,会非常的繁琐,而且代码冗余。为了提高开发效率,因此就有了今天这篇文章。

思路:

  • 用户请求我们的方法会携带一个Token,通过Filter过滤器将会员信息查出来并放到request请求参数中。接着在Cotroller层的请求方法中接收一个MemberDeatails类型的参数,就能直接获得会员信息了。

详细步骤:

1. Gradle引入需要的Jar包:
compile "com.fasterxml.jackson.core:jackson-databind:2.8.10"
2. 定义一个Login注解
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Login {

    String value() default "";

}

3. 定义一个MemberDetails.class,用于封装用户信息
public class MemberDetails {

    private String memberId;

    private String memberName;

    private String memberNickname;

    private String memberPhone;

    private String memberEmail;

}

5. 定义一个会员接口类
/**
 * @Author: XiongFeng
 * @Description: 会员接口
 * @Date: Created in 19:40 2018/4/10
 */
public interface MemberService {

    /** 根据TokenId获取用户信息 */
    MemberDto getMemberByToken(String token);
}
6. 定义一个会员接口实现类,在里面写上用户信息获取方法
@Service
public class MemberServiceImpl implements MemberService {

    @Override
    public MemberDetails getMemberDetailsByToken(String token) {
        if (StringUtils.isBlank(token)) return null;
        if (!"123".equals(token)) return null;
        MemberDetails memberDetails = new MemberDetails();
        memberDetails.setMemberId("123");
        memberDetails.setMemberName("哈哈123");
        memberDetails.setMemberEmail("seifon@seifon.cn");
        memberDetails.setMemberNickname("Seifon");
        memberDetails.setMemberPhone("13100001111");
        return memberDetails;
    }
}
7. 定义一个Request请求包装类
  • 通过继承HttpServletRequestWrapper类,重写它里面的多个方法,对前端传过来的参数进行重新封装。因为在Filter,虽然可以通过request.getParameterMap()拿到一个含有参数的map,但是不能直接对request里面东西进行修改操作,一旦重新修改,就会报错。后来我发现j2ee已经给我们提供了解决的办法,使用HttpServletRequestWrapper类来解决向request添加额外参数的功能。于是我对HttpServletRequest进行重新包装,在里面重新定义一个map,将以前的参数put进去,并将我们需要添加的参数放进去,达到我们想要的效果。
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Vector;

/**
 * @Author: XiongFeng
 * @Description: 对Request请求重新包装
 * @Date: Created in 11:17 2018/4/13
 */
public class ParameterRequestWrapper extends HttpServletRequestWrapper {
    
    private Map<String , String[]> params = new HashMap<String, String[]>();


    @SuppressWarnings("unchecked")
    public ParameterRequestWrapper(HttpServletRequest request) {
        // 将request交给父类,以便于调用对应方法的时候,将其输出,其实父亲类的实现方式和第一种new的方式类似
        super(request);
        //将参数表,赋予给当前的Map以便于持有request中的参数
        this.params.putAll(request.getParameterMap());
    }
    //重载一个构造方法
    public ParameterRequestWrapper(HttpServletRequest request , Map<String , Object> extendParams) {
        this(request);
        addAllParameters(extendParams);//这里将扩展参数写入参数表
    }

    /**
     * 复写获取key的方法
     */
    @Override
    public Enumeration getParameterNames() {
        Vector names = new Vector(params.keySet());
        return names.elements();
    }

    /**
     * 复写获取值value的方法
     */
    @Override
    public String getParameter(String name) {
        Object v = params.get(name);
        if (v == null) {
            return null;
        } else if (v instanceof String[]) {
            String[] strArr = (String[]) v;
            if (strArr.length > 0) {
                return strArr[0];
            } else {
                return null;
            }
        } else if (v instanceof String) {
            return (String) v;
        } else {
            return v.toString();
        }
    }

    @Override
    public String[] getParameterValues(String name) {
        Object v = params.get(name);
        if (v == null) {
            return null;
        } else if (v instanceof String[]) {
            return (String[]) v;
        } else if (v instanceof String) {
            return new String[] { (String) v };
        } else {
            return new String[] { v.toString() };
        }
    }

    public void addAllParameters(Map<String , Object>otherParams) {//增加多个参数
        for(Map.Entry<String , Object>entry : otherParams.entrySet()) {
            addParameter(entry.getKey() , entry.getValue());
        }
    }


    public void addParameter(String name , Object value) {//增加参数
        if(value != null) {
            if(value instanceof String[]) {
                params.put(name , (String[])value);
            }else if(value instanceof String) {
                params.put(name , new String[] {(String)value});
            }else {
                params.put(name , new String[] {String.valueOf(value)});
            }
        }
    }

    /** 简单封装,请根据需求改进 */
    public void addObject(Object obj) {
        Class<?> clazz = obj.getClass();
        Method[] methods = clazz.getMethods();
        try {
            for (Method method : methods) {
                if (!method.getName().startsWith("get")) {
                    continue;
                }
                Object invoke = method.invoke(obj);
                if (invoke == null || "".equals(invoke)) {
                    continue;
                }

                String filedName = method.getName().replace("get", "");
                filedName = WordUtils.uncapitalize(filedName);

                if (invoke instanceof Collection) {
                    Collection collections = (Collection) invoke;
                    if (collections != null && collections.size() > 0) {
                        String[] strings = (String[]) collections.toArray();
                        addParameter(filedName, strings);
                        return;
                    }
                }

                addParameter(filedName, invoke);
            }
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

}
8. 定义一个过滤器
  • 在这个过滤里面,主要校验Token是否有效以及将会员信息添加到request。首先,从Request请求头中拿到前端传过来的Token,并使用Token调用会员信息获取接口,得到用户的资料,然后将用户信息put到ParameterMap中,这个ParameterMap是我们通过ParameterRequestWrapper重新包装的一个map,因此可以在里面添加会员的参数,然后将新的request传递出去。
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * @Author: XiongFeng
 * @Description: 会员登录信息过滤器
 * @Date: Created in 11:17 2018/4/13
 */
@Component
@WebFilter(urlPatterns = "/*")
public class MemberFilter implements Filter {

    MemberService memberService = new MemberServiceImpl();

    ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

        HttpServletRequest req = (HttpServletRequest) request;

        String tokenId = req.getHeader("X-Authorization");

        if (tokenId == null || "".equals(tokenId) || tokenId.isEmpty()) {
            chain.doFilter(request, response);
            return;
        }

        MemberDetails memberDetails = memberService.getMemberDetailsByToken(tokenId);
        if (memberDetails == null) this.respFail(response);

        ParameterRequestWrapper requestWrapper = new ParameterRequestWrapper(req);
        requestWrapper.addObject(memberDetails);
        chain.doFilter(requestWrapper, response);
    }

    /** 返回失败结果Json数据 */
    private void respFail(ServletResponse response) throws IOException {
        Map<String, Object> map = new HashMap<>();
        map.put("status", 500);
        map.put("message", "登录失效,请登录");
        map.put("data", null);
        String s = objectMapper.writeValueAsString(map);
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        response.getWriter().write(s);
    }

    @Override
    public void destroy() {

    }
}
9. 定义一个SpringMVC拦截器
  • 在这个拦截器里面,主要验证Controller方法中是否需要MemberDetails和是否标了@Login注解。首先,从HandlerMethod中获取所有入参,看有没有需要MemberDetails参数,如果有,就从HttpServletRequest中拿memberId,如果不存在说明没有登录,存在就通过。然后HandlerMethod获取@Login注解,判断是否存在,如果存在,就看有没有memberId,没有就不通过。
package cn.seifon.paymodle.interceptor;

import cn.seifon.paymodle.annotations.Login;
import cn.seifon.paymodle.dto.MemberDetails;
import cn.seifon.paymodle.service.manager.member.MemberService;
import cn.seifon.paymodle.service.manager.member.impl.MemberServiceImpl;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.core.MethodParameter;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;

/**
 * @Author: XiongFeng
 * @Description: 会员登录信息拦截器
 * @Date: Created in 11:17 2018/4/13
 */
public class MemberInterceptor extends HandlerInterceptorAdapter {

    ObjectMapper objectMapper = new ObjectMapper();

    MemberService memberService = new MemberServiceImpl();

    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {

        HandlerMethod method = (HandlerMethod) handler;
        String[] memberIds = request.getParameterValues("memberId");

        MethodParameter[] methodParameters = method.getMethodParameters();
        //判断方法类是否有MemberDetails入参
        if (methodParameters.length > 0) {
            for (MethodParameter methodParameter : methodParameters) {
                Type genericParameterType = methodParameter.getGenericParameterType();
                String typeName = genericParameterType.getTypeName();
                if (!typeName.equals(MemberDetails.class.getTypeName())) continue;
                if (memberIds == null || memberIds.length <= 0) return this.respFail(response); //如果找不到用户信息就返回失败
                break;
            }
        }

        //判断是否有Login注解
        Login login = method.getMethodAnnotation(Login.class);
        if (login == null) return true;

        if (memberIds == null || memberIds.length <= 0) return this.respFail(response); //如果找不到用户信息就返回失败
        return true;
    }


    /** 返回失败结果Json数据 */
    private boolean respFail(HttpServletResponse response) throws IOException {
        Map<String, Object> map = new HashMap<>();
        map.put("status", 500);
        map.put("message", "登录失效,请登录");
        map.put("data", null);
        String s = objectMapper.writeValueAsString(map);
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        response.getWriter().write(s);
        return false;
    }

}

10. 将拦截器注册到WebMvcConfigurer中
@Configuration
public class MyWebAppConfigurer
        extends WebMvcConfigurerAdapter {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // addPathPatterns 用于添加拦截规则
        registry.addInterceptor(new MemberInterceptor()).addPathPatterns("/**");
        super.addInterceptors(registry);
    }

}
11. 定义会员Controller
  • 经过一个过滤器和一个拦截器,request请求终于来到了我们Controller层。这时候,我们只需要在方法里面写入MemberDetails memberDetails 就OK了,不用做任何操作,我们就可以获取会员信息了,是不是炒鸡方便!另外还可以在方法上标@Login注解。
@RestController
public class MemberController {


    @RequestMapping("/token")
    @Login
    public Map<String, Object> getUser(MemberDetails memberDetails) {
        //User user = userManager.selectByPrimaryKey(id);
        Map<String, Object> map = new LinkedHashMap<>();
        map.put("status", 200);
        map.put("message", "请求成功");
        map.put("data", memberDetails);
        return map;
    }
    
}

运行结果:

img
img

遇到的坑:

  • 当时我尝试过把会员参数放到session域中的Attribute,也尝试过在Model里setAttribute。后来发现这是行不通的,在filter中直接使用request.setAttribute()是无效的。放在Modle也是可行,但是Controller里面的方法需要加@ModelAttribute("...")才能得到用户信息,很不方便。唯有通过request.getParameterMap() put()进去,才是最方便的。

  • 一开始我没想到用过滤器,因此我就尝试在拦截器里,直接通过ParameterRequestWrapper对request包装,后来发现不管我怎么弄都不成功。当时非常绝望,后来想了想会不会是拦截器不支持重新包装request,于是我就通过filter去做,没想到成功了。这时,我想既然用到了filter,那干脆直接在filter里面获取@Login注解和获取方法参数得了,后来发现filter里面拿不到方法的信息,哭。后来想到一个办法,可以通过先filter,后拦截器。于是就成功了!

后记:

  • 这篇文章只是记录了我的一点小小经验,如果有什么不对的地方或者有更好的方法,请大家在评论里留言指正!

参考文章:http://www.importnew.com/19023.html

原文链接:http://www.seifon.cn/2018/04/21/利用Filter和拦截器,将用户信息动态传入Request方法/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,640评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,254评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,011评论 0 355
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,755评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,774评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,610评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,352评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,257评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,717评论 1 315
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,894评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,021评论 1 350
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,735评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,354评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,936评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,054评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,224评论 3 371
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,974评论 2 355

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,657评论 18 139
  • 监听器(listener) 监听器简介 :监听器就是一个实现特定接口的普通java程序,这个程序专门用于监听另一个...
    奋斗的老王阅读 2,511评论 0 53
  • 本文包括:1、Filter简介2、Filter是如何实现拦截的?3、Filter开发入门4、Filter的生命周期...
    廖少少阅读 7,273评论 3 56
  • gjcvbmnm
    ezizjan阅读 180评论 0 0
  • 复盘一周了,真是快。接触到这个平台我考虑了一周,才决定加入,因为怕自己坚持不下来。虽然质量不高,但也写了。...
    vivi_d4b8阅读 466评论 0 0