当前位置:   article > 正文

SpringSecurity OAuth2过滤器源码解读。_retrieveuserauthtoken

retrieveuserauthtoken

一,client_id与security的验证以及获取token的全过程。

1,这里是配置client_id与security的代码。

2,当用户访问时,比如(/oauth/token)uri时,第一个拦截的filter是ClientCredentialsTokenEndpointFilter,而拦截的方法,在其父类AbstractAuthenticationProcessingFilter的doFilter方法中。

 3,requiresAuthentication(request,response)方法,该方法主要是进行uri路径的匹配,只有当访问的uri是/oauth/token时,才会返回true。

4,attemptAuthentication(request,response)方法是后续校验client_id等的逻辑,

从request从获取请求的参数client_id与secret的值。

SecurityContextHolder.getContext().getAuthentication()中获取Authenticatoin。

authentication不为null,表示之前已经有过成功的验证,那么直接返回authentication的信息。

否则,将clientId与secret封装到UserNamePasswordAuthenticationToken中。

5,this.getAuthenticationManager().authenticate后续接着验证clientId的逻辑,

6,这里调用的AuthenticationManager为ProviderManager

这里获取到的AuthenticationProvider有两个,一个为AnonymousAuthenticationProvider

一个为DaoAuthenticationProvider。

//这里的support逻辑很简单,就是判断一下authentication.getClass()与Provider支持的类类型是否一致或父子继承关系

然后只有DaoAuthenticationProvider是匹配的,

接着会调用provider.authenticate(authentication)方法。

DaoAuthenticationProvider类是重点。 

7,AbstractUserDetailsAuthenticationProvider是DaoAuthenticationProvider的父类。authenticate方法逻辑在父类中。

String username = this.determineUsername(authentication)

是其实是从authentication对象中获取client_id。

this.userCache.getUserFromCache(username)中获取UserDetails。默认userCache对象是不缓存对象。

接着往后执行。真正的校验逻辑在this.retrieveUser(username,authentication)方法中。

8,retrieveUser方法。

正在执行校验的逻辑其实就是在this.getUserDetailsService().loadUserByUsername(username);

这行代码当中。this.getUserDetailsService()就是获取DaoAuthenticationProvider中的

protected UserDetailsService getUserDetailsService() {
    return this.userDetailsService;
}这行代码。

获取对象中的UserDetailsService,这里的实际对象类是ClientDetailsUserDetailsService

9,ClientDetailsUserDetailsService,该类中有属性ClientDetailsService。该属性的值其实是一个

InMemoryClientDetailsService的类对象

10,InMemoryClientDetailsService,最终执行校验的逻辑就是在这里。

clientDetailsStore的值就是在一开始配置的配置类中配置的值。

这里根据接口传进来的client_id去clientDetailsStore这个map中找,如果找到了ClientDetais不为null,那么就说明这个client_id后台是能匹配到的,那么接着往后面执行校验secrity等逻辑。

如果为null那么直接就是失败。

11,当client_id是正确的以后,后续接着判断secret。将配置类中的secret获取以后,最终放到User对象中,并返回User对象。

12,将返回的User对象进行后续secret等进行校验。

13,this.preAuthenticationChecks.check(user)进行后续校验。

DefaultPreAuthenticationChecks是一个内部类,且实现了UserDetailsChecker.

对UserDetais的账号是否锁定,是否可用,是否过期等进行校验。 

14,additionalAuthenticationChecks方法校验,authentication.getCredentials()获取的其实是接口传进来的secret参数的值。

!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())

方法是最终进行匹配接口传进来的密钥的值与配置类中配置的密钥的值是否相等。

 15,clientid与secret验证成功后,代码返回到authenticate方法。

 this.createSuccessAuthentication(principalToReturn, authentication, user);方法返回一个Authentication对象。

16,createSuccessAuthentication方法。这里只要是将Authentication对象的authenticated的属性值设置为true。即重新创建一个认证成功的Authentication对象。

protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) {
    UsernamePasswordAuthenticationToken result = UsernamePasswordAuthenticationToken.authenticated(principal, authentication.getCredentials(), this.authoritiesMapper.mapAuthorities(user.getAuthorities()));
    result.setDetails(authentication.getDetails());
    this.logger.debug("Authenticated user");
    return result;
}底层中会调用super.setAuthenticated(true);

17, 返回到,ProviderManager中后续,主要是将secret删除掉,底层就是赋值为null。

if (result != null) { if (this.eraseCredentialsAfterAuthentication && result instanceof CredentialsContainer) { ((CredentialsContainer)result).eraseCredentials(); }

然后返回result。

18,最终返回doFilter, 默认this.sessionStrategy实例对象是

NullAuthenticatedSessionStrategy,这个类的onAuthentication是空的,即默认不将authentication放到session中。
this.sessionStrategy.onAuthentication(authenticationResult, request, response);

NullAuthenticatedSessionStrategy类

19,this.successfulAuthentication(request, response, chain, authenticationResult);方法。

首先

SecurityContext context = SecurityContextHolder.createEmptyContext();创建一个空的SecurityContext 

context.setAuthentication(authResult);将验证成功了clientid与secret的authentication放到context中。
SecurityContextHolder.setContext(context);然后将context放到SecurityContextHolder中,后续可以从SecurityContextHolder中获取authentication对象。

this.securityContextRepository.saveContext(context, request, response);默认这个securityContextRepository是NullSecurityContextRepository,也就是saveContext方法是空方法。
空方法。
public void saveContext(SecurityContext context, HttpServletRequest request, HttpServletResponse response) {
}

this.rememberMeServices.loginSuccess(request, response, authResult);也是new NullRememberMeServices();默认一样是空方法。

接着调用
this.successHandler.onAuthenticationSuccess(request, response, authResult);

20,this.successHandler.onAuthenticationSuccess(request, response, authResult);方法。

private AuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
ClientCredentialsTokenEndpointFilter类中 successHandler 是通过以下代码实例化的,也就是说
onAuthenticationSuccess方法其实是一个空方法。
this.setAuthenticationSuccessHandler(new AuthenticationSuccessHandler() {
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
    }
});

21,执行完super.successfulAuthentication(request, response, chain, authResult);之后接着调用

chain.doFilter(request, response);执行后面的过滤器。

这个方法就是

super.successfulAuthentication(request, response, chain, authResult);

22, chain.doFilter(request, response);执行后面的过滤器。目前执行完的是ClientCredentialsTokenEndpointFilter过滤器。

 chain.doFilter源码。就是通过currentPosition位置来一个一个的执行filter。所有filter放在additionalFilters中,通过get(currentPosition)来获取filter。然后执行nextFilter.doFilter就会执行对应的过滤器。

23,执行BasicAuthenticationFilter,该filter继承了OncePerRequestFilter,doFilter方法在OncePerRequestFilter中。

public class BasicAuthenticationFilter extends OncePerRequestFilter

24,BasicAuthenticationFilter的作用:

处理HTTP请求中的BASIC authorization头部,把认证结果写入SecurityContextHolder

当一个HTTP请求中包含一个名字为Authorization的头部,并且其值格式是Basic xxx时,该Filter会认为这是一个BASIC authorization头部,其中xxx部分应该是一个base64编码的{username}:{password}字符串。比如用户名/密码分别为 admin/secret, 则对应的该头部是 : Basic YWRtaW46c2VjcmV0 。

该过滤器会从 HTTP BASIC authorization头部解析出相应的用户名和密码然后调用AuthenticationManager进行认证,成功的话会把认证了的结果写入到SecurityContextHolderSecurityContext的属性authentication上面。同时还会做其他一些处理,比如Remember Me相关处理等等。

如果头部分析失败,该过滤器会抛出异常BadCredentialsException

如果认证失败,则会清除SecurityContextHolder中的SecurityContext。并且不再继续filter chain的执行。

25,FilterChainProxy的doFilter方法中,根据alreadFilteredAttributeName(当前要执行的filter)

从request.getAttribute(alreadFilteredAttributeName)中获取,如果为null表示该filter未执行过,如果不为null那么就要表示该filter重复执行了,报错处理。

如果为null,往后执行,先将该name通过setAttribute方法做个保留记号。然后接着去执行该filter的真正逻辑,也就是在this.doFilterInternal(httpReuqest,httpResponse,filterChain)中。

当前过滤器为BasciAuthenticationFilter

 

26,BasciAuthenticationFilter的dofilterInternal方法。

 

convert方法。将Header中的值封装到UsernamePasswordAuthenticationToken中,当前是请求是获取token故而这里必定返回null.

 

接着执行chain.foFilter方法。

 

后续过滤器,RquestCacheAwareFilter

RequestCacheAwareFilter:从session中获取SavedRequest,如果当前请求信息和SaveRequest信息一致(一般是登录成功后重定向),则返回SavedRequestAwareWrapper的HttpServletRequest包装类,这样的话,可以获取认证失败前的请求参数等信息

 

, SecurityContextHolderAwareRquestFilter 

SecurityContextHolderAwareRequestFilter

介绍

Spring Security TokenEndpoint中获取token的请求,有这样一个参数:Principal。 对于一个普通HttpServletRequest,是没有Principal参数类型的。SecurityContextHolderAwareRequestFilter通过HttpServletRequestFactory将HttpServletRequest请求包装成SecurityContextHolderAwareRequestWrapper,它实现了HttpServletRequest,并进行了扩展,添加一些额外的方法,比如:getPrincipal()方法等。这样就可以那些需要Principal等参数的Controller就可以接收到对应参数了。除了这个地方的应用,在其他地方,也可以直接调用request#getUserPrincipal()获取对应信息

 AnonymousAuthenticationFilter,

SessionManagementFilter,

27,AnonymousAuthenticationFilter,就相当于是给security创建了一个内部的匿名用户。

这里

SecurityContextHolder.getContext().getAuthentication() == null这个是不成立的,因为前面已经认证成功了client_id与secret。所以这里这个filter不起作用,直接
chain.doFilter(req, res);执行下一个过滤器。

 

28,SessionManageMentFilter过滤器

authentication != null && !this.trustResolver.isAnonymous(authentication)
authentication不为null并且不是AnonymouseAuthentication那么就执行
this.sessionAuthenticationStrategy.onAuthentication(authentication, request, response);

 

29.ExceptionTranslationFilter过滤器。暂时不看。

30.FilterSecurityInterceptor过滤器。(重点。)

oAuth2RequestValidator.validateScope(tokenRequest, authenticatedClient);

校验scope是否正常。tokenRequest是根据请求参数生成的对象,authenticatedClient是从内部获取的即配置类配置的信息封装而成。

 

return "refresh_token".equals(parameters.get("grant_type")) && parameters.get("refresh_token") != null;

校验是否为refresh_token请求。如果是的话,

刷新令牌有自己的默认作用域,因此我们应该忽略工厂在此处添加的任何作用域。

 

31,

getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);方法。

 

32,getTokenGranter()获取的TokenGranter对象的类是

AuthorizationServerEndpointsConfigurer的内部类。

 

getDefaultTokenGranters

 

根据
grantType 去查找匹配的Granter类,然后调用类对象的grant方法。生成
OAuth2AccessToken

 

grant方法中会匹配grantType。

 

最后匹配的granter是ResourceOwnerPasswordTokenGranter类。

ResourceOwnerPasswordTokenGranter的grant方法。

 

getOAuth2Authentication(client, tokenRequest)

Authentication userAuth = new UsernamePasswordAuthenticationToken(username, password);

将请求参数的用户名与密码生成Authentication对象。

 

33,验证用户名与密码也是在此处调用。最终会调用到自己实现的
UserDetailsService中。
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);

 

34,创建刷新token。 

35,创建token。

 

createAccessToken()方法

 

这里进行token的增强。如果存在accessTokenEnhancer.

return accessTokenEnhancer != null ? accessTokenEnhancer.enhance(token, authentication) : token;

这里的TokenEnhance是TokenEnhancerChain。调用enhance方法,遍历delegates中的TokenEnhancer,对OAuthen2AccessToken进行增强。其实就是token内容的添加。比如添加一些信息clientid,userId等信息进去。 

这里写的例子,其实就是将要添加到token的信息放到accessToken的additionalInformation属性中。这个属性是一个map。

 36,JwtAccessTokenConvert的enhance方法。

 

37,encode(result,authtication)

result.setValue(encode(result, authentication));

 

tokenConverter.convertAccessToken(accessToken, authentication)方法返回的是一个map集合,

其实就是将accessToken,authentication中的信息封装到一个map集合中。注意这里的jti其实前面的token。如图

 

 

前面的时间乘了1000,这里又除以1000.这样最终时间就跟配置的时间是一样的了。
response.put(EXP, token.getExpiration().getTime() / 1000);

 

content = objectMapper.formatMap(tokenConverter.convertAccessToken(accessToken, authentication));

最后content是一个json字符串。(map的属性以及值)

 

 

38, String token = JwtHelper.encode(content, signer).getEncoded();

 根据context(包含用户信息的json字符串)以及signer(密钥)生成token。

refresh_token的加密。

 

39,获取完  token并且封装到Oauth2AccessToken中。  OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken);

 

tokenStore.storeAccessToken(accessToken, authentication);将token信息存放起来。

而这里tokenStore是默认的JwtTokenStore,因为配置类中没有配置,故而这里不存放token信息,token另外在其他地方存储。同样的这里refreshToken也不做存储。 

DefaultOauth2AccessToken结构如下。

 

40,最终返回TokenEndpoint类,拿到Oauth2AccessToken。

 

getResponse方法如下,其实就是堆token对象的封装。 

到这里mall-auth获取token就结束了。

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/84040
推荐阅读
相关标签
  

闽ICP备14008679号