java構(gòu)建OAuth2授權(quán)服務(wù)器
從表現(xiàn)形式上看,OAuth2 授權(quán)服務(wù)器也是一個獨立的微服務(wù),因此構(gòu)建授權(quán)服務(wù)器的方法也是創(chuàng)建一個 SpringBoot 應(yīng)用程序,我們需要引入對應(yīng)的 Maven 依賴,如下所示:
<dependency> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth2</artifactId></dependency>
這里的 spring-security-oauth2 就是來自 Spring Security 中的 OAuth2 庫。現(xiàn)在 Maven 依賴已經(jīng)添加完畢,下一步就是構(gòu)建 Bootstrap 類作為訪問的入口:
@SpringBootApplication@EnableAuthorizationServerpublic class AuthServer { public static void main(String[] args) {SpringApplication.run(AuthServer.class, args); }}
請注意,這里出現(xiàn)了一個新的注解 @EnableAuthorizationServer,這個注解的作用在于為微服務(wù)運行環(huán)境提供一個基于 OAuth2 協(xié)議的授權(quán)服務(wù),該授權(quán)服務(wù)會暴露一系列基于 RESTful 風格的端點(例如 /oauth/authorize 和 /oauth/token)供 OAuth2 授權(quán)流程使用。
構(gòu)建 OAuth2 授權(quán)服務(wù)只是集成 OAuth2 協(xié)議的第一步,授權(quán)服務(wù)器是一種集中式系統(tǒng),管理著所有與安全性流程相關(guān)的客戶端和用戶信息。因此,接下來我們需要在授權(quán)服務(wù)器中對這些基礎(chǔ)信息進行初始化,而 Spring Security 為我們提供了各種配置類來實現(xiàn)這一目標。
設(shè)置客戶端和用戶認證信息OAuth2.0 有幾個授權(quán)模式:授權(quán)碼模式、簡化模式、密碼模式、客戶端憑證模式。其中,密碼模式以其簡單性得到了廣泛的應(yīng)用。在接下來的內(nèi)容中,我們就以密碼模式為例展開講解。
在密碼模式下,用戶向客戶端提供用戶名和密碼,并將用戶名和密碼發(fā)給授權(quán)服務(wù)器從而請求 Token。授權(quán)服務(wù)器首先會對密碼憑證信息進行認證,確認無誤后,向客戶端發(fā)放 Token。整個流程如下圖所示:
請注意,授權(quán)服務(wù)器在這里執(zhí)行認證操作的目的是驗證傳入的用戶名和密碼是否正確。在密碼模式下,這一步是必需的,如果采用其他授權(quán)模式,不一定會有用戶認證這一環(huán)節(jié)。
確定采用密碼模式后,我們來看為了實現(xiàn)這一授權(quán)模式,需要對授權(quán)服務(wù)器做哪些開發(fā)工作。首先我們需要設(shè)置一些基礎(chǔ)數(shù)據(jù),包括客戶端信息和用戶信息。
設(shè)置客戶端信息我們先來看如何設(shè)置客戶端信息。設(shè)置客戶端時,用到的配置類是 ClientDetailsServiceConfigurer,該配置類用來配置客戶端詳情服務(wù) ClientDetailsService。用于描述客戶端詳情的 ClientDetails 接口則包含了與安全性控制相關(guān)的多個重要方法,該接口中的部分方法定義如下:
public interface ClientDetails extends Serializable { //客戶端唯一性 Id String getClientId(); //客戶端安全碼 String getClientSecret(); //客戶端的訪問范圍 Set<String> getScope(); //客戶端可以使用的授權(quán)模式 Set<String> getAuthorizedGrantTypes(); …}
首先 ClientId 是一個必備屬性,用來唯一標識客戶的 Id,而 ClientSecret 代表客戶端安全碼。這里的 Scope 用來限制客戶端的訪問范圍,如果這個屬性為空,客戶端就擁有全部的訪問范圍。常見的設(shè)置方式可以是 webclient 或 mobileclient,分別代表 Web 端和移動端。
最后,authorizedGrantTypes 代表客戶端可以使用的授權(quán)模式,可選的范圍包括代表授權(quán)碼模式的 authorization_code、代表隱式授權(quán)模式 implicit、代表密碼模式的 password 以及代表客戶端憑據(jù)模式的 client_credentials。這個屬性在設(shè)置上也可以添加 refresh_token,通過刷新操作獲取以上授權(quán)模式下產(chǎn)生的新 Token。
和實現(xiàn)認證過程類似,Spring Security 也提供了 AuthorizationServerConfigurerAdapter 這個配置適配器類來簡化配置類的使用方式。我們可以通過繼承該類并覆寫其中的 configure(ClientDetailsServiceConfigurer clients) 方法進行配置。使用 AuthorizationServerConfigurerAdapter 進行客戶端信息配置的基本代碼結(jié)構(gòu)如下:
@Configurationpublic class SpringAuthorizationServerConfigurer extends AuthorizationServerConfigurerAdapter { @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory().withClient('spring').secret('{noop}spring_secret').authorizedGrantTypes('refresh_token', 'password', 'client_credentials').scopes('webclient', 'mobileclient'); }}
可以看到,我們創(chuàng)建了一個 SpringAuthorizationServerConfigurer 類來繼承 AuthorizationServerConfigurerAdapter,并通過 ClientDetailsServiceConfigurer 配置類設(shè)置了授權(quán)模式為密碼模式。在授權(quán)服務(wù)器中存儲客戶端信息有兩種方式,一種就是如上述代碼所示的基于內(nèi)存級別的存儲,另一種則是通過 JDBC 在數(shù)據(jù)庫中存儲詳情信息。為了簡單起見,這里使用了內(nèi)存級別的存儲方式。
同時我們注意到,在設(shè)置客戶端安全碼時使用了'{noop}spring_secret'這種格式。這是因為在 Spring Security 5 中統(tǒng)一使用 PasswordEncoder 對密碼進行編碼,在設(shè)置密碼時要求格式為“{id}password”。而這里的前綴“{noop}”就是代表具體 PasswordEncoder 的 id,表示我們使用的是 NoOpPasswordEncoder。
@EnableAuthorizationServer 注解會暴露一系列的端點,而授權(quán)過程是使用 AuthorizationEndpoint 這個端點進行控制的。要想對該端點的行為進行配置,你可以使用 AuthorizationServerEndpointsConfigurer 這個配置類。和 ClientDetailsServiceConfigurer 配置類一樣,我們也通過使用 AuthorizationServerConfigurerAdapter 配置適配器類進行配置。
因為我們指定了授權(quán)模式為密碼模式,而密碼模式包含認證環(huán)節(jié)。所以針對 AuthorizationServerEndpointsConfigurer 配置類需要指定一個認證管理器 AuthenticationManager,用于對用戶名和密碼進行認證。同樣因為我們指定了基于密碼的授權(quán)模式,所以需要指定一個自定義的 UserDetailsService 來替換全局的實現(xiàn),可以通過如下代碼來配置 AuthorizationServerEndpointsConfigurer:
@Configurationpublic class SpringAuthorizationServerConfigurer extends AuthorizationServerConfigurerAdapter { @Autowired private AuthenticationManager authenticationManager; @Autowired private UserDetailsService userDetailsService; @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.authenticationManager(authenticationManager).userDetailsService(userDetailsService); } }
至此,客戶端設(shè)置工作全部完成,我們所做的事情就是實現(xiàn)了一個自定義的 SpringAuthorizationServerConfigurer 配置類并覆寫了對應(yīng)的配置方法。
設(shè)置用戶認證信息設(shè)置用戶認證信息所依賴的配置類是 WebSecurityConfigurer 類,Spring Security 同樣提供了 WebSecurityConfigurerAdapter 類來簡化該配置類的使用方式,我們可以繼承 WebSecurityConfigurerAdapter 類并且覆寫其中的 configure() 的方法來完成配置工作。
關(guān)于 WebSecurityConfigurer 配置類,我們首先需要明確配置的內(nèi)容。實際上,設(shè)置用戶信息非常簡單,只需要指定用戶名(User)、密碼(Password)和角色(Role)這三項數(shù)據(jù)即可,如下所示:
@Configurationpublic class SpringWebSecurityConfigurer extends WebSecurityConfigurerAdapter { @Override @Bean public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Override @Bean public UserDetailsService userDetailsServiceBean() throws Exception { return super.userDetailsServiceBean(); } @Override protected void configure(AuthenticationManagerBuilder builder) throws Exception { builder .inMemoryAuthentication() .withUser('user1') .password('{noop}password1') .roles('USER') .and() .withUser('admin') .password('{noop}password2') .roles('USER', 'ADMIN'); }}
結(jié)合上面的代碼,我們看到構(gòu)建了具有不同角色和密碼的兩個用戶,請注意'user1'代表的角色是一個普通用戶,'admin'則具有管理員角色。我們在設(shè)置密碼時,同樣需要添加前綴“{noop}”。同時,我們還看到 authenticationManagerBean()和 userDetailsServiceBean() 方法分別返回了父類的默認實現(xiàn),而這里返回的 UserDetailsService 和 AuthenticationManager 在前面設(shè)置客戶端時會用到。
生成 Token現(xiàn)在,OAuth2 授權(quán)服務(wù)器已經(jīng)構(gòu)建完畢,啟動這個授權(quán)服務(wù)器,我們就可以獲取 Token。我們在構(gòu)建 OAuth2 服務(wù)器時已經(jīng)提到授權(quán)服務(wù)器中會暴露一批端點供 HTTP 請求進行訪問,而獲取 Token 的端點就是http://localhost:2000/oauth/token。在使用該端點時,我們需要提供前面配置的客戶端信息和用戶信息。
這里使用 Postman 來模擬 HTTP 請求,客戶端信息設(shè)置方式如下圖所示:
我們在“Authorization”請求頭中指定認證類型為“Basic Auth”,然后設(shè)置客戶端名稱和客戶端安全碼分別為“spring”和“spring_secret”。
接下來我們指定針對授權(quán)模式的專用配置信息。首先是用于指定授權(quán)模式的 grant_type 屬性,以及用于指定客戶端訪問范圍的 scope 屬性,這里分別設(shè)置為 “password”和“webclient”。既然設(shè)置了密碼模式,所以也需要指定用戶名和密碼用于識別用戶身份,這里,我們以“spring_user”這個用戶為例進行設(shè)置,如下圖所示:
在 Postman 中執(zhí)行這個請求,會得到如下所示的返回結(jié)果:
{ 'access_token':'d2066f68-665b-4038-9dbe-5dd1035e75a0', 'token_type':'bearer', 'refresh_token':'44009836-731c-4e6a-9cc3-274ce3af8c6b', 'expires_in':3599, 'scope':'all'}
可以看到,除了作為請求參數(shù)的 scope,這個返回結(jié)果中還包含了 access_token、token_type、refresh_token 和 expires_in 等屬性。這些屬性都很重要。當然,因為每次請求生成的 Token 都是唯一的,所以你在嘗試時獲取的結(jié)果可能與我的不同。
到此這篇關(guān)于java構(gòu)建OAuth2授權(quán)服務(wù)器的文章就介紹到這了,更多相關(guān)java構(gòu)建OAuth2授權(quán)服務(wù)器內(nèi)容請搜索好吧啦網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持好吧啦網(wǎng)!
相關(guān)文章:
