1. 程式人生 > >最詳細的Spring Boot OAuth2.0密碼模式伺服器實現

最詳細的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容&#64056;、生產準備特性)、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容&#64056;、生產準備特性)、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 。 前期了解