1. 程式人生 > 程式設計 >看完這篇文章你還感覺SpringSecurity整合OAuth2自定義查詢使用者複雜嗎?

看完這篇文章你還感覺SpringSecurity整合OAuth2自定義查詢使用者複雜嗎?

SpringSecurity整合OAuth2是開發者公認的資源保護服務認證的最佳搭配夥伴,這對好基友一直在默默的守護著應用服務的安全,根據訪問者的不同角色可以顆粒度控制到具體的介面,從而實現許可權的細微劃分。

SpringSecurity框架在安全框架的隊伍中算是入門比較高的,雖然Spring通過SpringBoot進行了封裝,但是使用起來還是有很多容易遺漏的配置,因為配置比較多,讓初學者理解起來也比較困難,針對這個問題ApiBootSpringSecurity以及OAuth2進行了封裝,在基礎上極大的簡化了配置(只做簡化、增強,SpringSecurity的基礎語法、配置還可以正常使用)

ApiBoot Security 系列文章

建立專案

使用IDEA開發工具建立一個SpringBoot專案。

ApiBoot的底層是SpringBoot,而且ApiBoot為了支援SpringBoot2.2.x分支,也對應的建立了2.2.x分支版本。

新增ApiBoot統一依賴

建立完專案後我們需要在pom.xml新增ApiBoot的統一版本依賴,如下所示:

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.minbox.framework</groupId>
      <artifactId>api-boot-dependencies</artifactId>
      <version>2.2.0.RELEASE</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>複製程式碼

新增相關依賴

本章我們需要查詢資料庫內的使用者資訊進行認證,所以需要在pom.xml新增資料庫相關的依賴,如下所示:

<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
  <dependency>
    <groupId>org.minbox.framework</groupId>
    <artifactId>api-boot-starter-security-oauth-jwt</artifactId>
  </dependency>
  <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
  </dependency>
  <dependency>
    <groupId>com.zaxxer</groupId>
    <artifactId>HikariCP</artifactId>
  </dependency>
  <dependency>
    <groupId>org.minbox.framework</groupId>
    <artifactId>api-boot-starter-mybatis-enhance</artifactId>
  </dependency>
</dependencies>複製程式碼

在本章使用到了ApiBoot Mybatis Enhance,具體的使用請訪問官方檔案ApiBoot MyBatis Enhance使用檔案

配置資料來源

新增資料庫相關的依賴後,在application.yml檔案內新增如下配置資訊:

spring:
  application:
    name: apiboot-security-oauth-custom-certification-user
  # 資料來源配置
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    url: jdbc:mysql://127.0.0.1:3306/test?characterEncoding=utf8&serverTimezone=Asia/Shanghai
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
server:
  port: 9090複製程式碼

配置ApiBoot Security

ApiBoot Security預設採用的是記憶體方式(memory)讀取使用者資訊,我們本章需要修改為JDBC方式,並且禁用預設讀取使用者資訊ApiBoot Security內部提供了預設的表結構,建表後新增資料即可直接使用使用者資訊進行認證,詳見:ApiBoot Security使用檔案)。

application.yml配置檔案中新增如下配置:

# ApiBoot配置
api:
  boot:
    security:
      # ApiBoot Security 使用JDBC方式讀取使用者
      away: jdbc
      # 禁用預設的讀取使用者方式
      enable-default-store-delegate: false複製程式碼

api.boot.security.enable-default-store-delegate配置引數預設值為true,也就是會自動讀取資料來源對應資料庫內的api_boot_user_info使用者資訊表,當我們設定為false後需要通過實現ApiBootStoreDelegate介面來進行自定義查詢的使用者資訊。

配置ApiBoot OAuth

api-boot-starter-security-oauth-jwt這個依賴內部也預設集成了OAuth2,而且預設的資料儲存方式與Spring Security一致也是記憶體方式(memory),我們本章的主要目的是查詢認證使用者資訊,而不是客戶端資訊,所以我們還是採用預設的記憶體方式,不過修改下客戶端的預設配置資訊,在application.yml檔案內新增配置如下所示:

# ApiBoot配置
api:
  boot:
    oauth:
      # ApiBoot OAuth2的客戶端列表
      clients:
        - clientId: hengboy
          clientSecret: chapter
          grantTypes: password,refresh_token複製程式碼

ApiBootOAuth2預設的客戶端配置資訊,可以通過檢視org.minbox.framework.api.boot.autoconfigure.oauth.ApiBootOauthProperties.Client原始碼瞭解詳情。

使用者認證

配置已經完成,下面我們來編寫查詢使用者資訊,將使用者資訊交給ApiBoot Security框架進行認證生成AccessToken等操作。

本章使用的持久化框架是ApiBoot MyBatis Enhance,具體的使用方法請檢視官方檔案

建立使用者表

我們在資料庫內建立一張名為system_user的系統使用者資訊表,表結構如下所示:

CREATE TABLE `system_user` (
  `su_id` varchar(36) COLLATE utf8mb4_general_ci NOT NULL COMMENT '使用者編號',`su_login_name` varchar(30) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '登入名',`su_nick_name` varchar(30) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '暱稱',`su_password` varchar(200) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '使用者密碼',`su_create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '建立時間',`su_status` int(11) DEFAULT '1' COMMENT '使用者狀態,1:正常,0:凍結,-1:已刪除',PRIMARY KEY (`su_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='系統使用者資訊表';複製程式碼

system_user使用者表建立完成後,我們往這張表內新增一條使用者資料,如下所示:

INSERT INTO `system_user` VALUES ('9b69fd26-14db-11ea-b743-dcd28627348e','yuqiyu','恆宇少年 - 於起宇','$2a$10$RbJGpi.v3PwkjrYENzOzTuMxazuanX3Qa2hwI/f55cYsZhFT/nX3.','2019-12-02 08:13:22',1);複製程式碼

我們在登入時使用者名稱對應su_login_name欄位,而密碼則是對應su_password欄位,yuqiyu這個使用者的密碼初始化為123456,密碼的格式必須為BCryptPasswordEncoder加密後的密文。

建立使用者實體

針對system_user表我們需要來建立一個ApiBoot MyBatis Enhance使用的實體,建立一個名為SystemUser的實體如下所示:


/**
 * 系統使用者基本資訊
 *
 * @author 恆宇少年
 */
@Data
@Table(name = "system_user")
public class SystemUser implements UserDetails {
    /**
     * 使用者編號
     */
    @Id(generatorType = KeyGeneratorTypeEnum.UUID)
    @Column(name = "su_id")
    private String userId;
    /**
     * 登入名
     */
    @Column(name = "su_login_name")
    private String loginName;
    /**
     * 暱稱
     */
    @Column(name = "su_nick_name")
    private String nickName;
    /**
     * 密碼
     */
    @Column(name = "su_password")
    private String password;
    /**
     * 建立時間
     */
    @Column(name = "su_create_time")
    private String createTime;
    /**
     * 使用者狀態
     * 1:正常,0:已凍結,-1:已刪除
     */
    @Column(name = "su_status")
    private Integer status;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return Collections.EMPTY_LIST;
    }

    @Override
    public String getUsername() {
        return this.loginName;
    }

    @Override
    public String getPassword() {
        return this.password;
    }

    /**
     * UserDetails提供的方法,使用者是否未過期
     * 可根據自己使用者資料表內的欄位進行擴充套件,這裡為了演示配置為true
     *
     * @return
     */
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    /**
     * UserDetails提供的方法,使用者是否未鎖定
     * 可根據自己使用者資料表內的欄位進行擴充套件,這裡為了演示配置為true
     *
     * @return
     */
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    /**
     * UserDetails提供的方法,憑證是否未過期
     * 可根據自己使用者資料表內的欄位進行擴充套件,這裡為了演示配置為true
     *
     * @return
     */
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    /**
     * UserDetails提供的方法,是否啟用
     *
     * @return
     */
    @Override
    public boolean isEnabled() {
        return this.status == 1;
    }
}複製程式碼

具體的註解使用詳見ApiBoot MyBatis Enhance檔案,這裡還一點需要注意的是,SystemUser實現了UserDetails介面,如果使用過Spring Security的同學應該都知道這是Spring Security提供的使用者詳情介面定義,我們如果自定義查詢使用者就應該讓我們自定義的使用者實體(注:這是的自定義使用者實體也就是SystemUser實體)實現這個介面並全部實現UserDetails介面內提供的方法。

建立使用者資料介面

使用者的實體已經建立完成,我們本章需要一個根據使用者的登入名來查詢使用者基本的資料介面,建立一個名為SystemUserEnhanceMapper的介面如下所示:

/**
 * ApiBoot Enhance提供的增強Mapper
 * 自動被掃描並且註冊到IOC
 *
 * @author 恆宇少年
 * @see org.minbox.framework.api.boot.autoconfigure.enhance.ApiBootMyBatisEnhanceAutoConfiguration
 */
public interface SystemUserEnhanceMapper extends EnhanceMapper<SystemUser,Integer> {
    /**
     * 根據使用者登入名查詢使用者資訊
     *
     * @param loginName {@link SystemUser#getLoginName()}
     * @return {@link SystemUser}
     */
    SystemUser findByLoginName(@Param("loginName") String loginName);
}複製程式碼

該介面繼承了EnhanceMapper,id>介面,可以自動被掃描到建立代理的例項後並且加入IOC,這樣我們在專案其他的地方可以直接注入使用。

注意:findByXxx方法是ApiBoot MyBatis Enhance提供的方法命名規則查詢,多個查詢條件可以使用And或者Or追加,會自動根據方法的規則生成對應的SQL

實現ApiBootStoreDelegate介面

ApiBoot Security提供了一個介面ApiBootStoreDelegate,這個介面主要是用來查詢登入使用者的具體資訊的作用,當我們通過grant_type=password&username=xxx的方式進行獲取AccessToken時,ApiBoot Security會直接把username的引數值傳遞給ApiBootStoreDelegate#loadUserByUsername的方法內,這樣我們就可以根據username進行查詢使用者並返回給ApiBoot Security做後續的認證操作。

我們來建立一個名為UserService的類並實現ApiBootStoreDelegate介面,如下所示:


/**
 * 自定義讀取使用者資訊
 *
 * @author 恆宇少年
 */
@Service
public class UserService implements ApiBootStoreDelegate {
    /**
     * logger instance
     */
    static Logger logger = LoggerFactory.getLogger(UserService.class);
    /**
     * 使用者資料介面
     */
    @Autowired
    private SystemUserEnhanceMapper mapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        UserDetails userDetails = mapper.findByLoginName(username);
        if (ObjectUtils.isEmpty(userDetails)) {
            throw new UsernameNotFoundException("使用者:" + username + ",不存在.");
        }
        logger.info("登入使用者的資訊:{}",JSON.toJSONString(userDetails));
        return userDetails;
    }
}複製程式碼

loadUserByUsername方法的返回值是UserDetails介面型別,在之前我們已經將SystemUser實現了該介面,所以我們可以直接將SystemUser例項作為返回值。

執行測試

程式碼一切就緒,通過XxxxApplication的方式來啟動專案。

測試點:獲取AccessToken

在獲取AccessToken之前,我們需要確認application.yml檔案內配置的api.boot.oauth.clients的客戶端的clientIdclientSecret配置內容,下面是通過CURL的方式:

➜ ~ curl hengboy:chapter@localhost:9090/oauth/token -d 'grant_type=password&username=yuqiyu&password=123456'
{"access_token":"3beb1bee-9ca6-45e1-9fb8-5fc181670f63","token_type":"bearer","refresh_token":"d2243e18-8ab3-4842-a98f-ebd79da94e2e","expires_in":7199,"scope":"api"}複製程式碼

測試點:重新整理AccessToken

複製上面獲取到的refresh_token的值進行重新整理,下面是重新整理AccessTokenCURL方式:

➜ ~ curl hengboy:chapter@localhost:9090/oauth/token -d 'grant_type=refresh_token&refresh_token=d2243e18-8ab3-4842-a98f-ebd79da94e2e'
{"access_token":"e842c2ee-5672-49db-a530-329186f36492","scope":"api"}複製程式碼

hengboy這個OAuth2客戶端在application.yml中通過配置grantTypes授權了兩種grant_type,分別是passwordrefresh_token,如果需要別的方式可以在配置檔案內對應新增。

敲黑板,劃重點

ApiBoot整合Spring Security以及OAuth2後讀取自定義使用者資訊,我們只需要關注具體怎麼讀取使用者資訊,之前那些懵懵懂懂的程式碼配置都可以通過配置檔案的方式代替,本章的主要內容是ApiBootStoreDelegate這個介面,ApiBoot所提供的功能還不止這些,會陸續分享給大家。

程式碼示例

微信掃描下圖二維碼關注“程式設計師恆宇少年”後,回覆“原始碼”即可獲取原始碼倉庫地址。

本章節原始碼在spring-boot-chapter倉庫內目錄為SpringBoot2.x/apiboot-security-oauth-custom-certification-user

作者個人 部落格

使用開源框架 ApiBoot 助你成為Api介面服務架構師