詳細介紹OAuth2.0及實現和SpringSecurity的整合應用

一、OAuth2.0介紹

GitHub地址案例代碼地址

1.概念說明

  先說OAuth,OAuth是Open Authorization的簡寫。
  OAuth協議爲用戶資源的授權提供了一個安全的、開放而又簡易的標準。與以往的授權方式不同之處是OAuth的授權不會使第三方觸及到用戶的帳號信息(如用戶名與密碼),即第三方無需使用用戶的用戶名與密碼就可以申請獲得該用戶資源的授權,因此OAuth是安全的。
  OAuth2.0是OAuth協議的延續版本,但不向前兼容(即完全廢止了OAuth1.0)。

2.使用場景

  假設,A網站是一個打印照片的網站,B網站是一個存儲照片的網站,二者原本毫無關聯。如果一個用戶想使用A網站打印自己存儲在B網站的照片,那麼A網站就需要使用B網站的照片資源纔行。按照傳統的思考模式,我們需要A網站具有登錄B網站的用戶名和密碼纔行,但是,現在有了OAuth2,只需要A網站獲取到使用B網站照片資源的一個通行令牌即可!這個令牌無需具備操作B網站所有資源的權限,也無需永久有效,只要滿足A網站打印照片需求即可。這麼聽來,是不是有點像單點登錄?NONONO!千萬不要混淆概念!單點登錄是用戶一次登錄,自己可以操作其他關聯的服務資源。OAuth2則是用戶給一個系統授權,可以直接操作其他系統資源的一種方式。但SpringSecurity的OAuth2也是可以實現單點登錄的!
  總結一句:SpringSecurity的OAuth2可以做服務之間資源共享,也可以實現單點登錄!

3.OAuth2.0中四種授權方式

  爲了說明四種模式先準備一張圖
在這裏插入圖片描述

3.1授權碼模式(authorization code)

流程
說明:【A服務客戶端】需要用到【B服務資源服務】中的資源

  1. 【A服務客戶端】將用戶自動導航到【B服務認證服務】,這一步用戶需要提供一個回調地址,以備【B服務認證服務】返回授權碼使用。
  2. 用戶點擊授權按鈕表示讓【A服務客戶端】使用【B服務資源服務】,這一步需要用戶登錄B服務,也就是說用戶要事先具有B服務的使用權限。
  3. 【B服務認證服務】生成授權碼,授權碼將通過第一步提供的回調地址,返回給【A服務客戶端】。
    注意這個授權碼並非通行【B服務資源服務】的通行憑證。
  4. 【A服務認證服務】攜帶上一步得到的授權碼向【B服務認證服務】發送請求,獲取通行憑證token。
  5. 【B服務認證服務】給【A服務認證服務】返回令牌token和更新令牌refresh token。

使用場景授權碼模式是OAuth2中最安全最完善的一種模式,應用場景最廣泛,可以實現服務之間的調用,常見的微信,QQ等第三方登錄也可採用這種方式實現。

3.2簡化模式(implicit)

流程
說明:簡化模式中沒有【A服務認證服務】這一部分,全部有【A服務客戶端】與B服務交互,整個過程不再有授權碼,token直接暴露在瀏覽器。

  1. 【A服務客戶端】將用戶自動導航到【B服務認證服務】,這一步用戶需要提供一個回調地址,以備【B服務認證服務】返回token使用,還會攜帶一個【A服務客戶端】的狀態標識state。
  2. 用戶點擊授權按鈕表示讓【A服務客戶端】使用【B服務資源服務】,這一步需要用戶登錄B服務,也就是說用戶要事先具有B服務的使用權限。
  3. 【B服務認證服務】生成通行令牌token,token將通過第一步提供的回調地址,返回給【A服務客戶端】。

使用場景
適用於A服務沒有服務器的情況。比如:純手機小程序,JavaScript語言實現的網頁插件等。

3.3密碼模式(resource owner password credentials)

流程

  1. 直接告訴【A服務客戶端】自己的【B服務認證服務】的用戶名和密碼
  2. 【A服務客戶端】攜帶【B服務認證服務】的用戶名和密碼向【B服務認證服務】發起請求獲取token。
  3. 【B服務認證服務】給【A服務客戶端】頒發token。

使用場景
此種模式雖然簡單,但是用戶將B服務的用戶名和密碼暴露給了A服務,需要兩個服務信任度非常高才能使用。

3.4客戶端模式(client credentials)

流程
說明:這種模式其實已經不太屬於OAuth2的範疇了。A服務完全脫離用戶,以自己的身份去向B服務索取token。換言之,用戶無需具備B服務的使用權也可以。完全是A服務與B服務內部的交互,與用戶無關了。

  1. A服務向B服務索取token。
  2. B服務返回token給A服務。

使用場景
A服務本身需要B服務資源,與用戶無關。

4.OAuth2.0中表結構說明

說明
  如果只是寫個測試案例,完全可以不用連接數據庫,直接將用戶等信息寫在項目中就行。
  但是,我們應該把眼光放在企業開發中。試想,我們自己做的一個軟件,想使用微信第三方登錄。難道你還指望微信去修改他們的代碼,讓我們去訪問?想都別想!那麼微信會怎麼做呢?微信會提供好一個接入的入口,讓我們自己去申請訪問權限。這些數據自然而然需要保存在數據庫中!所以,我們將直接講解數據庫版實現方式!
建表語句
官方SQL地址:
https://github.com/spring-projects/spring-security-oauth/blob/master/spring-securityoauth2/src/test/resources/schema.sql

/*
SQLyog Ultimate v12.08 (64 bit)
MySQL - 8.0.16 : Database - security_authority
*********************************************************************
*/


/*!40101 SET NAMES utf8 */;

/*!40101 SET SQL_MODE=''*/;

/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
/*Table structure for table `oauth_access_token` */

DROP TABLE IF EXISTS `oauth_access_token`;

CREATE TABLE `oauth_access_token` (
  `token_id` varchar(255) DEFAULT NULL,
  `token` longblob,
  `authentication_id` varchar(255) DEFAULT NULL,
  `user_name` varchar(255) DEFAULT NULL,
  `client_id` varchar(255) DEFAULT NULL,
  `authentication` longblob,
  `refresh_token` varchar(255) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

/*Data for the table `oauth_access_token` */

/*Table structure for table `oauth_approvals` */

DROP TABLE IF EXISTS `oauth_approvals`;

CREATE TABLE `oauth_approvals` (
  `userId` varchar(255) DEFAULT NULL,
  `clientId` varchar(255) DEFAULT NULL,
  `scope` varchar(255) DEFAULT NULL,
  `status` varchar(10) DEFAULT NULL,
  `expiresAt` datetime DEFAULT NULL,
  `lastModifiedAt` datetime DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

/*Data for the table `oauth_approvals` */

/*Table structure for table `oauth_client_details` */

DROP TABLE IF EXISTS `oauth_client_details`;

CREATE TABLE `oauth_client_details` (
  `client_id` varchar(255) NOT NULL,
  `resource_ids` varchar(255) DEFAULT NULL,
  `client_secret` varchar(255) DEFAULT NULL,
  `scope` varchar(255) DEFAULT NULL,
  `authorized_grant_types` varchar(255) DEFAULT NULL,
  `web_server_redirect_uri` varchar(255) DEFAULT NULL,
  `authorities` varchar(255) DEFAULT NULL,
  `access_token_validity` int(11) DEFAULT NULL,
  `refresh_token_validity` int(11) DEFAULT NULL,
  `additional_information` varchar(255) DEFAULT NULL,
  `autoapprove` varchar(255) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

/*Data for the table `oauth_client_details` */

/*Table structure for table `oauth_client_token` */

DROP TABLE IF EXISTS `oauth_client_token`;

CREATE TABLE `oauth_client_token` (
  `token_id` varchar(255) DEFAULT NULL,
  `token` longblob,
  `authentication_id` varchar(255) DEFAULT NULL,
  `user_name` varchar(255) DEFAULT NULL,
  `client_id` varchar(255) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

/*Data for the table `oauth_client_token` */

/*Table structure for table `oauth_code` */

DROP TABLE IF EXISTS `oauth_code`;

CREATE TABLE `oauth_code` (
  `code` varchar(255) DEFAULT NULL,
  `authentication` varbinary(2550) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

/*Data for the table `oauth_code` */

/*Table structure for table `oauth_refresh_token` */

DROP TABLE IF EXISTS `oauth_refresh_token`;

CREATE TABLE `oauth_refresh_token` (
  `token_id` varchar(255) DEFAULT NULL,
  `token` longblob,
  `authentication` longblob
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

/*Data for the table `oauth_refresh_token` */

/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;

5.表字段說明

5.1oauth_client_details【核心表】

字段名 字段說明
client_id 主鍵,必須唯一,不能爲空. 用於唯一標識每一個客戶端(client); 在註冊時必須填寫(也可由服務 端自動生成). 對於不同的grant_type,該字段都是必須的. 在實際應用中的另一個名稱叫 appKey,與client_id是同一個概念.
resource_ids 客戶端所能訪問的資源id集合,多個資源時用逗號(,)分隔,如: “unity-resource,mobile- resource”. 該字段的值必須來源於與security.xml中標籤?oauth2:resource-server的屬性 resource-id值一致. 在security.xml配置有幾個?oauth2:resource-server標籤, 則該字段可以 使用幾個該值. 在實際應用中, 我們一般將資源進行分類,並分別配置對應 的?oauth2:resource-server,如訂單資源配置一個?oauth2:resource-server, 用戶資源又配置 一個?oauth2:resource-server. 當註冊客戶端時,根據實際需要可選擇資源id,也可根據不同的 註冊流程,賦予對應的資源id.
client_secret 用於指定客戶端(client)的訪問密匙; 在註冊時必須填寫(也可由服務端自動生成). 對於不同的 grant_type,該字段都是必須的. 在實際應用中的另一個名稱叫appSecret,與client_secret是 同一個概念.
scope 指定客戶端申請的權限範圍,可選值包括read,write,trust;若有多個權限範圍用逗號(,)分隔,如: “read,write”. scope的值與security.xml中配置的?intercept-url的access屬性有關係. 如?intercept-url的配置爲?intercept-url pattern="/m/**" access=“ROLE_MOBILE,SCOPE_READ”/>則說明訪問該URL時的客戶端必須有read權限範 圍. write的配置值爲SCOPE_WRITE, trust的配置值爲SCOPE_TRUST. 在實際應該中, 該值一 般由服務端指定, 常用的值爲read,write.
authorized_grant_types 指定客戶端支持的grant_type,可選值包括 authorization_code,password,refresh_token,implicit,client_credentials, 若支持多個 grant_type用逗號(,)分隔,如: “authorization_code,password”. 在實際應用中,當註冊時,該字 段是一般由服務器端指定的,而不是由申請者去選擇的,最常用的grant_type組合有: “authorization_code,refresh_token”(針對通過瀏覽器訪問的客戶端); “password,refresh_token”(針對移動設備的客戶端). implicit與client_credentials在實際中 很少使用.
web_server_redirect_uri 客戶端的重定向URI,可爲空, 當grant_type爲authorization_code或implicit時, 在Oauth的流 程中會使用並檢查與註冊時填寫的redirect_uri是否一致. 下面分別說明:當 grant_type=authorization_code時, 第一步 從 spring-oauth-server獲取 'code’時客戶端發 起請求時必須有redirect_uri參數, 該參數的值必須與 web_server_redirect_uri的值一致. 第 二步 用 ‘code’ 換取 ‘access_token’ 時客戶也必須傳遞相同的redirect_uri. 在實際應用中, web_server_redirect_uri在註冊時是必須填寫的, 一般用來處理服務器返回的code, 驗證 state是否合法與通過code去換取access_token值.在spring-oauth-client項目中, 可具體參考 AuthorizationCodeController.java中的authorizationCodeCallback方法.當 grant_type=implicit時通過redirect_uri的hash值來傳遞access_token值. 如:http://localhost:7777/spring-oauth-client/implicit#access_token=dc891f4a-ac88- 4ba6-8224-a2497e013865&token_type=bearer&expires_in=43199然後客戶端通過JS等從 hash值中取到access_token值.
authorities 指定客戶端所擁有的Spring Security的權限值,可選, 若有多個權限值,用逗號(,)分隔, 如: "ROLE_
access_token_validity 設定客戶端的access_token的有效時間值(單位:秒),可選, 若不設定值則使用默認的有效時間 值(60 * 60 * 12, 12小時). 在服務端獲取的access_token JSON數據中的expires_in字段的值 即爲當前access_token的有效時間值. 在項目中, 可具體參考DefaultTokenServices.java中屬 性accessTokenValiditySeconds. 在實際應用中, 該值一般是由服務端處理的, 不需要客戶端 自定義.refresh_token_validity 設定客戶端的refresh_token的有效時間值(單位:秒),可選, 若不設定值則使用默認的有效時間值(60 * 60 * 24 * 30, 30天). 若客戶端的grant_type不包 括refresh_token,則不用關心該字段 在項目中, 可具體參考DefaultTokenServices.java中屬 性refreshTokenValiditySeconds. 在實際應用中, 該值一般是由服務端處理的, 不需要客戶端 自定義.
additional_information 這是一個預留的字段,在Oauth的流程中沒有實際的使用,可選,但若設置值,必須是JSON格式的 數據,如:{“country”:“CN”,“country_code”:“086”}按照spring-security-oauth項目中對該字段 的描述 Additional information for this client, not need by the vanilla OAuth protocol but might be useful, for example,for storing descriptive information. (詳見 ClientDetails.java的getAdditionalInformation()方法的註釋)在實際應用中, 可以用該字段來 存儲關於客戶端的一些其他信息,如客戶端的國家,地區,註冊時的IP地址等等.create_time 數據的創建時間,精確到秒,由數據庫在插入數據時取當前系統時間自動生成(擴展字段)
archived 用於標識客戶端是否已存檔(即實現邏輯刪除),默認值爲’0’(即未存檔). 對該字段的具體使用請 參考CustomJdbcClientDetailsService.java,在該類中,擴展了在查詢client_details的SQL加上 archived = 0條件 (擴展字段)
trusted 設置客戶端是否爲受信任的,默認爲’0’(即不受信任的,1爲受信任的). 該字段只適用於 grant_type="authorization_code"的情況,當用戶登錄成功後,若該值爲0,則會跳轉到讓用戶 Approve的頁面讓用戶同意授權, 若該字段爲1,則在登錄後不需要再讓用戶Approve同意授權 (因爲是受信任的). 對該字段的具體使用請參考OauthUserApprovalHandler.java. (擴展字 段)
autoapprove 設置用戶是否自動Approval操作, 默認值爲 ‘false’, 可選值包括 ‘true’,‘false’, ‘read’,‘write’. 該 字段只適用於grant_type="authorization_code"的情況,當用戶登錄成功後,若該值爲’true’或 支持的scope值,則會跳過用戶Approve的頁面, 直接授權. 該字段與 trusted 有類似的功能, 是 spring-security-oauth2 的 2.0 版本後添加的新屬性. 在項目中,主要操作 oauth_client_details表的類是JdbcClientDetailsService.java, 更多的細節請參考該類. 也可 以根據實際的需要,去擴展或修改該類的實現.

5.2oauth_client_token

字段名 字段說明
create_time 數據的創建時間,精確到秒,由數據庫在插入數據時取當前系統時間自動生成(擴展字段)
token_id 從服務器端獲取到的access_token的值.
token 這是一個二進制的字段, 存儲的數據是OAuth2AccessToken.java對象序列化後的二進制數據.
authentication_id 該字段具有唯一性, 是根據當前的username(如果有),client_id與scope通過MD5加密生成的. 具體實現請參考DefaultClientKeyGenerator.java類.
user_name 登錄時的用戶名
client_id

該表用於在客戶端系統中存儲從服務端獲取的token數據, 在spring-oauth-server項目中未使用到. 對oauth_client_token表的主要操作在JdbcClientTokenServices.java類中, 更多的細節請參考該類.

5.3oauth_access_token

字段名 字段說明
create_time 數據的創建時間,精確到秒,由數據庫在插入數據時取當前系統時間自動生成(擴展字段)
token_id 從服務器端獲取到的access_token的值.
token 這是一個二進制的字段, 存儲的數據是OAuth2AccessToken.java對象序列化後的二進制數據.
authentication_id 該字段具有唯一性, 是根據當前的username(如果有),client_id與scope通過MD5加密生成的. 具體實現請參考DefaultClientKeyGenerator.java類.
user_name 登錄時的用戶名
client_id
authentication 存儲將OAuth2Authentication.java對象序列化後的二進制數據.
refresh_token 該字段的值是將refresh_token的值通過MD5加密後存儲的. 在項目中,主要操作oauth_access_token表的對象是JdbcTokenStore.java. 更多的細節請參考該類

5.4oauth_refresh_token

字段名 字段說明
create_time 數據的創建時間,精確到秒,由數據庫在插入數據時取當前系統時間自動生成(擴展字段)
token_id 該字段的值是將refresh_token的值通過MD5加密後存儲的.
token 存儲將OAuth2RefreshToken.java對象序列化後的二進制數據
authentication 存儲將OAuth2RefreshToken.java對象序列化後的二進制數據

5.5oauth_code

字段名 字段說明
create_time 數據的創建時間,精確到秒,由數據庫在插入數據時取當前系統時間自動生成(擴展字段)
code 存儲服務端系統生成的code的值(未加密).
authentication 存儲將AuthorizationRequestHolder.java對象序列化後的二進制數據.

二、OAuth2.0實戰案例

  本案例同樣通過maven的聚合工程來實現。

1.創建父工程

在這裏插入圖片描述
設置pom文件

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.3.RELEASE</version>
    <relativePath/>
</parent>

<properties>
    <spring-cloud.version>Greenwich.RELEASE</spring-cloud.version>
</properties>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

<repositories>
    <repository>
        <id>spring-snapshots</id>
        <name>Spring Snapshots</name>
        <url>https://repo.spring.io/snapshot</url>
        <snapshots>
            <enabled>true</enabled>
        </snapshots>
    </repository>
    <repository>
        <id>spring-milestones</id>
        <name>Spring Milestones</name>
        <url>https://repo.spring.io/milestone</url>
        <snapshots>
            <enabled>false</enabled>
        </snapshots>
    </repository>
</repositories>

2.創建資源項目

  接下來創建我們的資源項目

2.1創建項目

在這裏插入圖片描述

2.2導入依賴

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.springframework.security.oauth.boot/spring-security-oauth2-autoconfigure -->
    <dependency>
        <groupId>org.springframework.security.oauth.boot</groupId>
        <artifactId>spring-security-oauth2-autoconfigure</artifactId>
        <version>2.1.0.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-oauth2</artifactId>
        <version>2.1.0.RELEASE</version>
        <exclusions>
            <exclusion>
                <artifactId>org.springframework.security.oauth.boot</artifactId>
                <groupId>spring-security-oauth2-autoconfigure</groupId>
            </exclusion>
        </exclusions>
    </dependency>


    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.47</version>
    </dependency>
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.1.0</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.1.10</version>
    </dependency>
</dependencies>

2.3配置文件

server:
  port: 9002
spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/srm
    username: root
    password: 123456
    type: com.alibaba.druid.pool.DruidDataSource
  main:
    allow-bean-definition-overriding: true #允許我們自己覆蓋spring放入到IOC容器的對象
mybatis:
  type-aliases-package: com.dpb.domain
  mapper-locations: classpath:mapper/*.xml
logging:
  level:
    com.dpb: debug

2.4啓動類

package com.dpb;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @program: springboot-security-oauth2-demo
 * @description:
 * @author: 波波烤鴨
 * @create: 2019-12-04 22:33
 */
@SpringBootApplication
@MapperScan("com.dpb.mapper")
public class OAuthSourceApp {

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

2.5控制器

/**
 * @program: springboot-security-oauth2-demo
 * @description:
 * @author: 波波烤鴨
 * @create: 2019-12-04 22:34
 */
@RestController
public class ProductController {

    @RequestMapping("/findAll")
    public String findAll(){
        return "產品列表信息...";
    }
}

  因爲我們引入了 SpringSecurity,所以我們此時沒法直接方法 findAll方法,啓動服務後訪問如下:
在這裏插入圖片描述

那麼如何解決呢?前面我們是採用單點登錄的方式解決了這個問題,那麼今天我們把這個資源交給OAuth2來管理,使用通行的token來訪問資源

2.6將訪問資源作爲OAuth2的資源來管理

複製前面介紹的JWT中的相關代碼(GitHub地址會提供)
即便是用OAuth2管理資源,也一樣需要認證,這兩個對象還是需要的。
在這裏插入圖片描述

2.7編寫資源管理配置類

package com.dpb.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;

import javax.sql.DataSource;

/**
 * @program: springboot-security-oauth2-demo
 * @description:
 * @author: 波波烤鴨
 * @create: 2019-12-04 22:47
 */
@Configuration
@EnableResourceServer
public class OAuthSourceConfig extends ResourceServerConfigurerAdapter {

    @Autowired
    private DataSource dataSource;

    /**
     * 指定token的持久化策略
     * InMemoryTokenStore表示將token存儲在內存
     * Redis表示將token存儲在redis中
     * JdbcTokenStore存儲在數據庫中
     * @return
     */
    @Bean
    public TokenStore jdbcTokenStore(){
        return new JdbcTokenStore(dataSource);
    }

    /**
     * 指定當前資源的id和存儲方案
     * @param resources
     * @throws Exception
     */
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.resourceId("product_api").tokenStore(jdbcTokenStore());
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                //指定不同請求方式訪問資源所需要的權限,一般查詢是read,其餘是write。
                .antMatchers(HttpMethod.GET, "/**").access("#oauth2.hasScope('read')")
                .antMatchers(HttpMethod.POST, "/**").access("#oauth2.hasScope('write')")
                .antMatchers(HttpMethod.PATCH, "/**").access("#oauth2.hasScope('write')")
                .antMatchers(HttpMethod.PUT, "/**").access("#oauth2.hasScope('write')")
                .antMatchers(HttpMethod.DELETE, "/**").access("#oauth2.hasScope('write')")
                .and()
                .headers().addHeaderWriter((request, response) -> {
            response.addHeader("Access-Control-Allow-Origin", "*");//允許跨域
            if (request.getMethod().equals("OPTIONS")) {//如果是跨域的預檢請求,則原封不動向下傳達請求頭信息
                response.setHeader("Access-Control-Allow-Methods", request.getHeader("Access-Control-Request-Method"));
                response.setHeader("Access-Control-Allow-Headers", request.getHeader("Access-Control-Request-Headers"));
            }
        });
    }
}

3.創建認證項目

  接下來我們創建認證相關的項目

3.1創建項目

在這裏插入圖片描述

3.2導入依賴

和source項目的一樣

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.springframework.security.oauth.boot/spring-security-oauth2-autoconfigure -->
    <dependency>
        <groupId>org.springframework.security.oauth.boot</groupId>
        <artifactId>spring-security-oauth2-autoconfigure</artifactId>
        <version>2.1.0.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-oauth2</artifactId>
        <version>2.1.0.RELEASE</version>
        <exclusions>
            <exclusion>
                <artifactId>org.springframework.security.oauth.boot</artifactId>
                <groupId>spring-security-oauth2-autoconfigure</groupId>
            </exclusion>
        </exclusions>
    </dependency>


    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.47</version>
    </dependency>
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.1.0</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.1.10</version>
    </dependency>
</dependencies>

3.3配置文件

server:
  port: 9001
spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/srm
    username: root
    password: 123456
    type: com.alibaba.druid.pool.DruidDataSource
  main:
    allow-bean-definition-overriding: true #允許我們自己覆蓋spring放入到IOC容器的對象
mybatis:
  type-aliases-package: com.dpb.domain
  mapper-locations: classpath:mapper/*.xml
  
logging:
  level:
    com.dpb: debug

3.4啓動類

/**
 * @program: springboot-security-oauth2-demo
 * @description:
 * @author: 波波烤鴨
 * @create: 2019-12-04 23:06
 */
@SpringBootApplication
@MapperScan("com.dpb.mapper")
public class OAuthServerApp {
    public static void main(String[] args) {
        SpringApplication.run(OAuthServerApp.class,args);
    }
}

3.5複製之前認證的代碼

在這裏插入圖片描述

3.6提供SpringSecurity的配置類

package com.dpb.config;

import com.dpb.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
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.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

/**
 * @program: springboot-security-oauth2-demo
 * @description:
 * @author: 波波烤鴨
 * @create: 2019-12-04 23:09
 */
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserService userService;

    @Bean
    public BCryptPasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginProcessingUrl("/login")
                .permitAll()
                .and()
                .csrf()
                .disable();
    }

    //AuthenticationManager對象在OAuth2認證服務中要使用,提前放入IOC容器中
    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

3.7提供OAuth2的配置類

package com.dpb.config;

import com.dpb.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
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.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.approval.ApprovalStore;
import org.springframework.security.oauth2.provider.approval.JdbcApprovalStore;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.JdbcAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;

import javax.sql.DataSource;

/**
 * @program: springboot-security-oauth2-demo
 * @description:
 * @author: 波波烤鴨
 * @create: 2019-12-04 23:12
 */
@Configuration
@EnableAuthorizationServer
public class OauthServerConfig extends AuthorizationServerConfigurerAdapter {
    //數據庫連接池對象
    @Autowired
    private DataSource dataSource;

    //認證業務對象
    @Autowired
    private UserService userService;

    //授權模式專用對象
    @Autowired
    private AuthenticationManager authenticationManager;

    //客戶端信息來源
    @Bean
    public JdbcClientDetailsService jdbcClientDetailsService(){
        return new JdbcClientDetailsService(dataSource);
    }

    //token保存策略
    @Bean
    public TokenStore tokenStore(){
        return new JdbcTokenStore(dataSource);
    }

    //授權信息保存策略
    @Bean
    public ApprovalStore approvalStore(){
        return new JdbcApprovalStore(dataSource);
    }

    //授權碼模式數據來源
    @Bean
    public AuthorizationCodeServices authorizationCodeServices(){
        return new JdbcAuthorizationCodeServices(dataSource);
    }

    //指定客戶端信息的數據庫來源
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(jdbcClientDetailsService());
    }

    //檢查token的策略
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.allowFormAuthenticationForClients();
        security.checkTokenAccess("isAuthenticated()");
    }

    //OAuth2的主配置信息
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
                .approvalStore(approvalStore())
                .authenticationManager(authenticationManager)
                .authorizationCodeServices(authorizationCodeServices())
                .tokenStore(tokenStore());
    }
}

4.測試

4.1在數據庫中手動添加客戶端信息

  所有要使用當前項目資源的項目,都是我們的客戶端。比如我們之前舉的例子,A服務打印照片,B服務存儲照片。A服務要使用B服務的資源,那麼A服務就是B服務的客戶端。這裏要區分用戶的信息和客戶端信息,用戶信息是用戶在B服務上註冊的用戶信息,在sys_user表中。客戶端信息是A服務在B服務中註冊的賬號,在OAuth2的oauth_client_details表中。
測試數據sql語句如下:

INSERT INTO `oauth_client_details` (
	`client_id`,
	`resource_ids`,
	`client_secret`,
	`scope`,
	`authorized_grant_types`,
	`web_server_redirect_uri`,
	`authorities`,
	`access_token_validity`,
	`refresh_token_validity`,
	`additional_information`,
	`autoapprove`
)
VALUES
	(
		'bobo_one',
		'product_api',
		'$2a$10$CYX9OMv0yO8wR8rE19N2fOaXDJondci5uR68k2eQJm50q8ESsDMlC',
		'read, write',
		'client_credentials,implicit,authorization_code,refresh_token,password',
		'http://www.baidu.com',
		NULL,
		NULL,
		NULL,
		NULL,
		'false'
	);

這裏注意resource_ids不要寫錯,回調地址web_server_redirect_uri先寫成百度。
啓動兩個服務測試

4.2 授權碼模式測試

在地址欄訪問地址
http://localhost:9001/oauth/authorize?response_type=code&client_id=bobo_one
跳轉到SpringSecurity默認認證頁面,提示用戶登錄個人賬戶【這裏是sys_user表中的數據】
在這裏插入圖片描述

登錄成功後詢問用戶是否給予操作資源的權限,具體給什麼權限。Approve是授權,Deny是拒絕。這裏我們選擇read和write都給予Approve
在這裏插入圖片描述
點擊Authorize後跳轉到回調地址並獲取授權碼

在這裏插入圖片描述

在這裏插入圖片描述

使用授權碼到服務器申請通行令牌token(測試使用的是PostMan)
在這裏插入圖片描述
重啓資源服務器,然後攜帶通行令牌再次去訪問資源服務器,大功告成!

在這裏插入圖片描述

4.3簡化模式測試

在地址欄訪問地址
http://localhost:9001/oauth/authorize?response_type=token&client_id=bobo_one

由於上面用戶已經登錄過了,所以無需再次登錄,其實和上面是有登錄步驟的,這時,瀏覽器直接返回了token
在這裏插入圖片描述
直接訪問資源服務器

在這裏插入圖片描述

4.4密碼模式測試

申請token

在這裏插入圖片描述

4.5客戶端模式測試

申請token

在這裏插入圖片描述

在這裏插入圖片描述

搞定~相關案例實現

GitHub地址案例代碼地址

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章