天天看點

springboot oauth2登入成功處理器_寫了一個 SSO 單點登入的代碼示例給胖友!

springboot oauth2登入成功處理器_寫了一個 SSO 單點登入的代碼示例給胖友!

摘要: 原創出處http://www.iocoder.cn/Spring-Security/OAuth2-learning-sso/「芋道源碼」歡迎轉載,保留摘要,謝謝!

1. 概述

在前面的文章中,我們學習了 Spring Security OAuth 的

簡單

使用。

  • 《芋道 Spring Security OAuth2 入門》
  • 《芋道 Spring Security OAuth2 存儲器》

今天我們來搞

波“大”的,通過 Spring Security OAuth 實作一個單點登入

的功能。

可能會有

粉絲不太了解單點登入是什麼?單點登入,英文是

Single Sign On

,簡稱為

SSO

,指的是當有

多個

系統需要登入時,使用者隻需要登入一個

統一

的登入系統,而無需在

多個

系統重複登入。

舉個最常見的

例子

,我們在浏覽器中使用阿裡“全家桶”:

“求助信:麻煩有認識阿裡的胖友,讓他們給打下錢。。。
  • 淘寶:https://www.taobao.com
  • 天貓:https://www.tmall.com
  • 飛豬:https://www.fliggy.com
  • ...

我們隻需要在

統一登入系統

(https://login.taobao.com)進行登入即可,而後就可以“愉快”的自由剁手,并且無需分别在淘寶、天貓、飛豬等等系統重新登入。

springboot oauth2登入成功處理器_寫了一個 SSO 單點登入的代碼示例給胖友!

登入系統

“友情提示:更多單點登入的介紹,可見《維基百科 —— 單點登入》。

下面,我們正式搭建 Spring Security OAuth 實作 SSO 的

示例項目

,如下圖所示:

springboot oauth2登入成功處理器_寫了一個 SSO 單點登入的代碼示例給胖友!

項目結構

  • 建立

    lab-68-demo21-authorization-server-on-sso

    項目,作為 統一登入系統

    “旁白君:機智的胖友,是不是發現這個項目和

    授權 伺服器非常相似!!!
  • 建立

    lab-68-demo21-resource-server-on-sso

    項目,模拟需要登入的 XXX 系統

    “旁白君:機智的胖友,是不是發現這個項目和

    資源 伺服器非常相似!!!

2. 搭建統一登入系統

“示例代碼對應倉庫:

統一登入系統:

lab-68-demo21-authorization-server-on-sso

建立

lab-68-demo21-authorization-server-on-sso

項目,作為

統一登入系統

“友情提示:整個實作代碼,和我們前文看到的 授權 伺服器是基本一緻的。

2.1 初始化資料庫

resources/db

目錄下,有四個 SQL 腳本,分别用于初始化 User 和 OAuth 相關的表。

springboot oauth2登入成功處理器_寫了一個 SSO 單點登入的代碼示例給胖友!

SQL 腳本

2.1.1 初始化 OAuth 表

① 執行

oauth_schema.sql

腳本,建立資料庫

表結構

drop table if exists oauth_client_details;
create table oauth_client_details (
  client_id VARCHAR(255) PRIMARY KEY,
  resource_ids VARCHAR(255),
  client_secret VARCHAR(255),
  scope VARCHAR(255),
  authorized_grant_types VARCHAR(255),
  web_server_redirect_uri VARCHAR(255),
  authorities VARCHAR(255),
  access_token_validity INTEGER,
  refresh_token_validity INTEGER,
  additional_information VARCHAR(4096),
  autoapprove VARCHAR(255)
);

create table if not exists oauth_client_token (
  token_id VARCHAR(255),
  token LONG VARBINARY,
  authentication_id VARCHAR(255) PRIMARY KEY,
  user_name VARCHAR(255),
  client_id VARCHAR(255)
);

create table if not exists oauth_access_token (
  token_id VARCHAR(255),
  token LONG VARBINARY,
  authentication_id VARCHAR(255) PRIMARY KEY,
  user_name VARCHAR(255),
  client_id VARCHAR(255),
  authentication LONG VARBINARY,
  refresh_token VARCHAR(255)
);

create table if not exists oauth_refresh_token (
  token_id VARCHAR(255),
  token LONG VARBINARY,
  authentication LONG VARBINARY
);

create table if not exists oauth_code (
  code VARCHAR(255), authentication LONG VARBINARY
);

create table if not exists oauth_approvals (
 userId VARCHAR(255),
 clientId VARCHAR(255),
 scope VARCHAR(255),
 status VARCHAR(10),
 expiresAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
 lastModifiedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
           

結果如下圖所示:

springboot oauth2登入成功處理器_寫了一個 SSO 單點登入的代碼示例給胖友!

表結構

springboot oauth2登入成功處理器_寫了一個 SSO 單點登入的代碼示例給胖友!
“旁白君:這裡的表結構設計,我們可以借鑒參考,實作自己的 OAuth 2.0 的功能。

② 執行

oauth_data.sql

腳本,插入一個用戶端記錄。

INSERT INTO oauth_client_details
 (client_id, client_secret, scope, authorized_grant_types,
 web_server_redirect_uri, authorities, access_token_validity,
 refresh_token_validity, additional_information, autoapprove)
VALUES
 ('clientapp', '112233', 'read_userinfo,read_contacts',
 'password,authorization_code,refresh_token', 'http://127.0.0.1:9090/login', null, 3600, 864000, null, true);
           
注意 !這條記錄的

web_server_redirect_uri

字段,我們設定為 http:// 127.0.0.1:9090/login ,這是稍後我們搭建的 XXX 系統的回調位址。

統一登入系統采用 OAuth 2.0 的

授權碼

模式進行授權。

授權成功後,浏覽器會跳轉 http:// 127.0.0.1:9090/login 回調位址,然後 XXX 系統會通過

授權碼 向統一登入系統擷取 通路令牌

通過這樣的方式,完成一次

單點登入 的過程。

結果如下圖所示:

springboot oauth2登入成功處理器_寫了一個 SSO 單點登入的代碼示例給胖友!

2.1.2 初始化 User 表

① 執行

user_schema.sql

腳本,建立資料庫

表結構

DROP TABLE IF EXISTS `authorities`;
CREATE TABLE `authorities` (
  `username` varchar(50) NOT NULL,
  `authority` varchar(50) NOT NULL,
  UNIQUE KEY `ix_auth_username` (`username`,`authority`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` (
  `username` varchar(50) NOT NULL,
  `password` varchar(500) NOT NULL,
  `enabled` tinyint(1) NOT NULL,
  PRIMARY KEY (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
           

結果如下圖所示:

springboot oauth2登入成功處理器_寫了一個 SSO 單點登入的代碼示例給胖友!

表結構

springboot oauth2登入成功處理器_寫了一個 SSO 單點登入的代碼示例給胖友!

② 執行

user_data.sql

腳本,插入一個使用者記錄和一個授權記錄。

INSERT INTO `authorities` VALUES ('yunai', 'ROLE_USER');

INSERT INTO `users` VALUES ('yunai', '112233', '1');
           

結果如下圖所示:

springboot oauth2登入成功處理器_寫了一個 SSO 單點登入的代碼示例給胖友!

users 和 authorities 表記錄

2.2 引入依賴

建立

pom.xml

檔案,引入 Spring Security OAuth 依賴。

<?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">
    <parent>
        <artifactId>lab-68</artifactId>
        <groupId>cn.iocoder.springboot.labs</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>lab-68-demo21-authorization-server-on-sso</artifactId>

    <properties>
        <!-- 依賴相關配置 -->
        <spring.boot.version>2.2.4.RELEASE</spring.boot.version>
        <!-- 插件相關配置 -->
        <maven.compiler.target>1.8</maven.compiler.target>
        <maven.compiler.source>1.8</maven.compiler.source>
    </properties>

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

    <dependencies>
        <!-- 實作對 Spring MVC 的自動配置 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- 實作對 Spring Security OAuth2 的自動配置 -->
        <dependency>
            <groupId>org.springframework.security.oauth.boot</groupId>
            <artifactId>spring-security-oauth2-autoconfigure</artifactId>
            <version>${spring.boot.version}</version>
        </dependency>

        <!-- 實作對資料庫連接配接池的自動化配置 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency> <!-- 本示例,我們使用 MySQL -->
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.48</version>
        </dependency>

    </dependencies>

</project>
           

2.3 配置檔案

建立

application.yaml

配置檔案,添加

資料庫連接配接池

的配置:

spring:
  # datasource 資料源配置内容,對應 DataSourceProperties 配置屬性類
  datasource:
    url: jdbc:mysql://127.0.0.1:43063/demo-68-authorization-server-sso?useSSL=false&useUnicode=true&characterEncoding=UTF-8
    driver-class-name: com.mysql.jdbc.Driver
    username: root # 資料庫賬号
    password: 123456 # 資料庫密碼
           

2.4 SecurityConfig

建立 SecurityConfig 配置類,通過

Spring Security

提供

使用者認證

的功能。代碼如下:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     * 資料源 DataSource
     */
    @Autowired
    private DataSource dataSource;

    @Override
    @Bean(name = BeanIds.AUTHENTICATION_MANAGER)
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Bean
    public static NoOpPasswordEncoder passwordEncoder() {
        return (NoOpPasswordEncoder) NoOpPasswordEncoder.getInstance();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.jdbcAuthentication()
                .dataSource(dataSource);
    }

}
           

2.5 OAuth2AuthorizationServerConfig

建立 OAuth2AuthorizationServerConfig 配置類,通過

Spring Security OAuth

提供

授權伺服器

的功能。代碼如下:

@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    /**
     * 使用者認證 Manager
     */
    @Autowired
    private AuthenticationManager authenticationManager;

    /**
     * 資料源 DataSource
     */
    @Autowired
    private DataSource dataSource;

    @Bean
    public TokenStore jdbcTokenStore() {
        return new JdbcTokenStore(dataSource);
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(authenticationManager)
                .tokenStore(jdbcTokenStore());
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
        oauthServer.checkTokenAccess("isAuthenticated()");
    }

    @Bean
    public ClientDetailsService jdbcClientDetailsService() {
        return new JdbcClientDetailsService(dataSource);
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(jdbcClientDetailsService());
    }

}
           

2.6 AuthorizationServerApplication

建立 AuthorizationServerApplication 類,統一登入系統的啟動類。代碼如下:

@SpringBootApplication
public class AuthorizationServerApplication {

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

}
           

2.7 簡單測試

執行 AuthorizationServerApplication 啟動統一登入系統。下面,我們使用 Postman 模拟一個 Client,

測試我們是否搭建成功

POST

請求 http://localhost:8080/oauth/token 位址,使用密碼模式進行

授權

。如下圖所示:

springboot oauth2登入成功處理器_寫了一個 SSO 單點登入的代碼示例給胖友!

密碼模式

成功擷取到通路令牌,成功!

3. 搭建 XXX 系統

“示例代碼對應倉庫:

XXX 系統:

lab-68-demo21-resource-server-on-sso

建立

lab-68-demo21-resource-server-on-sso

項目,搭建

XXX 系統

,接入

統一登入系統

實作 SSO 功能。

“友情提示:整個實作代碼,和我們前文看到的 資源 伺服器是基本一緻的。

3.1 引入依賴

建立

pom.xml

檔案,引入 Spring Security OAuth 依賴。

<?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">
    <parent>
        <artifactId>lab-68</artifactId>
        <groupId>cn.iocoder.springboot.labs</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>lab-68-demo21-resource-server</artifactId>

    <properties>
        <!-- 依賴相關配置 -->
        <spring.boot.version>2.2.4.RELEASE</spring.boot.version>
        <!-- 插件相關配置 -->
        <maven.compiler.target>1.8</maven.compiler.target>
        <maven.compiler.source>1.8</maven.compiler.source>
    </properties>

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

    <dependencies>
        <!-- 實作對 Spring MVC 的自動配置 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- 實作對 Spring Security OAuth2 的自動配置 -->
        <dependency>
            <groupId>org.springframework.security.oauth.boot</groupId>
            <artifactId>spring-security-oauth2-autoconfigure</artifactId>
            <version>${spring.boot.version}</version>
        </dependency>
    </dependencies>

</project>
           

3.2 配置檔案

建立

application.yaml

配置檔案,添加

SSO

相關配置:

server:
  port: 9090
  servlet:
    session:
      cookie:
        name: SSO-SESSIONID # 自定義 Session 的 Cookie 名字,防止沖突。沖突後,會導緻 SSO 登入失敗。

security:
  oauth2:
    # OAuth2 Client 配置,對應 OAuth2ClientProperties 類
    client:
      client-id: clientapp
      client-secret: 112233
      user-authorization-uri: http://127.0.0.1:8080/oauth/authorize # 擷取使用者的授權碼位址
      access-token-uri: http://127.0.0.1:8080/oauth/token # 擷取通路令牌的位址
    # OAuth2 Resource 配置,對應 ResourceServerProperties 類
    resource:
      token-info-uri: http://127.0.0.1:8080/oauth/check_token # 校驗通路令牌是否有效的位址
           

server.servlet.session.cookie.name

配置項,自定義 Session 的 Cookie 名字,防止沖突。沖突後,會導緻 SSO 登入失敗。

“友情提示:具體的值,胖友可以根據自己的喜歡設定。

security.oauth2.client

配置項,OAuth2 Client 配置,對應 OAuth2ClientProperties 類。在這個配置項中,我們添加了用戶端的

client-id

client-secret

security.oauth2.client.user-authorization-uri

配置項,擷取使用者的

授權碼

位址。

在通路 XXX 系統需要登入的位址時,Spring Security OAuth 會自動跳轉到

統一登入系統

,進行統一登入擷取

授權

而這裡配置的

security.oauth2.client.user-authorization-uri

位址,就是之前

授權

伺服器的

oauth/authorize

接口,可以進行

授權碼

模式的授權。

“友情提示:如果胖友忘記 授權 伺服器的

oauth/authorize

接口,建議回看下《芋道 Spring Security OAuth2 入門》的「3. 授權碼模式」小節。

security.oauth2.client.access-token-uri

配置項,擷取

通路令牌

的位址。

統一登入系統

完成統一登入并授權後,浏覽器會跳轉回 XXX 系統的回調位址。在該位址上,會調用

統一登入系統

security.oauth2.client.user-authorization-uri

位址,通過

授權碼

擷取到

通路令牌

而這裡配置的

security.oauth2.client.user-authorization-uri

位址,就是之前

授權

伺服器的

oauth/token

接口。

security.oauth2.resource.client.token-info-uri

配置項,校驗

通路令牌

是否有效的位址。

在擷取到

通路令牌

之後,每次請求 XXX 系統時,都會調用

統一登入系統

security.oauth2.resource.client.token-info-uri

位址,校驗通路令牌的有效性,同時傳回

使用者的基本資訊

而這裡配置的

security.oauth2.resource.client.token-info-uri

位址,就是之前

授權

伺服器的

oauth/check_token

接口。

至此,我們可以發現,Spring Security OAuth 實作的 SSO 單點登入功能,是基于其

授權碼模式

實作的。這一點,非常重要,稍後我們示範下會更加容易了解到。

3.3 OAuthSsoConfig

建立 OAuthSsoConfig 類,配置接入 SSO 功能。代碼如下:

@Configuration
@EnableOAuth2Sso // 開啟 Sso 功能
public class OAuthSsoConfig {

}
           

在類上添加

@EnableOAuth2Sso

注解,聲明基于 Spring Security OAuth 的方式接入 SSO 功能。

“友情提示:想要深入的胖友,可以看看 SsoSecurityConfigurer 類。

3.4 UserController

建立 UserController 類,提供擷取目前使用者的

/user/info

接口。代碼如下:

@RestController
@RequestMapping("/user")
public class UserController {

    @RequestMapping("/info")
    public Authentication info(Authentication authentication) {
        return authentication;
    }

}
           

3.5 ResourceServerApplication

建立 ResourceServerApplication 類,XXX 系統的啟動類。代碼如下:

@SpringBootApplication
public class ResourceServerApplication {

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

}
           

3.6 簡單測試(第一彈)

執行 ResourceServerApplication 啟動 XXX 系統。下面,我們來示範下 SSO 單點登入的過程。

① 使用浏覽器,通路

XXX 系統

的 http://127.0.0.1:9090/user/info 位址。因為暫未登入,是以被重定向到

統一登入系統

的 http://127.0.0.1:8080/oauth/authorize

授權

位址。

又因為在

統一登入系統

暫未登入,是以被重定向到

統一登入系統

的 http://127.0.0.1:8080/login

登入

位址。如下圖所示:

springboot oauth2登入成功處理器_寫了一個 SSO 單點登入的代碼示例給胖友!

登入界面

② 輸入使用者的賬号密碼「yunai/1024」,進行

統一登入系統

的登入。登入完成後,進入

統一登入系統

的 http://127.0.0.1:8080/oauth/authorize

授權

位址。如下圖所示:

springboot oauth2登入成功處理器_寫了一個 SSO 單點登入的代碼示例給胖友!

授權界面

③ 點選「Authorize」按鈕,完成使用者的授權。授權完成後,浏覽器重定向到

XXX 系統

的 http://127.0.0.1:9090/login

回調

位址。

XX 系統

的回調位址,拿到授權的

授權碼

後,會

自動

請求

統一登入系統

,通過

授權碼

擷取到

通路令牌

。如此,我們便完成了

XXX 系統

的登入。

擷取授權碼完成後,

自動

跳轉到登入前的 http://127.0.0.1:9090/user/info 位址,列印出目前登入的使用者資訊。如下圖所示:

springboot oauth2登入成功處理器_寫了一個 SSO 單點登入的代碼示例給胖友!

使用者資訊

如此,我們從

統一登入系統

也拿到了

使用者資訊

。下面,我們來進一步将 Spring Security 的

權限控制

功能來示範下。

3.7 SecurityConfig

建立 SecurityConfig 配置類,添加 Spring Security 的功能。代碼如下:

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true) // 開啟對 Spring Security 注解的方法,進行權限驗證。
@Order(101) // OAuth2SsoDefaultConfiguration 使用了 Order(100),是以這裡設定為 Order(101),防止相同順序導緻報錯
public class SecurityConfig extends WebSecurityConfigurerAdapter {
}
           

在類上,增加

@EnableGlobalMethodSecurity

注解,開啟對 Spring Security 注解的方法,進行權限驗證。

3.8 DemoController

建立 DemoController 類,提供測試權限的功能的接口。代碼如下:

@RestController
@RequestMapping("/demo")
public class DemoController {

    @GetMapping("/admin-list")
    @PreAuthorize("hasRole('ADMIN')") // 要求管理者 ROLE_ADMIN 角色
    public String adminList() {
        return "管理者清單";
    }

    @GetMapping("/user-list")
    @PreAuthorize("hasRole('USER')") // 要求普通使用者 ROLE_USER 角色
    public String userList() {
        return "使用者清單";
    }

}
           

因為目前登入的使用者隻有

ROLE_USE

角色,是以

可以

通路

/demo/user-list

接口,

無法

通路

/demo/admin-list

接口。

3.9 簡單測試(第二彈)

執行 ResourceServerApplication

重新開機

XXX 系統。下面,我們來示範下 Spring Security 的權限控制功能。

① 使用浏覽器,通路 http://127.0.0.1:9090/demo/user-list 位址,

成功

。如下圖所示:

springboot oauth2登入成功處理器_寫了一個 SSO 單點登入的代碼示例給胖友!

成功通路

② 使用浏覽器,通路 http://127.0.0.1:9090/demo/admin-list 位址,

失敗

。如下圖所示:

springboot oauth2登入成功處理器_寫了一個 SSO 單點登入的代碼示例給胖友!

失敗通路

666. 彩蛋

至此,我們成功使用 Spring Security OAuth 實作了一個 SSO 單點登入的示例。下圖,是 SSO 的整體流程圖,胖友可以繼續深入了解下:

springboot oauth2登入成功處理器_寫了一個 SSO 單點登入的代碼示例給胖友!

後續,想要深入的胖友,可以看看 Spring Security OAuth 提供的如下兩個過濾器:

  • OAuth2ClientContextFilter
  • OAuth2ClientAuthenticationProcessingFilter

繼續閱讀