決定放棄 JWT 了!
JWT相信大家都有所了解,一種無(wú)狀態(tài)的認(rèn)證方式,因?yàn)镴WT本身就能存儲(chǔ)一些非敏感的身份信息,這種方式目前也被廣泛使用,在陳某之前的Spring Cloud Gateway整合Spring Security OAuth2中使用的就是JWT。
但是JWT雖好,使用過(guò)程中還是要依賴緩存,比如退出登錄,JWT唯一的失效途徑就是等待過(guò)期時(shí)間失效,因此在退出登錄時(shí)必須借助外力Redis才能達(dá)到效果。這個(gè)在之前的文章中也有介紹。
既然都要用Redis,為什么不采用Redis+Spring Security+OAuth2的認(rèn)證方式呢?這種方式也是企業(yè)中經(jīng)常采用的方案。
今天就介紹一下碼猿慢病云管理系統(tǒng)中是如何將利用Redis和Spring Security 整合實(shí)現(xiàn)分布式統(tǒng)一認(rèn)證登錄的。
在學(xué)習(xí)這節(jié)內(nèi)容之前先要了解Spring Security OAuth2 各種授權(quán)模式,在知識(shí)星球中《精盡Spring Cloud Alibaba》專欄有詳細(xì)的介紹和案例代碼演示,有需要的先去學(xué)習(xí)。
一、實(shí)現(xiàn)的效果
既然是直接使用Redis+Spring Security,身份信息肯定是存儲(chǔ)在Redis中且token也不是JWT生成的令牌,如下圖:
圖片
可以看到令牌和刷新令牌以及身份信息都存儲(chǔ)在Redis中。其中9d22b664-8540-48d1-98ed-4df1ce90b74f就是生成的令牌,無(wú)任何特殊含義,只是隨機(jī)生成的UUID,相較于JWT短小了很多。
二、登錄的客戶端有哪些?
碼猿慢病云管理系統(tǒng)中需要登錄的客戶端如下:
- WEB端
- PDA端
- PAD端
- 患者端
- 小程序
今天先來(lái)介紹前三種,后面的兩種后文介紹。
1. WEB端
登錄頁(yè)面如下:
web端登錄
三個(gè)參數(shù):
- 用戶名
- 密碼
- 醫(yī)院ID
請(qǐng)求的報(bào)文如下:
POST /auth/oauth2/token?grant_type=password&scope=server HTTP/1.1
Host: codeape-gateway:9999
Authorization: Basic dGVzdDp0ZXN0
Content-Type: application/x-www-form-urlencoded
Content-Length: 32
username=admin&password=YehdBPev&hosId=1659018792143663105
因?yàn)槭嵌嘧鈶舻哪J?,所以在登錄中做了醫(yī)院的選擇,這點(diǎn)也是對(duì)代碼改造的一部分,下文介紹如何改造。
2. PDA端
PDA是護(hù)士的手持設(shè)備,用于采集數(shù)據(jù),因此也是需要認(rèn)證才能上傳、查看數(shù)據(jù)。
PDA端登錄只需要護(hù)士輸入如下兩個(gè)參數(shù):
- 用戶名
- 密碼
為什么呢?不需要選擇醫(yī)院?jiǎn)幔?/p>
前面的文章中也有介紹過(guò),PDA這種手持設(shè)備只有在平臺(tái)上錄入了才能使用,錄入的地方:設(shè)備管理->設(shè)備列表->新增
圖片
設(shè)備SN號(hào)是設(shè)備的唯一識(shí)別號(hào),在設(shè)備取得注冊(cè)證書后頒發(fā)的,所以可以作為唯一識(shí)別標(biāo)志。
這里就是根據(jù)根據(jù)SN號(hào)去唯一關(guān)聯(lián)這臺(tái)設(shè)備,這也就是為什么PDA登錄不用選擇醫(yī)院的原因。
PDA在發(fā)出登錄請(qǐng)求時(shí)只需要攜帶這個(gè)SN號(hào),請(qǐng)求報(bào)文如下:
POST /auth/oauth2/token?grant_type=password&scope=server HTTP/1.1
Host: codeape-gateway:9999
Authorization: Basic dGVzdDp0ZXN0
Content-Type: application/x-www-form-urlencoded
Content-Length: 32
username=admin&password=YehdBPev&sn=3981293B102
3. PAD端
平板一般是醫(yī)生查房時(shí)作為移動(dòng)端使用,住院醫(yī)生每天都需要去病房查看病人病情,需要結(jié)合測(cè)量的數(shù)據(jù)才能了解患者的病情,因此PAD也是需要醫(yī)生認(rèn)證登錄。
PAD端登錄其實(shí)有兩種方案:
- 和WEB端相同,選擇醫(yī)院登錄
- 通過(guò)設(shè)備MAC地址綁定登錄
碼猿慢病云管理系統(tǒng)采用的第一種方案,需要選擇醫(yī)院,請(qǐng)求報(bào)文如下:
POST /auth/oauth2/token?grant_type=password&scope=server HTTP/1.1
Host: codeape-gateway:9999
Authorization: Basic dGVzdDp0ZXN0
Content-Type: application/x-www-form-urlencoded
Content-Length: 32
username=admin&password=YehdBPev&hosId=1659018792143663105
三、 密碼模式登錄
上面介紹的WEB端、PDA端、PAD端都是基于密碼模式改造的,在介紹認(rèn)證流程之前需要將登錄接口給導(dǎo)入接口工具,這里使用的是Apifox,下載下方密碼模式腳本,直接導(dǎo)入Apifox。
導(dǎo)入成功后,你將會(huì)得到一個(gè)接口,如下圖:
圖片
點(diǎn)擊運(yùn)行,發(fā)出請(qǐng)求登錄,返回的信息如下圖:
圖片
上述返回信息幾個(gè)比較重要的屬性如下:
1. access_token
這個(gè)則是認(rèn)證成功生成token,后續(xù)請(qǐng)求資源時(shí)只需要攜帶這個(gè)token則能通過(guò)認(rèn)證
PS:這里的token似乎很短小,其實(shí)并不是JWT生成token,而是UUID。
2. refresh_token
這個(gè)是token過(guò)期后的刷新令牌,當(dāng)token過(guò)期后則拿著這個(gè)refresh_token即可重新獲取新的access_token,無(wú)需再次認(rèn)證登錄
3. user_info
這部分是當(dāng)前用戶登錄成功后返回一些個(gè)人信息,比如權(quán)限、醫(yī)院ID、所屬的科室/病區(qū)ID等,詳細(xì)信息如下圖:
- username:用戶名
- authorities:權(quán)限
- id:主鍵ID
- deptId:科室/病區(qū)ID
- hosId:醫(yī)院ID
- deptAuths:科室/病區(qū)權(quán)限
- roleCodes:角色編碼
- phone:手機(jī)號(hào)
- clientId:客戶端ID
- sn:登錄的PDA的SN號(hào)
- name:姓名
圖片
4. scope
對(duì)應(yīng)的資源的權(quán)限
四、密碼模式登錄字段加密
密碼模式的登錄有兩個(gè)點(diǎn)比較重要,以WEB端登錄報(bào)文為例:
POST /auth/oauth2/token?grant_type=password&scope=server HTTP/1.1
Host: codeape-gateway:9999
Authorization: Basic dGVzdDp0ZXN0
Content-Type: application/x-www-form-urlencoded
Content-Length: 32
username=admin&password=YehdBPev&hosId=1659018792143663105
從上面的報(bào)文可以看到有兩處進(jìn)行了加密,如下:
- Authorization:這里是對(duì)client_id:client_secret,這里采用的是base64編碼,比如WEB端的原始Authorization為:Basic web:web
- password:這里也對(duì)密碼進(jìn)行了AES加密處理
五、服務(wù)端認(rèn)證的流程
先上一張整體的流程圖,如下:
圖片
按照Apifox的密碼模式登錄接口發(fā)出登錄請(qǐng)求后,將會(huì)按照上方的流程圖逐一處理,流程解析如下:
1. 網(wǎng)關(guān)前置處理
網(wǎng)關(guān)的前置處理分為兩個(gè)部分:
- 驗(yàn)證碼校驗(yàn)
- 密碼解密
這兩個(gè)功能都是使用過(guò)濾器處理的,在網(wǎng)關(guān)的配置文件中可以看到對(duì)認(rèn)證中心codeape-auth配置了兩個(gè)過(guò)濾器,如下:
圖片
關(guān)于網(wǎng)關(guān)的過(guò)濾器不理解的請(qǐng)看知識(shí)星球中《精盡Spring Cloud Alibaba》專欄網(wǎng)關(guān)的部分。
1)驗(yàn)證碼校驗(yàn)
在前面文章中介紹了碼猿慢病云管理系統(tǒng)中是對(duì)WEB端、PDA端、PAD端將驗(yàn)證碼關(guān)閉的,但是對(duì)于院外患者端,比如患者APP端還是需要驗(yàn)證碼的。
驗(yàn)證碼對(duì)應(yīng)的代碼在com.code.ape.codeape.gateway.filter.ValidateCodeGatewayFilter中,里面的邏輯在前文介紹過(guò),這里就不再詳細(xì)說(shuō)了,有一行代碼需要注意一下,代碼如下:
//解析請(qǐng)求頭中的ClientId,和配置文件configProperties中的比較,忽略不需要校驗(yàn)clientId
boolean isIgnoreClient =configProperties.getIgnoreClients().contains(WebUtils.getClientId(request));
為什么需要注意呢?
上文說(shuō)過(guò),客戶端ID和客戶端秘鑰是放在Authorization中經(jīng)過(guò)base64編碼后發(fā)送給服務(wù)端,因此后端取client_id是不是也要經(jīng)過(guò)解碼,WebUtils.getClientId(request)這個(gè)方法就是對(duì)Authorization解碼獲取client_id,代碼如下:
/**
* 從request 獲取CLIENT_ID
* com.code.ape.codeape.common.core.util.WebUtils#getClientId
*/
@SneakyThrows
public String getClientId(ServerHttpRequest request) {
String header = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
return splitClient(header)[0];
}
/**
* 對(duì)請(qǐng)求頭中的Authorization拆分且解碼
* com.code.ape.codeape.common.core.util.WebUtils#splitClient
*/
@NotNull
private static String[] splitClient(String header) {
if (header == null || !header.startsWith(BASIC_)) {
throw new CheckedException("請(qǐng)求頭中client信息為空");
}
byte[] base64Token = header.substring(6).getBytes(StandardCharsets.UTF_8);
byte[] decoded;
try {
//解碼
decoded = Base64.decode(base64Token);
}
catch (IllegalArgumentException e) {
throw new CheckedException("Failed to decode basic authentication token");
}
String token = new String(decoded, StandardCharsets.UTF_8);
int delim = token.indexOf(":");
if (delim == -1) {
throw new CheckedException("Invalid basic authentication token");
}
return new String[] { token.substring(0, delim), token.substring(delim + 1) };
}
2) 密碼解密
密碼解密對(duì)應(yīng)的過(guò)濾器:com.code.ape.codeape.gateway.filter.PasswordDecoderFilter,邏輯很簡(jiǎn)單:
- 校驗(yàn)是否是登錄請(qǐng)求
- 校驗(yàn)授權(quán)類型,如果是刷新令牌則直接放行
- 解密
代碼很簡(jiǎn)單,注釋很清楚,這里就不再詳細(xì)貼出來(lái)了。
注意:客戶端和服務(wù)端的加密因子需要保持一致才能正確加解密。
2. OAuth2ClientAuthenticationFilter
這個(gè)過(guò)濾器的作用是用于 OAuth2 的客戶端身份驗(yàn)證,主要用于處理客戶端使用客戶端憑證(client credentials)訪問(wèn)受保護(hù)資源的情況。
整體的邏輯如下圖:
代碼①
這個(gè)很好理解,只有登錄請(qǐng)求/oauth2/token才會(huì)校驗(yàn)客戶端信息,其他的請(qǐng)求直接放行
代碼②
這行代碼是將請(qǐng)求頭中客戶端信息提取出來(lái)轉(zhuǎn)換為Authentication客戶端認(rèn)證對(duì)象,這里用到了認(rèn)證轉(zhuǎn)換器AuthenticationConverter,在該過(guò)濾器構(gòu)造時(shí)默認(rèn)傳入了四個(gè),如下圖:
this.authenticationConverter.convert(request)該方法調(diào)用的是DelegatingAuthenticationConverter#convert方法,內(nèi)部是循環(huán)調(diào)用上述的四個(gè)才轉(zhuǎn)換器,如下:
上述四個(gè)認(rèn)證轉(zhuǎn)換器比較重要的是其中兩個(gè):
1. ClientSecretBasicAuthenticationConverter
這個(gè)是處理將客戶端信息存放在請(qǐng)求頭中轉(zhuǎn)換器,在內(nèi)部對(duì)請(qǐng)求頭中的客戶端信息進(jìn)行base64解碼,具體的代碼邏輯如下:
這個(gè)轉(zhuǎn)換器正好是碼猿慢病云管理系統(tǒng)中的請(qǐng)求方式相匹配,因此走的則是這個(gè)邏輯。
2. ClientSecretPostAuthenticationConverter
這個(gè)轉(zhuǎn)換器是處理POST請(qǐng)求,且客戶端信息通過(guò)Body傳輸?shù)?,里面邏輯也是非常?jiǎn)單,直接從請(qǐng)求參數(shù)中獲取client_id和client_secret,具體的代碼就不帶大家看了,有興趣可以看一下。
代碼③
這里就是執(zhí)行真正的校驗(yàn)邏輯了,內(nèi)部調(diào)用的RegisteredClientRepository#findByClientId()方法校驗(yàn)。
對(duì)應(yīng)的則是整體的流程圖的第②部分,這里調(diào)用的則是自定義的CodeapeRemoteRegisteredClientRepository#findByClientId方法,內(nèi)部邏輯非常簡(jiǎn)單:查詢Redis緩存,存在緩存直接取,不存在則查數(shù)據(jù)庫(kù)codeape/sys_oauth_client_details(通過(guò)feign接口遠(yuǎn)程調(diào)用服務(wù)查詢)。
代碼如下圖:
代碼④
這部分是客戶端認(rèn)證成功的處理邏輯,是將客戶端認(rèn)證的信息存放到SecurityContext上下文中,方便后面流程獲取,代碼OAuth2ClientAuthenticationFilter#onAuthenticationSuccess如下:
代碼⑤
處理客戶端認(rèn)證失敗的結(jié)果,這里最終執(zhí)行的是自定義的失敗處理器CodeapeAuthenticationFailureEventHandler#onAuthenticationFailure(),這個(gè)下文會(huì)介紹。
3. RegisteredClientRepository
這個(gè)是客戶端的持久層查詢的類,在上文已經(jīng)介紹過(guò)
4. OAuth2TokenEndpointFilter
OAuth2TokenEndpointFilter 這個(gè)過(guò)濾器的作用是用于處理 OAuth2 認(rèn)證和授權(quán)請(qǐng)求的。它會(huì)攔截所有請(qǐng)求,并根據(jù)請(qǐng)求的 URI 判斷是否是授權(quán)請(qǐng)求(/oauth2/token)。
如果是授權(quán)請(qǐng)求,則它會(huì)根據(jù)請(qǐng)求的參數(shù)構(gòu)造一個(gè) OAuth2AuthenticationToken 對(duì)象,并將其交給 AuthenticationManager 進(jìn)行身份認(rèn)證。如果認(rèn)證成功,則根據(jù)請(qǐng)求中攜帶的授權(quán)類型(grant_type)決定使用哪個(gè) OAuth2 授權(quán)提供者來(lái)生成授權(quán)令牌(access_token),并將生成的授權(quán)令牌返回給請(qǐng)求方。
如果認(rèn)證失敗,則返回相應(yīng)的錯(cuò)誤信息。該過(guò)濾器通常用于實(shí)現(xiàn) OAuth2 認(rèn)證和授權(quán)功能的后端服務(wù)。
這個(gè)過(guò)濾器才是真正處理登錄請(qǐng)求邏輯
整體的邏輯如下:
5. AuthenticationConverter
這個(gè)在第4步中的第②個(gè)步驟,會(huì)根據(jù)請(qǐng)求中的參數(shù)和授權(quán)類型組裝成對(duì)應(yīng)的授權(quán)認(rèn)證對(duì)象。它的幾個(gè)重要的實(shí)現(xiàn)類如下:
先來(lái)看一下自定義的抽象類:OAuth2ResourceOwnerBaseAuthenticationConverter,三個(gè)抽象方法如下:
- boolean support(String grantType):判斷是否支持指定的授權(quán)類型
- void checkParams(HttpServletRequest request):校驗(yàn)請(qǐng)求參數(shù),比如密碼模式下的username、password不能為空,手機(jī)驗(yàn)證碼登錄則手機(jī)號(hào)不能為空都是在這校驗(yàn)
- T buildToken():這個(gè)是構(gòu)建認(rèn)證登錄對(duì)象的方法
實(shí)現(xiàn)的convert()方法代碼如下:
@Override
public Authentication convert(HttpServletRequest request) {
// grant_type (REQUIRED) ① 校驗(yàn)授權(quán)類型,調(diào)用抽象方法support
String grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE);
if (!support(grantType)) {
return null;
}
//② 獲取請(qǐng)求參數(shù),比如密碼模式:username、password、hosId,scope...
MultiValueMap<String, String> parameters = OAuth2EndpointUtils.getParameters(request);
// scope (OPTIONAL) ③ 提取出scope
String scope = parameters.getFirst(OAuth2ParameterNames.SCOPE);
if (StringUtils.hasText(scope) && parameters.get(OAuth2ParameterNames.SCOPE).size() != 1) {
OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.SCOPE,
OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI);
}
Set<String> requestedScopes = null;
if (StringUtils.hasText(scope)) {
requestedScopes = new HashSet<>(Arrays.asList(StringUtils.delimitedListToStringArray(scope, " ")));
}
// ④ 校驗(yàn)個(gè)性化參數(shù)
checkParams(request);
// ⑤ 獲取當(dāng)前已經(jīng)認(rèn)證的客戶端信息,這個(gè)是在OAuth2ClientAuthenticationFilter認(rèn)證成功客戶端認(rèn)證對(duì)象
Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication();
if (clientPrincipal == null) {
OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ErrorCodes.INVALID_CLIENT,
OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI);
}
// ⑥ 擴(kuò)展信息
Map<String, Object> additionalParameters = parameters.entrySet()
.stream()
.filter(e -> !e.getKey().equals(OAuth2ParameterNames.GRANT_TYPE)
&& !e.getKey().equals(OAuth2ParameterNames.SCOPE))
.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().get(0)));
// ⑦ 創(chuàng)建token 調(diào)用抽象方法buildToken()
return buildToken(clientPrincipal, requestedScopes, additionalParameters);
}
注釋非常清晰了,這里不再詳細(xì)解釋了。
其中密碼模式認(rèn)證登錄的實(shí)現(xiàn)類是:OAuth2ResourceOwnerPasswordAuthenticationConverter,里面的邏輯非常簡(jiǎn)單,這里不介紹了。
6. AuthenticationToken
`AuthenticationToken`是登錄認(rèn)證對(duì)象,在第4步中的第②步組裝,[碼猿慢病云管理系統(tǒng)](https://mp.weixin.qq.com/s?__biz=MzU3MDAzNDg1MA==&mid=2247526866&idx=1&sn=3820b44ff80c46749efa1a2c0b1f8aa7&chksm=fcf7b61fcb803f090688a542cfb766f5dc06385b5e6e04c2309b4af7b07c07580fb19ffc9d0a&scene=178&cur_album_id=2989600933141807115#rd)中對(duì)其進(jìn)行了擴(kuò)展,有如下三個(gè)類:
- OAuth2ResourceOwnerBaseAuthenticationToken:抽象類
- OAuth2ResourceOwnerPasswordAuthenticationToken:密碼模式的登錄認(rèn)證對(duì)象
- OAuth2ResourceOwnerSmsAuthenticationToken:短信驗(yàn)證碼登錄認(rèn)證對(duì)象
后續(xù)如有其他授權(quán)模式,直接繼承OAuth2ResourceOwnerBaseAuthenticationToken擴(kuò)展
7. AuthenticationProvider
AuthenticationProvider是Spring Security提供的一種機(jī)制,用于接收和驗(yàn)證用戶名和密碼等認(rèn)證信息,并返回一個(gè)已認(rèn)證的Authentication對(duì)象。其作用是封裝了整個(gè)認(rèn)證過(guò)程,包括認(rèn)證用戶的來(lái)源、密碼的加密和解密、對(duì)用戶賬戶狀態(tài)的判斷等。
AuthenticationProvider在第4步中的第③步中被調(diào)用,用于認(rèn)證;碼猿慢病云管理系統(tǒng)中自定義了三個(gè)實(shí)現(xiàn)類,如下:
1)OAuth2ResourceOwnerBaseAuthenticationProvider
抽象類,封裝了具體的執(zhí)行邏輯,有三個(gè)抽象方法供子類實(shí)現(xiàn),如下:
/**
* 構(gòu)建登錄認(rèn)證對(duì)象
* @param reqParameters
* @return
*/
public abstract UsernamePasswordAuthenticationToken buildToken(Map<String, Object> reqParameters);
/**
* 當(dāng)前provider是否支持此令牌類型
* @param authentication
* @return
*/
@Override
public abstract boolean supports(Class<?> authentication);
/**
* 當(dāng)前的請(qǐng)求客戶端是否支持此模式
* @param registeredClient
*/
public abstract void checkClient(RegisteredClient registeredClient);
具體的執(zhí)行邏輯都在OAuth2ResourceOwnerBaseAuthenticationProvider#authenticate()方法中,關(guān)鍵邏輯如下:
//① 構(gòu)建登錄認(rèn)證對(duì)象,交由子類實(shí)現(xiàn)
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = buildToken(reqParameters);
//② 交由Spring Security 認(rèn)證
Authentication usernamePasswordAuthentication = authenticationManager.authenticate(usernamePasswordAuthenticationToken);
// ----- Access token ----- ③ 構(gòu)建Access token
OAuth2TokenContext tokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.ACCESS_TOKEN).build();
OAuth2Token generatedAccessToken = this.tokenGenerator.generate(tokenContext);
// ----- Refresh token ----- ④ 認(rèn)證成功后,構(gòu)建刷新令牌
tokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.REFRESH_TOKEN).build();
OAuth2Token generatedRefreshToken = this.tokenGenerator.generate(tokenContext);
//⑤ 存儲(chǔ)令牌
this.authorizationService.save(authorization);
代碼①:構(gòu)建認(rèn)證登錄對(duì)象,提供了一個(gè)buildToken抽象方法交由子類實(shí)現(xiàn)
剩余代碼下文介紹
2)OAuth2ResourceOwnerPasswordAuthenticationProvider
密碼模式的AuthenticationProvider,繼承抽象類OAuth2ResourceOwnerBaseAuthenticationProvider實(shí)現(xiàn)三個(gè)抽象方法,邏輯很簡(jiǎn)單。
3)OAuth2ResourceOwnerSmsAuthenticationProvider
短信驗(yàn)證碼登錄模式的AuthenticationProvider,繼承抽象類OAuth2ResourceOwnerBaseAuthenticationProvider實(shí)現(xiàn)三個(gè)抽象方法。
8. DaoAuthenticationProvider
從DaoAuthenticationProvider這里就進(jìn)入真正的認(rèn)證邏輯了,從名字就可以看出涉及到數(shù)據(jù)庫(kù)的操作了。內(nèi)部的邏輯很簡(jiǎn)單,就是通過(guò)UserDetailService調(diào)用查詢用戶信息封裝成UserDetails
在第7步中的第②步驟中則會(huì)進(jìn)入:
//② 交由Spring Security 認(rèn)證
Authentication usernamePasswordAuthentication = authenticationManager.authenticate(usernamePasswordAuthenticationToken);
碼猿慢病云管理系統(tǒng)中自定義了一個(gè)CodeapeDaoAuthenticationProvider,執(zhí)行的邏輯將會(huì)在這個(gè)類中,先看下其中重載的兩個(gè)重要的方法:
//方法一:feign遠(yuǎn)程調(diào)用根據(jù)username查詢用戶信息,組裝成UserDetails
UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication);
//方法二:校驗(yàn)用戶信息、密碼
void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication);
1. retrieveUser 查詢用戶信息
retrieveUser這個(gè)方法邏輯很簡(jiǎn)單,則是調(diào)用UserDetailService查詢用戶信息,邏輯如下:
圖片
代碼①
從Request中獲取相關(guān)參數(shù):
- clientId:客戶端ID,由于是base64編碼傳輸,因此需要調(diào)用的convert方法解碼
- hosId:醫(yī)院ID,WEB、PAD登錄所需參數(shù)
- sn:設(shè)備的唯一識(shí)別SN號(hào),用于PDA登錄
代碼②
從IOC容器中獲取UserDetailSevice,碼猿慢病云管理系統(tǒng)中目前實(shí)現(xiàn)類有三個(gè):
- CodeapeAppUserDetailsServiceImpl:處理APP端的手機(jī)號(hào)登錄
- CodeapePDAUserDetailsServiceImpl:處理PDA端登錄
- CodeapeUserDetailsServiceImpl:處理PAD端和WEB端登錄
代碼③
調(diào)用UserDetailService中的loadUserByUsernameAndOther方法獲取UserDetails
2. additionalAuthenticationChecks 密碼校驗(yàn)
這個(gè)方法核心邏輯則是校驗(yàn)密碼,碼猿慢病云管理系統(tǒng)中的密碼校驗(yàn)是通過(guò)PasswordEncoder加密。
圖片
3. 用戶狀態(tài)校驗(yàn)
核心邏輯在:AbstractUserDetailsAuthenticationProvider.DefaultPreAuthenticationChecks#check方法中,代碼如下:
圖片
九、 UserDetailService
在第8步中說(shuō)到查詢用戶信息是通過(guò)UserDetailService查詢,碼猿慢病云管理系統(tǒng)中目前內(nèi)置三個(gè)實(shí)現(xiàn)類:
- CodeapeAppUserDetailsServiceImpl:處理APP端的手機(jī)號(hào)登錄
- CodeapePDAUserDetailsServiceImpl:處理PDA端登錄
- CodeapeUserDetailsServiceImpl:處理PAD端和WEB端登錄
這里都是通過(guò)feign調(diào)用解耦,當(dāng)然你也可以在auth模塊嵌入數(shù)據(jù)庫(kù),從數(shù)據(jù)庫(kù)查詢
這里調(diào)用的方法是loadUserByUsernameAndOther,比如CodeapeUserDetailsServiceImpl實(shí)現(xiàn)如下:
圖片
最終的組裝UserDetails通過(guò)getUserDetails方法,如下:
圖片
需要注意的是:碼猿慢病云管理系統(tǒng)中的用戶信息是封裝在CodeapeUser中,方便后續(xù)擴(kuò)展,其中的屬性如下:
圖片
可以看到這里和登錄返回的信息中user_info是對(duì)應(yīng)的:
圖片
十. 生成OAuth2AccessToken
在第7步中的第③步中生成access_token,自定義的實(shí)現(xiàn)類為:CustomeOAuth2AccessTokenGenerator
圖片
十一. OAuth2AuthorizationService 令牌持久化
在第7步中的第⑤步驟中執(zhí)行了令牌的持久化,Spring Security 默認(rèn)支持兩種持久化方式:
- InMemoryOAuth2AuthorizationService:持久化在內(nèi)存中
- JdbcOAuth2AuthorizationService:持久化在數(shù)據(jù)庫(kù)中
碼猿慢病云管理系統(tǒng)中擴(kuò)展了Redis中持久化,自定義的實(shí)現(xiàn)類:CodeapeRedisOAuth2AuthorizationService
圖片
持久化成功后將會(huì)在Redis中看到對(duì)應(yīng)的信息:
圖片
十二. AuthenticationSuccessHandler 登錄成功處理
在第4步中的第④步驟中認(rèn)證成功,則調(diào)用AuthenticationSuccessHandler 處理登錄成功的邏輯,將認(rèn)證信息輸出返回給客戶端。
碼猿慢病云管理系統(tǒng)中自定義類:CodeapeAuthenticationSuccessEventHandler
圖片
總結(jié)
本節(jié)內(nèi)容詳細(xì)介紹了碼猿慢病云管理系統(tǒng)中完整的認(rèn)證登錄生成token的流程,相信你對(duì)整體的流程有了清晰的了解。