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

前言:

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

思路:

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

详细步骤:

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

String value() default "";

}
3. 定义一个MemberDetails.class,用于封装用户信息
1
2
3
4
5
6
7
8
9
10
11
12
13
public class MemberDetails {

private String memberId;

private String memberName;

private String memberNickname;

private String memberPhone;

private String memberEmail;

}
5. 定义一个会员接口类
1
2
3
4
5
6
7
8
9
10
/**
* @Author: XiongFeng
* @Description: 会员接口
* @Date: Created in 19:40 2018/4/10
*/
public interface MemberService {

/** 根据TokenId获取用户信息 */
MemberDto getMemberByToken(String token);
}
6. 定义一个会员接口实现类,在里面写上用户信息获取方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@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进去,并将我们需要添加的参数放进去,达到我们想要的效果。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
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传递出去。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
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,没有就不通过。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
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中
1
2
3
4
5
6
7
8
9
10
11
12
@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注解。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@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