本文介紹瞭如何使用Spring Security OAuth2構建一個授權服務器來驗證用戶身份以提供access_token,並使用這個access_token來從資源服務器請求數據。
1.概述
OAuth2是一種授權方法,用於通過HTTP協議提供對受保護資源的訪問。首先,OAuth2使第三方應用程序能夠獲得對HTTP服務的有限訪問權限,然後通過資源所有者和HTTP服務之間的批准交互來讓第三方應用程序代表資源所有者獲取訪問權限。
1.1 角色
OAuth定義了四個角色
- 資源所有者 - 應用程序的用戶。
- 客戶端 - 需要訪問資源服務器上的用戶數據的應用程序。
- 資源服務器 - 存儲用戶數據和http服務,可以將用戶數據返回給經過身份驗證的客戶端。
- 授權服務器 - 負責驗證用戶的身份並提供授權令牌。資源服務器接受此令牌並驗證您的身份。
OAuth2的交互過程:
1.2 訪問令牌與刷新令牌
訪問令牌代表一個向客戶授權的字符串。令牌包含了由資源所有者授予的權限範圍和訪問持續時間,並由資源服務器和授權服務器強制執行。
授權服務器向客戶端發出刷新令牌,用於在當前訪問令牌失效或過期時獲取新的訪問令牌,或獲取具有相同或更窄範圍的其他訪問令牌,新的訪問令牌可能具有比資源所有者授權的更短的生命週期和更少的權限。根據授權服務器的判斷,發佈刷新令牌是可選的。
訪問令牌的責任是在數據到期之前訪問它。
刷新令牌的責任是在現有訪問令牌過期時請求新的訪問令牌。
2. OAuth2 - 授權服務器
要使用spring Security OAuth2模塊創建授權服務器,我們需要使用註解@EnableAuthorizationServer並擴展AuthorizationServerConfigurerAdapter類。
@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServer extends AuthorizationServerConfigurerAdapter {
@Autowired
private BCryptPasswordEncoder passwordEncoder;
@Override
public void configure(AuthorizationServerSecurityConfigurer security)
throws Exception {
security.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()")
.allowFormAuthenticationForClients();
}
@Override
public void configure(ClientDetailsServiceConfigurer clients)
throws Exception {
clients.inMemory().withClient("clientapp")
.secret(passwordEncoder.encode("654321"))
.authorizedGrantTypes("password", "authorization_code",
"refresh_token")
.authorities("READ_ONLY_CLIENT").scopes("read_user_info")
.resourceIds("oauth2-resource")
.redirectUris("http://localhost:8081/login")
.accessTokenValiditySeconds(5000)
.refreshTokenValiditySeconds(50000);
}
}
Spring Security OAuth2會公開了兩個端點,用於檢查令牌(/oauth/check_token和/oauth/token_key),這些端點默認受保護denyAll()。tokenKeyAccess()和checkTokenAccess()方法會打開這些端點以供使用。
-
ClientDetailsServiceConfigurer用於定義客戶端詳細的服務信息,它可以在內存中或在數據庫中定義。
在本例中我們使用了內存實現。它具有以下重要屬性:
- clientId - (必需)客戶端ID。
- secret - (可信客戶端所需)客戶端密鑰(可選)。
- scope - 客戶受限的範圍。如果範圍未定義或爲空(默認值),則客戶端不受範圍限制。
- authorizedGrantTypes - 授權客戶端使用的授權類型。默認值爲空。
- authorities - 授予客戶的權限(常規Spring Security權限)。
- redirectUris - 將用戶代理重定向到客戶端的重定向端點。它必須是絕對URL。
3. OAuth2 - 資源服務器
要創建資源服務器組件,請使用@EnableResourceServer註解並擴展ResourceServerConfigurerAdapter類。
@Configuration
@EnableResourceServer
public class OAuth2ResourceServer extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/api/**").authenticated();
}
}
以上配置啓用/api下所有端點的保護,但可以自由訪問其他端點。
資源服務器還提供了一種對用戶自己進行身份驗證的機制。在大多數情況下,它通常是基於表單的登錄。
@Configuration
@Order(1)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**")
.requestMatchers()
.antMatchers("/oauth/authorize**", "/login**", "/error**")
.and()
.authorizeRequests().anyRequest().authenticated()
.and()
.formLogin().permitAll();
}
@Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.inMemoryAuthentication().withUser("peterwanghao")
.password(passwordEncoder().encode("123456")).roles("USER");
}
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
4. OAuth2保護的REST資源
本例中只創建了一個RESTful API,它返回登錄用戶的姓名和電子郵件。
@Controller
public class RestResource {
@RequestMapping("/api/users/me")
public ResponseEntity<UserInfo> profile() {
User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
String email = user.getUsername() + "@126.com";
UserInfo profile = new UserInfo();
profile.setName(user.getUsername());
profile.setEmail(email);
return ResponseEntity.ok(profile);
}
}
public class UserInfo {
private String name;
private String email;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
@Override
public String toString() {
return "User [name=" + name + ", email=" + email + "]";
}
}
5.演示
我們有一個API http://localhost:8080/api/users/me ,我們想通過第三方應用程序來訪問API需要OAuth2令牌。
5.1 從用戶獲取授權許可代碼
如上面的序列圖所示,第一步是從URL獲取資源所有者的授權:
http://localhost:8080/oauth/authorize?client_id=clientapp&response_type=code&scope=read_user_info
通過瀏覽器訪問上面的URL地址,它將展現一個登錄頁面。提供用戶名和密碼。對於此示例,請使用“peterwanghao”和“123456”。
登錄後,您將被重定向到授予訪問頁面,您可以在其中選擇授予對第三方應用程序的訪問權限。
它會重定向到URL,如:http://localhost:8081/login?code=TUXuk9 。這裏'TUXuk9'是第三方應用程序的授權代碼。
5.2 從授權服務器獲取訪問令牌
現在,應用程序將使用授權碼來獲取訪問令牌。在這裏,我們需要提出以下請求。使用此處第一步中獲得的代碼。
curl -X POST --user clientapp:654321 http://localhost:8080/oauth/token -H "content-type: application/x-www-form-urlencoded" -d "code=TUXuk9&grant_type=authorization_code&redirect_uri=http://localhost:8081/login&scope=read_user_info"
訪問令牌響應
{
"access_token": "168aad01-05dc-4446-9fba-fd7dbe8adb9e",
"token_type": "bearer",
"refresh_token": "34065175-1e92-4bb0-918c-a5a6ece1dc5f",
"expires_in": 4999,
"scope": "read_user_info"
}
5.3 從資源服務器訪問用戶數據
一旦我們有了訪問令牌,我們就可以轉到資源服務器來獲取受保護的用戶數據。
curl -X GET http://localhost:8080/api/users/me -H "authorization: Bearer 168aad01-05dc-4446-9fba-fd7dbe8adb9e"
獲得資源響應
{
"name":"peterwanghao",
"email":"[email protected]"
}
總結
本文講解了OAuth2授權框架的實現機制,通過一個例子說明了第三方應用程序如何通過授權服務器的授權去資源服務器上獲取受保護的數據。本例的完整代碼在GitHub上。