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