亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

首頁 慕課教程 Spring Security Spring Security OAuth2 集成構造認證授權服務器

OAuth2 集成 - 構造認證授權服務器

1. 前言

上一節中,我們使用了 Spring Security 提供的社交化組件,實現了利用第三方認證平臺完成用戶身份識別的過程。

雖然使用第三方平臺作為認證中心十分的方便,但是如果我們的系統是在內部環境下使用,或者我們的用戶沒有注冊過 Github、微信這類平臺,又或者我們希望自己的平臺為其他應用提供認證服務時,就需要考慮創建自己的認證中心了。

本節將重點討論如何創建自己的 OAuth2 認證中心。

本小節實例開發環境

本小節所使用的實例代碼是基于 Spring 官網中提供的最小化 HelloWorld 模板創建,請點此下載完整的 HelloWorld 模板壓縮包。

  • 編譯環境:JDK 1.8,點此下載;
  • 構建工具:Maven 3.5.3,點此下載
  • 開發工具:VS Code,點此下載、控制臺;
  • 其他依賴性:
    • spring-security-oauth2-autoconfigure

2. OAuth2 授權原理介紹

OAuth 的全稱為 Open Authorization 即「開放授權」。它被設計成為一個通用安全協議,用于實現桌面應用(包括手機應用)及 B / S 應用的統一 API 鑒權服務。它通過頒發令牌的方式,允許第三方網站在特定時間、操作范圍內訪問資源而避免了重新輸入密碼。

OAuth 授權有三個主要特點:

  1. 簡單:易于理解和實現;
  2. 安全:過程中不暴露敏感信息(如:用戶名、密碼等);
  3. 開放:誰都可以用。

OAuth 標準一共出現了兩代,1.0 在 2007 年底提出,只適用于瀏覽器 B / S 應用,后來在 2011 年,OAuth 發布了協議 2.0,并開始支持終端應用的認證。現在 OAuth 2.0 協議基本完全替代了 OAuth 1.0 協議。

OAuth 2.0 有四種授權方式,也就是四種獲得令牌的方式,分別是:授權碼式、隱蔽式、密碼式、客戶端憑證式。

2.1 授權碼式

這種方式下,APP(或網站)首先申請授權碼并保存在客戶端(或瀏覽器)中,再用授權碼去換取令牌,并將令牌保存在服務器上。這樣就實現了即認證客戶端,又認證了服務器。

圖片描述

授權碼式

2.2 隱蔽式

有時會遇到應用只有前端,沒有后端,上述方式就無法實現了,此時我們需要將令牌保存在前端,于是出現了第二種方式:隱蔽式。

這種方式下應用客戶端直接向認證服務器請求令牌。

圖片描述

隱蔽式

注意:這種方式下,令牌被存儲在客戶端,容易被攻擊者攔截,所以用完后應及時銷毀。

2.3 密碼式

加入客戶端應用實可信的,既用戶允許客戶端知道自己的用戶名密碼,此時就可以使用密碼式換取令牌。

這種方式下,由客戶端認證用戶,并攜帶用戶的認證信息一并發送到認證服務器換取令牌。

圖片描述

密碼式

2.4 客戶端憑證式

有的應用并沒有明確的前端應用,比如控制臺程序或者是服務接口,這種情況下就需要用到客戶端憑證式獲得憑證了。

這種方式下,沒有「人」的參與,只有認證服務對后臺服務的認證。

圖片描述

客戶端憑證式

3. 過程實現

在前面章節,我們討論了如何快速建立一個 Spring Security 的認證服務器,此處我們將在前述實例上擴展 OAuth2.0 認證支持。

3.1 創建 Spring Boot web 服務端應用

工程目錄結構如下:

? OAuth2AuthorizationServer/
  ? src/
    ? main/
      ? java/imooc/springsecurity/oauth2/server/
        ? config/
            OAuth2ServerConfiguration.java          # OAuth2 相關配置類
            UserConfiguration.java                  # 基礎認證配置類,用于配置用戶信息
          OAuth2AuthorizationServerApplication.java # 程序入口
      ? resources/
          application.properties                    # 配置文件,本例中無特殊配置
    ? test/java/
    pom.xml

在 pom.xml 文件中增加依賴項,相比「用戶名密碼認證實例」,此處注意添加了 OAuth2 自動配置的相關依賴。spring-security-oauth2-autoconfigure。完整 pom.xml 文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>imooc.springsecurity</groupId>
    <artifactId>OAuth2AuthorizationServerSample</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.security.oauth.boot</groupId>
            <artifactId>spring-security-oauth2-autoconfigure</artifactId>
            <version>2.3.1.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

創建 SpringSecurity OAuth2 配置類: OAuth2ServerConfiguration.java。

src/
  main/
    java/
      imooc/
        springsecurity/
          oauth2/
            server/
              OAuth2ServerConfiguration.java
  1. 使其繼承 org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter 類,并其增加 @EnableAuthorizationServer 標簽,以聲明此類作為 OAuth2 認證服務器的配置依據;
  2. configure(AuthorizationServerEndpointsConfigurer endpoints) 方法中配置其 TokenStore,為了便于演示,此例中 TokenStore 采用內存形式,賬戶信息寫死在代碼中;
  3. configure(ClientDetailsServiceConfigurer clients) 方法中為 OAuth2 認證服務器設置可用于認證的客戶端信息。

完整代碼如下:

package imooc.springsecurity.oauth2.server.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;

@EnableAuthorizationServer
@Configuration
public class OAuth2ServerConfiguration extends AuthorizationServerConfigurerAdapter {

    private AuthenticationManager authenticationManager;

    public OAuth2ServerConfiguration(
            AuthenticationConfiguration authenticationConfiguration) throws Exception {
        this.authenticationManager = authenticationConfiguration.getAuthenticationManager();
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients)
            throws Exception {
        // 配置授信客戶端信息
        clients.inMemory() // 內存模式
                .withClient("reader") // 第一個客戶端用戶,其名稱為「reader」
                .authorizedGrantTypes("password") // 授權模式為「password」
                .secret("{noop}secret") // 認證密碼為「secret」,加密方式為「NoOp」
                .scopes("message:read") // 權限的使用范圍
                .accessTokenValiditySeconds(600_000_000) // 票據有效期
                .and()  // 增加第二個授權客戶端,設置方法一致,但擁有不同的范圍權限
                .withClient("writer")
                .authorizedGrantTypes("password")
                .secret("{noop}secret")
                .scopes("message:write")
                .accessTokenValiditySeconds(600_000_000);
    }
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints
                .authenticationManager(this.authenticationManager)
                .tokenStore(tokenStore()); // 使用虛機內存存儲票據信息,也可替換成 Mysql、Redis 等。
    }

    @Bean
    public TokenStore tokenStore() {
        return new InMemoryTokenStore();
    }
}

除了設置授權客戶端之外,還要增加客戶端中被授權的用戶。

創建類 UserConfiguration.java

src/
  main/
    java/
      imooc/
        springsecurity/
          oauth2/
            server/
              UserConfiguration.java

并在其中配置用戶信息。

package imooc.springsecurity.oauth2.server.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;

@Configuration
public class UserConfiguration extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .anyRequest().authenticated() // 任何地址都受到保護,需要首先認證
                .and()
                .httpBasic() // 支持基本認證,因為 OAuth2 認證往往用于不同種類客戶端,所以基本認證支持是必要的。
                .and()
                .csrf().disable();
    }

    @Bean
    @Override
    public UserDetailsService userDetailsService() {
        InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
        inMemoryUserDetailsManager.createUser(User.withUsername("admin").password("$2a$10$sR.KWdKOWYseh0KVHHnzMOveh/S7wvOkd.JrTyP2AzHhEcCSZfAmK").roles("USER").build()); // 用戶名: admin; 密碼: 123456
        return inMemoryUserDetailsManager;
    }
}

3.2 運行及測試

我們用 curl 工具測試 OAuth2.0 認證服務器。

在 OAuth2.0 框架中,實現 Password 認證需要提供四個參數:

  1. 客戶端標識:clientID;
  2. 客戶端認證密碼:clientSecret;
  3. 授權類型:grant_type,該值固定為「password」;
  4. 認證用戶的用戶名:username;
  5. 認證用戶的密碼:password。

完整的請求表達式為:

curl [clientID]:[clientSecret]@ip:port/oauth/token -d grant_type=password -d username=[username] -d password=[password]

在本實例中,測試指令可定義為:

curl reader:secret@localhost:8080/oauth/token -d grant_type=password -d username=admin -d password=123456

如果認證成功,服務端將返回以下內容:

{
	"access_token": "OOwNfgjvJKHItYnk4buWC8BMGtU=",
	"token_type": "bearer",
	"expires_in": 599995027,
	"scope": "message:read"
}

其中,access_token 值在 OAuth2 體系中作為統一票據,用于各個資源服務的認證。

至此,OAuth2 認證服務器的 Password 模式授權模式就已完成。Spring Security 對 OAuth2.0 的其他幾種授權模式已有成熟支持,在使用時需要配置對應的客戶端授權模式權限。

3.3 使用數據庫作為認證源

如果使用數據庫(例如:mysql)作為數據源時,需要創建 JdbcClientDetailsService 對象,并配置到 ClientDetailsServiceConfigurer 之中。具體代碼為

    @Bean
    public ClientDetailsService clientDetailsService() {
        // 新增部分,用于從數據庫獲取客戶端信息
        return new JdbcClientDetailsService(dataSource);
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients)
            throws Exception {
        // 此處去掉內存配置項,改為 jdbc 數據源
        clients.withClientDetails(clientDetailsService);
    }

除此之外,還需要在書庫中插入相關數據表,表結構定義如下,也可以從 spring 項目主頁 中獲取。

-- used in tests that use HSQL
create table oauth_client_details (
  client_id VARCHAR(256) PRIMARY KEY,
  resource_ids VARCHAR(256),
  client_secret VARCHAR(256),
  scope VARCHAR(256),
  authorized_grant_types VARCHAR(256),
  web_server_redirect_uri VARCHAR(256),
  authorities VARCHAR(256),
  access_token_validity INTEGER,
  refresh_token_validity INTEGER,
  additional_information VARCHAR(4096),
  autoapprove VARCHAR(256)
);

create table oauth_client_token (
  token_id VARCHAR(256),
  token LONGVARBINARY,
  authentication_id VARCHAR(256) PRIMARY KEY,
  user_name VARCHAR(256),
  client_id VARCHAR(256)
);

create table oauth_access_token (
  token_id VARCHAR(256),
  token LONGVARBINARY,
  authentication_id VARCHAR(256) PRIMARY KEY,
  user_name VARCHAR(256),
  client_id VARCHAR(256),
  authentication LONGVARBINARY,
  refresh_token VARCHAR(256)
);

create table oauth_refresh_token (
  token_id VARCHAR(256),
  token LONGVARBINARY,
  authentication LONGVARBINARY
);

create table oauth_code (
  code VARCHAR(256), authentication LONGVARBINARY
);

create table oauth_approvals (
	userId VARCHAR(256),
	clientId VARCHAR(256),
	scope VARCHAR(256),
	status VARCHAR(10),
	expiresAt TIMESTAMP,
	lastModifiedAt TIMESTAMP
);


-- customized oauth_client_details table
create table ClientDetails (
  appId VARCHAR(256) PRIMARY KEY,
  resourceIds VARCHAR(256),
  appSecret VARCHAR(256),
  scope VARCHAR(256),
  grantTypes VARCHAR(256),
  redirectUrl VARCHAR(256),
  authorities VARCHAR(256),
  access_token_validity INTEGER,
  refresh_token_validity INTEGER,
  additionalInformation VARCHAR(4096),
  autoApproveScopes VARCHAR(256)
);

4. 小結

本節我們討論了如何構建自己的 OAuth2.0 認證中心,主要知識點有:

圖片描述

OAuth 2.0 認證服務器
  • Spring Security 對 OAuth2.0 認證標準已經有成熟的支持,僅需幾個注解就可以構造出 OAuth2.0 認證服務器;
  • Spring Security 對 OAuth2.0 認證同樣提供了多種認證支持,比如內存認證、數據庫認證及 Redis 認證等,可以根據需要進行配置或調整。

下節我們繼續 OAuth2.0 話題,討論如何在 OAuth2.0 協議下,如何保護私密資源被合理訪問。