再談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中實現。

推薦理由:

  • 驗證和參數設置在一塊兒,不用考慮傳遞問題

猿天地
相關文章
相關標籤/搜索