从@ResourceServer 注解进去可以看到导入了ResourceServerConfiguration这个类,
我们来看这个配置初始化了一些配置,后面会用到。
protected void configure(HttpSecurity http) throws Exception {
//新增一个ResourceServerSecurityConfigurer,这个是重点。
ResourceServerSecurityConfigurer resources = new ResourceServerSecurityConfigurer();
//如果配置了多个tokenService 则取第一个
ResourceServerTokenServices services = resolveTokenServices();
if (services != null) {
resources.tokenServices(services);
}
else {
if (tokenStore != null) {
resources.tokenStore(tokenStore);
}
else if (endpoints != null) {
resources.tokenStore(endpoints.getEndpointsConfigurer().getTokenStore());
}
}
if (eventPublisher != null) {
resources.eventPublisher(eventPublisher);
}
for (ResourceServerConfigurer configurer : configurers) {
configurer.configure(resources);
}
// @formatter:off
http.authenticationProvider(new AnonymousAuthenticationProvider("default"))
// N.B. exceptionHandling is duplicated in resources.configure() so that
// it works
.exceptionHandling()
.accessDeniedHandler(resources.getAccessDeniedHandler()).and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.csrf().disable();
// @formatter:on
http.apply(resources);
if (endpoints != null) {
// Assume we are in an Authorization Server
http.requestMatcher(new NotOAuthRequestMatcher(endpoints.oauth2EndpointHandlerMapping()));
}
for (ResourceServerConfigurer configurer : configurers) {
// Delegates can add authorizeRequests() here
configurer.configure(http);
}
if (configurers.isEmpty()) {
// Add anyRequest() last as a fall back. Spring Security would
// replace an existing anyRequest() matcher with this one, so to
// avoid that we only add it if the user hasn't configured anything.
http.authorizeRequests().anyRequest().authenticated();
}
}
ResourceServerSecurityConfigurer.configure
@Override
public void configure(HttpSecurity http) throws Exception {
//配置认证管理器
AuthenticationManager oauthAuthenticationManager = oauthAuthenticationManager(http);
//配置认证过滤器
resourcesServerFilter = new OAuth2AuthenticationProcessingFilter();
resourcesServerFilter.setAuthenticationEntryPoint(authenticationEntryPoint);
resourcesServerFilter.setAuthenticationManager(oauthAuthenticationManager);
if (eventPublisher != null) {
resourcesServerFilter.setAuthenticationEventPublisher(eventPublisher);
}
if (tokenExtractor != null) {
resourcesServerFilter.setTokenExtractor(tokenExtractor);
}
if (authenticationDetailsSource != null) {
resourcesServerFilter.setAuthenticationDetailsSource(authenticationDetailsSource);
}
resourcesServerFilter = postProcess(resourcesServerFilter);
resourcesServerFilter.setStateless(stateless);
// @formatter:off
http
.authorizeRequests().expressionHandler(expressionHandler)
.and()
.addFilterBefore(resourcesServerFilter, AbstractPreAuthenticatedProcessingFilter.class)
.exceptionHandling()
.accessDeniedHandler(accessDeniedHandler)
.authenticationEntryPoint(authenticationEntryPoint);
// @formatter:on
}
在ResourceServer中需要对token进行校验需要走如下过滤器,我们来分析下token校验的认证过程是什么样的
OAuth2AuthenticationProcessingFilter.doFilter
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException,
ServletException {
final boolean debug = logger.isDebugEnabled();
final HttpServletRequest request = (HttpServletRequest) req;
final HttpServletResponse response = (HttpServletResponse) res;
try {
//同request中提取认证信息,TokenExtractor的具体实现
//BearerTokenExtractor.extract方法中分为两种情况,一种是从头部
//获取token 即调用extractHeaderToken方法获取Authorization该属
//性,并判断是否以Bearer开头不区分大小写,头部获取不到token时将从
//请求参数中获取,请求参数中获取的key为access_token;获取到token之
//后将往请求中塞入如下属性request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_TYPE, OAuth2AccessToken.BEARER_TYPE);
//并返回token,然后交给PreAuthenticatedAuthenticationToken处理,
//最后将token塞给Authentication 对象中的principal,返回当前对象。
Authentication authentication = tokenExtractor.extract(request);
if (authentication == null) {
if (stateless && isAuthenticated()) {
if (debug) {
logger.debug("Clearing security context.");
}
SecurityContextHolder.clearContext();
}
if (debug) {
logger.debug("No token in request, will continue chain.");
}
}
else {
//获取到token之后将token值塞给request中的一个固定属性。
//并通过OAuth2AuthenticationDetails 类创建details;
//改对象做的事情是详细见Ⅰ返回一个 字符串对象塞给needsDetails
request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE, authentication.getPrincipal());
if (authentication instanceof AbstractAuthenticationToken) {
AbstractAuthenticationToken needsDetails = (AbstractAuthenticationToken) authentication;
needsDetails.setDetails(authenticationDetailsSource.buildDetails(request));
}
//这里进行认证校验,authenticationManager为ResourceServerSecurityConfigurer
//中进行设置的为oauthAuthenticationManager,其中tokenService也都在这里配置,这里为DefaultTokenServices
//内部调用tokenServices.loadAuthentication(token),该方法进行认证,tokenServices有好几个实现类,
//可以是RemoteTokenServices,也可以是UserInfoTokenServices,还可以是DefaultTokenServices。
//或者自定义实现。
//token校验流程可以参见Ⅱ
Authentication authResult = authenticationManager.authenticate(authentication);
if (debug) {
logger.debug("Authentication success: " + authResult);
}
//发布认证成功事件
eventPublisher.publishAuthenticationSuccess(authResult);
//认证成功就将解析,将信息交给SecurityContextHolder中
SecurityContextHolder.getContext().setAuthentication(authResult);
}
}
catch (OAuth2Exception failed) {
SecurityContextHolder.clearContext();
if (debug) {
logger.debug("Authentication request failed: " + failed);
}
eventPublisher.publishAuthenticationFailure(new BadCredentialsException(failed.getMessage(), failed),
new PreAuthenticatedAuthenticationToken("access-token", "N/A"));
authenticationEntryPoint.commence(request, response,
new InsufficientAuthenticationException(failed.getMessage(), failed));
return;
}
chain.doFilter(request, response);
}
Ⅰ、OAuth2AuthenticationDetails
public OAuth2AuthenticationDetails(HttpServletRequest request) {
//从请求中拿出token
this.tokenValue = (String) request.getAttribute(ACCESS_TOKEN_VALUE);
//获取token类型。这边为Bearer
this.tokenType = (String) request.getAttribute(ACCESS_TOKEN_TYPE);
//获取反问地址
this.remoteAddress = request.getRemoteAddr();
//获取session,如果为空返回null,如果不为null将返回sessionid并将以上元
//素拼接成一个字符串最后返回;remoteAddress=xxx,sessionId=<SESSION>,tokenType=xxxxxtokenValue=<TOKEN>
HttpSession session = request.getSession(false);
this.sessionId = (session != null) ? session.getId() : null;
StringBuilder builder = new StringBuilder();
if (remoteAddress!=null) {
builder.append("remoteAddress=").append(remoteAddress);
}
if (builder.length()>1) {
builder.append(", ");
}
if (sessionId!=null) {
builder.append("sessionId=<SESSION>");
if (builder.length()>1) {
builder.append(", ");
}
}
if (tokenType!=null) {
builder.append("tokenType=").append(this.tokenType);
}
if (tokenValue!=null) {
builder.append("tokenValue=<TOKEN>");
}
this.display = builder.toString();
}
//该对象重写了toString方法,即返回上面所生成的字符串
@Override
public String toString() {
return display;
}
Ⅱ、DefaultTokenServices.loadAuthentication解析
public OAuth2Authentication loadAuthentication(String accessTokenValue) throws AuthenticationException,
InvalidTokenException {
//读取token
OAuth2AccessToken accessToken = tokenStore.readAccessToken(accessTokenValue);
if (accessToken == null) {
throw new InvalidTokenException("Invalid access token: " + accessTokenValue);
}
//判断token是否过期
else if (accessToken.isExpired()) {
tokenStore.removeAccessToken(accessToken);
throw new InvalidTokenException("Access token expired: " + accessTokenValue);
}
//解析token。根据你设置的tokenStore进行解析。解析完成直接返回。
OAuth2Authentication result = tokenStore.readAuthentication(accessToken);
if (result == null) {
// in case of race condition
throw new InvalidTokenException("Invalid access token: " + accessTokenValue);
}
if (clientDetailsService != null) {
String clientId = result.getOAuth2Request().getClientId();
try {
clientDetailsService.loadClientByClientId(clientId);
}
catch (ClientRegistrationException e) {
throw new InvalidTokenException("Client not valid: " + clientId, e);
}
}
return result;
}