步驟分析:
1.首先我們用戶會找到我們的網(wǎng)站
8081是網(wǎng)關(guān)端口,/api/是網(wǎng)關(guān)放行的路徑
- 比如地址是:http:localhost:8001/api/oauth/toLogin,這時會給他顯示第三方登錄暴露的接口圖標(biāo),比如(qq,微信,微博)
1.1 用戶點(diǎn)擊微信登錄
- 微信會展示一個二維碼讓用戶進(jìn)行掃碼認(rèn)證,掃碼后微信會提示用戶是否授權(quán)(意思是暴露用戶的信息),用戶點(diǎn)擊確定后,微信會提供一個授權(quán)碼給給我們的網(wǎng)站
我們的網(wǎng)站會拿到code授權(quán)碼,我們會攜帶授權(quán)碼去找微信接口申請令牌,,然后微信會頒發(fā)給我們一個token令牌,我們會拿到這個令牌訪問微信接口,接口會自動解析令牌,返回用戶的信息(用戶名和頭像)給我們網(wǎng)站,根據(jù)用戶信息關(guān)聯(lián)查詢我們自己的用戶數(shù)據(jù).我們這時第三方登錄就已經(jīng)完成了. - 根據(jù)我們的業(yè)務(wù)需求,可以選擇拿到信息后直接在后端過濾器放行,生成令牌,存cookie和redis中,用戶信息存入數(shù)據(jù)庫.
1.2 業(yè)務(wù)需求(再次網(wǎng)站登錄)
- 也可以讓用戶進(jìn)行我們網(wǎng)站的登錄,輸入用戶名和密碼進(jìn)行認(rèn)證,用戶點(diǎn)擊后直接會訪問網(wǎng)關(guān),我們會在網(wǎng)關(guān)的AuthFilter過濾器中判斷用戶是否是登錄請求,如果是登錄,和相關(guān)必須要放行的請求,我們就可以直接放行,讓它去訪問登錄等相關(guān)資源.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
2.login頁面
用戶看到登錄的頁面就可以輸入賬號密碼進(jìn)行登錄娃善,當(dāng)用戶點(diǎn)擊登錄就會發(fā)送一個請求http:localhost:8001/api/oauth/login登錄請求,網(wǎng)關(guān)放行(UrlFilter包含了需要放行的請求)
if ("/api/oauth/login".equals(path) || !UrlFilter.hasAuthorize(path) ){
// if ("/api/oauth/login".equals(path) ){
//直接放行
return chain.filter(exchange);
}
3.生成令牌(Spring Security框架自動校驗(yàn)用戶名密碼,也解決單點(diǎn)登錄問題)
將用戶輸入的用戶名和密碼發(fā)送到login的controller中,這時如果輸入為空直接 返回錯誤信息
然后去調(diào)用申請令牌的service中的exchange方法傳入客戶端id和客戶端密碼和用戶輸入的用戶名和密碼.在申請令牌的時候,通過用戶傳來的用戶名去工具類中用過feign的遠(yuǎn)程調(diào)用user服務(wù),查詢用戶信息客,通過Spring Security框架去校驗(yàn)用戶傳來的密碼和查詢出來的密碼比對,通過客戶端id和客戶端密碼,自動查詢oauth_client_details表,通過后,生成最終的jwt令牌,它包含了6部分?jǐn)?shù)據(jù):
//申請令牌
AuthToken token = authService.login(username, password, clientId, clientSecret);
/* * url ,請求路徑 http://localhost:9200/oauth/token
* HttpMethod ;請求方式
* requestEntity :封裝請求的數(shù)據(jù)
* Map :數(shù)據(jù)的返回方式 */
//這里的參數(shù)是注冊到Eureka上的服務(wù)名稱
ServiceInstance choose = loadBalancerClient.choose("user-auth");
URI uri = choose.getUri();
String url = uri+"/oauth/token";
restTemplate.exchange(url, HttpMethod.POST, requestEntity, Map.class); //反回申請的令牌
access_token:訪問令牌论衍,攜帶此令牌訪問資源
token_type:有MAC Token與Bearer Token兩種類型,兩種的校驗(yàn)算法不同聚磺,Oauth2采 用 Bearer Token坯台。
refresh_token:刷新令牌,使用此令牌可以延長訪問令牌的過期時間瘫寝。
expires_in:過期時間蜒蕾,單位為秒稠炬。
scope:范圍,與定義的客戶端范圍一致咪啡。
jti:當(dāng)前token的唯一標(biāo)識
拿到令牌后我們會在方法中取出jwt令牌的值,存儲到redis中,設(shè)置它的過期時間
//3.將jwt作為redis中的key, 將jwt作為redis中的value進(jìn)行數(shù)據(jù)的存儲
/*
參數(shù) authToken設(shè)置 redis key
set 對應(yīng)的值
ttl 過期時間
TimeUnit :時間單位 SECONDS:秒
* */
stringRedisTemplate.boundValueOps(authToken.getJti()).set(authToken.getAccessToken(),ttl, TimeUnit.SECONDS);
然后返回令牌,在controller層拿到令牌后 取出jti短令牌存儲到cookie中
CookieUtil.addCookie(response,cookieDomain,"/","uid",jti,cookieMaxAge,false);
到這里,用戶就登錄成功了提示用戶,也可以跳轉(zhuǎn)相關(guān)頁面.
4.訪問受保護(hù)的資源
這時用戶就能夠訪問我們受保護(hù)的資源了,用戶會發(fā)起請求路徑到相關(guān)服務(wù)層,會經(jīng)過網(wǎng)關(guān)過濾器,不是登錄請求不放行,然后校驗(yàn)cookie中有沒有短令牌,如果有判斷redis中有沒有存儲jwt令牌信息(存在過期),不滿足條件返回401權(quán)限不足: return response.setComplete();
都滿足條件,對這次用戶請求頭進(jìn)行增強(qiáng)操作:
//4.對當(dāng)前的請求對象進(jìn)行增強(qiáng),讓它會攜帶令牌的信息
request.mutate().header("Authorization","Bearer "+jwt);
return chain.filter(exchange);
用戶請求到達(dá)服務(wù)層后,認(rèn)證服務(wù)會自動對jwt令牌進(jìn)行校驗(yàn)解析,解析通過后訪問被保護(hù)的資源,
解析不通過返回權(quán)限不足錯誤.解析通過我們還會在每一個服務(wù)的方法上加@PreAuthorize權(quán)限注解,判斷是否滿足權(quán)限條件,不滿足則跳轉(zhuǎn)到登錄頁面
//跳轉(zhuǎn)登錄頁面
private Mono<Void> toLoginPage(String loginUrl, ServerWebExchange exchange) {
ServerHttpResponse response = exchange.getResponse();
//設(shè)置狀態(tài)碼 //302 重定向
response.setStatusCode(HttpStatus.SEE_OTHER);
//設(shè)置頭信息 loginUrl跳轉(zhuǎn)到位置
response.getHeaders().set("Location",loginUrl);
//響應(yīng)回去 進(jìn)行跳轉(zhuǎn)
return response.setComplete();
}
//擁有normal或者admin角色的用戶都可以方法helloUser()方法首启。
@PreAuthorize("hasRole('normal') AND hasRole('admin')")
public String helloUser() {
return "hello,user";
}
@PreAuthorize(value = "#oauth2.hasAnyScope('A','B','C','D')")//添加機(jī)構(gòu)編碼權(quán)限,判斷該機(jī)構(gòu)是否有權(quán)限調(diào)用
@PreAuthorize(value="isAuthenticated()")//添加登錄權(quán)限判斷撤摸,登錄才可以調(diào)用
public String getInformation(){
return info;
}