/*** 用户信息共享的ThreadLocal类*/
public class UserInfoShareHolder {private static final ThreadLocal USER_INFO_THREAD_LOCAL = new TransmittableThreadLocal<>();/*** 存储用户信息*/public static void setUserInfo(UserInfo userInfo) {USER_INFO_THREAD_LOCAL.set(userInfo);}/*** 获取用户相关信息*/public static UserInfo getUserInfo() {return USER_INFO_THREAD_LOCAL.get();}/*** 清除ThreadLocal信息*/public static void remove() {USER_INFO_THREAD_LOCAL.remove();}
}
/*** 拦截器:用户信息本地线程存储*/
public class UserInfoInterceptor extends HandlerInterceptorAdapter {/*** 拦截所有请求,在Controller层方法之前调用*/@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 判断用户是否被认证,如果没有认证不放行 boolean isAuthenticated = request.authenticate(response);if (!isAuthenticated) {return false;}// 存储用户信息到本地线程Principal userPrincipal = request.getUserPrincipal();OAuth2Authentication oAuth2Authentication = (OAuth2Authentication) userPrincipal;AuthUser ngsocUser = (AuthUser) oAuth2Authentication.getUserAuthentication().getPrincipal();UserInfo userInfo = ngsocUser.getUserInfo();UserInfoShareHolder.setUserInfo(userInfo);// 放行,继续执行Controller层的方法return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {UserInfoShareHolder.remove();super.afterCompletion(request, response, handler, ex);}
}
/*** 配置拦截器*/
@Configuration
@EnableWebMvc
public class CommonWebMvcAutoConfiguration implements WebMvcConfigurer {@Beanpublic UserInfoInterceptor userInfoInterceptor() {return new UserInfoInterceptor();}@Overridepublic void addInterceptors(@NonNull InterceptorRegistry registry) {// 注意:这里的excludePath不需要自己再加上contextPath, spring会自动加// 添加存储用户信息的拦截器,配置拦截请求路径// 拦截器会拦截所有请求,需要配置放行的请求registry.addInterceptor(userInfoInterceptor())// 放行的请求.excludePathPatterns("/api/v1/login");}
}
@RestController
@RequestMapping("/api/v1")
public class HelloController {@GetMapping("/hello")public String hello(HttpServletRequest request){String username = UserInfoShareHolder.getUserInfo().getUsername();return username;}
}


当一个认证用户访问系统的受限资源时,请求首先被OAuth2AuthenticationProcessingFilter过滤器拦截,在该过滤器的doFilter方法中主要做了以下事情:
Authentication authentication = tokenExtractor.extract(request);request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE, authentication.getPrincipal());Authentication authResult = authenticationManager.authenticate(authentication);eventPublisher.publishAuthenticationSuccess(authResult);SecurityContextHolder.getContext().setAuthentication(authResult);所以在请求一开始就把完成的认证对象的放在SecurityContextHolder.getContext()中,我们就可以从SecurityContextHolder.getContext()中获取Authentication对象了。

经过SpringSecurity的一系列过滤器链后,会进入UserInfoInterceptor#preHandle方法

public interface HttpServletRequest extends ServletRequest {Principal getUserPrincipal();
}

public class SecurityContextHolderAwareRequestWrapper extends HttpServletRequestWrapper {private Authentication getAuthentication() {//从SecurityContextHolder.getContext()中Authentication对象Authentication auth = SecurityContextHolder.getContext().getAuthentication();if (!trustResolver.isAnonymous(auth)) {return auth;}return null;}@Overridepublic Principal getUserPrincipal() {Authentication auth = getAuthentication();if ((auth == null) || (auth.getPrincipal() == null)) {return null;}return auth;}
}

@RestController
@RequestMapping("/api/v1")
public class HelloController {@GetMapping("/hello")public String hello(HttpServletRequest request){String username = UserInfoShareHolder.getUserInfo().getUsername();return username;}
}

假如我们在配置拦截器时,拦截所有请求,不放行/api/v1/login请求会如何?
/*** 配置拦截器*/
@Configuration
@EnableWebMvc
public class CommonWebMvcAutoConfiguration implements WebMvcConfigurer {@Beanpublic UserInfoInterceptor userInfoInterceptor() {return new UserInfoInterceptor();}@Overridepublic void addInterceptors(@NonNull InterceptorRegistry registry) {// 注意:这里的excludePath不需要自己再加上contextPath, spring会自动加// 拦截器会拦截所有请求,需要配置放行的请求registry.addInterceptor(userInfoInterceptor());}
}
注意:资源服务器配置类 ResourceServerAutoConfiguration 中已经配置了该请求接口不需要认证就能访问
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceServerAutoConfiguration extends ResourceServerConfigurerAdapter {@Autowiredprivate TokenStore tokenStore;@Value("${spring.application.name}")private String appName;@Overridepublic void configure(ResourceServerSecurityConfigurer resources) {resources.resourceId(appName);resources.tokenStore(tokenStore);}@Overridepublic void configure(HttpSecurity http) throws Exception {http.authorizeRequests()// 配置不需要认证就可以访问的请求.antMatchers("/api/v1/login").permitAll()// 其他请求必须认证才能访问.anyRequest().authenticated().and().csrf().disable();}
}




结论:资源服务器配置类 ResourceServerAutoConfiguration 中已经配置了该请求接口不需要认证就能访问,但是拦截器UserInfoInterceptor#preHandle 方法会在AuthController#authority方法执行之前执行,在该拦截器中从请求中获取Authentication对象判断用户是否认证,如果用户未认证则不放行,因此就不会执行AuthController#authority方法,所以拦截器必须配置不拦截的请求路径;
final class HttpServlet3RequestFactory implements HttpServletRequestFactory {@Overridepublic boolean authenticate(HttpServletResponse response)
throws IOException, ServletException {AuthenticationEntryPoint entryPoint = HttpServlet3RequestFactory.this.authenticationEntryPoint;if (entryPoint == null) {return super.authenticate(response);}if (isAuthenticated()) {return true;}// 异常处理entryPoint.commence(this, response,new AuthenticationCredentialsNotFoundException( "User is not Authenticated"));return false;}}
}

假如我们在配置拦截器时放行了/api/v1/login请求,但是资源服务器中没有放行/api/v1/login请求的认证会如何?
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceServerAutoConfiguration extends ResourceServerConfigurerAdapter {@Autowiredprivate TokenStore tokenStore;@Value("${spring.application.name}")private String appName;@Overridepublic void configure(ResourceServerSecurityConfigurer resources) {resources.resourceId(appName);resources.tokenStore(tokenStore);}@Overridepublic void configure(HttpSecurity http) throws Exception {http.authorizeRequests()// 所有请求必须认证才能访问.anyRequest().authenticated().and().csrf().disable();}
}

经过debug发现,请求首先会进入过滤器OAuth2AuthenticationProcessingFilter#doFilter方法,然后判断用户未登录,但是最终不会进入AuthController#authority方法。