前面我写了一篇《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中实现。

推荐理由:

  • 验证和参数设置在一起,不用考虑传递问题

再谈Token认证,如何快速方便获取用户信息的更多相关文章

  1. Token认证,如何快速方便获取用户信息

    背景 我们有一个Web项目,这个项目提供了很多的Rest API.也做了权限控制,访问API的请求必须要带上事先认证后获取的Token才可以. 认证的话就在Filter中进行的,会获取请求的Token ...

  2. 从SpringMVC获取用户信息谈起

    Github地址:https://github.com/andyslin/spring-ext 编译.运行环境:JDK 8 + Maven 3 + IDEA + Lombok spring-boot: ...

  3. 微信快速开发框架(八)-- V2.3--增加语音识别及网页获取用户信息,代码已更新至Github

    不知不觉,版本以每周更新一次的脚步进行着,接下来应该是重构我的代码及框架的结构,有朋友反应代码有点乱,确实如此,当时写的时候只是按照订阅号来写的,后来才慢慢增加到支持API接口.目前还在开发第三方微信 ...

  4. .NET微信开发通过Access Token和OpenID获取用户信息

    本文介绍如何获得微信公众平台关注用户的基本信息,包括昵称.头像.性别.国家.省份.城市.语言. 本文的方法将囊括订阅号和服务号以及自定义菜单各种场景,无论是否有高级接口权限,都有办法来获得用户基本信息 ...

  5. Spring Cloud云架构 - SSO单点登录之OAuth2.0 根据token获取用户信息(4)

    上一篇我根据框架中OAuth2.0的使用总结,画了SSO单点登录之OAuth2.0 登出流程,今天我们看一下根据用户token获取yoghurt信息的流程: /** * 根据token获取用户信息 * ...

  6. 整合spring cloud云架构 - 根据token获取用户信息

    根据用户token获取yoghurt信息的流程: /** * 根据token获取用户信息 * @param accessToken * @return * @throws Exception */ @ ...

  7. 微信第三方登陆,无需注册一键登录,获取用户信息,PHP实现方法

    今天讲讲利用微信oauth2实现第三方登陆的实现方法. 先说说前提吧! 首先你得是服务号,并且是经过认证的.这样微信会给你很多第三方接口的权限,如果是订阅号或者没有认证的服务号那就不用想了! 一开始你 ...

  8. Laravel OAuth2 (一) ---简单获取用户信息

    前言 本来要求是使用微信进行第三方登陆,所以想着先用 github 测试成功再用微信测试,可是最近拖了好久都还没申请好微信开放平台的 AppID ,所以就只写 github 的第三方登陆吧,估计微信的 ...

  9. JAVA获取微信小程序openid和获取公众号openid,以及通过openid获取用户信息

    一,首先说明下这个微信的openid 为了识别用户,每个用户针对每个公众号会产生一个安全的OpenID,如果需要在多公众号.移动应用之间做用户共通,则需前往微信开放平台,将这些公众号和应用绑定到一个开 ...

随机推荐

  1. 新安装ubuntu系统的简单优化

    新安装的ubuntu系统,需要做下简单的优化,使其符合常用习惯,优化过程的命令与centos大都不一致,撰文备份,以备所需: 1.获取ubuntu系统root权限 在终端输入sudo passwd r ...

  2. IT兄弟连 Java语法教程 数据类型2

    整型 Java定义了4种整数类型:byte.short.int和long.所有这些类型都是有符号的.正或负的整数.Java不支持无符号的.只是正值的整数.许多其它计算机语言同时支持有符号和无符号整数. ...

  3. 【Oracle命令】sql语句之排序(order by)

    通过对数据库数据进行降序排序来达到显示最新数据在前面的效果 -- 降序排序(最新的显示在前面) SELECT * FROM 表名 t ORDER BY t.uploadDatetime DESC; 格 ...

  4. npm install 安装依赖报错

    npm ERR! Unexpected end of JSON input while parsing near '...2.4.8","karma":"~0. ...

  5. pixijs shader fade 从左到有右淡入 从下到上淡入效果

    pixijs shader fade 从左到有右淡入     从下到上淡入效果 const app = new PIXI.Application({ transparent: true }); doc ...

  6. Visual Studio 2019 16.1 使用 .NET Core 3.0

    一.前言 早在很久之前微软便公布 .NET Core 3.0 将支持开发Winform应用程序等等新特性,现如今 .NET Core 3.0 预览版已经出来第五个预览版了,从 .NET Core 2. ...

  7. 简单的计时器 (倒计时)--html Demo

    <!DOCTYPE html> <html> <head> <meta http-equiv="Content-type" content ...

  8. Hive_hdfs导入csv文件

    转自:Hive_hdfs csv导入hive demo   1 create csv file.student.csv 4,Rose,M,78,77,76 5,Mike,F,99,98,98 2 pu ...

  9. go-面向对象编程(下)

    面向对象编程思想-抽象 抽象的介绍 我们在前面去定义一个结构体时候,实际上就是把一类事物的共有的 属性( 字段)和 行为( 方法)提取 出来,形成一个 物理模型(结构体).这种研究问题的方法称为抽象 ...

  10. Spring Boot 中如何定制 Banner

    本人免费整理了Java高级资料,涵盖了Java.Redis.MongoDB.MySQL.Zookeeper.Spring Cloud.Dubbo高并发分布式等教程,一共30G,需要自己领取.传送门:h ...