再谈Token认证,如何快速方便获取用户信息
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了再谈Token认证,如何快速方便获取用户信息相关的知识,希望对你有一定的参考价值。
前面我写了一篇《Token认证,如何快速方便获取用户信息》的文章,引起了各位读者的积极参与,除了文章中我提出的三种方式,各位读者大佬们也贡献了其他多种实现方式。
今天决定基于大家提供的思路再写一篇文章,主要是有读者留言说想要知道其他的实现方式,没办法,只能自己先研究下,然后分享出来,我就是这么宠读者,哈哈。
总结起来就是ThreadLocal,AOP,HandlerMethodArgumentResolver这三种方式,当然这些都是别人提供的方案,也许他们在实际工作中使用过,我本人是没接触过这块,但是我临时去实现了一下,不知道是不是跟各位留言中的实现一致,但是效果肯定是实现了的。
仅供大家参考,写的不好不要嘲笑我哈。
ThreadLocal
如果用ThreadLocal的话也挺简单的,在过滤器中解析Token之后将用户ID set 到ThreadLocal中,在Controller中get就可以获取到了,如下:
// 定义
public
static
ThreadLocal
<
Long
> loginUserThreadLocal =
new
ThreadLocal
<>();
// 设置
loginUserThreadLocal.
set
(userId);
// 获取
loginUserThreadLocal.
get
()
需要注意的是:如果你的Controller方法用了@HystrixCommand注解,意味着这个方法执行的线程就是hystrix的线程了,过滤器中是容器的线程,这个时候用ThreadLocal是获取不到值的,这就涉及到了一个跨线程传递的问题了,我之前也有写过类似的文章,用的是transmittable-thread-local这个框架来解决的。
文章可以参考这2篇:
http://cxytiandi.com/blog/detail/13331
http://cxytiandi.com/blog/detail/18782
AOP
还有一位朋友提到了ThreadLocal+AOP的方式,我想他的意思应该是从Filter中解析出用户ID, 然后存储到ThreadLocal中,在AOP中获取ThreadLocal中的用户ID, 然后注入到参数中,这样感觉整个操作流程都变长了。
我们还是按照这个思路来实现下吧:
我们直接在切面中对参数进行修改,最简单的方式是直接获取参数列表,然后修改,比如:
Object
[] args = joinPoint.getArgs();
args[
1
] = 用户ID;
return
joinPoint.proceed(args);
这段代码很明显不好,因为通过下标的方式去修改参数,也就意味着所有的接口方法都得将参数放在固定的位置,如下:
@GetMapping
(
"/article/callHello"
)
public
String
callHello(
String
name,
Long
userId) {
// userId 可以获取到值
}
正如前面有位朋友提到的,可以自定义注解来标识,除了普通的参数,还有实体类这种参数,所以自定义注解是一个比较好的方式。如果不自定义注解,那么就是基于约定的方式,约定好变量名也行,前面我们讲的都是基于约定来的。
我们基于约定好的变量名来讲解,反射获取方法名不是很方便,当jdk1.8中其实已经支持了,为了简化,我们可以用注解的方式来获取参数名称,当然这个注解你可以自定义,也可以用一些现成的,比如@RequestParam:
@GetMapping
(
"/article/callHello"
)
public
String
callHello(
String
name,
@RequestParam
(name=
"userId"
,required=
false
)
Long
userId) {
}
这样我们在切面中可以获取当前访问方法中的参数注解列表,然后获取到对应的名称进行匹配,再进行参数值的替换:
Object
[] args = joinPoint.getArgs();
Object
target = joinPoint.getTarget();
// 方法名
String
methodName = joinPoint.getSignature().getName();
Class
<?> clz = target.getClass();
Method
[] methods = clz.getDeclaredMethods();
for
(
Method
method : methods) {
// 匹配当前访问的方法
if
(methodName.equals(method.getName())) {
// 获取参数注解
Annotation
[][] parameterAnnotaions = method.getParameterAnnotations();
for
(
int
i =
0
; i < parameterAnnotaions.length; i++) {
Annotation
[] oneParameterAnnotaions = parameterAnnotaions[i];
for
(
int
j =
0
; j < oneParameterAnnotaions.length; j++) {
// 匹配注解
if
(oneParameterAnnotaions[j].annotationType() ==
RequestParam
.
class
) {
RequestParam
param = (
RequestParam
) oneParameterAnnotaions[j];
// 匹配参数名称
if
(param.name().equals(
"userId"
)) {
// 设置参数值
args[i] = 用户ID;
}
}
}
}
}
}
result = joinPoint.proceed(args);
这边需要注意的是我这边比对当前方法是直接通过方法名去对比的,会存在一个问题就是如果有相同名称的方法就会出问题,建议大家还是要加上参数的对比,获取直接根据class和方法名称和参数列表进行反射动态获取。这边只为了演示跟大家说明下,我还是建议用过滤器的方式实现,更简单点。
HandlerMethodArgumentResolver
SpringMVC提供了HandlerMethodArgumentResolver接口来处理我们的自定义参数的解析。 我们可以利用这个功能将用户登录的信息绑定到参数中。
最好的方式是单独加一个用户信息实体类,直接作为一个参数进行注入,使用也方便,首先我们定义一个参数类:
@Data
public
class
LoginUser
{
private
Long
userId;
}
然后定义一个注解,用来标识是否要注入用户参数信息:
@Target
({
ElementType
.PARAMETER})
@Retention
(
RetentionPolicy
.RUNTIME)
@Documented
public
@interface
LoginUserAnno
{
}
实现HandlerMethodArgumentResolver接口,自定义参数注入的逻辑:
public
class
LoginUserMethodArgumentResolver
implements
HandlerMethodArgumentResolver
{
@Override
public
boolean
supportsParameter(
MethodParameter
parameter) {
return
parameter.hasParameterAnnotation(
LoginUserAnno
.
class
);
}
@Override
public
Object
resolveArgument(
MethodParameter
parameter,
ModelAndViewContainer
mavContainer,
NativeWebRequest
webRequest,
WebDataBinderFactory
binderFactory)
throws
Exception
{
LoginUser
user =
new
LoginUser
();
user.setUserId(用户ID);
return
user;
}
}
配置HandlerMethodArgumentResolver:
@Configuration
public
class
Config
implements
WebMvcConfigurer
{
@Override
public
void
addArgumentResolvers(
List
<
HandlerMethodArgumentResolver
> argumentResolvers) {
argumentResolvers.add(
new
LoginUserMethodArgumentResolver
());
}
}
使用的话就很简单了,如下:
@PostMapping
(
"/add"
)
public
User
add(
@LoginUserAnno
LoginUser
loginUser,
User
user) {
return
user;
}
@GetMapping
(
"/article/callHello"
)
public
String
callHello(
String
name,
@LoginUserAnno
LoginUser
loginUser) {
}
loginUser会自动进行注入,然后就可以拿到我们想要的数据了,这个其实是属于参数注入这块的,在这边做验证显示不合适,验证还是得在过滤器中做,那么问题就是验证完后,拿到用户ID还得传递到HandlerMethodArgumentResolver中才可以完全注入的效果,我们可以用ThreadLocal传递,或者请求头,或者参数等方式都可以,因为在HandlerMethodArgumentResolver中可以获取到这些信息。
webRequest.getParameter(
"name"
);
webRequest.getHeader(
"xxx"
);
如果真要传递的话推荐下面的方式:
// Filter中
httpRequest.setAttribute(
"userId"
,
100
);
// HandlerMethodArgumentResolver中
webRequest.getAttribute(
"userId"
,
WebRequest
.SCOPE_REQUEST);
文章导到这里就全部结束了,讲解了这么多方式,我个人认为最优的还是在Filter中实现。
推荐理由:
验证和参数设置在一起,不用考虑传递问题
加入星球特权
1、从前端到后端玩转Spring Cloud
2、实战分库分表中间件Sharding-JDBC
3、实战分布式任务调度框架Elastic Job
4、配置中心Apollo实战
5、高并发解决方案之缓存
6、更多课程等你来解锁,20+课程
尹吉欢
我不差钱啊
喜欢作者
以上是关于再谈Token认证,如何快速方便获取用户信息的主要内容,如果未能解决你的问题,请参考以下文章