當授權過程涉及使用者,Password Credentials 核發流程是最簡單也容易理解的類型,使用者運用名稱、密碼來請求核發 Access Token,基本上是傳統驗證授權的延伸,運用 Password Credentials 核發流程的情境,通常是客戶端與服務屬於同一個單位,該單位本身就擁有使用者的註冊資訊,由於服務只認同 Access Token 就可以使用,也可用來實現 Single sign-on,也就是一次登入,就可使用各個獨立服務的功能。
想實作 Password Credentials 核發流程中的授權伺服器,起手式基本上與〈Client Credentials 核發流程(一)〉相同,然而,要結合已註冊的使用者名稱、密碼來進行驗證,例如:
package cc.openhome;
...略
@SpringBootApplication
@EnableAuthorizationServer
public class AuthSvrApplication {
...略
@Autowired
private PasswordEncoder passwordEncoder;
@Bean
public WebSecurityConfigurerAdapter webSecurityConfig() {
return new WebSecurityConfigurerAdapter() {
@Override
protected void configure(AuthenticationManagerBuilder builder) throws Exception {
builder.inMemoryAuthentication()
.passwordEncoder(passwordEncoder)
.withUser("caterpillar")
.password(passwordEncoder.encode("12345678"))
.roles("MEMBER");
}
};
}
@Autowired
@Qualifier("webSecurityConfig")
private WebSecurityConfigurerAdapter webSecurityConfigurerAdapter;
@Bean
public AuthorizationServerConfigurer authorizationServerConfigurer() {
return new AuthorizationServerConfigurerAdapter() {
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("memberclient")
.secret(passwordEncoder.encode("memberclient12345678"))
.scopes("message")
.resourceIds("resource")
.authorizedGrantTypes("password", "refresh_token");
}
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.checkTokenAccess("isAuthenticated()");
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(webSecurityConfigurerAdapter.authenticationManagerBean())
.userDetailsService(webSecurityConfigurerAdapter.userDetailsServiceBean());
}
};
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
在上頭可以看到,WebSecurityConfigurerAdapter
實例定義了註冊的使用者名稱與密碼(當然,實際上可能來自資料庫,或者是另一個使用者服務),而在 AuthorizationServerConfigurer
的定義中,定義了客戶端名稱 memberclient
,密鑰 memberclient12345678
,以及 scope、資源 ID 等。
在這邊 authorizedGrantTypes
定義了 password
,這表示使用 Password Credentials 核發流程,至於 refresh_token
,表示在核發 Access Token 時,也核發 Refresh Token,後者的有效期更長(預設 30 天),之後若 Refresh Token 過期時,可以直接使用 Refresh Token 來更新 Access Token,然而不需要再附上使用者名稱、密碼,這可以用來實作自動登入之類的功能,如果沒有定義 refresh_token
,就不會核發 Refresh Token。
為了能驗證使用者名稱與密碼,需要透過 AuthorizationServerEndpointsConfigurer
的 authenticationManager
設定使用 WebSecurityConfigurerAdapter
的 AuthenticationManager
,而為了要能使用 Refresh Token 來更新 Access Token,要使用 userDetailsService
設定使用 WebSecurityConfigurerAdapter
的 userDetailsServiceBean
。
要請求核發 Access Token,同樣地,客戶端以 POST
指定 grant_type
、scope
,以及 username
、password
:
你必須以 BASIC 驗證方式,告知授權伺服器客戶端名稱與密鑰:
在這邊可以看到 access_token
以及 refresh_token
,若要使用 Refresh Token 來更新 Access Token,與請求核發類似,不過 grant_type
指定 refresh_token
,並以 refresh_token
請求參數附上 Refresh Token:
同樣地,你可以如〈Client Credentials 核發流程(二)〉中的方式,拿著 Access Token 請求 oauth/check_token
:
在回傳的 JSON 中包含了使用者角色等相關資訊,資源伺服器若要取得這項資訊,可以透過 OAuth2Authentication
的 getPrincipal
來取得,例如修改〈Client Credentials 核發流程(二)〉中資源伺服器的 hello
方法:
@GetMapping("/hello")
public String hello(OAuth2Authentication oauth) {
return "hello " + oauth.getPrincipal();
}
因為我們修改了客戶端 ID 與密鑰,因此記得 application.properties 也要改一下:
security.oauth2.client.clientId: memberclient
security.oauth2.client.clientSecret: memberclient12345678
security.oauth2.resource.token-info-uri: http://localhost:8081/oauth/check_token
底下是請求資源伺服器的 hello 結果:
你可以在 OAuth2PasswordGrant 找到以上的範例專案。