@CurrentUser注解新配方

今天办公室有点冷

Posted by MatthewHan on 2019-10-29

背景

自定义@CurrentUser注解想实现当前已登录的用户对象在各层之间进行数据交互,在简书上有一篇比较出名的解决方法:通过自定义@CurrentUser获取当前登录用户

但是在安全框架Shiro中,通过webRequest.getAttribute("currentUser", RequestAttributes.SCOPE_REQUEST)却并不可行,👴也不⑧知道什么原因,也是按照该篇文章通过在登陆的业务中通过HttpServletRequest的request.setAttribute()方法存入需要的信息。

分析

通过对下面的一段覆写代码,可以看出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 增加方法注入,将含有 @CurrentUser 注解的方法参数注入当前登录用户
*/
public class CurrentUserMethodArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterType().isAssignableFrom(User.class)
&& parameter.hasParameterAnnotation(CurrentUser.class);
}

@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
User user = (User) webRequest.getAttribute("currentUser", RequestAttributes.SCOPE_REQUEST);
if (user != null) {
return user;
}
throw new MissingServletRequestPartException("currentUser");
}
}

绑定了该注解@CurrentUser的解析器是通过实现HandlerMethodArgumentResolver接口,然后通过webRequest对象获取之前在request作用域中的currentUser

那么这个NativeWebRequest是如何得到这个值的呢?我们打开它的源码,发现WebRequest是Spring框架提供的统一请求访问接口,不仅仅可以访问请求相关数据(如参数区数据、请求头数据,但访问不到Cookie区数据),还可以访问会话和上下文中的数据;NativeWebRequest继承了WebRequest,并提供访问本地Servlet API的方法。

1
2
3
4
5
6
7
8
public interface RequestAttributes {
int SCOPE_REQUEST = 0;
int SCOPE_SESSION = 1;
String REFERENCE_REQUEST = "request";
String REFERENCE_SESSION = "session";

@Nullable
Object getAttribute(String var1, int var2);

ServletRequestAttributes的方法则是getAttribute()的实现,通过对scope的不同来控制作用域。

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
private final HttpServletRequest request;
@Nullable
private volatile HttpSession session;
private final Map<String, Object> sessionAttributesToUpdate;

// ...

public Object getAttribute(String name, int scope) {
if (scope == 0) {
if (!this.isRequestActive()) {
throw new IllegalStateException("Cannot ask for request attribute - request is not active anymore!");
} else {
return this.request.getAttribute(name);
}
} else {
HttpSession session = this.getSession(false);
if (session != null) {
try {
Object value = session.getAttribute(name);
if (value != null) {
this.sessionAttributesToUpdate.put(name, value);
}

return value;
} catch (IllegalStateException var5) {
}
}

return null;
}
}
  • 当scope为RequestAttributes.SCOPE_REQUEST的时候getAttribute(name)方法会返回当前线程的HttpServletRequest的对象的getAttribute(name)的值。
  • 当scope为RequestAttributes.SCOPE_REQUEST时会把session对象的getAttribute(name)的value存入Map<String, Object> sessionAttributesToUpdate中。

既然我之前没有从HttpServletRequest作用域中得到我想要的结果,那么为什么不试试利用session呢。我们可以在登陆的业务中将当前已登录的用户的信息存入session中。

1
session.setAttribute("currentUser", currentUserDTO/token/id);

可以是当前登录对象的数据传输对象,也可以是token或者id。

1
2
3
4
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
return webRequest.getAttribute("currentUser", RequestAttributes.SCOPE_SESSION);
}