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



前言

《Spring Microservices in Action》
《Spring Cloud Alibaba 微服務原理與實戰》
《B站 尚硅谷 SpringCloud 框架開發教程 周陽》

OAuth2 是一個基於令牌的安全驗證和授權框架。他允許用戶使用第三方驗證服務進行驗證。 如果用戶成功進行了驗證, 則會出示一個令牌,該令牌必須與每個請求一起發送。然后,驗證服務可以對令牌進行確認;


1. OAuth2 基礎知識

1.1 安全性的 4 個組成部分

  • 受保護資源:Resource Server,開發人員想要保護的資源(如一個微服務),需要確保只有已通過驗證並且具有適當授權的用戶才能訪問它;
  • 資源所有者:Resource Owner,資源所有者定義哪些應用程序可以調用其服務,哪些用戶可以訪問該服務,以及他們可以使用該服務完成哪些事情。 資源所有者注冊的每個應用程序都將獲得一個應用程序名稱,該應用程序名稱與應用程序密鑰一起標識應用程序。 應用程序名稱和密鑰的組合是在驗證 OAuth2 令牌時傳遞的憑據的一部分;
  • 應用程序:Client,這是代表用戶調用服務的應用程序。畢竟,用戶很少直接調用服務 。相反,他們依賴應用程序為他們工作。
  • OAuth2 驗證服務器:Authorization Server,OAuth2 驗證服務器是應用程序和正在使用的服務之間的中間人。 OAuth2 驗證服務器允許用戶對自己進行驗證,而不必將用戶憑據傳遞給由應用程序代表用戶調用的每個服務;

1.2 OAuth2 的工作原理

  • 第三方客戶端資源所有者(用戶)申請認證請求;
  • 【關鍵】用戶同意請求,返回一個許可;
  • 客戶端根據許可向認證服務器申請認證令牌 Token;
  • 客戶端根據認證令牌向資源服務器申請相關資源;

Oauth執行流程

1.3 OAuth2 規范的 4 種類型的授權

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

1.4 OAuth2 的優勢

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

1.5 OAuth2 核心原理

  • 先有一個 OAuth2 認證服務器,用來創建和管理 OAuth2 訪問令牌;
  • 接着在受保護資源主程序類上添加一個注解:@EnableResourceServer,該注解會強制執行一個過濾器,該過濾器會攔截對服務的所有傳入調用,檢查傳入調用的 HTTP 首部中是否存在 OAuth2 訪問令牌,然后調用 security.oauth2.resource.userInfoUri 中定義的回調 URL 告訴客戶端與 OAuth2 認證服務器交互,查看令牌是否有效;
  • 一旦獲悉令牌是有效的,@EnableResourceServer 注解也會應用任何訪問控制規則,以控制什么人可以訪問服務;

1.6 JSON Web Token


2. 建立 OAuth2 服務器

  • 驗證服務將驗證用戶憑據並頒發令牌;
  • 每當用戶嘗試訪問由,如正服務保護的服務時,驗證服務將確認 OAuth2 令牌是否已由其頒發並且尚未過期;

2.1 引入 pom.xml 依賴文件

<!--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>

2.2 主程序類上添加注解

  • @EnableAuthorizationServer:該服務將作為 OAuth2 服務;
  • @EnableResourceServer:表示該服務是受保護資源;(該注解在 3.3 詳解)

2.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;
}

2.4 定義哪些應用程序可以使用服務

在 config 包下;

  • ClientDetailsServiceConfigurer 支持兩種類型的儲存:內存存儲和JDBC存儲,如下分點所示:

2.4.1 使用 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 = ?";
}

2.4.2 使用內存儲存

@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);
    }
}

2.5 為應用程序定義用戶 ID、密碼和角色

在 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 用戶;

2.6 通過發送 POST 請求驗證用戶

  • 發送:POST http://localhost:8901/auth/oauth/token
  • 並在 POST 的請求體里帶上應用程序名稱、密鑰、用戶 ID 和密碼,可以模擬用戶獲取 OAuth2 令牌;

3. 使用 OAuth2 建立並保護服務資源

  • 創建和管理 OAuth2 訪問令牌是 OAuth2 服務器的職責;
  • 定義哪些用戶角色有權執行哪些操作在單個服務級別上的;

3.1 引入 pom.xml 依賴文件

<!--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>

3.2 添加 bootstrap.yml 配置文件

security:
  oauth2:
   resource:
      userInfoUri: http://localhost:8901/auth/user
  • 這里添加回調 URL,客戶端訪問受保護服務時,受保護服務將調用 /auth/user 端點,向 OAuth2 服務器檢查訪問令牌是否生效;

3.3 在主程序類上添加注解

  • @EnableResourceServer:表示該服務是受保護資源;
  • 該注解會強制執行一個過濾器,該過濾器會攔截對服務的所有傳入調用,檢查傳入調用的 HTTP 首部中是否存在 OAuth2 訪問令牌,然后調用 security.oauth2.resource.userInfoUri 中定義的回調 URL 來查看令牌是否有效;
  • 一旦獲悉令牌是有效的,@EnableResourceServer 注解也會應用任何訪問控制規則,以控制什么人可以訪問服務;
@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);
    }
}

3.4 定義訪問控制規則

在 config 包或 security 包下;

  • 要定義訪問控制規則,需要擴展 ResourceServerConfigurerAdapter 類井覆蓋 configure() 方法;
  • 有多種定義方法,這里給出常見的兩種定義示例:

3.4.1 通過驗證用戶保護服務

  • 即:只由已通過身份驗證的用戶訪問;
//必須使用該注解,且需要擴展 ResourceServerConfigurerAdapter 類
@Configuration
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
    //訪問規則在 configure() 方法中定義,並且通過傳入方法的 HttpSecurity 對象配置
    @Override
    public void configure(HttpSecurity http) throws Exception{
        http.authorizeRequests().anyRequest().authenticated();
    }
}
  • anyRequest().authenticated() 表示需要由已通過驗證的用戶訪問;

3.4.2 通過特定角色保護服務

  • 限制只有 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 用戶訪問資源將被通過;

4. 在上下游服務中傳播 OAuth2 訪問令牌

傳播 OAuth2 訪問令牌

  • 用戶已經向 OAuth2 服務器進行了驗證,調用 EagleEye Web 客戶端;
  • EagleEye Web 應用程序( OAuth2 服務器)將通過 HTTP 首都 Authorization 添加 OAuth2 訪問令牌;
  • Zuul 將查找許可證服務端點,然后將調用轉發到其中一個許可證服務的服務器;
  • 服務網關需要從傳入的調用中復制 HTTP 首部 Authorization
  • 受保護服務使用 OAuth2 服務器確認令牌;

4.1 配置服務網關的黑名單

在 Zuul 的 application.yml 的配置文件里;

  • 因為在整個驗證流程中,我們需要將 HTTP 首部 Authorization 傳遞上下游進行權限認證;

  • 但在默認情況下,Zuul 不會將敏感的 HTTP 首部(如 Cookie、Set-Cokkie 和 Authorization)轉發到下游服務;

  • 需要配置 Zuul 的黑名單放行 Authorization;

    zuul:
      sensitiveHeaders: Cookie , Set-Cookie
    
  • 上述配置表示攔截 Cookie , Set-Cookie 傳遞下游,而 Authorization 會放行;

4.2 修改上游服務業務代碼

  • 業務代碼需要保證將 HTTP 首部 Authorization 注入服務的上下游;

4.2.1 下游服務

  • 這里的下游服務就是受保護的服務;
  • 其構建方法同本篇的《3. 使用 OAuth2 建立並保護服務資源》

4.2.2 在上游服務中公開 OAuth2RestTemplate 類

可以在主程序類上,也可以在主程序所在包及其子包里創建類;

  • 使該類可以被自動裝配到調用另一個受 OAuth2 保護的服務;

    @Bean
    public OAuth2RestTemplate oauth2RestTemplate(OAuth2ClientContext oauth2ClientContext, OAuth2ProtectedResourceDetails details) {
        return new OAuth2RestTemplate(details, oauth2ClientContext);
    }
    

4.2.3 在上游服務中用 OAuth2RestTemplate 來傳播 OAuth2 訪問令牌

  • 自動裝配 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();
    }
}

最后

新人制作,如有錯誤,歡迎指出,感激不盡!
歡迎關注公眾號,會分享一些更日常的東西!
如需轉載,請標注出處!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM