最詳細的Spring Boot OAuth2.0密碼模式伺服器實現
前言
由於專案要用到OAuth2.0授權,需要自己開發一個OAuth2.0授權伺服器,在網上看到Java Oauth2.0授權用的比較多兩個框架Spring Security和Apache Oltu,因為專案都是基於Spring的,所以決定使用Spring Security來做Oauth2.0.
在網上搜索了好多教程,看完了還是雲裡霧裡,有些細節也沒有講明白,結合網上教程和自己的慢慢摸索,浪費了好多時間,所以準備寫這個教程,希望大家能少走彎路.
正文
專案框架
Maven
Spring Boot
Spring Security
Druid
MySql
目錄結構
建立Oauth2.0需要建立三個相關的表,直接使用官方的SQL指令碼即可生成(不要修改表名和欄位名).
-- ----------------------------
-- Table structure for oauth_access_token
-- ----------------------------
DROP TABLE IF EXISTS `oauth_access_token`;
CREATE TABLE `oauth_access_token` (
`token_id` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`token` blob NULL,
`authentication_id` varchar(250) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`user_name` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`client_id` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`authentication` blob NULL,
`refresh_token` varchar(256 ) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`authentication_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for oauth_client_details
-- ----------------------------
DROP TABLE IF EXISTS `oauth_client_details`;
CREATE TABLE `oauth_client_details` (
`client_id` varchar(250) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`resource_ids` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`client_secret` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`scope` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`authorized_grant_types` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`web_server_redirect_uri` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`authorities` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`access_token_validity` int(11) NULL DEFAULT NULL,
`refresh_token_validity` int(11) NULL DEFAULT NULL,
`additional_information` varchar(4096) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`autoapprove` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`client_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for oauth_refresh_token
-- ----------------------------
DROP TABLE IF EXISTS `oauth_refresh_token`;
CREATE TABLE `oauth_refresh_token` (
`token_id` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`token` blob NULL,
`authentication` blob NULL
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
pom.xml
spring-boot-starter-parent 版本不要使用2.0以上版本,否則AuthenticationManager會NullPointerException.(在這裡卡了好久,目前發現的解決辦法就是使用2.0以下的版本)
<?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>
<groupId>com.lanxiaotu</groupId>
<artifactId>oauth</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>oauth</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.9.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!--MySql驅動-->
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>6.0.6</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!--Druid資料來源-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.19</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.0.14.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-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>
application.properties
#DataSource
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name = com.mysql.cj.jdbc.Driver
spring.datasource.url = jdbc:mysql://localhost:3306/splus?serverTimezone=Asia/Shanghai&useSSL=false&useUnicode=true&characterEncoding=utf-8
spring.datasource.username = root
spring.datasource.password = root
初始化大小,最小,最大
spring.datasource.initialSize=5
spring.datasource.minIdle=5
spring.datasource.maxActive=20
# 配置獲取連線等待超時的時間
spring.datasource.maxWait=60000
# 配置間隔多久才進行一次檢測,檢測需要關閉的空閒連線,單位是毫秒
spring.datasource.timeBetweenEvictionRunsMillis=60000
# 配置一個連線在池中最小生存的時間,單位是毫秒
spring.datasource.minEvictableIdleTimeMillis=300000
spring.datasource.validationQuery=SELECT 1 FROM DUAL
spring.datasource.testWhileIdle=true
spring.datasource.testOnBorrow=false
spring.datasource.testOnReturn=false
# 開啟PSCache,並且指定每個連線上PSCache的大小
spring.datasource.poolPreparedStatements=true
spring.datasource.maxPoolPreparedStatementPerConnectionSize=20
配置DataSource資料來源
@Configuration
public class DruidDataSourceConfig {
@Value("${spring.datasource.url:#{null}}")
private String dbUrl;
@Value("${spring.datasource.username: #{null}}")
private String username;
@Value("${spring.datasource.password:#{null}}")
private String password;
@Value("${spring.datasource.driver-class-name:#{null}}")
private String driverClassName;
@Value("${spring.datasource.initialSize:#{null}}")
private Integer initialSize;
@Value("${spring.datasource.minIdle:#{null}}")
private Integer minIdle;
@Value("${spring.datasource.maxActive:#{null}}")
private Integer maxActive;
@Value("${spring.datasource.maxWait:#{null}}")
private Integer maxWait;
@Value("${spring.datasource.timeBetweenEvictionRunsMillis:#{null}}")
private Integer timeBetweenEvictionRunsMillis;
@Value("${spring.datasource.minEvictableIdleTimeMillis:#{null}}")
private Integer minEvictableIdleTimeMillis;
@Value("${spring.datasource.validationQuery:#{null}}")
private String validationQuery;
@Value("${spring.datasource.testWhileIdle:#{null}}")
private Boolean testWhileIdle;
@Value("${spring.datasource.testOnBorrow:#{null}}")
private Boolean testOnBorrow;
@Value("${spring.datasource.testOnReturn:#{null}}")
private Boolean testOnReturn;
@Value("${spring.datasource.poolPreparedStatements:#{null}}")
private Boolean poolPreparedStatements;
@Value("${spring.datasource.maxPoolPreparedStatementPerConnectionSize:#{null}}")
private Integer maxPoolPreparedStatementPerConnectionSize;
@Bean
@Primary
public DataSource dataSource(){
DruidDataSource datasource = new DruidDataSource();
datasource.setUrl(this.dbUrl);
datasource.setUsername(username);
datasource.setPassword(password);
datasource.setDriverClassName(driverClassName);
//configuration
if(initialSize != null) {
datasource.setInitialSize(initialSize);
}
if(minIdle != null) {
datasource.setMinIdle(minIdle);
}
if(maxActive != null) {
datasource.setMaxActive(maxActive);
}
if(maxWait != null) {
datasource.setMaxWait(maxWait);
}
if(timeBetweenEvictionRunsMillis != null) {
datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
}
if(minEvictableIdleTimeMillis != null) {
datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
}
if(validationQuery!=null) {
datasource.setValidationQuery(validationQuery);
}
if(testWhileIdle != null) {
datasource.setTestWhileIdle(testWhileIdle);
}
if(testOnBorrow != null) {
datasource.setTestOnBorrow(testOnBorrow);
}
if(testOnReturn != null) {
datasource.setTestOnReturn(testOnReturn);
}
if(poolPreparedStatements != null) {
datasource.setPoolPreparedStatements(poolPreparedStatements);
}
if(maxPoolPreparedStatementPerConnectionSize != null) {
datasource.setMaxPoolPreparedStatementPerConnectionSize(maxPoolPreparedStatementPerConnectionSize);
}
return datasource;
}
}
配置AuthorizationServerConfiguration
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
@Autowired
private DataSource dataSource;
@Bean // 宣告TokenStore實現
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
@Bean // 宣告 ClientDetails實現
public ClientDetailsService clientDetails() {
return new JdbcClientDetailsService(dataSource);
}
@Autowired
private TokenStore tokenStore;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserService userService;
@Autowired
private ClientDetailsService clientDetails;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource);
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager);
endpoints.tokenStore(tokenStore());
endpoints.userDetailsService(userService);
endpoints.setClientDetailsService(clientDetails);
//配置TokenServices引數
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setTokenStore(endpoints.getTokenStore());
tokenServices.setSupportRefreshToken(true);
tokenServices.setClientDetailsService(endpoints.getClientDetailsService());
tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer());
tokenServices.setAccessTokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(1)); // 1天
endpoints.tokenServices(tokenServices);
}
@Bean
@Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setSupportRefreshToken(true);
tokenServices.setTokenStore(tokenStore);
return tokenServices;
}
}
配置ResourceServerConfiguration
@Configuration
@EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/**").authenticated()
.anyRequest().authenticated();
}
}
配置WebSecurityConfiguration
@Configuration
public class WebSecurityConfiguration extends GlobalAuthenticationConfigurerAdapter {
private final UserService userService;
@Autowired
public WebSecurityConfiguration(UserService userService) {
this.userService = userService;
}
@Override
public void init(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService);
}
}
User物件
需要實現Serializable介面
public class User implements Serializable{
private String username;
private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
UserService繼承UserDetailsService,並在UserServiceImpl實現loadUserByUsername
UserService
public interface UserService extends UserDetailsService{
}
UserServiceImpl
@Service
public class UserServiceImpl implements UserService {
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
/*模擬資料庫操作*/
User user = new User();
user.setUsername("10086");
user.setPassword("123456");
return new CustomUserDetails(user);
}
}
建立UserDetails物件
此處繼承的是org.springframework.security.core.userdetails.User,不是自己定義的User物件
public class CustomUserDetails extends org.springframework.security.core.userdetails.User {
private User user;
public CustomUserDetails(User user) {
super(user.getUsername(), user.getPassword(), true, true, true, true, Collections.EMPTY_SET);
this.user = user;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
在Controller定義一個方法
@RestController
public class IndexController {
@GetMapping("/sayHello")
private String sayHello(){
System.out.println("Hello World");
return "Hello World";
}
}
測試結果
使用比較常用Postman模擬請求
第一步:直接訪問sayHello介面,可以看到未授權
第二步:獲取授權
在oauth_client_details表新增一個客戶端
這裡的username和password屬於oauth_client_details表裡的client_id和client_secret
這裡的username和password屬於User表裡的使用者名稱和密碼
獲取到access_token和refresh_token後再次請求sayHello介面
在請求頭帶上token,其中value的格式是 bearer + token
重新整理token
相關推薦
最詳細的Spring Boot OAuth2.0密碼模式伺服器實現
前言 由於專案要用到OAuth2.0授權,需要自己開發一個OAuth2.0授權伺服器,在網上看到Java Oauth2.0授權用的比較多兩個框架Spring Security和Apache Oltu,因為專案都是基於Spring的,所以決定使用Spring S
Security-OAuth2.0 密碼模式客戶端實現
super temp auto bsp mas es2017 success ann turn 我的OAuth2.0 客戶端項目目錄 pom 的配置 <?xml version="1.0" encoding="UTF-8"?> <project x
分享最全Spring Boot 2.0深度實踐之核心技術篇
第1章 系列總覽 總覽 Spring Boot 2.0 深度實踐系列課程的整體議程,包括 Spring Boot 三大核心特性(元件自動裝配、嵌入式Web容器、生產準備特性)、Web 應用(傳統 Servlet、Spring Web MVC、Spri
Spring Security OAuth2 Demo —— 密碼模式(Password)
前情回顧 前幾節分享了OAuth2的流程與授權碼模式和隱式授權模式兩種的Demo,我們瞭解到授權碼模式是OAuth2四種模式流程最複雜模式,複雜程度由大至小:授權碼模式 > 隱式授權模式 > 密碼模式 > 客戶端模式 其中密碼模式的流程是:讓使用者填寫表單提交到授權伺服器,表單中包含使用者的
spring boot使用nginx和ftp伺服器實現圖片上傳下載(windows server)
本人使用的springboot為1.5.6版本<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-pa
Spring security oauth2-客戶端模式,簡化模式,密碼模式(Finchley版本)
一、客戶端模式原理解析(來自理解OAuth 2.0) 客戶端模式(Client Credentials Grant)指客戶端以自己的名義,而不是以使用者的名義,向"服務提供商"進行認證。嚴格地說,客戶端模式並不屬於OAuth框架所要解決的問題。在這種模式中,使用者直接向客戶端註冊,客戶端
spring boot 2.0 使用Hikari連線池——號稱java平臺最快的,替換druid
摘自 springboot 2.0 預設連線池就是Hikari了,所以引用parents後不用專門加依賴 配置(時間單位都是毫秒) # jdbc_config datasource spring.datasource.driver-class-name=
Spring Boot 2.0深度實踐之核心技術篇 (最全)
情見程式碼: #!/usr/bin/env python2 # -*- coding: utf-8 -*- """ Created on Sat Dec 2 15:40:35 2017 @author: 260207 """ from xlutils.copy import
2018年Spring Boot 2.0深度實踐之核心技術篇 最全
第1章 系列總覽 總覽 Spring Boot 2.0 深度實踐系列課程的整體議程,包括 Spring Boot 三大核心特性(元件自動裝配、嵌入式Web容器、生產準備特性)、Web 應用(傳統 Servlet、Spring Web MVC、Spring WebFlux)
OAuth2.0學習(4-1)Spring Security OAuth2.0 - 代碼分析
endpoint manager authent work cor tro 過程 pro efi 1、org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter
spring security oauth2.0 實現
規範 ppi basic final pre 代碼 處理 state 三方 oauth應該屬於security的一部分。關於oauth的的相關知識可以查看阮一峰的文章:http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.htm
spring boot 2.0之使用spring boot
架構 spring spring boot依賴每一個spring boot的發型版本都包含了所依賴的版本,如果升級spring boot版本,其依賴也會同步更新升級。maven的用戶可以通過繼承spring-boot-starter-parent。其包含了一些合理的值的設置:1. 默認設置的編譯器為J
Spring Boot 2.0.0.M7 生產環境部署
nbsp list left print 20px package active padding over springboot 生產環境註意事項 1.去除不需要的 jar開發工具jar:spring-boot-devtools監控一定要做好權限控制或者去除控制jar:sp
Spring Boot 2.0(一):【重磅】Spring Boot 2.0權威發布
Spring Boot就在昨天Spring Boot2.0.0.RELEASE正式發布,今天早上在發布Spring Boot2.0的時候還出現一個小插曲,將Spring Boot2.0同步到Maven倉庫的時候出現了錯誤,然後Spring Boot官方又趕緊把 GitHub 上發布的 v2.0.0.RELEA
阿裏P9告訴你 Spring Boot 2.0正式發布,升還是不升呢?
Java spring spring Boot Spring帝國Spring幾乎是每一位Java開發人員都耳熟能詳的開發框架,不論您是一名初出茅廬的程序員還是經驗豐富的老司機,都會對其有一定的了解或使用經驗。在現代企業級應用架構中,Spring技術棧幾乎成為了Java語言的代名詞,那麽Spring
Spring Boot 2.0(二):Spring Boot 2.0嘗鮮-動態 Banner
版本 手動 block OS 動態 posit 下載 網站 dep Spring Boot 2.0 提供了很多新特性,其中就有一個小彩蛋:動態 Banner,今天我們就先拿這個來嘗嘗鮮。 配置依賴 使用 Spring Boot 2.0 首先需要將項目依賴包替換為剛剛發布的
Spring boot 2.0 新特性之動態 Banner
body 回復 更換 and 超過 方式 有一個 特性 pos Spring Boot 2.0 提供了很多新特性,其中就有一個小彩蛋:動態 Banner,今天我們就先拿這個來嘗嘗鮮。 配置依賴 使用 Spring Boot 2.0 首先需要將項目依賴包替換為剛剛發布的
Spring Boot 2.0(三):Spring Boot 開源軟件都有哪些?
Spring Boot 開源 2016年 Spring Boot 還沒有被廣泛使用,在網上查找相關開源軟件的時候沒有發現幾個,到了現在經過2年的發展,很多互聯網公司已經將 Spring Boot 搬上了生產,而使用 Spring Boot 的開源軟件在 Github/碼雲 上面已有不少,這篇文章就給大
spring-boot 2.0 多模塊化項目和EurekaServer的搭建
集群 gem 開啟 優點 pac cat reg manage 框架 Spring boot由於其 1、易於開發和維護。2、單個微服務啟動快。3、局部修改部署容易。4、技術棧不受語言限制等優點受到越來越多公司的重視。spring-boot還集成了許多關於微服務開發的框
Spring Boot 2.0(五):Docker Compose + Spring Boot + Nginx + Mysql 實踐
work 加載 ports access 分享圖片 htm 初始化 visit edi 我知道大家這段時間看了我寫關於 docker 相關的幾篇文章,不疼不癢的,仍然沒有感受 docker 的便利,是的,我也是這樣認為的,I know your felling 。 前期了解