天天看點

微服務架構 | 7.1 基于 OAuth2 的安全認證

目錄

  • 前言
  • 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;
  • 用戶端根據認證令牌向資源伺服器申請相關資源;
微服務架構 | 7.1 基于 OAuth2 的安全認證

  • 密碼( password ) ;
  • 用戶端憑據( client credential ) ;;
  • 授權碼( authorization code) ;
  • 隐式( imp licit );

  • 允許開發人員輕松與第三方雲服務提供商內建,并使用這些服務進行使用者驗證和授權,而無須不斷地将使用者的憑據傳遞給第三方服務;

  • 先有一個 OAuth2 認證伺服器,用來建立和管理 OAuth2 通路令牌;
  • 接着在受保護資源主程式類上添加一個注解:@EnableResourceServer,該注解會強制執行一個過濾器,該過濾器會攔截對服務的所有傳入調用,檢查傳入調用的 HTTP 首部中是否存在 OAuth2 通路令牌,然後調用

    security.oauth2.resource.userInfoUri

    中定義的回調 URL 告訴用戶端與 OAuth2 認證伺服器互動,檢視令牌是否有效;
  • 一旦獲悉令牌是有效的,@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

    端點,當受保護的服務調用

    /auth/user

    時,将會确認 OAuth2 通路令牌,并檢索發文手背歐虎服務所配置設定的角色;
/**
 * 使用者資訊校驗
 * 由受保護服務調用,确認 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 包下;
  • ClientDetailsServiceConfigurer

    支援兩種類型的儲存:記憶體存儲和JDBC存儲,如下分點所示:

  • 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");
    }
}
           
  • 上述例子中

    john.carnell

    使用者擁有 USER 使用者;
  • william.woodward

    擁有 ADMIN 使用者;

  • 發送: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,用戶端通路受保護服務時,受保護服務将調用

    /auth/user

    端點,向 OAuth2 伺服器檢查通路令牌是否生效;

  • @EnableResourceServer:表示該服務是受保護資源;
  • 該注解會強制執行一個過濾器,該過濾器會攔截對服務的所有傳入調用,檢查傳入調用的 HTTP 首部中是否存在 OAuth2 通路令牌,然後調用

    security.oauth2.resource.userInfoUri

    中定義的回調 URL 來檢視令牌是否有效;
@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 使用者通路資源将被通過;

微服務架構 | 7.1 基于 OAuth2 的安全認證
  • 使用者已經向 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();
    }
}
           

新人制作,如有錯誤,歡迎指出,感激不盡!

歡迎關注公衆号,會分享一些更日常的東西!

如需轉載,請标注出處!

繼續閱讀