首頁 > 軟體

SpringBoot淺析安全管理之OAuth2框架

2022-08-12 18:02:44

OAuth2簡介

OAuth 是一個開放標準,該標準允許使用者讓第三方應用存取該使用者在某一網站上儲存的私密資源(如頭像、照片、視訊等),而在這個過程中無須將使用者名稱和密碼提供給第三方應用。

實現這一功能是通過一個令牌(token),而不是使用者名稱和密碼來存取他們存放在特定服務提供者的資料。每一個令牌授權一個特定的網站在特定的時間段記憶體取特定的資源。

這樣 OAuth 讓使用者可以授權第三方網站靈活的存取儲存在另一些資源伺服器的特定資訊,而非所有內容。例如,使用者想通過 QQ 登入知乎,這時知乎就是一個第三方應用,知乎要存取使用者的一些基本資訊就需要得到使用者的授權,如果使用者把自己的 QQ 使用者名稱和密碼告訴知乎,那麼知乎就能存取使用者的所有資料,並且只有使用者修改密碼才能收回許可權,這種授權方式安全隱患很大,如果使用 OAuth ,就能很好的解決這一問題。

採用令牌的方式可以讓使用者靈活的對第三方應用授權或者收回許可權。OAuth 2 是 OAuth 協定的下一版本,但不向下相容 OAuth 1.0 。

OAuth 2 關注使用者端開發者的簡易型,同時為Web應用、桌面應用,移動裝置、起居室裝置提供專門的認證流程。傳統的 Web 開發登入認證一般都是基於 Session 的,但是前後端分離的架構中繼續使用 Session 會有許多不便,因為行動端(Android、IOS、微信小程式等)要麼不支援 Cookie(微信小程式),要麼使用非常不便,對於這些問題,使用 OAuth 2 認證都能解決。

OAuth2角色

先了解 OAuth 2 中幾個基本的角色

  • 資源所有者:即使用者,具有頭像、照片、視訊等資源
  • 使用者端:即第三方應用
  • 授權伺服器:用來驗證使用者提供的資訊是否正確,並返回一個令牌給第三方應用
  • 資源伺服器:提供給使用者資源的伺服器,例如頭像、照片、視訊等資源

一般來說,授權伺服器和資源伺服器可以是同一臺伺服器。

OAuth2授權流程

步驟01:使用者端(第三方應用)向用戶請求授權。

步驟02:使用者單擊使用者端所呈現的服務授權頁面上的同意授權按鈕後,伺服器端返回一個授權許可憑證給使用者端。

步驟03:使用者端拿著授權許可證去授權伺服器申請令牌。

步驟04:授權伺服器驗證資訊無誤後,發放令牌給使用者端。

步驟05:使用者端拿著令牌去資源伺服器存取資源。

步驟06:資源伺服器驗證令牌無誤後開放資源。

授權模式

OAuth 協定的授權模式共分為 4 種,如下

  • 授權碼模式:授權碼(authorization code)是功能最完整、流程最嚴謹的授權模式。它的特點就是通過使用者端的伺服器與授權伺服器進行互動,國內常見的第三方平臺登入功能基本都是使用這種模式
  • 簡化模式:簡化模式不需要使用者端伺服器參與,直接在瀏覽器中向授權伺服器申請令牌,一般若是純靜態頁面,則可以採用這種方式
  • 密碼模式:使用者把使用者名稱密碼直接告訴使用者端,使用者端使用這些資訊向授權伺服器申請令牌。這需要使用者對使用者端高度資訊,例如使用者端應用和服務提供商是同一家公司
  • 使用者端模式:使用者端使用自己的名義而不是使用者的名義想服務提供者申請授權。嚴格來說,使用者端模式並不能算作 OAuth 協定要解決的問題的一種解決方案,但是,對於開發者而言,在一些前後端分離應用或者為行動端提供的認證授權伺服器上使用這種模式還是非常方便的

4 種模式各有千秋,分別適用於不同的開發場景,開發者根據實際情況進行選擇

實踐

此處介紹的是在前後端分離應用(或為行動端、微信小程式等)提供的認證伺服器中如何搭建 OAuth 服務,因此主要介紹密碼模式。

1. 建立專案新增依賴

建立 Spring Boot Web 專案,新增如下依賴

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
  <exclusions>
    <exclusion>
      <groupId>io.lettuce</groupId>
      <artifactId>lettuce-core</artifactId>
    </exclusion>
  </exclusions>
</dependency>
<dependency>
  <groupId>redis.clients</groupId>
  <artifactId>jedis</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.security.oauth</groupId>
  <artifactId>spring-security-oauth2</artifactId>
  <version>2.3.3.RELEASE</version>
</dependency>

由於 Spring Boot 中的 OAuth 協定是在 Spring Security 的基礎上完成的,因此首先要新增 Spring Security 依賴,要用到 OAuth 2,因此新增 OAuth 2 相關依賴,令牌可以儲存在 Redis 快取伺服器上,同時 Redis 具有過期等功能,很適合令牌的儲存,因此也加入 Redis 依賴。

設定 application.properties

spring.redis.database=0
spring.redis.host=ip地址
spring.redis.port=6379
spring.redis.password=root
spring.redis.jedis.pool.max-active=8
spring.redis.jedis.pool.max-idle=8
spring.redis.jedis.pool.max-wait=-1ms
spring.redis.jedis.pool.min-idle=0

2. 設定授權伺服器

授權伺服器和資源伺服器可以是同一臺伺服器,也可以是不同伺服器,此處假設是同一臺伺服器,通過不同的設定分別開啟授權伺服器和資源伺服器,首先是授權伺服器:

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    @Autowired
    AuthenticationManager authenticationManager;
    @Autowired
    RedisConnectionFactory redisConnectionFactory;
    @Autowired
    UserDetailsService userDetailsService;
    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("password")
                .authorizedGrantTypes("password", "refresh_token")
                .accessTokenValiditySeconds(1800)
                .resourceIds("rid")
                .scopes("all")
                .secret("$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq");
    }
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.tokenStore(new RedisTokenStore(redisConnectionFactory))
                .authenticationManager(authenticationManager)
                .userDetailsService(userDetailsService);
    }
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.allowFormAuthenticationForClients();
    }
}

程式碼解釋:

  • 自定義類繼承自 AuthorizationServerConfigurerAdapter ,完成對授權伺服器的設定,然後通過 @EnableAuthorizationServer 註解開啟授權伺服器
  • 注入 AuthenticationManager 用來支援 password 模式
  • 注入 RedisConnectionFactory 用來完成 Redis 快取,將令牌資訊儲存到 Redis 快取中
  • 注入 UserDetailsService 該物件為重新整理 token 提供支援
  • 在 configure(ClientDetailsServiceConfigurer clients) 方法中設定 password 授權模式,authorizedGrantTypes 表示 OAuth 2 中的授權模式為 password 和 refresh_token 兩種,在標準的 OAuth 2 協定中,授權模式並不包括 refresh_token ,但是在 Spring Security 的實現中將其歸為一種,因此如果要實現 access_token 的重新整理,就需要新增這樣一種授權模式;accessTokenValiditySeconds 方法設定了 access_token 的過期時間;resourceIds 設定了資源 id;secret 方法設定了加密後的密碼,明文是 123
  • configure(AuthorizationServerEndpointsConfigurer endpoints) 方法設定了令牌的儲存,AuthenticationManager 和 UserDetailsService 主要用於支援 password 模式以及令牌的重新整理
  • configure(AuthorizationServerSecurityConfigurer security) 方法設定表示支援 client_id 和 client_secret 做登入認證

3. 設定資源伺服器

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.resourceId("rid").stateless(true);
    }
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/admin/**").hasRole("admin")
                .antMatchers("/user/**").hasRole("user")
                .anyRequest().authenticated();
    }
}

程式碼解釋:

  • 自定義類繼承自 ResourceServerConfigurerAdapter ,並新增 @EnableResourceServer 註解開啟資源伺服器設定
  • resources.resourceId(“rid”).stateless(true); 設定資源 id,這裡的資源 id 和授權伺服器中的資源 id 一直,然後設定這些資源僅基於令牌認證
  • configure(HttpSecurity http) 方法設定 HttpSecurity

4. 設定 Security

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
    @Bean
    @Override
    protected UserDetailsService userDetailsService() {
        return super.userDetailsService();
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("admin")
                .password("$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq")
                .roles("admin")
                .and()
                .withUser("sang")
                .password("$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq")
                .roles("user");
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.antMatcher("/oauth/**").authorizeRequests()
                .antMatchers("/oauth/**").permitAll()
                .and().csrf().disable();
    }
}

這裡兩個 Bean 將注入授權伺服器設定類中使用,另外,這裡的 HttpSecurity 設定主要是設定 /oauth/** 模式的 URL ,這一類的請求直接放行。在 Spring Security 設定和資源伺服器設定中,一共涉及兩個 HttpSecurity ,其中 Spring Security 中的設定優先順序高於資源伺服器中的設定,即請求地址先經過 Spring Security 的 HttpSecurity ,再經過資源伺服器的 HttpSecurity。

5. 驗證測試

首先建立三個簡單的請求地址

@RestController
public class HelloController {
    @GetMapping("/admin/hello")
    public String admin() {
        return "Hello admin!";
    }
    @GetMapping("/user/hello")
    public String user() {
        return "Hello user!";
    }
    @GetMapping("/hello")
    public String hello() {
        return "hello";
    }
}

根據前文的設定,要請求這三個地址,分別需要 admin 角色、user 角色以及登入後存取。

所有都設定完成後,啟動 Redis 伺服器,再啟動 Spring Boot 專案,首先傳送一個 POST 請求獲取 token,請求地址如下(注意這裡是一個 POST 請求,為了顯示方便,將引數寫在位址列中):http://localhost:8080/oauth/token?username=sang&password=123&grant_type=password&client_id=password&scope=all&client_secret=123

請求地址中包含的引數有使用者名稱、密碼、授權模式、使用者端 id 、scope 以及使用者端密碼,基本就是授權伺服器中所設定的資料,請求結果如圖

其中 access_token 是獲取其它資源時要用的令牌,refresh_token 用來重新整理令牌,expires_in 表示 access_token 過期時間,當 access_token 過期後,使用 refresh_token 重新獲取新的 access_token (前提是 refresh_token 未過期),請求地址(注意也是POST請求):http://localhost:8080/oauth/token?grant_type=refresh_token&refresh_token=693b0e36-4515-442a-8c5d-90bade3c74d2&client_id=password&client_secret=123

獲取新的 access_token 時需要攜帶上 refresh_token ,同事授權模式設定為 refresh_token ,在獲取的結果中 access_token 會變化,同時 access_token 有效期也會變化,如圖

接下來存取所有資源,攜帶上 access_token 引數即可,例如 /user/hello 介面:http://localhost:8080/user/hello?access_token=0497e4bc-df37-460e-8755-b813b9dbf36a,存取結果如圖

如果非法存取一個資源,例如 sang 使用者存取 /admin/hello 介面,結果如圖

到此,一個 password 模式的 OAuth 認證體系就搭建成功了。

OAuth 中的認證模式有 4 中,開發者需要結合自己開發的實際情況選擇其中一種,此處介紹的是在前後端分離應用中常用的 password 模式,其它的授權模式也都有自己的使用場景。

整體來講,Spring Security OAuth 2 的使用還是較複雜的,設定也比較繁瑣,如果開發者的應用場景比較簡單,完全可以按照此處介紹的授權流程自己搭建 OAuth 2 認證體系。

到此這篇關於SpringBoot淺析安全管理之OAuth2框架的文章就介紹到這了,更多相關SpringBoot OAuth2框架內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com