oAuth2基于JDBC存儲(chǔ)令牌

概述

基于 JDBC 存儲(chǔ)令牌 的模式用于演示最基本的操作怨绣,幫助大家快速理解 oAuth2 認(rèn)證服務(wù)器中 "認(rèn)證"杜窄、"授權(quán)"、"訪問(wèn)令牌” 的基本概念

操作流程


  • 初始化 oAuth2 相關(guān)表

  • 在數(shù)據(jù)庫(kù)中配置客戶端

  • 配置認(rèn)證服務(wù)器

    • 配置數(shù)據(jù)源:DataSource
    • 配置令牌存儲(chǔ)方式:TokenStore -> JdbcTokenStore
    • 配置客戶端讀取方式:ClientDetailsService -> JdbcClientDetailsService
    • 配置服務(wù)端點(diǎn)信息:AuthorizationServerEndpointsConfigurer
      • tokenStore:設(shè)置令牌存儲(chǔ)方式
    • 配置客戶端信息:ClientDetailsServiceConfigurer
      • withClientDetails:設(shè)置客戶端配置讀取方式
  • 配置 Web 安全

    • 配置密碼加密方式:BCryptPasswordEncoder
    • 配置認(rèn)證信息:AuthenticationManagerBuilder
  • 通過(guò) GET 請(qǐng)求訪問(wèn)認(rèn)證服務(wù)器獲取授權(quán)碼

    • 端點(diǎn):/oauth/authorize
  • 通過(guò) POST 請(qǐng)求利用授權(quán)碼訪問(wèn)認(rèn)證服務(wù)器獲取令牌

    • 端點(diǎn):/oauth/token
      附:默認(rèn)的端點(diǎn) URL
  • /oauth/authorize:授權(quán)端點(diǎn)

  • /oauth/token:令牌端點(diǎn)

  • /oauth/confirm_access:用戶確認(rèn)授權(quán)提交端點(diǎn)

  • /oauth/error:授權(quán)服務(wù)錯(cuò)誤信息端點(diǎn)

  • /oauth/check_token:用于資源服務(wù)訪問(wèn)的令牌解析端點(diǎn)

  • /oauth/token_key:提供公有密匙的端點(diǎn),如果你使用 JWT 令牌的話

初始化 oAuth2 相關(guān)表

使用官方提供的建表腳本初始化 oAuth2 相關(guān)表芥备,地址如下:

https://github.com/spring-projects/spring-security-oauth/blob/master/spring-security-oauth2/src/test/resources/schema.sql

由于我們使用的是 MySQL 數(shù)據(jù)庫(kù),默認(rèn)建表語(yǔ)句中主鍵為 VARCHAR(256)舌菜,這超過(guò)了最大的主鍵長(zhǎng)度萌壳,請(qǐng)手動(dòng)修改為 128,并用BLOB 替換語(yǔ)句中的 LONGVARBINARY 類(lèi)型日月,修改后的建表腳本如下:

CREATE TABLE `clientdetails` (
  `appId` varchar(128) NOT NULL,
  `resourceIds` varchar(256) DEFAULT NULL,
  `appSecret` varchar(256) DEFAULT NULL,
  `scope` varchar(256) DEFAULT NULL,
  `grantTypes` varchar(256) DEFAULT NULL,
  `redirectUrl` varchar(256) DEFAULT NULL,
  `authorities` varchar(256) DEFAULT NULL,
  `access_token_validity` int(11) DEFAULT NULL,
  `refresh_token_validity` int(11) DEFAULT NULL,
  `additionalInformation` varchar(4096) DEFAULT NULL,
  `autoApproveScopes` varchar(256) DEFAULT NULL,
  PRIMARY KEY (`appId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `oauth_access_token` (
  `token_id` varchar(256) DEFAULT NULL,
  `token` blob,
  `authentication_id` varchar(128) NOT NULL,
  `user_name` varchar(256) DEFAULT NULL,
  `client_id` varchar(256) DEFAULT NULL,
  `authentication` blob,
  `refresh_token` varchar(256) DEFAULT NULL,
  PRIMARY KEY (`authentication_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

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

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

CREATE TABLE `oauth_client_token` (
  `token_id` varchar(256) DEFAULT NULL,
  `token` blob,
  `authentication_id` varchar(128) NOT NULL,
  `user_name` varchar(256) DEFAULT NULL,
  `client_id` varchar(256) DEFAULT NULL,
  PRIMARY KEY (`authentication_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `oauth_code` (
  `code` varchar(256) DEFAULT NULL,
  `authentication` blob
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

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

在數(shù)據(jù)庫(kù)中配置客戶端

<p>在表 <code>oauth_client_details</code> 中增加一條客戶端配置記錄袱瓮,需要設(shè)置的字段如下:</p>

在表 oauth_client_details 中增加一條客戶端配置記錄,需要設(shè)置的字段如下:

  • client_id:客戶端標(biāo)識(shí)
  • client_secret:客戶端安全碼爱咬,此處不能是明文尺借,需要加密
  • scope:客戶端授權(quán)范圍
  • authorized_grant_types:客戶端授權(quán)類(lèi)型
  • web_server_redirect_uri:服務(wù)器回調(diào)地址
    使用 BCryptPasswordEncoder 為客戶端安全碼加密,代碼如下:

使用 BCryptPasswordEncoder 為客戶端安全碼加密精拟,代碼如下:

System.out.println(new BCryptPasswordEncoder().encode("secret"));

數(shù)據(jù)庫(kù)配置客戶端效果圖如下:

image

POM

由于使用了 JDBC 存儲(chǔ)燎斩,我們需要增加相關(guān)依賴虱歪,數(shù)據(jù)庫(kù)連接池部分棄用 Druid 改為 HikariCP(號(hào)稱(chēng)全球最快連接池)

<dependency>
    <groupId>com.zaxxer</groupId>
    <artifactId>HikariCP</artifactId>
    <version>${hikaricp.version}</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
    <exclusions>
        <!-- 排除 tomcat-jdbc 以使用 HikariCP -->
        <exclusion>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-jdbc</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>${mysql.version}</version>
</dependency>

配置認(rèn)證服務(wù)器

創(chuàng)建一個(gè)類(lèi)繼承 AuthorizationServerConfigurerAdapter 并添加相關(guān)注解:

  • @Configuration
  • @EnableAuthorizationServer
package com.xxm.oauth2.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
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.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;

import javax.sql.DataSource;

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

    @Bean
    @Primary
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource dataSource() {
        // 配置數(shù)據(jù)源(注意,我使用的是 HikariCP 連接池)栅表,以上注解是指定數(shù)據(jù)源笋鄙,否則會(huì)有沖突
        return DataSourceBuilder.create().build();
    }

    @Bean
    public TokenStore tokenStore() {
        // 基于 JDBC 實(shí)現(xiàn),令牌保存到數(shù)據(jù)
        return new JdbcTokenStore(dataSource());
    }

    @Bean
    public ClientDetailsService jdbcClientDetails() {
        // 基于 JDBC 實(shí)現(xiàn)怪瓶,需要事先在數(shù)據(jù)庫(kù)配置客戶端信息
        return new JdbcClientDetailsService(dataSource());
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        // 設(shè)置令牌
        endpoints.tokenStore(tokenStore());
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        // 讀取客戶端配置
        clients.withClientDetails(jdbcClientDetails());
    }
}

服務(wù)器安全配置

創(chuàng)建一個(gè)類(lèi)繼承WebSecurityConfigurerAdapter并添加相關(guān)注解:

  • @Configuration
  • @EnableWebSecurity
  • `@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true):全局方法攔截
package com.xxm.oauth2.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        // 設(shè)置默認(rèn)的加密方式
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {

        auth.inMemoryAuthentication()
                // 在內(nèi)存中創(chuàng)建用戶并為密碼加密
                .withUser("user").password(passwordEncoder().encode("123456")).roles("USER")
                .and()
                .withUser("admin").password(passwordEncoder().encode("123456")).roles("ADMIN");

    }
}

application.yml

spring:
  application:
    name: oauth2-server
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    jdbc-url: jdbc:mysql://192.168.141.128:3307/oauth2?useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: root
    password: 123456
    hikari:
      minimum-idle: 5
      idle-timeout: 600000
      maximum-pool-size: 10
      auto-commit: true
      pool-name: MyHikariCP
      max-lifetime: 1800000
      connection-timeout: 30000
      connection-test-query: SELECT 1

server:
  port: 8080

訪問(wèn)獲取授權(quán)碼

打開(kāi)瀏覽器萧落,輸入地址:

http://localhost:8080/oauth/authorize?client_id=client&response_type=code

第一次訪問(wèn)會(huì)跳轉(zhuǎn)到登錄頁(yè)面

image

驗(yàn)證成功后會(huì)詢問(wèn)用戶是否授權(quán)客戶端

http://www.baidu.com/?code=1JuO6V

有了這個(gè)授權(quán)碼就可以獲取訪問(wèn)令牌了

通過(guò)授權(quán)碼向服務(wù)器申請(qǐng)令牌

通過(guò) CURL 或是 Postman 請(qǐng)求

curl -X POST -H "Content-Type: application/x-www-form-urlencoded" -d 'grant_type=authorization_code&code=1JuO6V' "http://client:secret@localhost:8080/oauth/token"

得到響應(yīng)結(jié)果如下:

{
    "access_token": "016d8d4a-dd6e-4493-b590-5f072923c413",
    "token_type": "bearer",
    "expires_in": 43199,
    "scope": "app"
}

操作成功后數(shù)據(jù)庫(kù) oauth_access_token 表中會(huì)增加一筆記錄,效果圖如下:

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末劳殖,一起剝皮案震驚了整個(gè)濱河市铐尚,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌哆姻,老刑警劉巖宣增,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異矛缨,居然都是意外死亡爹脾,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)箕昭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)灵妨,“玉大人,你說(shuō)我怎么就攤上這事落竹∶诨簦” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵述召,是天一觀的道長(zhǎng)朱转。 經(jīng)常有香客問(wèn)我,道長(zhǎng)积暖,這世上最難降的妖魔是什么藤为? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮夺刑,結(jié)果婚禮上缅疟,老公的妹妹穿的比我還像新娘。我一直安慰自己遍愿,他們只是感情好存淫,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著错览,像睡著了一般纫雁。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上倾哺,一...
    開(kāi)封第一講書(shū)人閱讀 49,036評(píng)論 1 285
  • 那天轧邪,我揣著相機(jī)與錄音刽脖,去河邊找鬼。 笑死忌愚,一個(gè)胖子當(dāng)著我的面吹牛曲管,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播硕糊,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼院水,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了简十?” 一聲冷哼從身側(cè)響起檬某,我...
    開(kāi)封第一講書(shū)人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎螟蝙,沒(méi)想到半個(gè)月后恢恼,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡胰默,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年场斑,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片牵署。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡漏隐,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出奴迅,到底是詐尸還是另有隱情青责,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布取具,位于F島的核電站爽柒,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏者填。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一做葵、第九天 我趴在偏房一處隱蔽的房頂上張望占哟。 院中可真熱鬧,春花似錦酿矢、人聲如沸榨乎。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)蜜暑。三九已至,卻和暖如春策肝,著一層夾襖步出監(jiān)牢的瞬間肛捍,已是汗流浹背隐绵。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留拙毫,地道東北人依许。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像缀蹄,于是被迫代替她去往敵國(guó)和親峭跳。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容