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

前面我写了一篇《Token认证,如何快速方便获取用户信息》的文章,引发了各位读者的积极参与,除了文章中我提出的三种方式,各位读者大佬们也贡献了其余多种实现方式。web

今天决定基于你们提供的思路再写一篇文章,主要是有读者留言说想要知道其余的实现方式,没办法,只能本身先研究下,而后分享出来,我就是这么宠读者,哈哈。bash

总结起来就是ThreadLocal,AOP,HandlerMethodArgumentResolver这三种方式,固然这些都是别人提供的方案,也许他们在实际工做中使用过,我本人是没接触过这块,可是我临时去实现了一下,不知道是否是跟各位留言中的实现一致,可是效果确定是实现了的。app

仅供你们参考,写的很差不要嘲笑我哈。框架

ThreadLocal

若是用ThreadLocal的话也挺简单的,在过滤器中解析Token以后将用户ID set 到ThreadLocal中,在Controller中get就能够获取到了,以下:ide

// 定义
public static ThreadLocal<Long> loginUserThreadLocal = new ThreadLocal<>();

// 设置
loginUserThreadLocal.set(userId);

// 获取
loginUserThreadLocal.get()
复制代码

须要注意的是:若是你的Controller方法用了@HystrixCommand注解,意味着这个方法执行的线程就是hystrix的线程了,过滤器中是容器的线程,这个时候用ThreadLocal是获取不到值的,这就涉及到了一个跨线程传递的问题了,我以前也有写过相似的文章,用的是transmittable-thread-local这个框架来解决的。ui

文章能够参考这2篇:spa

cxytiandi.com/blog/detail…线程

cxytiandi.com/blog/detail…3d

AOP

还有一位朋友提到了ThreadLocal+AOP的方式,我想他的意思应该是从Filter中解析出用户ID, 而后存储到ThreadLocal中,在AOP中获取ThreadLocal中的用户ID, 而后注入到参数中,这样感受整个操做流程都变长了。code

咱们仍是按照这个思路来实现下吧:

咱们直接在切面中对参数进行修改,最简单的方式是直接获取参数列表,而后修改,好比:

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中实现。

推荐理由:

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

猿天地
相关文章
相关标签/搜索