目錄
- 前言
- 1. OAuth2 基礎知識
- 1.1 安全性的 4 個組成部分
- 1.2 OAuth2 的工作原理
- 1.3 OAuth2 規範的 4 種類型的授權
- 1.4 OAuth2 的優勢
- 1.5 OAuth2 核心原理
- 1.6 JSON Web Token
- 2. 建立 OAuth2 伺服器
- 2.1 引入 pom.xml 依賴檔案
- 2.2 主程式類上添加注解
- 2.3 添加受保護對象的端點
- 2.4 定義哪些應用程式可以使用服務
- 2.4.1 使用 JDBC 存儲
- 2.4.2 使用記憶體儲存
- 2.5 為應用程式定義使用者 ID、密碼和角色
- 2.6 通過發送 POST 請求驗證使用者
- 3. 使用 OAuth2 建立并保護服務資源
- 3.1 引入 pom.xml 依賴檔案
- 3.2 添加 bootstrap.yml 配置檔案
- 3.3 在主程式類上添加注解
- 3.4 定義通路控制規則
- 3.4.1 通過驗證使用者保護服務
- 3.4.2 通過特定角色保護服務
- 4. 在上下遊服務中傳播 OAuth2 通路令牌
- 4.1 配置服務網關的黑名單
- 4.2 修改上遊服務業務代碼
- 4.2.1 下遊服務
- 4.2.2 在上遊服務中公開 OAuth2RestTemplate 類
- 4.2.3 在上遊服務中用 OAuth2RestTemplate 來傳播 OAuth2 通路令牌
- 最後
《Spring Microservices in Action》
《Spring Cloud Alibaba 微服務原理與實戰》
《B站 尚矽谷 SpringCloud 架構開發教程 周陽》
OAuth2 是一個基于令牌的安全驗證和授權架構。他允許使用者使用第三方驗證服務進行驗證。 如果使用者成功進行了驗證, 則會出示一個令牌,該令牌必須與每個請求一起發送。然後,驗證服務可以對令牌進行确認;
- 受保護資源:Resource Server,開發人員想要保護的資源(如一個微服務),需要確定隻有已認證驗證并且具有适當授權的使用者才能通路它;
- 資源所有者:Resource Owner,資源所有者定義哪些應用程式可以調用其服務,哪些使用者可以通路該服務,以及他們可以使用該服務完成哪些事情。 資源所有者注冊的每個應用程式都将獲得一個應用程式名稱,該應用程式名稱與應用程式密鑰一起辨別應用程式。 應用程式名稱和密鑰的組合是在驗證 OAuth2 令牌時傳遞的憑據的一部分;
- 應用程式:Client,這是代表使用者調用服務的應用程式。畢竟,使用者很少直接調用服務 。相反,他們依賴應用程式為他們工作。
- OAuth2 驗證伺服器:Authorization Server,OAuth2 驗證伺服器是應用程式和正在使用的服務之間的中間人。 OAuth2 驗證伺服器允許使用者對自己進行驗證,而不必将使用者憑據傳遞給由應用程式代表使用者調用的每個服務;
- 第三方用戶端向資源所有者(使用者)申請認證請求;
- 【關鍵】使用者同意請求,傳回一個許可;
- 用戶端根據許可向認證伺服器申請認證令牌 Token;
- 用戶端根據認證令牌向資源伺服器申請相關資源;
- 密碼( password ) ;
- 用戶端憑據( client credential ) ;;
- 授權碼( authorization code) ;
- 隐式( imp licit );
- 允許開發人員輕松與第三方雲服務提供商內建,并使用這些服務進行使用者驗證和授權,而無須不斷地将使用者的憑據傳遞給第三方服務;
- 先有一個 OAuth2 認證伺服器,用來建立和管理 OAuth2 通路令牌;
- 接着在受保護資源主程式類上添加一個注解:@EnableResourceServer,該注解會強制執行一個過濾器,該過濾器會攔截對服務的所有傳入調用,檢查傳入調用的 HTTP 首部中是否存在 OAuth2 通路令牌,然後調用
中定義的回調 URL 告訴用戶端與 OAuth2 認證伺服器互動,檢視令牌是否有效;security.oauth2.resource.userInfoUri
- 一旦獲悉令牌是有效的,@EnableResourceServer 注解也會應用任何通路控制規則,以控制什麼人可以通路服務;
- 考慮到篇幅有限,JWT 相關令牌存儲将在《微服務架構 | 7.2 建構使用 JWT 令牌存儲的 OAuth2 安全認證》中講解;
- 驗證服務将驗證使用者憑據并頒發令牌;
- 每當使用者嘗試通路由,如正服務保護的服務時,驗證服務将确認 OAuth2 令牌是否已由其頒發并且尚未過期;
<!--security 通用安全庫-->
<dependency>
<groupid>org.springframework.cloud</groupid>
<artifactid>spring-cloud-security</artifactid>
</dependency>
<!--oauth2.0-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
- @EnableAuthorizationServer:該服務将作為 OAuth2 服務;
- @EnableResourceServer:表示該服務是受保護資源;(該注解在 3.3 詳解)
在 controller 包下;
- 該端點将映射到
端點,當受保護的服務調用/auth/user
時,将會确認 OAuth2 通路令牌,并檢索發文手背歐虎服務所配置設定的角色;/auth/user
/**
* 使用者資訊校驗
* 由受保護服務調用,确認 OAuth2 通路令牌,并檢索通路受保護服務的使用者所配置設定的角色
* @param OAuth2Authentication 資訊
* @return 使用者資訊
*/
@RequestMapping(value = { "/user" }, produces = "application/json")
public Map<String, Object> user(OAuth2Authentication user) {
Map<String, Object> userInfo = new HashMap<>();
userInfo.put("user", user.getUserAuthentication().getPrincipal());
userInfo.put("authorities", AuthorityUtils.authorityListToSet(user.getUserAuthentication().getAuthorities()));
return userInfo;
}
在 config 包下;
-
支援兩種類型的儲存:記憶體存儲和JDBC存儲,如下分點所示:ClientDetailsServiceConfigurer
- OAuth2Config 類:
@Configuration
//繼承 AuthorizationServerConfigurerAdapter 類
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {
@Autowired
private DataSource dataSource;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserDetailsService userDetailsService;
@Override
//定義哪些用戶端将注冊到服務
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
//JDBC存儲:
JdbcClientDetailsService clientDetailsService = new JdbcClientDetailsService(dataSource);
clientDetailsService.setSelectClientDetailsSql(SecurityConstants.DEFAULT_SELECT_STATEMENT); //設定我們的自定義的sql查找語句
clientDetailsService.setFindClientDetailsSql(SecurityConstants.DEFAULT_FIND_STATEMENT); //設定我們的自定義的sql查找語句
clients.withClientDetails(clientDetailsService); //從 jdbc 查出資料來存儲
}
@Override
//使用 Spring 提供的預設驗證管理器和使用者詳細資訊服務
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService);
}
}
- SecurityConstants 類:裡面存放上述提到的 SQL 查詢語句;
public interface SecurityConstants {
/**
* sys_oauth_client_details 表的字段,不包括client_id、client_secret
*/
String CLIENT_FIELDS = "client_id, client_secret, resource_ids, scope, "
+ "authorized_grant_types, web_server_redirect_uri, authorities, access_token_validity, "
+ "refresh_token_validity, additional_information, autoapprove";
/**
*JdbcClientDetailsService 查詢語句
*/
String BASE_FIND_STATEMENT = "select " + CLIENT_FIELDS + " from sys_oauth_client_details";
/**
* 預設的查詢語句
*/
String DEFAULT_FIND_STATEMENT = BASE_FIND_STATEMENT + " order by client_id";
/**
* 按條件client_id 查詢
*/
String DEFAULT_SELECT_STATEMENT = BASE_FIND_STATEMENT + " where client_id = ?";
}
@Configuration
//繼承 AuthorizationServerConfigurerAdapter 類
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserDetailsService userDetailsService;
@Override
//定義哪些用戶端将注冊到服務
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("eagleeye") //名稱
.secret("thisissecret") //密鑰
.authorizedGrantTypes("refresh_token", "password", "client_credentials") //授權類型清單
.scopes("webclient", "mobileclient"); //擷取通路令牌時可以操作的範圍
}
@Override
//使用 Spring 提供的預設驗證管理器和使用者詳細資訊服務
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService);
}
}
在 config 包下:
- 可以從記憶體資料存儲、支援 JDBC 的關系資料庫或 LDAP 伺服器中存儲和檢索使用者資訊;
@Configuration
@EnableWebSecurity
//擴充核心 Spring Security 的 WebSecurityConfigurerAdapter
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
//用來處理驗證
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
//處理傳回使用者資訊
@Override
@Bean
public UserDetailsService userDetailsServiceBean() throws Exception {
return super.userDetailsServiceBean();
}
//configure() 方法定義使用者、密碼與角色
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("john.carnell").password("password1").roles("USER")
.and()
.withUser("william.woodward").password("password2").roles("USER", "ADMIN");
}
}
- 上述例子中
使用者擁有 USER 使用者;john.carnell
-
擁有 ADMIN 使用者;william.woodward
- 發送:POST
;http://localhost:8901/auth/oauth/token
- 并在 POST 的請求體裡帶上應用程式名稱、密鑰、使用者 ID 和密碼,可以模拟使用者擷取 OAuth2 令牌;
- 建立和管理 OAuth2 通路令牌是 OAuth2 伺服器的職責;
- 定義哪些使用者角色有權執行哪些操作在單個服務級别上的;
<!--security 通用安全庫-->
<dependency>
<groupid>org.springframework.cloud</groupid>
<artifactid>spring-cloud-security</artifactid>
</dependency>
<!--oauth2.0-->
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
</dependency>
security:
oauth2:
resource:
userInfoUri: http://localhost:8901/auth/user
- 這裡添加回調 URL,用戶端通路受保護服務時,受保護服務将調用
端點,向 OAuth2 伺服器檢查通路令牌是否生效;/auth/user
- @EnableResourceServer:表示該服務是受保護資源;
- 該注解會強制執行一個過濾器,該過濾器會攔截對服務的所有傳入調用,檢查傳入調用的 HTTP 首部中是否存在 OAuth2 通路令牌,然後調用
中定義的回調 URL 來檢視令牌是否有效;security.oauth2.resource.userInfoUri
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker //斷路器
@EnableResourceServer //表示受保護資源
public class Application {
//注入一個過濾器,會攔截對服務的所有傳入調用
@Bean
public Filter userContextFilter() {
UserContextFilter userContextFilter = new UserContextFilter();
return userContextFilter;
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
在 config 包或 security 包下;
- 要定義通路控制規則,需要擴充 ResourceServerConfigurerAdapter 類井覆寫
方法;configure()
- 有多種定義方法,這裡給出常見的兩種定義示例:
- 即:隻由已認證身份驗證的使用者通路;
//必須使用該注解,且需要擴充 ResourceServerConfigurerAdapter 類
@Configuration
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
//通路規則在 configure() 方法中定義,并且通過傳入方法的 HttpSecurity 對象配置
@Override
public void configure(HttpSecurity http) throws Exception{
http.authorizeRequests().anyRequest().authenticated();
}
}
-
表示需要由已認證驗證的使用者通路;anyRequest().authenticated()
- 限制隻有 ADMIN 使用者才能調用該服務的 DELETE 方法;
@Configuration
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception{
http
.authorizeRequests()
.antMatchers(HttpMethod.DELETE, "/v1/xxxservices/**") //運作部開發人員限制對受保護的 URL 和 HTTP DELETE 動詞的調用
.hasRole("ADMIN") //允許通路的角色清單
.anyRequest()
.authenticated();
}
}
-
表示仍需要由已認證驗證的使用者通路;anyRequest().authenticated()
- 結合本篇《2.5 為應用程式定義使用者 ID、密碼和角色》的示例,這裡使用 john.carnell USER 使用者通路資源将被拒絕,而使用 william.woodward ADMIN 使用者通路資源将被通過;
- 使用者已經向 OAuth2 伺服器進行了驗證,調用 EagleEye Web 用戶端;
- EagleEye Web 應用程式( OAuth2 伺服器)将通過 HTTP 首都 Authorization 添加 OAuth2 通路令牌;
- Zuul 将查找許可證服務端點,然後将調用轉發到其中一個許可證服務的伺服器;
- 服務網關需要從傳入的調用中複制 HTTP 首部 Authorization;
- 受保護服務使用 OAuth2 伺服器确認令牌;
在 Zuul 的 application.yml 的配置檔案裡;
- 因為在整個驗證流程中,我們需要将 HTTP 首部 Authorization 傳遞上下遊進行權限認證;
- 但在預設情況下,Zuul 不會将敏感的 HTTP 首部(如 Cookie、Set-Cokkie 和 Authorization)轉發到下遊服務;
- 需要配置 Zuul 的黑名單放行 Authorization;
zuul: sensitiveHeaders: Cookie , Set-Cookie
- 上述配置表示攔截 Cookie , Set-Cookie 傳遞下遊,而 Authorization 會放行;
- 業務代碼需要保證将 HTTP 首部 Authorization 注入服務的上下遊;
- 這裡的下遊服務就是受保護的服務;
- 其建構方法同本篇的《3. 使用 OAuth2 建立并保護服務資源》
可以在主程式類上,也可以在主程式所在包及其子包裡建立類;
- 使該類可以被自動裝配到調用另一個受 OAuth2 保護的服務;
@Bean public OAuth2RestTemplate oauth2RestTemplate(OAuth2ClientContext oauth2ClientContext, OAuth2ProtectedResourceDetails details) { return new OAuth2RestTemplate(details, oauth2ClientContext); }
- 自動裝配 OAuth2RestTemplate;
@Component
public class OrganizationRestTemplateClient {
//OAuth2RestTemplate 是标準的 RestTemplate 的增強式替代品,可處理 OAuth2 通路令牌
@Autowired
OAuth2RestTemplate restTemplate;
public Organization getOrganization(String organizationId){
//調用組織服務的方式與标準的 RestTemplate 完全相同
ResponseEntity<Organization> restExchange =
restTemplate.exchange(
"http://zuulserver:5555/api/organization/v1/organizations/{organizationId}",
HttpMethod.GET,
null, Organization.class, organizationId);
return restExchange.getBody();
}
}
新人制作,如有錯誤,歡迎指出,感激不盡!
歡迎關注公衆号,會分享一些更日常的東西!
如需轉載,請标注出處!