微服務(wù)架構(gòu) | 7.1 基于 OAuth2 的安全認(rèn)證

前言

《Spring Microservices in Action》
《Spring Cloud Alibaba 微服務(wù)原理與實(shí)戰(zhàn)》
《B站 尚硅谷 SpringCloud 框架開(kāi)發(fā)教程 周陽(yáng)》

OAuth2 是一個(gè)基于令牌的安全驗(yàn)證和授權(quán)框架。他允許用戶(hù)使用第三方驗(yàn)證服務(wù)進(jìn)行驗(yàn)證。 如果用戶(hù)成功進(jìn)行了驗(yàn)證, 則會(huì)出示一個(gè)令牌,該令牌必須與每個(gè)請(qǐng)求一起發(fā)送。然后,驗(yàn)證服務(wù)可以對(duì)令牌進(jìn)行確認(rèn);


1. OAuth2 基礎(chǔ)知識(shí)

1.1 安全性的 4 個(gè)組成部分

  • 受保護(hù)資源:Resource Server,開(kāi)發(fā)人員想要保護(hù)的資源(如一個(gè)微服務(wù)),需要確保只有已通過(guò)驗(yàn)證并且具有適當(dāng)授權(quán)的用戶(hù)才能訪問(wèn)它;
  • 資源所有者:Resource Owner,資源所有者定義哪些應(yīng)用程序可以調(diào)用其服務(wù),哪些用戶(hù)可以訪問(wèn)該服務(wù),以及他們可以使用該服務(wù)完成哪些事情。 資源所有者注冊(cè)的每個(gè)應(yīng)用程序都將獲得一個(gè)應(yīng)用程序名稱(chēng),該應(yīng)用程序名稱(chēng)與應(yīng)用程序密鑰一起標(biāo)識(shí)應(yīng)用程序。 應(yīng)用程序名稱(chēng)和密鑰的組合是在驗(yàn)證 OAuth2 令牌時(shí)傳遞的憑據(jù)的一部分;
  • 應(yīng)用程序:Client,這是代表用戶(hù)調(diào)用服務(wù)的應(yīng)用程序。畢竟,用戶(hù)很少直接調(diào)用服務(wù) 。相反,他們依賴(lài)應(yīng)用程序?yàn)樗麄児ぷ鳌?/li>
  • OAuth2 驗(yàn)證服務(wù)器:Authorization Server,OAuth2 驗(yàn)證服務(wù)器是應(yīng)用程序和正在使用的服務(wù)之間的中間人。 OAuth2 驗(yàn)證服務(wù)器允許用戶(hù)對(duì)自己進(jìn)行驗(yàn)證,而不必將用戶(hù)憑據(jù)傳遞給由應(yīng)用程序代表用戶(hù)調(diào)用的每個(gè)服務(wù);

1.2 OAuth2 的工作原理

  • 第三方客戶(hù)端資源所有者(用戶(hù))申請(qǐng)認(rèn)證請(qǐng)求;
  • 【關(guān)鍵】用戶(hù)同意請(qǐng)求,返回一個(gè)許可;
  • 客戶(hù)端根據(jù)許可向認(rèn)證服務(wù)器申請(qǐng)認(rèn)證令牌 Token;
  • 客戶(hù)端根據(jù)認(rèn)證令牌向資源服務(wù)器申請(qǐng)相關(guān)資源;
Oauth執(zhí)行流程.png

1.3 OAuth2 規(guī)范的 4 種類(lèi)型的授權(quán)

  • 密碼( password ) ;
  • 客戶(hù)端憑據(jù)( client credential ) ;;
  • 授權(quán)碼( authorization code) ;
  • 隱式( imp licit );

1.4 OAuth2 的優(yōu)勢(shì)

  • 允許開(kāi)發(fā)人員輕松與第三方云服務(wù)提供商集成,并使用這些服務(wù)進(jìn)行用戶(hù)驗(yàn)證和授權(quán),而無(wú)須不斷地將用戶(hù)的憑據(jù)傳遞給第三方服務(wù);

1.5 OAuth2 核心原理

  • 先有一個(gè) OAuth2 認(rèn)證服務(wù)器,用來(lái)創(chuàng)建和管理 OAuth2 訪問(wèn)令牌;
  • 接著在受保護(hù)資源主程序類(lèi)上添加一個(gè)注解:@EnableResourceServer,該注解會(huì)強(qiáng)制執(zhí)行一個(gè)過(guò)濾器,該過(guò)濾器會(huì)攔截對(duì)服務(wù)的所有傳入調(diào)用,檢查傳入調(diào)用的 HTTP 首部中是否存在 OAuth2 訪問(wèn)令牌,然后調(diào)用 security.oauth2.resource.userInfoUri 中定義的回調(diào) URL 告訴客戶(hù)端與 OAuth2 認(rèn)證服務(wù)器交互,查看令牌是否有效;
  • 一旦獲悉令牌是有效的,@EnableResourceServer 注解也會(huì)應(yīng)用任何訪問(wèn)控制規(guī)則,以控制什么人可以訪問(wèn)服務(wù);

1.6 JSON Web Token


2. 建立 OAuth2 服務(wù)器

  • 驗(yàn)證服務(wù)將驗(yàn)證用戶(hù)憑據(jù)并頒發(fā)令牌;
  • 每當(dāng)用戶(hù)嘗試訪問(wèn)由,如正服務(wù)保護(hù)的服務(wù)時(shí),驗(yàn)證服務(wù)將確認(rèn) OAuth2 令牌是否已由其頒發(fā)并且尚未過(guò)期;

2.1 引入 pom.xml 依賴(lài)文件

<!--security 通用安全庫(kù)-->
<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 主程序類(lèi)上添加注解

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

2.3 添加受保護(hù)對(duì)象的端點(diǎn)

在 controller 包下;

  • 該端點(diǎn)將映射到 /auth/user 端點(diǎn),當(dāng)受保護(hù)的服務(wù)調(diào)用 /auth/user 時(shí),將會(huì)確認(rèn) OAuth2 訪問(wèn)令牌,并檢索發(fā)文手背歐虎服務(wù)所分配的角色;
/**
 * 用戶(hù)信息校驗(yàn)
 * 由受保護(hù)服務(wù)調(diào)用,確認(rèn) OAuth2 訪問(wèn)令牌,并檢索訪問(wèn)受保護(hù)服務(wù)的用戶(hù)所分配的角色
 * @param OAuth2Authentication 信息
 * @return 用戶(hù)信息
 */
@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 定義哪些應(yīng)用程序可以使用服務(wù)

在 config 包下;

  • ClientDetailsServiceConfigurer 支持兩種類(lèi)型的儲(chǔ)存:內(nèi)存存儲(chǔ)和JDBC存儲(chǔ),如下分點(diǎn)所示:

2.4.1 使用 JDBC 存儲(chǔ)

  • OAuth2Config 類(lèi)
@Configuration
//繼承 AuthorizationServerConfigurerAdapter 類(lèi)
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private DataSource dataSource;
    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    //定義哪些客戶(hù)端將注冊(cè)到服務(wù)
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        //JDBC存儲(chǔ):
        JdbcClientDetailsService clientDetailsService = new JdbcClientDetailsService(dataSource);
        clientDetailsService.setSelectClientDetailsSql(SecurityConstants.DEFAULT_SELECT_STATEMENT); //設(shè)置我們的自定義的sql查找語(yǔ)句
        clientDetailsService.setFindClientDetailsSql(SecurityConstants.DEFAULT_FIND_STATEMENT); //設(shè)置我們的自定義的sql查找語(yǔ)句
        clients.withClientDetails(clientDetailsService); //從 jdbc 查出數(shù)據(jù)來(lái)存儲(chǔ)
    }
    
    @Override
    //使用 Spring 提供的默認(rèn)驗(yàn)證管理器和用戶(hù)詳細(xì)信息服務(wù)
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
      endpoints
        .authenticationManager(authenticationManager)
        .userDetailsService(userDetailsService);
    }
}
  • SecurityConstants 類(lèi):里面存放上述提到的 SQL 查詢(xún)語(yǔ)句;
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 查詢(xún)語(yǔ)句
     */
    String BASE_FIND_STATEMENT = "select " + CLIENT_FIELDS + " from sys_oauth_client_details";

    /**
     * 默認(rèn)的查詢(xún)語(yǔ)句
     */
    String DEFAULT_FIND_STATEMENT = BASE_FIND_STATEMENT + " order by client_id";

    /**
     * 按條件client_id 查詢(xún)
     */
    String DEFAULT_SELECT_STATEMENT = BASE_FIND_STATEMENT + " where client_id = ?";
}

2.4.2 使用內(nèi)存儲(chǔ)存

@Configuration
//繼承 AuthorizationServerConfigurerAdapter 類(lèi)
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    //定義哪些客戶(hù)端將注冊(cè)到服務(wù)
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("eagleeye")  //名稱(chēng)
                .secret("thisissecret")  //密鑰
                .authorizedGrantTypes("refresh_token", "password", "client_credentials")  //授權(quán)類(lèi)型列表
                .scopes("webclient", "mobileclient");  //獲取訪問(wèn)令牌時(shí)可以操作的范圍
    }

    @Override
    //使用 Spring 提供的默認(rèn)驗(yàn)證管理器和用戶(hù)詳細(xì)信息服務(wù)
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
      endpoints
        .authenticationManager(authenticationManager)
        .userDetailsService(userDetailsService);
    }
}

2.5 為應(yīng)用程序定義用戶(hù) ID、密碼和角色

在 config 包下:

  • 可以從內(nèi)存數(shù)據(jù)存儲(chǔ)、支持 JDBC 的關(guān)系數(shù)據(jù)庫(kù)或 LDAP 服務(wù)器中存儲(chǔ)和檢索用戶(hù)信息;
@Configuration
@EnableWebSecurity
//擴(kuò)展核心 Spring Security 的 WebSecurityConfigurerAdapter
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {

    //用來(lái)處理驗(yàn)證
    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    //處理返回用戶(hù)信息
    @Override
    @Bean
    public UserDetailsService userDetailsServiceBean() throws Exception {
        return super.userDetailsServiceBean();
    }
    
    //configure() 方法定義用戶(hù)、密碼與角色
    @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 用戶(hù)擁有 USER 用戶(hù);
  • william.woodward 擁有 ADMIN 用戶(hù);

2.6 通過(guò)發(fā)送 POST 請(qǐng)求驗(yàn)證用戶(hù)

  • 發(fā)送:POST http://localhost:8901/auth/oauth/token
  • 并在 POST 的請(qǐng)求體里帶上應(yīng)用程序名稱(chēng)、密鑰、用戶(hù) ID 和密碼,可以模擬用戶(hù)獲取 OAuth2 令牌;


3. 使用 OAuth2 建立并保護(hù)服務(wù)資源

  • 創(chuàng)建和管理 OAuth2 訪問(wèn)令牌是 OAuth2 服務(wù)器的職責(zé);
  • 定義哪些用戶(hù)角色有權(quán)執(zhí)行哪些操作在單個(gè)服務(wù)級(jí)別上的;

3.1 引入 pom.xml 依賴(lài)文件

<!--security 通用安全庫(kù)-->
<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
  • 這里添加回調(diào) URL,客戶(hù)端訪問(wèn)受保護(hù)服務(wù)時(shí),受保護(hù)服務(wù)將調(diào)用 /auth/user 端點(diǎn),向 OAuth2 服務(wù)器檢查訪問(wèn)令牌是否生效;

3.3 在主程序類(lèi)上添加注解

  • @EnableResourceServer:表示該服務(wù)是受保護(hù)資源;
  • 該注解會(huì)強(qiáng)制執(zhí)行一個(gè)過(guò)濾器,該過(guò)濾器會(huì)攔截對(duì)服務(wù)的所有傳入調(diào)用,檢查傳入調(diào)用的 HTTP 首部中是否存在 OAuth2 訪問(wèn)令牌,然后調(diào)用 security.oauth2.resource.userInfoUri 中定義的回調(diào) URL 來(lái)查看令牌是否有效;
  • 一旦獲悉令牌是有效的,@EnableResourceServer 注解也會(huì)應(yīng)用任何訪問(wèn)控制規(guī)則,以控制什么人可以訪問(wèn)服務(wù);
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker //斷路器
@EnableResourceServer //表示受保護(hù)資源
public class Application {
    //注入一個(gè)過(guò)濾器,會(huì)攔截對(duì)服務(wù)的所有傳入調(diào)用
    @Bean
    public Filter userContextFilter() {
        UserContextFilter userContextFilter = new UserContextFilter();
        return userContextFilter;
    }

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

3.4 定義訪問(wèn)控制規(guī)則

在 config 包或 security 包下;

  • 要定義訪問(wèn)控制規(guī)則,需要擴(kuò)展 ResourceServerConfigurerAdapter 類(lèi)井覆蓋 configure() 方法;
  • 有多種定義方法,這里給出常見(jiàn)的兩種定義示例:

3.4.1 通過(guò)驗(yàn)證用戶(hù)保護(hù)服務(wù)

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

3.4.2 通過(guò)特定角色保護(hù)服務(wù)

  • 限制只有 ADMIN 用戶(hù)才能調(diào)用該服務(wù)的 DELETE 方法;
@Configuration
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
    @Override
    public void configure(HttpSecurity http) throws Exception{
        http
        .authorizeRequests()
          .antMatchers(HttpMethod.DELETE, "/v1/xxxservices/**")  //運(yùn)行部開(kāi)發(fā)人員限制對(duì)受保護(hù)的 URL 和 HTTP DELETE 動(dòng)詞的調(diào)用
          .hasRole("ADMIN")  //允許訪問(wèn)的角色列表
          .anyRequest()
          .authenticated();
    }
}
  • anyRequest().authenticated() 表示仍需要由已通過(guò)驗(yàn)證的用戶(hù)訪問(wèn);
  • 結(jié)合本篇《2.5 為應(yīng)用程序定義用戶(hù) ID、密碼和角色》的示例,這里使用 john.carnell USER 用戶(hù)訪問(wèn)資源將被拒絕,而使用 william.woodward ADMIN 用戶(hù)訪問(wèn)資源將被通過(guò);


4. 在上下游服務(wù)中傳播 OAuth2 訪問(wèn)令牌

傳播 OAuth2 訪問(wèn)令牌.png
  • 用戶(hù)已經(jīng)向 OAuth2 服務(wù)器進(jìn)行了驗(yàn)證,調(diào)用 EagleEye Web 客戶(hù)端;
  • EagleEye Web 應(yīng)用程序( OAuth2 服務(wù)器)將通過(guò) HTTP 首都 Authorization 添加 OAuth2 訪問(wèn)令牌;
  • Zuul 將查找許可證服務(wù)端點(diǎn),然后將調(diào)用轉(zhuǎn)發(fā)到其中一個(gè)許可證服務(wù)的服務(wù)器;
  • 服務(wù)網(wǎng)關(guān)需要從傳入的調(diào)用中復(fù)制 HTTP 首部 Authorization;
  • 受保護(hù)服務(wù)使用 OAuth2 服務(wù)器確認(rèn)令牌;

4.1 配置服務(wù)網(wǎng)關(guān)的黑名單

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

  • 因?yàn)樵谡麄€(gè)驗(yàn)證流程中,我們需要將 HTTP 首部 Authorization 傳遞上下游進(jìn)行權(quán)限認(rèn)證;

  • 但在默認(rèn)情況下,Zuul 不會(huì)將敏感的 HTTP 首部(如 Cookie、Set-Cokkie 和 Authorization)轉(zhuǎn)發(fā)到下游服務(wù);

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

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

4.2 修改上游服務(wù)業(yè)務(wù)代碼

  • 業(yè)務(wù)代碼需要保證將 HTTP 首部 Authorization 注入服務(wù)的上下游;

4.2.1 下游服務(wù)

  • 這里的下游服務(wù)就是受保護(hù)的服務(wù);
  • 其構(gòu)建方法同本篇的《3. 使用 OAuth2 建立并保護(hù)服務(wù)資源》

4.2.2 在上游服務(wù)中公開(kāi) OAuth2RestTemplate 類(lèi)

可以在主程序類(lèi)上,也可以在主程序所在包及其子包里創(chuàng)建類(lèi);

  • 使該類(lèi)可以被自動(dòng)裝配到調(diào)用另一個(gè)受 OAuth2 保護(hù)的服務(wù);

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

4.2.3 在上游服務(wù)中用 OAuth2RestTemplate 來(lái)傳播 OAuth2 訪問(wèn)令牌

  • 自動(dòng)裝配 OAuth2RestTemplate;
@Component
public class OrganizationRestTemplateClient {
    //OAuth2RestTemplate 是標(biāo)準(zhǔn)的 RestTemplate 的增強(qiáng)式替代品,可處理 OAuth2 訪問(wèn)令牌
    @Autowired
    OAuth2RestTemplate restTemplate;

    public Organization getOrganization(String organizationId){
        //調(diào)用組織服務(wù)的方式與標(biāo)準(zhǔn)的 RestTemplate 完全相同
        ResponseEntity<Organization> restExchange =
                restTemplate.exchange(
                        "http://zuulserver:5555/api/organization/v1/organizations/{organizationId}",
                        HttpMethod.GET,
                        null, Organization.class, organizationId);
        return restExchange.getBody();
    }
}

最后

\color{blue}{\rm\small{新人制作,如有錯(cuò)誤,歡迎指出,感激不盡!}}

\color{blue}{\rm\small{歡迎關(guān)注我,并與我交流!}}

\color{blue}{\rm\small{如需轉(zhuǎn)載,請(qǐng)標(biāo)注出處!}}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容