利用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;
}
}
运行结果:
遇到的坑:
当时我尝试过把会员参数放到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方法/
利用Filter和拦截器,将用户信息动态传入Request方法的更多相关文章
- 三种实现日志过滤器的方式 (过滤器 (Filter)、拦截器(Interceptors)和切面(Aspect))
1.建立RequestWrapper类 import com.g2.order.server.utils.HttpHelper; import java.io.BufferedReader; impo ...
- SpringBoot拦截器中无法注入bean的解决方法
SpringBoot拦截器中无法注入bean的解决方法 在使用springboot的拦截器时,有时候希望在拦截器中注入bean方便使用 但是如果直接注入会发现无法注入而报空指针异常 解决方法: 在注册 ...
- 过滤器(Filter)、拦截器(Interceptor)、监听器(Listener)
一.Filter 过滤器 1.简介 Filter也称之为过滤器,它是Servlet技术中最实用的技术,WEB开发人员通过Filter技术,对web服务器管理的所有web资源:例如Jsp, Servle ...
- 过滤器(Filter)和拦截器(Interceptor)
过滤器(Filter) Servlet中的过滤器Filter是实现了javax.servlet.Filter接口的服务器端程序.它依赖于servlet容器,在实现上,基于函数回调,它可以对几乎所有请求 ...
- Java Filter(拦截器)
多个Filter按照在配置文件中配置的filter顺序执行. 在web.xml文件中配置该Filter,使用init-param元素为该Filter配置参数,init-param可接受如下两个子元素: ...
- Spring filter和拦截器(Interceptor)的区别和执行顺序
转载自:http://listenup.iteye.com/blog/1559553 1.Filter过滤器只过滤jsp文件不过滤action请求解决方案 解决办法:在web.xml中将filter的 ...
- struts 用拦截器进行用户权限隔离,未登录用户跳到登录界面 *** 最爱那水货
一般,我们的web应用都是只有在用户登录之后才允许操作的,也就是说我们不允许非登录认证的用户直接访问某些页面或功能菜单项.对于个别页面来说,可能不需要进行拦截,此时,如果项目采用struts view ...
- 12、Filter(拦截器)
一.过滤器(Filter):又称拦截器.实现Filter接口的类我们称之为Filter(过滤器或拦截器),Filter可以对用户访问的资源进行拦截.例如:客户端发送请求是,先将请求拦截下来,判断用户是 ...
- springmvc拦截器实现用户登录权限验证
实现用户登录权限验证 先看一下我的项目的目录,我是在intellij idea 上开发的 1.先创建一个User类 package cn.lzc.po; public class User { pri ...
随机推荐
- Mysql官方文档翻译系列-7.3.1 Establishing a Backup Policy
原文链接 (https://dev.mysql.com/doc/refman/5.7/en/backup-policy.html) 正文 To be useful, backups must be s ...
- 高级控件 popwindow 与gridview的组合应用
Gridview 的布局设置 <GridView android:layout_width="wrap_content" android:layout_height=&quo ...
- python Josnp(跨域)
python Josnp(跨域) 所谓的跨域就是进行不用域名内的请求,好比如说我在A域名想访问B域名的内容就是一种跨域的行为. 但是在我们浏览器端会有一个同源策略的设置,这个同源策略只对Ajax请求有 ...
- ZOJ-1655 Transport Goods---dijkstra变形&&最长路
题目链接: https://vjudge.net/problem/ZOJ-1655 题目大意: 有N-1个城市给首都(第N个城市)支援物资,有M条路,走每条路要耗费一定百分比的物资.问给定N-1个城市 ...
- requests+正则爬取豆瓣图书
#requests+正则爬取豆瓣图书 import requests import re def get_html(url): headers = {'User-Agent':'Mozilla/5.0 ...
- winform 如何加载Url图像(图像)
解决方法: 1pictureBox1.Image = Image.FromStream(System.Net.WebRequest.Create(http://www.baidu/new.gif ). ...
- Spring Cloud学习笔记-005
服务消费者 之前已经搭建好了微服务中的核心组件——服务注册中心(包括单节点模式和高可用模式).也有了服务提供者,接下来搭建一个服务消费者,它主要完成两个目标,发现服务以及消费服务.其中,服务发现的任务 ...
- web攻击和防御措施
1.SQL注入:参照下面的链接 http://www.cnblogs.com/chenhaoyu/p/8758888.html 2.跨网站脚本攻击(Cross Site Scripting, XSS) ...
- 超级好用的前端开发测试Chrome插件-基于REST的Web服务客户端
基于REST的Web服务客户端是一款功能强大的谷歌浏览器插件,使用基于REST的Web服务客户端(模拟REST客户端)可以让用户使用谷歌浏览器模拟REST请求来测试REST风格. 基于REST的Web ...
- [Luogu 3674]小清新人渣的本愿
Description 题库链接 给你一个序列 \(A\) ,长度为 \(n\) ,有 \(m\) 次操作,每次询问一个区间是否可以 选出两个数它们的差为 \(x\) : 选出两个数它们的和为 \(x ...