概述
基于 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è)置客戶端配置讀取方式
-
- 配置數(shù)據(jù)源:
-
配置 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
- 端點(diǎn):/oauth/token
/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ù)配置客戶端效果圖如下:
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è)面
驗(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ì)增加一筆記錄,效果圖如下: