每一個線上系統幾乎都是離不開認證和受權的,Vert.x提供了靈活、簡單、便捷的認證和受權的支持。Vert.x抽象出了兩個核心的認證和受權的接口,一個是 AuthProvider,另外一個是User。經過這兩個接口,咱們能夠很是靈活的實現咱們自定義的認證和受權方法。固然,Vert.x也給咱們提供了使用 JDBC、Shiro、MongoDB、JWT等受權的實現,咱們能夠直接使用。html
Vert.x提供的認證和受權都很是簡單,多種受權方式都有必定的規律性。通常來說不須要刻意的學習,在使用的過程當中,多讀下Vert.x的源碼就可以很是清楚的瞭解到Vert.x認證和受權底層的邏輯。但不是每一位開發者都有時間或者心情去讀源碼的,因此,這裏我簡單列出關於Vert.x的認證和受權的應用。java
使用Vert.x認證和受權,須要經歷三個階段web
1. 本身實現AuthProvider和User接口實現一個簡單的認證和受權。數據庫
2. 使用Vert.x提供的受權方式,如JDBCapache
3. 在Web中使用認證和受權來管理訪問權限json
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)應用代碼ide
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 }
用法很是簡單,首先建立一個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 }
看到上面的代碼,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 }
這裏只是重寫了
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>
②建立數據表,爲了簡單,咱們使用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 }
1 public static void main(String[] args) { 2 Vertx.vertx().deployVerticle(new JDBCAuthTest()); 3 } 4 }
運行以後發現,表也找不到,字段也找不到,爲啥呢,由於咱們建立的表和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());
如上代碼,就能夠在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方法能夠判斷是否有權限。這就完成了認證和受權的整個過程。