Vert.x(vertx) 認證和授權


每個線上系統幾乎都是離不開認證和授權的,Vert.x提供了靈活、簡單、便捷的認證和授權的支持。Vert.x抽象出了兩個核心的認證和授權的接口,一個是 AuthProvider,另一個是User。通過這兩個接口,我們可以非常靈活的實現我們自定義的認證和授權方法。當然,Vert.x也給我們提供了使用 JDBC、Shiro、MongoDB、JWT等授權的實現,我們可以直接使用。

Vert.x提供的認證和授權都非常簡單,多種授權方式都有一定的規律性。一般來講不需要刻意的學習,在使用的過程中,多讀下Vert.x的源碼就能夠非常清楚的了解到Vert.x認證和授權底層的邏輯。但不是每一位開發者都有時間或者心情去讀源碼的,所以,這里我簡單列出關於Vert.x的認證和授權的應用。

使用Vert.x認證和授權,需要經歷三個階段

1. 自己實現AuthProvider和User接口實現一個簡單的認證和授權。

2. 使用Vert.x提供的授權方式,如JDBC

3. 在Web中使用認證和授權來管理訪問權限

1. 自定義授權實現
自定義授權就是自己實現AuthProvider和User這兩個接口,重寫這兩個接口中定義的認證和授權方法,這是Vert.x認證和授權最核心的也是最為底層的,把這兩個接口弄明白了,后面使用JDBC授權,jwt等等都非常簡單。當然了,如果你僅僅是為了使用,那么你可以直接關注第三個階段,認證和授權在Web中的應用。

比如我們要實現一個最簡單的根據用戶名和密碼的認證,只要認證通過了,就可以訪問系統的所有資源這么一個需求,代碼如下:

(0) pom中需要引入依賴

<dependency>
  <groupId>io.vertx</groupId>
  <artifactId>vertx-auth-common</artifactId>
  <version>3.6.2</version>
</dependency>

 

(1)應用代碼

 1 /**
 2 * 認證與授權測試
 3 * 
 4 * @author lenovo
 5 *
 6 */
 7 public class AuthTest extends AbstractVerticle {
 8 
 9 @Override
10 public void start() throws Exception {
11 
12 // 創建認證的Provider
13 AuthProvider provider = new UserNameAndPasswordProvider();
14 JsonObject authInfo = new JsonObject().put("username", "admin").put("password", "admin");
15 
16 // 調用認證方法,將認證的數據傳入
17 provider.authenticate(authInfo, res -> {
18 if (res.succeeded()) {
19 // 認證通過,可以獲取到User對象,通過User對象可以進行權限校驗
20 User user = res.result();
21 
22 // 授權
23 user.isAuthorized("save:user", auth -> {
24 if (auth.succeeded()) {
25 System.out.println("授權成功");
26 } else {
27 System.out.println("授權失敗");
28 }
29 });
30 } else {
31 System.out.println("認證失敗!");
32 }
33 });
34 
35 }
36 
37 public static void main(String[] args) {
38 Vertx.vertx().deployVerticle(new AuthTest());
39 }
40 
41 }
View Code

 


用法非常簡單,首先創建一個AuthProvider,這里我們使用了

UserNameAndPasswordProvider
這個類是我們自己定義的一個使用用戶名和密碼進行認證的一個Provider,這個類需要username和password,所以我們將這兩個參數放到authInfo中,傳遞給

authenticate
這個方法,這個方法會異步返回認證的結果。如果認證成功,會返回授權的對象User,調用User對象的

isAuthorized
可以進行驗證是否授權。下面是UserNameAndPasswordProvider的一個簡單實現

(2) UserNameAndPasswordProvider 代碼如下

 1 /**
 2 * 自定義認證
 3 *
 4 * @author lenovo
 5 */
 6 public class UserNameAndPasswordProvider implements AuthProvider {
 7 
 8 @Override
 9 public void authenticate(JsonObject authInfo, Handler<AsyncResult<User>> resultHandler) {
10 
11 // authInfo中存儲了認證需要的相關信息,由調用者傳入
12 String username = authInfo.getString("username");
13 String password = authInfo.getString("password");
14 
15 // 判斷用戶名和密碼是否正確
16 if ("admin".equals(username) && "admin".equals(password)) {
17 // 密碼驗證通過,需要實例化授權對象,並在Future中響應給調用者
18 
19 // 實例化授權對象,可以將認證信息傳入
20 User user = new MyUser(authInfo);
21 // 所有情況均成功返回,並將授權對象響應回去
22 resultHandler.handle(Future.succeededFuture(user));
23 } else {
24 // 密碼驗證不通過,響應認證失敗
25 resultHandler.handle(Future.failedFuture("用戶名或者密碼錯誤"));
26 }
27 
28 }
29 
30 }
View Code

 


看到上面的代碼,AuthTest中的邏輯就更加清晰了,代碼非常簡單,就不多描述了。

(3)User接口實現

 1 /**
 2 * 授權
 3 * 
 4 * @author lenovo
 5 *
 6 */
 7 public class MyUser implements User {
 8 
 9 private JsonObject authInfo;
10 
11 public MyUser(JsonObject authInfo) {
12 this.authInfo = authInfo;
13 }
14 
15 /**
16 * 這里依然是通過resultHandle響應授權信息,返回值為當前對象是為了Fluent調用模式
17 */
18 @Override
19 public User isAuthorized(String authority, Handler<AsyncResult<Boolean>> resultHandler) {
20 // 一直返回成功
21 resultHandler.handle(Future.succeededFuture(true));
22 return this;
23 }
24 
25 @Override
26 public User clearCache() {
27 return null;
28 }
29 
30 @Override
31 public JsonObject principal() {
32 return authInfo;
33 }
34 
35 @Override
36 public void setAuthProvider(AuthProvider authProvider) {
37 
38 }
39 
40 }
View Code

 


這里只是重寫了

isAuthorized
這個方法,這個方法里,一直異步響應授權成功,並同步返回當前類的實例,是為了級聯調用起來比較方便。這里也非常簡單,不多說。

2. 使用Vert.x提供的授權方式
(1)JDBC授權實現
通過Vert.x提供的接口我們可以自己實現認證和授權,但一般的情況下,我們可能都會選擇使用數據庫來保存認證和授權信息,如果每次我們都要自己實現JDBCAuthProvider會非常麻煩,重復造輪子,因此Vert.x給我們提供了JDBC授權的實現。用法非常簡單。對自定義授權熟悉之后,JDBC授權就非常好理解了。

① 引入pom依賴

1 <dependency>
2 <groupId>io.vertx</groupId>
3 <artifactId>vertx-auth-jdbc</artifactId>
4 <version>3.6.2</version>
5 </dependency>
View Code

 


②創建數據表,為了簡單,我們使用5張表

 1 -- 用戶表
 2 create table t_user (
 3 id int primary key auto_increment,
 4 username varchar(40) not null,
 5 password varchar(255) not null
 6 );
 7 
 8 -- 角色表
 9 create table t_role(
10 id int primary key auto_increment,
11 role_name varchar(40) not null
12 );
13 
14 -- 權限表
15 create table t_permission(
16 id int primary key auto_increment,
17 prefix varchar(40) not null
18 );
19 
20 -- 用戶角色對應關系表
21 create table t_user_role (
22 id int primary key auto_increment,
23 user_id int not null,
24 role_id int not null
25 );
26 
27 -- 角色權限對應關系表
28 create table t_role_permission(
29 id int primary key auto_increment,
30 role_id int not null,
31 per_id int not null
32 );
33 ③編寫測試類
34 
35 
36 public class JDBCAuthTest extends AbstractVerticle {
37 
38 private JDBCClient jdbcClient;
39 
40 @Override
41 public void start() throws Exception {
42 // 獲取到數據庫的客戶端
43 jdbcClient = new JdbcUtils(vertx).getDbClient();
44 
45 // 這個就是實現了AuthProvider接口的認證的類
46 JDBCAuth auth = JDBCAuth.create(vertx, jdbcClient);
47 
48 // 創建用於認證的參數
49 JsonObject authInfo = new JsonObject();
50 auth.authenticate(authInfo, res -> {
51 if (res.succeeded()) {
52 // 獲取到授權接口
53 User user = res.result();
54 System.out.println("認證成功");
55 } else {
56 // 認證失敗
57 System.out.println("認證失敗");
58 }
59 });
60 
61 }
View Code

 

1 public static void main(String[] args) {
2 Vertx.vertx().deployVerticle(new JDBCAuthTest());
3 }
4 }
View Code

 


運行之后發現,表也找不到,字段也找不到,為啥呢,因為我們創建的表和Vert.x創建的表的表名和字段名都不一樣。那么如果我們想要使用我們自己的表結構,就要給JDBCAuth設置要執行的SQL有下面的幾個方法

1 auth.setAuthenticationQuery(""); // 指定認證的SQL
2 auth.setPermissionsQuery(""); // 指定獲取用戶權限的SQL
3 auth.setRolesQuery(""); // 指定獲取用戶角色的SQL

好了,到這里,JDBC的認證就完成了,是不是用起來還是比較簡單的。不再需要我們來實現Provider和User接口了。

(2)JWT授權實現
JWT 是 JSON Web Token 的簡稱,通過名字就可以知道,JWT一般是用在web開發中,一種token的認證方式。在開發中用的還是比較多的。Web開發認證的實現主要有兩種方式,第一種是Session的方式,第二種是Token方式,這兩種認證方式的優劣我們這里不進行比較。

JWT認證和上面提到的基於JDBC的認證以及自定義實現的認證不同,JWT認證可以認為是在JDBC認證、手機短信認證或者自定義的認證證實身份之后,給認證者的一個唯一標識,以后認證只需要帶着這個標識就可以了,而不需要再帶着用戶名或者密碼進行認證。以此來保證用戶信息安全。 對於帶着用戶名或者密碼的這種認證方式,在上送用戶名和密碼這些敏感信息的時候,要使用https來保證傳輸信息的安全。

JWT認證核心兩個,一個是生成token,第二個是驗證客戶端上送的token是否正確。下面是具體的開發步驟

①引入pom

<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-auth-jwt</artifactId>
<version>3.6.2</version>
</dependency>

②JDBC認證,並生成token,返回給客戶端

JWTAuthOptions config = new JWTAuthOptions()
.addPubSecKey(new PubSecKeyOptions()
.setAlgorithm("HS256")
.setPublicKey("keyboard cat")
.setSymmetric(true));

JWTAuth provider = JWTAuth.create(vertx, config);

// 模擬認證通過
if("admin".equals("username") ) {
String token = provider.generateToken(new JsonObject(), new JWTOptions());
System.out.println(token);
}
③第二次客戶端帶着token來,服務端進行校驗

 1 JWTAuthOptions config = new JWTAuthOptions()
 2 .addPubSecKey(
 3 new PubSecKeyOptions().setAlgorithm("HS256")
 4 .setPublicKey("keyboard cat")
 5 .setSymmetric(true)
 6 );
 7 
 8 JWTAuth provider = JWTAuth.create(vertx, config);
 9 
10 provider.authenticate(new JsonObject().put("jwt", "dfasdfsadfsadfsdfs"), res -> {
11 System.out.println(res.result());
12 });

 


在token中帶數據

jwt中可以附帶一些非敏感的數據,比如用戶的ID,再或者時間戳等。那么該怎么帶數據呢,非常簡單。注意上面生成token的代碼中,傳入了兩個參數,一個是JsonObject,另一個是JWTOptions。其中,JsonObject就是在token中附帶的數據。

1 JsonObject data = new JsonObject()
2 .put("userId","admin");
3 
4 String token = provider.generateToken(data, new JWTOptions());
View Code

 


如上代碼,就可以在token中帶上userId,那么當我們解析token的時候,就可以取出userId的值了,代碼如下。

 1 // 使用jwt進行認證
 2 provider.authenticate(new JsonObject().put("jwt", jwt), auth -> {
 3 if (auth.succeeded()) {
 4 JWTUser user = (JWTUser) auth.result();
 5 JsonObject authData = user.principal(); // 這里就是token中解析的數據
 6 String userId = authData.getString("userId");
 7 System.out.println(userId);
 8 request.response().end("認證成功!");
 9 } else {
10 System.out.println("認證失敗");
11 request.response().end("token無效");
12 }
13 });

 


使用jwt的整個認證過程如下:

 1 package stu.vertx.auth.jwt;
 2 
 3 import io.vertx.core.AbstractVerticle;
 4 import io.vertx.core.Vertx;
 5 import io.vertx.core.http.HttpServer;
 6 import io.vertx.core.json.JsonObject;
 7 import io.vertx.ext.auth.PubSecKeyOptions;
 8 import io.vertx.ext.auth.jwt.JWTAuth;
 9 import io.vertx.ext.auth.jwt.JWTAuthOptions;
10 import io.vertx.ext.auth.jwt.impl.JWTUser;
11 import io.vertx.ext.jwt.JWTOptions;
12 import org.apache.commons.lang3.StringUtils;
13 import stu.vertx.auth.basic.UserNameAndPasswordProvider;
14 
15 public class JwtAuthVerticle extends AbstractVerticle {
16 
17 private JWTAuthOptions config = new JWTAuthOptions()
18 .addPubSecKey(new PubSecKeyOptions()
19 .setAlgorithm("HS256")
20 .setPublicKey("keyboard cat")
21 .setSymmetric(true));
22 
23 private JWTAuth provider = JWTAuth.create(vertx, config);
24 
25 @Override
26 public void start() throws Exception {
27 
28 HttpServer server = vertx.createHttpServer();
29 
30 // 處理客戶端請求
31 server.requestHandler(request -> {
32 JsonObject req = JwtAuthVerticle.parseQuery(request.query());
33 
34 // 判斷用戶是否帶token來認證,如果帶token,就直接通過token來認證,否則認為是第一次認證,通過用戶名和密碼的方式進行認證
35 String jwt = req.getString("jwt");
36 if (StringUtils.isBlank(jwt)) {
37 
38 // 先使用默認的用戶名密碼進行認證
39 UserNameAndPasswordProvider p = new UserNameAndPasswordProvider();
40 p.authenticate(req, auth -> {
41 if (auth.succeeded()) {
42 // 認證通過之后,再生成token,以后就使用token進行認證
43 JsonObject data = new JsonObject()
44 .put("userId", "admin");
45 
46 String token = provider.generateToken(data, new JWTOptions());
47 
48 request.response().end(token);
49 } else {
50 System.out.println("認證失敗");
51 request.response().end("認證失敗,請輸出正確的用戶名和密碼");
52 }
53 });
54 } else {
55 
56 // 使用jwt進行認證
57 provider.authenticate(new JsonObject().put("jwt", jwt), auth -> {
58 if (auth.succeeded()) {
59 JWTUser user = (JWTUser) auth.result();
60 JsonObject authData = user.principal();
61 String userId = authData.getString("");
62 System.out.println(userId);
63 request.response().end("認證成功!");
64 } else {
65 System.out.println("認證失敗");
66 request.response().end("token無效");
67 }
68 });
69 }
70 
71 });
72 server.listen(8080);
73 }
74 
75 
76 /**
77 * 把URL后跟的查詢字符串轉成json對象
78 *
79 * @param query
80 * @return
81 */
82 public static JsonObject parseQuery(String query) {
83 JsonObject data = new JsonObject();
84 String[] params = query.split("&");
85 for (String param : params) {
86 String[] k = param.split("=");
87 data.put(k[0], k[1]);
88 }
89 return data;
90 }
91 
92 public static void main(String[] args) {
93 Vertx.vertx().deployVerticle(new JwtAuthVerticle());
94 }
95 }

 


(3)Shiro授權
在應用開發中,很多的應用場景都使用shiro來進行認證和授權。在Vert.x中,也提供了對Shiro的支持。對於shiro的用法這里不再詳細的介紹,大家可以參考網絡上關於shiro的文章,我們這里僅僅介紹shiro在Vert.x中的應用。

在Vert.x中使用shiro,首先要導入shiro的依賴,以及Vert.x-shiro的依賴包,如下

<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-auth-shiro</artifactId>
<version>3.5.6</version>
</dependency>
下面創建shiro的配置文件auth.properties

user.tim=sausages,morris_dancer,developer,vtoons
role.developer=do_actual_work
role.vtoons=place_order
user.admin=admin,manager
在配置文件中,配置了一個admin用戶,他的密碼也是admin,他具有manager角色,下面是認證和授權的案例代碼

 1 /**
 2 * 使用shiro實現認證和授權的演示案例
 3 *
 4 * @author lenovo
 5 */
 6 public class ShiroAuthVerticle extends AbstractVerticle {
 7 
 8 @Override
 9 public void start() throws Exception {
10 JsonObject config = new JsonObject().put("properties_path", "classpath:auth.properties");
11 ShiroAuth provider = ShiroAuth.create(vertx, new ShiroAuthOptions().setType(ShiroAuthRealmType.PROPERTIES).setConfig(config));
12 
13 JsonObject data = new JsonObject()
14 .put("username", "admin")
15 .put("password", "admin");
16 provider.authenticate(data, auth -> {
17 if (auth.succeeded()) {
18 System.out.println("認證成功");
19 User user = auth.result();
20 user.isAuthorized("role:manager",res->{
21 if(res.result()) {
22 System.out.println("授權成功");
23 } else {
24 System.out.println("沒有權限");
25 }
26 });
27 } else {
28 System.out.println("認證失敗");
29 }
30 });
31 }
32 
33 @Override
34 public void stop() throws Exception {
35 super.stop();
36 }
37 
38 public static void main(String[] args) {
39 Vertx.vertx().deployVerticle(new ShiroAuthVerticle());
40 }
41 }

 


關於Vert.x提供的認證和授權還有 MongoDB認證,OAuth2認證等等,這里就不再多說了,大家感興趣的話,可以參考https://vertx.io/docs/vertx-auth-oauth2/java/這里有對oauth2的介紹,關於認證和授權,就說到這里了,下面是認證和授權在Web應用中的使用。

3. 認證和授權在Web中的應用
有了AuthProvider和User之后,再來看認證和授權在Web中的應用就非常簡單了。這里我們在Web應用中使用JDBC授權的實現,這也是Web開發中最為常用的。既然使用JDBC授權,那么首先就要創建數據庫,創建表,這里我們就使用Vert.x定義的表,如果你的需求比較復雜,可以定義更復雜的模型,這里為了簡單,就不再擴展了。

① 建表語句如下:

 1 CREATE TABLE `user` (
 2 `username` VARCHAR(255) NOT NULL,
 3 `password` VARCHAR(255) NOT NULL,
 4 `password_salt` VARCHAR(255) NOT NULL
 5 );
 6 
 7 CREATE TABLE `user_roles` (
 8 `username` VARCHAR(255) NOT NULL,
 9 `role` VARCHAR(255) NOT NULL
10 );
11 
12 CREATE TABLE `roles_perms` (
13 `role` VARCHAR(255) NOT NULL,
14 `perm` VARCHAR(255) NOT NULL
15 );

 


注意:MySQL8 默認對表名區分大小寫,JDBCAuth的實現類中,對表名是大寫的,這就會導致提示找不到表的問題。

② 在路由中使用授權

比如我們想對 /private/* 的請求需要進行認證,其他的請求不需要授權都可以訪問,那么我們就可以只針對/private/*實現攔截,然后進行權限的過濾。

router.route("/private/*").handler(authHandler);
路由后跟一個處理器,也就是攔截到/private/*的請求之后該如何處理,這里不需要再重復造輪子了,可以使用Vert.x提供的處理器RedirectAuthHandler,如下

AuthHandler authHandler = RedirectAuthHandler.create(authProvider,"/login.html");
create方法有兩個參數,第一個就是我們上面花了大量篇幅所描述的authProvider,第二個參數很明顯是一個url,表示如果認證失敗,要跳轉的頁面。當然認證失敗之后要跳轉到登錄頁面,讓用戶進行登錄了。下面是authProvider是如何創建的呢?

AuthProvider authProvider = JDBCAuth.create(vertx, jdbcClient);
到這里,在web應用中使用JDBC認證就完成了,是不是非常簡單。但到這里,我們只是實現了一個認證的處理器,是不是還需要提供一個登錄的處理器呢,不提供登錄的入口,不管如何訪問,都永遠會跳轉到登錄頁。對於登錄的實現也非常簡單,Vert.x也給我們提供了登錄的處理器。

1 FormLoginHandler formLoginHandler = FormLoginHandler.create(authProvider)
2 .setDirectLoggedInOKURL("/index.html");
3 
4 router.route("/login").handler(formLoginHandler);

 


當用戶訪問/login時,會使用FormLoginHandler,這個handle會讀取到表單提交上來的用戶名和密碼,然后傳遞給authProvider進行認證,如果認證通過,則會跳轉到setDirectLoggedInOKURL所指定的地址。當認證通過之后,再訪問/private下的資源就可以了。

1 router.route("/private/hello").handler(re -> {
2 re.user().isAuthorized("role:manager", a -> {
3 System.out.println(a.succeeded());
4 re.response().end("Over");
5 });
6 });

 


比如有上面的私有路徑,在登錄之前,訪問會跳轉到登錄頁面,當登錄成功之后,就可以進入到handle中。通過routeContext對象可以獲取到user對象,通過user對象的isAuthorized方法可以判斷是否有權限。這就完成了認證和授權的整個過程。

  

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM