柚子快報(bào)邀請(qǐng)碼778899分享:常用中間件-OAuth2
柚子快報(bào)邀請(qǐng)碼778899分享:常用中間件-OAuth2
1. 微服務(wù)權(quán)限校驗(yàn)Session共享
1.1 微服務(wù)權(quán)限校驗(yàn)
實(shí)現(xiàn)2號(hào)方案。使用Redis作為Session統(tǒng)一存儲(chǔ)。
首先配置一下Nacos,參考https://blog.csdn.net/weixin_43917045/article/details/132852850
然后為每個(gè)服務(wù)添加驗(yàn)證機(jī)制,先導(dǎo)入依賴
三個(gè)服務(wù)都需要添加以上依賴。
然后給三個(gè)服務(wù)都修改配置文件。 這樣,默認(rèn)情況下每個(gè)服務(wù)的接口都會(huì)被SpringSecurity所保護(hù),只有登錄成功后,才可以被訪問(wèn)。 啟動(dòng)nacos和三個(gè)服務(wù)。 此時(shí)訪問(wèn)borrow借閱接口http://localhost:8201/borrow/1會(huì)自動(dòng)跳轉(zhuǎn)到登陸接口http://localhost:8201/login 點(diǎn)擊登錄后進(jìn)入redis-cli可以看到用戶密碼已存在。 此時(shí)訪問(wèn)book服務(wù),可以看到直接能調(diào)用,不用登錄,因?yàn)樯洗蔚卿浽赽orrow服務(wù)訪問(wèn)中已經(jīng)存入賬戶密碼信息。 此時(shí)先退出登錄。 退出賬戶后訪問(wèn)http://localhost:8301/book/1就會(huì)跳轉(zhuǎn)到登錄頁(yè)面http://localhost:8301/book/1。 然后再進(jìn)行登錄,進(jìn)行借閱接口訪問(wèn),發(fā)現(xiàn)出錯(cuò),是由于borrow服務(wù)調(diào)用user和book服務(wù)是遠(yuǎn)程調(diào)用形式,,而這種形式?jīng)]有進(jìn)行任何驗(yàn)證。出現(xiàn)這種情況原因是RestTemplate遠(yuǎn)程調(diào)用的時(shí)候,由于請(qǐng)求沒(méi)有攜帶Session的Cookies,所以導(dǎo)致驗(yàn)證失敗,訪問(wèn)不成功,返回401。因此存在不便。
2. OAuth 2.0實(shí)現(xiàn)單點(diǎn)登錄
2.1 搭建驗(yàn)證服務(wù)器
還原不帶任何spring cloud alibaba依賴的項(xiàng)目, 加入spring cloud依賴 創(chuàng)建auth-service(左上角新增moudle)并導(dǎo)入依賴。(圖片中服務(wù)名稱a打成o了) 編寫(xiě)配置類
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated() //
.and()
.formLogin().permitAll(); //使用表單登錄
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
auth
.inMemoryAuthentication() //直接創(chuàng)建一個(gè)用戶,懶得搞數(shù)據(jù)庫(kù)了
.passwordEncoder(encoder)
.withUser("test").password(encoder.encode("123456")).roles("USER");
}
@Bean //這里需要將AuthenticationManager注冊(cè)為Bean,在OAuth配置中使用
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
@EnableAuthorizationServer //開(kāi)啟驗(yàn)證服務(wù)器
@Configuration
public class OAuth2Configuration extends AuthorizationServerConfigurerAdapter {
@Resource
private AuthenticationManager manager;
private final BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
/**
* 這個(gè)方法是對(duì)客戶端進(jìn)行配置,一個(gè)驗(yàn)證服務(wù)器可以預(yù)設(shè)很多個(gè)客戶端,
* 之后這些指定的客戶端就可以按照下面指定的方式進(jìn)行驗(yàn)證
* @param clients 客戶端配置工具
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients
.inMemory() //這里我們直接硬編碼創(chuàng)建,當(dāng)然也可以像Security那樣自定義或是使用JDBC從數(shù)據(jù)庫(kù)讀取
.withClient("web") //客戶端名稱,隨便起就行
.secret(encoder.encode("654321")) //只與客戶端分享的secret,隨便寫(xiě),但是注意要加密
.autoApprove(false) //自動(dòng)審批,這里關(guān)閉,要的就是一會(huì)體驗(yàn)?zāi)欠N感覺(jué)
.scopes("book", "user", "borrow") //授權(quán)范圍,這里我們使用全部all
.authorizedGrantTypes("client_credentials", "password", "implicit", "authorization_code", "refresh_token");
//授權(quán)模式,一共支持5種,除了之前我們介紹的四種之外,還有一個(gè)刷新Token的模式
//這里我們直接把五種都寫(xiě)上,方便一會(huì)實(shí)驗(yàn),當(dāng)然各位也可以單獨(dú)只寫(xiě)一種一個(gè)一個(gè)進(jìn)行測(cè)試
//現(xiàn)在我們指定的客戶端就支持這五種類型的授權(quán)方式了
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) {
security
.passwordEncoder(encoder) //編碼器設(shè)定為BCryptPasswordEncoder
.allowFormAuthenticationForClients() //允許客戶端使用表單驗(yàn)證,一會(huì)我們POST請(qǐng)求中會(huì)攜帶表單信息
.checkTokenAccess("permitAll()"); //允許所有的Token查詢請(qǐng)求
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
.authenticationManager(manager);
//由于SpringSecurity新版本的一些底層改動(dòng),這里需要配置一下authenticationManager,才能正常使用password模式
}
}
然后啟動(dòng)auth-service。 使用postman進(jìn)行接口測(cè)試。
2.1.1客戶端模式測(cè)試,
客戶端模式只需要提供id和secret即可直接拿到token,但還需要添加一個(gè)grant_type表面我們的授權(quán)方式,默認(rèn)請(qǐng)求路徑為http://localhost:8500/sso/oauth/token 通過(guò)訪問(wèn)http://localhost:8500/sso/oauth/check_token來(lái)驗(yàn)證token是否有效
2.1.2 密碼模式測(cè)試
密碼模式還需要提供具體的用戶名和密碼,授權(quán)模式定義為password即可。 還需要在請(qǐng)求頭中添加Basic驗(yàn)證信息,直接寫(xiě)id和secret即可
2.1.3 隱式授權(quán)模式
這種模式需要在驗(yàn)證服務(wù)器上進(jìn)行登錄操作,而不是直接請(qǐng)求Token,驗(yàn)證登陸地址http://localhost:8500/sso/oauth/authorize?client_id=web&response_type=token。 response_type一定是token類型,這樣才會(huì)返回Token。 請(qǐng)求上邊的驗(yàn)證登陸地址則會(huì)進(jìn)入登錄頁(yè)面。
登錄之后出現(xiàn)錯(cuò)誤,這是因?yàn)榈顷懞篁?yàn)證服務(wù)器需要將結(jié)果返回給客戶端,所以需要提供客戶端的回調(diào)地址,這樣瀏覽器會(huì)被重定向到指定的回調(diào)地址并且請(qǐng)求中回?cái)y帶Token信息,此時(shí)隨便配置一個(gè)回調(diào)地址, 配置回調(diào)地址,然后重啟 此時(shí)會(huì)要求進(jìn)行授權(quán),
2.1.4 授權(quán)碼模式-最安全
這種方式和1.2.3的流程一樣,但是請(qǐng)求的地址是code類型:http://localhost:8500/sso/oauth/authorize?client_id=web&response_type=code,訪問(wèn)該地址,因?yàn)樵?.2.3已經(jīng)登錄過(guò)了,可以看到訪問(wèn)之后,依然會(huì)進(jìn)入到回調(diào)地址,但是此時(shí)給的是授權(quán)碼code了,不是直接給token。但是有時(shí)候可能需要token。
按照2.1之前的第四個(gè)授權(quán)碼圖示原來(lái),需要攜帶授權(quán)碼和secret一起請(qǐng)求,才能拿到token,正常情況下是由回調(diào)的服務(wù)器進(jìn)行處理,在postman中測(cè)試進(jìn)行,復(fù)制剛得到的授權(quán)碼,接口請(qǐng)求localhost:8500/sso/oauth/token 可以看到正常拿到token 以上四種基本的Token請(qǐng)求方式。
2.1.5 刷新令牌
當(dāng)Token過(guò)期時(shí),就可以使用這個(gè)refresh_token來(lái)申請(qǐng)一個(gè)新的Token。 重新請(qǐng)求 改為點(diǎn)擊發(fā)送,出現(xiàn)異常 查看日志發(fā)現(xiàn),還需要單獨(dú)配置一個(gè)UserDetailsService,我們直接把Security中的實(shí)例注冊(cè)為Bean。 然后再Endpoint中設(shè)置
添加完重啟,重啟之后token就沒(méi)有了,需要重新申請(qǐng)以下。 訪問(wèn)http://localhost:8500/sso/oauth/authorize?client_id=web&response_type=code,先進(jìn)行登錄test,123456,然后授權(quán)獲得code
獲得code 獲得token 訪問(wèn)接口刷新token
3. 單點(diǎn)登錄客戶端
前面已經(jīng)將服驗(yàn)證服務(wù)器搭建完成了,下面實(shí)現(xiàn)單點(diǎn)登錄,SpringCloud提供了客戶端的直接實(shí)現(xiàn),只需要添加一個(gè)注解和少量配置即可將我們的服務(wù)作為一個(gè)單點(diǎn)登錄應(yīng)用,使用的是第四張授權(quán)碼模式。 這種模式只是將驗(yàn)證方式由原來(lái)的默認(rèn)登陸形式改變?yōu)榱私y(tǒng)一在授權(quán)服務(wù)器登錄的形式。
3.1 單點(diǎn)登錄(基于@EnableOAuth2Sso實(shí)現(xiàn))
在book-service中添加依賴 啟動(dòng)類添加注解 然后需要在配置文件中配置驗(yàn)證服務(wù)器相關(guān)的信息:
security:
oauth2:
client:
#不多說(shuō)了
client-id: web
client-secret: 654321
#Token獲取地址
access-token-uri: http://localhost:8500/sso/oauth/token
#驗(yàn)證頁(yè)面地址
user-authorization-uri: http://localhost:8500/sso/oauth/authorize
resource:
#Token信息獲取和校驗(yàn)地址
token-info-uri: http://localhost:8500/sso/oauth/check_token
啟動(dòng)book服務(wù) 訪問(wèn),由于book服務(wù)是8301端口,需要將重定向地址也改為8301
然后會(huì)登錄跳轉(zhuǎn)到授權(quán)頁(yè)面 授權(quán)后訪問(wèn)成功 此時(shí)訪問(wèn)成功了但,但是用戶信息是否也一并保存過(guò)來(lái)了,直接獲取以下SpringSecurity的Context查看用戶信息,獲取方式和之前一樣 再次訪問(wèn)book服務(wù),輸出用戶信息 接著將所有的服務(wù)都使用這種方式進(jìn)行驗(yàn)證,將重定向地址給所有服務(wù)都加上 將borrow-service,user-service都加上依賴,配置文件內(nèi)容,和啟動(dòng)類的注釋。 配置完啟動(dòng)user和borrow服務(wù)和auth服務(wù) 此時(shí)訪問(wèn)user服務(wù)和book服務(wù)都是授權(quán)后可登錄。但是此時(shí)三個(gè)服務(wù)session會(huì)互相占用,若訪問(wèn)完user服務(wù),再訪問(wèn)user服務(wù)不會(huì)再次驗(yàn)證,但是若再訪問(wèn)book或其他服務(wù)就會(huì)需要再次驗(yàn)證。
這里有兩個(gè)方案:一是和之前一樣Session統(tǒng)一存儲(chǔ)。二是設(shè)置context-path路徑,每個(gè)服務(wù)單獨(dú)設(shè)置,就不會(huì)互相占用了。
單點(diǎn)登錄無(wú)法解決服務(wù)間調(diào)用的問(wèn)題。
3.2 單點(diǎn)登錄(基于@EnableResourceServer實(shí)現(xiàn))
前面已經(jīng)實(shí)現(xiàn)了將服務(wù)作為單點(diǎn)登錄應(yīng)用直接實(shí)現(xiàn)單點(diǎn)登錄。但是如果是第三方訪問(wèn),則我們就需要將服務(wù)作為資源服務(wù)了,作為資源服務(wù)就不會(huì)再提供驗(yàn)證的過(guò)長(zhǎng),而是直接要求請(qǐng)求時(shí)攜帶Token,而驗(yàn)證過(guò)程使用postman進(jìn)行測(cè)試。 總之,與上邊實(shí)現(xiàn)的相比,下邊實(shí)現(xiàn)的訪問(wèn)過(guò)程只需要攜帶Token就能訪問(wèn)這些資源服務(wù)器了,客戶端被獨(dú)立了出來(lái),用于攜帶Token去訪問(wèn)這些服務(wù)。
此時(shí)需要添加注解和少量配置 修改完配置和注解重啟book-service,訪問(wèn)book服務(wù),顯示沒(méi)有權(quán)限訪問(wèn)資源。
這是由于請(qǐng)求頭中沒(méi)有攜帶token信息,有兩種方式可訪問(wèn)到資源 一:在URL后添加 access_token 請(qǐng)求參數(shù),值為T(mén)oken值 二:在請(qǐng)求頭中添加 Authorization 值為 Bearer + Token值
在URL后添加 access_token 請(qǐng)求參數(shù) 此時(shí)通過(guò)密碼模式訪問(wèn)拿到token
后綴參數(shù)加上獲取的access_token即可訪問(wèn) 在請(qǐng)求頭中添加 Authorization 值為 Bearer + Token值 直接訪問(wèn)沒(méi)有權(quán)限 然后可訪問(wèn)資源 到此,資源服務(wù)器就搭建完成
3.3 資源服務(wù)器深度自定義
編寫(xiě)一個(gè)配置類,使得用戶授權(quán)了某個(gè)Scope才可以訪問(wèn)此服務(wù)。例如,必須有book作用域才能訪問(wèn)book-service服務(wù)
@Configuration
public class ResourceConfiguration extends ResourceServerConfigurerAdapter { //繼承此類進(jìn)行高度自定義
@Override
public void configure(HttpSecurity http) throws Exception { //這里也有HttpSecurity對(duì)象,方便我們配置SpringSecurity
http
.authorizeRequests()
.anyRequest().access("#oauth2.hasScope('lbwnb')"); //添加自定義規(guī)則
//Token必須要有我們自定義scope授權(quán)才可以訪問(wèn)此資源
}
}
添加完重啟book-service 訪問(wèn)發(fā)現(xiàn)作用域不足 重啟后訪問(wèn)正常
實(shí)際上資源服務(wù)器完全沒(méi)有必要將Security的信息保存在Session中,因?yàn)楝F(xiàn)在只需要將Token告訴資源服務(wù)器,那么資源服務(wù)器就可以聯(lián)系驗(yàn)證服務(wù)器得到用戶信息,不需要使用之前的Session存儲(chǔ)機(jī)制了,所以會(huì)發(fā)現(xiàn)HttpSession中沒(méi)有SPRING_SECURITY_CONTEXT,現(xiàn)在Security信息都是通過(guò)連接資源服務(wù)器獲取。
但是目前每次訪問(wèn)都需要去驗(yàn)證一次,浪費(fèi)資源,這種可以通過(guò)JTW去解決。 接著將所有服務(wù)都改為基于@EnableResourceServer方式實(shí)現(xiàn),borrow-service和user-service的配置文件,啟動(dòng)類的注解都要修改
然后啟動(dòng)user、book、borrow服務(wù),此時(shí)這三個(gè)服務(wù)都是資源服務(wù)。
訪問(wèn)user服務(wù)
訪問(wèn)borrow服務(wù)還是會(huì)出問(wèn)題,因?yàn)檫h(yuǎn)程調(diào)用也需要攜帶token,但是遠(yuǎn)程調(diào)用過(guò)程沒(méi)有攜帶任何token’信息。因此需要想辦法將用戶傳來(lái)的token信息在進(jìn)行遠(yuǎn)程調(diào)用同時(shí)也攜帶上。因此可以直接使用OAuth2RestTemplate,它會(huì)在請(qǐng)求其他服務(wù)時(shí)攜帶當(dāng)前請(qǐng)求的Token信息,它繼承自RestTemplate,直接定義一個(gè)Bean
@Configuration
public class WebConfiguration {
@Resource
OAuth2ClientContext context;
@Bean
public OAuth2RestTemplate restTemplate(){
return new OAuth2RestTemplate(new ClientCredentialsResourceDetails(), context);
}
}
修改后重啟borrow,然后訪問(wèn)borrow服務(wù),此時(shí)正常調(diào)用。這樣遠(yuǎn)程調(diào)用的時(shí)候就會(huì)也攜帶token信息
3.4 Nacos加入,通過(guò)Feign實(shí)現(xiàn)遠(yuǎn)程調(diào)用
加入依賴
## 給最外層pom添加
## 給每個(gè)服務(wù)添加
配置Nacos服務(wù) 配置完啟動(dòng)nacos 三個(gè)服務(wù)分別添加配置
修改borrow服務(wù)service中的調(diào)用方式 配置完啟動(dòng)三個(gè)服務(wù),進(jìn)入nacos查看 訪問(wèn)borrow服務(wù),此時(shí)已經(jīng)加入了nacos、負(fù)載均衡。其中主要是要加入依賴和在WebConfiguration方法上加入@LoadBalanced注解
3.4.1 加入openfeign
在borrow服務(wù)添加依賴 在borrow服務(wù)下寫(xiě)客戶端
@FeignClient("book-service")
public interface BookClient {
@RequestMapping("/book/{bid}")
Book getBookById(@PathVariable("bid") int bid);
}
@FeignClient("user-service")
public interface UserClient {
@RequestMapping("/user/{uid}")
User getUserById(@PathVariable("uid") int uid);
}
在borrowServiceImpl直接注入 啟動(dòng)類加入注解 此時(shí)openfeign訪問(wèn)沒(méi)有攜帶token。還會(huì)出現(xiàn)之前的訪問(wèn)borrow服務(wù)出錯(cuò)。此時(shí)需要添加配置。
feign:
oauth2:
#開(kāi)啟Oauth支持,這樣就會(huì)在請(qǐng)求頭中攜帶Token了
enabled: true
#同時(shí)開(kāi)啟負(fù)載均衡支持
load-balanced: true
重新啟動(dòng)borrow-service,此時(shí)接口調(diào)用帶有token,則訪問(wèn)成功 但是每次訪問(wèn)一次就需要校驗(yàn)一次,若用戶多了,服務(wù)器壓力就很大。
3.5 使用JWT存儲(chǔ)Token
官網(wǎng)https://jwt.io JWT令牌的格式如下:
Base64不是加密算法,只是一種信息的編碼方式。
public void test(){
String str = "你們可能不知道只用20萬(wàn)贏到578萬(wàn)是什么概念";
//Base64不只是可以對(duì)字符串進(jìn)行編碼,任何byte[]數(shù)據(jù)都可以,編碼結(jié)果可以是byte[],也可以是字符串
String encodeStr = Base64.getEncoder().encodeToString(str.getBytes());
System.out.println("Base64編碼后的字符串:"+encodeStr);
System.out.println("解碼后的字符串:"+new String(Base64.getDecoder().decode(encodeStr)));
}
這里使用最簡(jiǎn)單的一種方式,對(duì)稱密鑰,對(duì)驗(yàn)證服務(wù)器進(jìn)行修改:
@Bean
public JwtAccessTokenConverter tokenConverter(){ //Token轉(zhuǎn)換器,將其轉(zhuǎn)換為JWT
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("lbwnb"); //這個(gè)是對(duì)稱密鑰,一會(huì)資源服務(wù)器那邊也要指定為這個(gè)
return converter;
}
@Bean
public TokenStore tokenStore(JwtAccessTokenConverter converter){ //Token存儲(chǔ)方式現(xiàn)在改為JWT存儲(chǔ)
return new JwtTokenStore(converter); //傳入剛剛定義好的轉(zhuǎn)換器
}
@Resource
TokenStore store;
@Resource
JwtAccessTokenConverter converter;
private AuthorizationServerTokenServices serverTokenServices(){ //這里對(duì)AuthorizationServerTokenServices進(jìn)行一下配置
DefaultTokenServices services = new DefaultTokenServices();
services.setSupportRefreshToken(true); //允許Token刷新
services.setTokenStore(store); //添加剛剛的TokenStore
services.setTokenEnhancer(converter); //添加Token增強(qiáng),其實(shí)就是JwtAccessTokenConverter,增強(qiáng)是添加一些自定義的數(shù)據(jù)到JWT中
return services;
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
.tokenServices(serverTokenServices()) //設(shè)定為剛剛配置好的AuthorizationServerTokenServices
.userDetailsService(service)
.authenticationManager(manager);
}
重啟auth-service服務(wù),通過(guò)密碼模式獲取token,此時(shí)返回的token是JWT令牌。可對(duì)其進(jìn)行Base64解碼 生成的access_token用base64解碼查看,需要在".“前后分開(kāi)解碼。 第一段為jwt 第二段用戶信息
然后對(duì)資源服務(wù)器進(jìn)行配置;
security:
oauth2:
resource:
jwt:
key-value: lbwnb #注意這里要跟驗(yàn)證服務(wù)器的密鑰一致,這樣算出來(lái)的簽名才會(huì)一致
然后在book-service,user-service都進(jìn)行以上相應(yīng)的修改,修改完重啟對(duì)應(yīng)服務(wù),并加上token進(jìn)行訪問(wèn) user,book服務(wù)訪問(wèn)正常
柚子快報(bào)邀請(qǐng)碼778899分享:常用中間件-OAuth2
參考鏈接
本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點(diǎn)和立場(chǎng)。
轉(zhuǎn)載請(qǐng)注明,如有侵權(quán),聯(lián)系刪除。