pigx登录代码走查
大体流程

6. /oauth/token 接收
-
因为发送的是 POST /oauth/token,所以会进入
org.springframework.security.oauth2.provider.endpoint.TokenEndpoint#postAccessToken -
在这个方法里面有个参数是
principal,根据调试可以发现,它的实际类型是UsernamePasswordAuthenticationToken。在这个方法里面还有个参数是parameters,它是一个map,里面存放了password、grant_type、username这三个字段 -
方法一开始判断
principal是否是org.springframework.security.core.Authentication的子类,不是的话抛出异常,告知用户增加一个合适的认证过滤器 -
principal里面实际存储的是clientId和clientSecret,所以方法从principal里面取出clientId,然后通过ClientDetailsService将数据库里面的Client详细信息获取出来 -
用parameters里面的
password、grant_type、username,以及刚才获取到的Client详细信息(clientId和scopes),构造出一个tokenRequest

-
然后方法中写了一个很奇怪的逻辑,将
clientId与tokenRequest.getClientId进行比较。这个比较有意义吗?难道还会不同?理解不了老外的想法 -
对第4步获取到的Client详细信息,将它与第5步获取到的
tokenRequest进行比较,比较scope是否合法。太奇怪了,tokenRequest不是从Client详细信息那里构造出来的吗?为啥还要判断scope是否合法?老外的想法太奇怪了 -
判断
grant_type是否是implicit,是的话抛出异常,告知用户/oauth/token不支持implicit方式登录 -
判断
grant_type是否是authorization_code(授权码模式),并且code是否不为空,是的话说明是授权码模式登录,我们用的是password(密码模式),所以这段逻辑先不看 -
判断
grant_type是否是refresh_token(刷新token模式),并且refresh_token是否不为空,是的话说明是刷新token模式,我们用的是password(密码模式),所以这段逻辑先不看 -
取出tokenGranter(我不知道这是啥东西),然后调用它的
grant方法,方法的参数是授权类型(也就是password)和第5步生成的tokenRequest -
grant方法里面,是调用代理对象delegate(我不太清楚这个是什么东西)的grant方法 -
代理对象是
CompositeTokenGranter类,它里面有个tokenGranters属性,该属性存储了五个TokenGranter

其中ResourceOwnerPasswordTokenGranter是我们所需要的TokenGranter -
在
org.springframework.security.oauth2.provider.token.AbstractTokenGranter#grant方法里面,首先会用clientDetailsService.loadClientByClientId取出ClientDetails,验证这个clientId是否有password(密码模式)认证类型,没有的话抛出异常 -
使用
ClientDetails和tokenRequest,调用getAccessToken方法 -
调用
getOAuth2Authentication方法,进入到org.springframework.security.oauth2.provider.password.ResourceOwnerPasswordTokenGranter#getOAuth2Authentication -
从
tokenRequest中取出username和password,新建出UsernamePasswordAuthenticationToken,再将tokenRequest里面的parameters Map表设置到该token的details属性里面 -
进入
org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter.AuthenticationManagerDelegator#authenticate。然后再进入到org.springframework.security.authentication.ProviderManager#authenticate -
在
ProviderManager#authenticate方法里面,有两个AuthenticationProvider,它们分别是AnonymousAuthenticationProvider和MobileAuthenticationProvider,恰好两个Provider的supports方法都表明UsernamePasswordAuthenticationToken不是它们继承的类,所以这些Provider的authenticate方法是不会进入的 -
如果上面两个都没有做过认证处理,那么再找父类,让父类来认证。父类也有
providers,它里面只有一个DaoAuthenticationProvider,这个AuthenticationProvider支持UsernamePasswordAuthenticationToken,所以调用DaoAuthenticationProvider的authenticate方法 -
从
UsernamePasswordAuthenticationToken取出username。通过username从userCache取出UserDetails,如果是null,则调用retrieveUser方法获取用户信息,在这个方法里面调用了loadUserByUsername -
对于返回的用户详情,首先进行预检查,依次检查是否被锁定、是否被禁用、是否账号过期。
-
然后再进入
additionalAuthenticationChecks,在这个方法里面,取出UsernamePasswordAuthenticationToken里面的密码原文,对比UserDetails里面的密码密文,使用passwordEncoder.matches进行密码校验 -
最后进入
org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider.DefaultPostAuthenticationChecks#check,检查密码是否过期 -
经过前面三步,都没有报错的话,说明用户输入的账号密码是正确的。将用户详情存入缓存。调用
createSuccessAuthentication -
在
createSuccessAuthentication方法里面,首先要判断密码是否要更新。我们在登录呢,不需要更新密码。然后进入父类的createSuccessAuthentication -
在父类的
createSuccessAuthentication里面,使用账号、密码、权限构造出一个token,再将认证里面的详情信息(也就是那个Map的信息)写入token的details里面,返回UsernamePasswordAuthenticationToken -
回到21步,擦除密码,将密码广播出去。返回上级AuthenticationProvider,也擦除一遍密码,返回认证信息
-
很多函数结束返回了,最终又返回到
org.springframework.security.oauth2.provider.password.ResourceOwnerPasswordTokenGranter#getOAuth2Authentication,调用getRequestFactory().createOAuth2Request(client, tokenRequest),移除掉map中的password,构造出modifiable,使用new OAuth2Request(modifiable, client.getClientId(), client.getAuthorities(), true, this.getScope(), client.getResourceIds(), null, null, null)生成OAuth2Request -
使用刚才生成的
OAuth2Request和28步返回的userAuth,构造出OAuth2Authentication -
返回上层,使用
tokenServices.createAccessToken创建token。进入到org.springframework.security.oauth2.provider.token.DefaultTokenServices#createAccessToken(org.springframework.security.oauth2.provider.OAuth2Authentication),调用tokenStore.getAccessToken -
在
tokenStore.getAccessToken里面,先调用authenticationKeyGenerator.extractKey生成一个字符串,在它前面加上auth_to_access:再调用serializeKey,获取redis连接从里面获取bytes,将bytes反序列化就是token了…… 我不看了,现在大概意思就是,先看看redis里面有没有token,有的话返回token;没有的话,就调用org.springframework.security.oauth2.provider.token.DefaultTokenServices#createAccessToken(org.springframework.security.oauth2.provider.OAuth2Authentication, org.springframework.security.oauth2.common.OAuth2RefreshToken)创建token,如果有token增强器就增强token,将token存储到tokenStore里面,返回token
