Vert.x(vertx) 認證和受權

每一個線上系統幾乎都是離不開認證和受權的,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 }
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方法能夠判斷是否有權限。這就完成了認證和受權的整個過程。

相關文章
相關標籤/搜索