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方法能够判断是否有权限。这就完成了认证和受权的整个过程。