SpringSecurity整合OAuth2是開發(fā)者公認的資源保護、服務認證的最佳搭配伙伴,這對好基友一直在默默的守護著應用服務的安全,根據(jù)訪問者的不同角色可以顆粒度控制到具體的接口,從而實現(xiàn)權限的細微劃分。
而SpringSecurity框架在安全框架的隊伍中算是入門比較高的,雖然Spring通過SpringBoot進行了封裝,但是使用起來還是有很多容易遺漏的配置,因為配置比較多,讓初學者理解起來也比較困難,針對這個問題ApiBoot對SpringSecurity以及OAuth2進行了封裝,在基礎上極大的簡化了配置(只做簡化、增強,SpringSecurity的基礎語法、配置還可以正常使用)
免費教程專題
恒宇少年在博客整理三套免費學習教程專題,由于文章偏多特意添加了閱讀指南,新文章以及之前的文章都會在專題內陸續(xù)填充,希望可以幫助大家解惑更多知識點。
創(chuàng)建項目
使用IDEA開發(fā)工具創(chuàng)建一個SpringBoot項目。
ApiBoot的底層是SpringBoot,而且ApiBoot為了支持SpringBoot的2.2.x分支,也對應的創(chuàng)建了2.2.x分支版本。
添加ApiBoot統(tǒng)一依賴
創(chuàng)建完項目后我們需要在pom.xml添加ApiBoot的統(tǒng)一版本依賴,如下所示:
<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>
添加相關依賴
本章我們需要查詢數(shù)據(jù)庫內的用戶信息進行認證,所以需要在pom.xml添加數(shù)據(jù)庫相關的依賴,如下所示:
<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使用文檔
配置數(shù)據(jù)源
添加數(shù)據(jù)庫相關的依賴后,在application.yml文件內添加如下配置信息:
spring:
application:
name: apiboot-security-oauth-custom-certification-user
# 數(shù)據(jù)源配置
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內部提供了默認的表結構,建表后添加數(shù)據(jù)即可直接使用用戶信息進行認證,詳見: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配置參數(shù)默認值為true,也就是會自動讀取數(shù)據(jù)源對應數(shù)據(jù)庫內的api_boot_user_info用戶信息表,當我們設置為false后需要通過實現(xiàn)ApiBootStoreDelegate接口來進行自定義查詢的用戶信息。
配置ApiBoot OAuth
api-boot-starter-security-oauth-jwt這個依賴內部也默認集成了OAuth2,而且默認的數(shù)據(jù)存儲方式與Spring Security一致也是內存方式(memory),我們本章的主要目的是查詢認證用戶信息,而不是客戶端信息,所以我們還是采用默認的內存方式,不過修改下客戶端的默認配置信息,在application.yml文件內添加配置如下所示:
# ApiBoot配置
api:
boot:
oauth:
# ApiBoot OAuth2的客戶端列表
clients:
- clientId: hengboy
clientSecret: chapter
grantTypes: password,refresh_token
在
ApiBoot中OAuth2默認的客戶端配置信息,可以通過查看org.minbox.framework.api.boot.autoconfigure.oauth.ApiBootOauthProperties.Client源碼了解詳情。
用戶認證
配置已經完成,下面我們來編寫查詢用戶信息,將用戶信息交給ApiBoot Security框架進行認證、生成AccessToken等操作。
本章使用的持久化框架是ApiBoot MyBatis Enhance,具體的使用方法請查看官方文檔。
創(chuàng)建用戶表
我們在數(shù)據(jù)庫內創(chuàng)建一張名為system_user的系統(tǒng)用戶信息表,表結構如下所示:
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 '創(chuàng)建時間',
`su_status` int(11) DEFAULT '1' COMMENT '用戶狀態(tài),1:正常,0:凍結,-1:已刪除',
PRIMARY KEY (`su_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='系統(tǒng)用戶信息表';
system_user用戶表創(chuàng)建完成后,我們往這張表內添加一條用戶數(shù)據(jù),如下所示:
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加密后的密文。
創(chuàng)建用戶實體
針對system_user表我們需要來創(chuàng)建一個ApiBoot MyBatis Enhance使用的實體,創(chuàng)建一個名為SystemUser的實體如下所示:
/**
* 系統(tǒng)用戶基本信息
*
* @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;
/**
* 創(chuàng)建時間
*/
@Column(name = "su_create_time")
private String createTime;
/**
* 用戶狀態(tài)
* 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提供的方法,用戶是否未過期
* 可根據(jù)自己用戶數(shù)據(jù)表內的字段進行擴展,這里為了演示配置為true
*
* @return
*/
@Override
public boolean isAccountNonExpired() {
return true;
}
/**
* UserDetails提供的方法,用戶是否未鎖定
* 可根據(jù)自己用戶數(shù)據(jù)表內的字段進行擴展,這里為了演示配置為true
*
* @return
*/
@Override
public boolean isAccountNonLocked() {
return true;
}
/**
* UserDetails提供的方法,憑證是否未過期
* 可根據(jù)自己用戶數(shù)據(jù)表內的字段進行擴展,這里為了演示配置為true
*
* @return
*/
@Override
public boolean isCredentialsNonExpired() {
return true;
}
/**
* UserDetails提供的方法,是否啟用
*
* @return
*/
@Override
public boolean isEnabled() {
return this.status == 1;
}
}
具體的注解使用詳見ApiBoot MyBatis Enhance文檔,這里還一點需要注意的是,SystemUser實現(xiàn)了UserDetails接口,如果使用過Spring Security的同學應該都知道這是Spring Security提供的用戶詳情接口定義,我們如果自定義查詢用戶就應該讓我們自定義的用戶實體(注:這是的自定義用戶實體也就是SystemUser實體)實現(xiàn)這個接口并全部實現(xiàn)UserDetails接口內提供的方法。
創(chuàng)建用戶數(shù)據(jù)接口
用戶的實體已經創(chuàng)建完成,我們本章需要一個根據(jù)用戶的登錄名來查詢用戶基本的數(shù)據(jù)接口,創(chuàng)建一個名為SystemUserEnhanceMapper的接口如下所示:
/**
* ApiBoot Enhance提供的增強Mapper
* 自動被掃描并且注冊到IOC
*
* @author 恒宇少年
* @see org.minbox.framework.api.boot.autoconfigure.enhance.ApiBootMyBatisEnhanceAutoConfiguration
*/
public interface SystemUserEnhanceMapper extends EnhanceMapper<SystemUser, Integer> {
/**
* 根據(jù)用戶登錄名查詢用戶信息
*
* @param loginName {@link SystemUser#getLoginName()}
* @return {@link SystemUser}
*/
SystemUser findByLoginName(@Param("loginName") String loginName);
}
該接口繼承了EnhanceMapper<Entity,ID>接口,可以自動被掃描到創(chuàng)建代理的實例后并且加入IOC,這樣我們在項目其他的地方可以直接注入使用。
注意:
findByXxx方法是ApiBoot MyBatis Enhance提供的方法命名規(guī)則查詢,多個查詢條件可以使用And或者Or追加,會自動根據(jù)方法的規(guī)則生成對應的SQL。
實現(xiàn)ApiBootStoreDelegate接口
ApiBoot Security提供了一個接口ApiBootStoreDelegate,這個接口主要是用來查詢登錄用戶的具體信息的作用,當我們通過grant_type=password&username=xxx的方式進行獲取AccessToken時,ApiBoot Security會直接把username的參數(shù)值傳遞給ApiBootStoreDelegate#loadUserByUsername的方法內,這樣我們就可以根據(jù)username進行查詢用戶并返回給ApiBoot Security做后續(xù)的認證操作。
我們來創(chuàng)建一個名為UserService的類并實現(xiàn)ApiBootStoreDelegate接口,如下所示:
/**
* 自定義讀取用戶信息
*
* @author 恒宇少年
*/
@Service
public class UserService implements ApiBootStoreDelegate {
/**
* logger instance
*/
static Logger logger = LoggerFactory.getLogger(UserService.class);
/**
* 用戶數(shù)據(jù)接口
*/
@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實現(xiàn)了該接口,所以我們可以直接將SystemUser實例作為返回值。
運行測試
代碼一切就緒,通過XxxxApplication的方式來啟動項目。
測試點:獲取AccessToken
在獲取AccessToken之前,我們需要確認application.yml文件內配置的api.boot.oauth.clients的客戶端的clientId、clientSecret配置內容,下面是通過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的值進行刷新,下面是刷新AccessToken的CURL方式:
? ~ 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","token_type":"bearer","refresh_token":"d2243e18-8ab3-4842-a98f-ebd79da94e2e","expires_in":7199,"scope":"api"}
hengboy這個OAuth2客戶端在application.yml中通過配置grantTypes授權了兩種grant_type,分別是password、refresh_token,如果需要別的方式可以在配置文件內對應添加。
敲黑板,劃重點
ApiBoot整合Spring Security以及OAuth2后讀取自定義用戶信息,我們只需要關注具體怎么讀取用戶信息,之前那些懵懵懂懂的代碼配置都可以通過配置文件的方式代替,本章的主要內容是ApiBootStoreDelegate這個接口,ApiBoot所提供的功能還不止這些,會陸續(xù)分享給大家。
代碼示例
微信掃描下圖二維碼關注“程序員恒宇少年”后,回復“源碼”即可獲取源碼倉庫地址。
本章節(jié)源碼在spring-boot-chapter倉庫內目錄為SpringBoot2.x/apiboot-security-oauth-custom-certification-user