每个线上系统几乎都是离不开认证和授权的,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)应用代码

/*** 认证与授权测试* * @author lenovo**/
public class AuthTest extends AbstractVerticle {@Overridepublic void start() throws Exception {// 创建认证的ProviderAuthProvider provider = new UserNameAndPasswordProvider();JsonObject authInfo = new JsonObject().put("username", "admin").put("password", "admin");// 调用认证方法,将认证的数据传入provider.authenticate(authInfo, res -> {if (res.succeeded()) {// 认证通过,可以获取到User对象,通过User对象可以进行权限校验User user = res.result();// 授权user.isAuthorized("save:user", auth -> {if (auth.succeeded()) {System.out.println("授权成功");} else {System.out.println("授权失败");}});} else {System.out.println("认证失败!");}});}public static void main(String[] args) {Vertx.vertx().deployVerticle(new AuthTest());}}

用法非常简单,首先创建一个AuthProvider,这里我们使用了

UserNameAndPasswordProvider

这个类是我们自己定义的一个使用用户名和密码进行认证的一个Provider,这个类需要username和password,所以我们将这两个参数放到authInfo中,传递给

authenticate

这个方法,这个方法会异步返回认证的结果。如果认证成功,会返回授权的对象User,调用User对象的

isAuthorized

可以进行验证是否授权。下面是UserNameAndPasswordProvider的一个简单实现

(2) UserNameAndPasswordProvider 代码如下

/*** 自定义认证** @author lenovo*/
public class UserNameAndPasswordProvider implements AuthProvider {@Overridepublic void authenticate(JsonObject authInfo, Handler<AsyncResult<User>> resultHandler) {// authInfo中存储了认证需要的相关信息,由调用者传入String username = authInfo.getString("username");String password = authInfo.getString("password");// 判断用户名和密码是否正确if ("admin".equals(username) && "admin".equals(password)) {// 密码验证通过,需要实例化授权对象,并在Future中响应给调用者// 实例化授权对象,可以将认证信息传入User user = new MyUser(authInfo);// 所有情况均成功返回,并将授权对象响应回去resultHandler.handle(Future.succeededFuture(user));} else {// 密码验证不通过,响应认证失败resultHandler.handle(Future.failedFuture("用户名或者密码错误"));}}}

看到上面的代码,AuthTest中的逻辑就更加清晰了,代码非常简单,就不多描述了。

(3)User接口实现

/*** 授权* * @author lenovo**/
public class MyUser implements User {private JsonObject authInfo;public MyUser(JsonObject authInfo) {this.authInfo = authInfo;}/*** 这里依然是通过resultHandle响应授权信息,返回值为当前对象是为了Fluent调用模式*/@Overridepublic User isAuthorized(String authority, Handler<AsyncResult<Boolean>> resultHandler) {// 一直返回成功resultHandler.handle(Future.succeededFuture(true));return this;}@Overridepublic User clearCache() {return null;}@Overridepublic JsonObject principal() {return authInfo;}@Overridepublic void setAuthProvider(AuthProvider authProvider) {}}

这里只是重写了

isAuthorized

这个方法,这个方法里,一直异步响应授权成功,并同步返回当前类的实例,是为了级联调用起来比较方便。这里也非常简单,不多说。

2. 使用Vert.x提供的授权方式

(1)JDBC授权实现

通过Vert.x提供的接口我们可以自己实现认证和授权,但一般的情况下,我们可能都会选择使用数据库来保存认证和授权信息,如果每次我们都要自己实现JDBCAuthProvider会非常麻烦,重复造轮子,因此Vert.x给我们提供了JDBC授权的实现。用法非常简单。对自定义授权熟悉之后,JDBC授权就非常好理解了。

① 引入pom依赖

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

②创建数据表,为了简单,我们使用5张表

-- 用户表
create table t_user (id int primary key auto_increment,username varchar(40) not null,password varchar(255) not null
);-- 角色表
create table t_role(id int primary key auto_increment,role_name varchar(40) not null
);-- 权限表
create table t_permission(id int primary key auto_increment,prefix varchar(40) not null
);-- 用户角色对应关系表
create table t_user_role (id int primary key auto_increment,user_id int not null,role_id int not null
);-- 角色权限对应关系表
create table t_role_permission(id int primary key auto_increment,role_id int not null,per_id int not null
);

③编写测试类


public class JDBCAuthTest extends AbstractVerticle {private JDBCClient jdbcClient;@Overridepublic void start() throws Exception {// 获取到数据库的客户端jdbcClient = new JdbcUtils(vertx).getDbClient();// 这个就是实现了AuthProvider接口的认证的类JDBCAuth auth = JDBCAuth.create(vertx, jdbcClient);// 创建用于认证的参数JsonObject authInfo = new JsonObject();auth.authenticate(authInfo, res -> {if (res.succeeded()) {// 获取到授权接口User user = res.result();System.out.println("认证成功");} else {// 认证失败System.out.println("认证失败");}});}public static void main(String[] args) {Vertx.vertx().deployVerticle(new JDBCAuthTest());}
}

运行之后发现,表也找不到,字段也找不到,为啥呢,因为我们创建的表和Vert.x创建的表的表名和字段名都不一样。那么如果我们想要使用我们自己的表结构,就要给JDBCAuth设置要执行的SQL有下面的几个方法

auth.setAuthenticationQuery(""); // 指定认证的SQL
auth.setPermissionsQuery(""); // 指定获取用户权限的SQL
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来,服务端进行校验

JWTAuthOptions config = new JWTAuthOptions().addPubSecKey(new PubSecKeyOptions().setAlgorithm("HS256").setPublicKey("keyboard cat").setSymmetric(true));JWTAuth provider = JWTAuth.create(vertx, config);provider.authenticate(new JsonObject().put("jwt", "dfasdfsadfsadfsdfs"), res -> {System.out.println(res.result());
});

在token中带数据

jwt中可以附带一些非敏感的数据,比如用户的ID,再或者时间戳等。那么该怎么带数据呢,非常简单。注意上面生成token的代码中,传入了两个参数,一个是JsonObject,另一个是JWTOptions。其中,JsonObject就是在token中附带的数据。

JsonObject data = new JsonObject().put("userId","admin");String token = provider.generateToken(data, new JWTOptions());

如上代码,就可以在token中带上userId,那么当我们解析token的时候,就可以取出userId的值了,代码如下。

// 使用jwt进行认证
provider.authenticate(new JsonObject().put("jwt", jwt), auth -> {if (auth.succeeded()) {JWTUser user = (JWTUser) auth.result();JsonObject authData = user.principal(); // 这里就是token中解析的数据String userId = authData.getString("userId");System.out.println(userId);request.response().end("认证成功!");} else {System.out.println("认证失败");request.response().end("token无效");}
});

使用jwt的整个认证过程如下:

package stu.vertx.auth.jwt;import io.vertx.core.AbstractVerticle;
import io.vertx.core.Vertx;
import io.vertx.core.http.HttpServer;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.auth.PubSecKeyOptions;
import io.vertx.ext.auth.jwt.JWTAuth;
import io.vertx.ext.auth.jwt.JWTAuthOptions;
import io.vertx.ext.auth.jwt.impl.JWTUser;
import io.vertx.ext.jwt.JWTOptions;
import org.apache.commons.lang3.StringUtils;
import stu.vertx.auth.basic.UserNameAndPasswordProvider;public class JwtAuthVerticle extends AbstractVerticle {private JWTAuthOptions config = new JWTAuthOptions().addPubSecKey(new PubSecKeyOptions().setAlgorithm("HS256").setPublicKey("keyboard cat").setSymmetric(true));private JWTAuth provider = JWTAuth.create(vertx, config);@Overridepublic void start() throws Exception {HttpServer server = vertx.createHttpServer();// 处理客户端请求server.requestHandler(request -> {JsonObject req = JwtAuthVerticle.parseQuery(request.query());// 判断用户是否带token来认证,如果带token,就直接通过token来认证,否则认为是第一次认证,通过用户名和密码的方式进行认证String jwt = req.getString("jwt");if (StringUtils.isBlank(jwt)) {// 先使用默认的用户名密码进行认证UserNameAndPasswordProvider p = new UserNameAndPasswordProvider();p.authenticate(req, auth -> {if (auth.succeeded()) {// 认证通过之后,再生成token,以后就使用token进行认证JsonObject data = new JsonObject().put("userId", "admin");String token = provider.generateToken(data, new JWTOptions());request.response().end(token);} else {System.out.println("认证失败");request.response().end("认证失败,请输出正确的用户名和密码");}});} else {// 使用jwt进行认证provider.authenticate(new JsonObject().put("jwt", jwt), auth -> {if (auth.succeeded()) {JWTUser user = (JWTUser) auth.result();JsonObject authData = user.principal();String userId = authData.getString("");System.out.println(userId);request.response().end("认证成功!");} else {System.out.println("认证失败");request.response().end("token无效");}});}});server.listen(8080);}/*** 把URL后跟的查询字符串转成json对象** @param query* @return*/public static JsonObject parseQuery(String query) {JsonObject data = new JsonObject();String[] params = query.split("&");for (String param : params) {String[] k = param.split("=");data.put(k[0], k[1]);}return data;}public static void main(String[] args) {Vertx.vertx().deployVerticle(new JwtAuthVerticle());}
}

(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角色,下面是认证和授权的案例代码

/*** 使用shiro实现认证和授权的演示案例** @author lenovo*/
public class ShiroAuthVerticle extends AbstractVerticle {@Overridepublic void start() throws Exception {JsonObject config = new JsonObject().put("properties_path", "classpath:auth.properties");ShiroAuth provider = ShiroAuth.create(vertx, new ShiroAuthOptions().setType(ShiroAuthRealmType.PROPERTIES).setConfig(config));JsonObject data = new JsonObject().put("username", "admin").put("password", "admin");provider.authenticate(data, auth -> {if (auth.succeeded()) {System.out.println("认证成功");User user = auth.result();user.isAuthorized("role:manager",res->{if(res.result()) {System.out.println("授权成功");} else {System.out.println("没有权限");}});} else {System.out.println("认证失败");}});}@Overridepublic void stop() throws Exception {super.stop();}public static void main(String[] args) {Vertx.vertx().deployVerticle(new ShiroAuthVerticle());}
}

关于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定义的表,如果你的需求比较复杂,可以定义更复杂的模型,这里为了简单,就不再扩展了。

① 建表语句如下:

CREATE TABLE `user` (`username` VARCHAR(255) NOT NULL,`password` VARCHAR(255) NOT NULL,`password_salt` VARCHAR(255) NOT NULL
);CREATE TABLE `user_roles` (`username` VARCHAR(255) NOT NULL,`role` VARCHAR(255) NOT NULL
);CREATE TABLE `roles_perms` (`role` VARCHAR(255) NOT NULL,`perm` VARCHAR(255) NOT NULL
);

注意: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也给我们提供了登录的处理器。

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

当用户访问/login时,会使用FormLoginHandler,这个handle会读取到表单提交上来的用户名和密码,然后传递给authProvider进行认证,如果认证通过,则会跳转到setDirectLoggedInOKURL所指定的地址。当认证通过之后,再访问/private下的资源就可以了。

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

比如有上面的私有路径,在登录之前,访问会跳转到登录页面,当登录成功之后,就可以进入到handle中。通过routeContext对象可以获取到user对象,通过user对象的isAuthorized方法可以判断是否有权限。这就完成了认证和授权的整个过程。

(一)Vert.x 简明介绍 https://blog.csdn.net/king_kgh/article/details/80772657

(二)Vert.x创建简单的HTTP服务 https://blog.csdn.net/king_kgh/article/details/80804078

(三)Vert.x Web开发之路由 https://blog.csdn.net/king_kgh/article/details/80848571

(四)Vert.x TCP服务实现 https://blog.csdn.net/king_kgh/article/details/84870775

(五)Vert.x数据库访问 https://blog.csdn.net/king_kgh/article/details/84894599

(六)Vert.x认证和授权 https://blog.csdn.net/king_kgh/article/details/85218454

(七)Vert.x事件总线(Event Bus)与远程服务调用 https://blog.csdn.net/king_kgh/article/details/86993812

Vert.x 案例代码:https://github.com/happy-fly

笔者就职于一家互联网支付公司,公司的核心项目使用的是Vert.x技术体系。记得笔者刚进入公司,接触Vert.x的时候,找遍了大大小小的网站,发现市面上关于Vert.x的文档除了官方文档外,几乎找不到其他资料。当时就励志要出一个专栏来写写Vert.x,以此帮助在Vert.x上采坑的朋友。因为笔者能力有限,文章中难免会有错误和疏漏,如果您对文章有意见或者好的建议,可以直接留言或者发送邮件到18366131816@163.com。如果您也对Vert.感兴趣,可以加入到我们,共同学习Vert.x,并推进国内开发者对Vert.x的认知。

Vert.x(vertx) 认证和授权详解(包含认证和授权在Web系统中的使用)相关推荐

  1. PMP、IPMP、ITPMP、CPMP详解(项目管理认证详解)

    PMP.IPMP.ITPMP.CPMP详解(项目管理认证详解) 多朋友会问相关认证考试的区别和认证体系,现就这一问题专门做讲解,希望大家能对照选取自己的认证考试.其实在我的博客里也有类似的区别对照表, ...

  2. PMP、IPMP、ITPMP、CPMP详解(项目管理认证类详解)

    PMP.IPMP.ITPMP.CPMP详解(项目管理认证详解) 多朋友会问相关认证考试的区别和认证体系,现就这一问题专门做讲解,希望大家能对照选取自己的认证考试.其实在我的博客里也有类似的区别对照表, ...

  3. 详解OAuth 2.0授权协议(Bearer token)

    OAuth 2.0授权协议 1 认证(Authentication) 2 授权(Authorization) 3 OAuth 2.0与认证机制的联系 4 详解OAuth 2.0授权协议 4.1 授权码 ...

  4. linux 子域dns,linux下搭建DNS子域及相关授权详解

    linux下搭建DNS子域及相关授权详解forward功能是本地无法解析的域名,转发给指定DNS服务器 forward only; 所有无法解析的域名,都转发给指定DNS服务器,必须有解析结果 for ...

  5. Asp.Net Core 中IdentityServer4 实战之角色授权详解

    一.前言 前几篇文章分享了IdentityServer4密码模式的基本授权及自定义授权等方式,最近由于改造一个网关服务,也用到了IdentityServer4的授权,改造过程中发现比较适合基于Role ...

  6. linux下搭建DNS子域及相关授权详解

    linux下搭建DNS子域及相关授权详解 forward功能是本地无法解析的域名,转发给指定DNS服务器 forward only; 所有无法解析的域名,都转发给指定DNS服务器,必须有解析结果 fo ...

  7. 思科ccna认证路由器路由表详解-ielab

    思科ccna认证路由器路由表详解-ielab互联网络将世界上各种类型的计算机以及其他终端设备连接在了一起,使得这些设备能够协同工作,使得能够进行相互通信.在IP网络中,数据遵循IP协议所定义的格式,设 ...

  8. 详解Shiro认证流程

    详解Shiro认证流程 isAccessAllowed Subject在如何得到? resolveSession doCreateSubject save(Subject subject) isAut ...

  9. linux做子域dns,linux下搭建DNS子域及相关授权详解

    linux下搭建DNS子域及相关授权详解 forward功能是本地无法解析的域名,转发给指定DNS服务器 forward only; 所有无法解析的域名,都转发给指定DNS服务器,必须有解析结果 fo ...

  10. UE4 输入系统详解一、 UE4如何获取win系统输入消息

    UE4 输入系统详解一. UE4如何获取win系统输入消息 UE4版本:4.253 按键输入 1.当我们按下键盘时输入时,FEngineLoop::Tick()里的每个tick执行的PumpMessa ...

最新文章

  1. C语言文件实验要求,实验教学的目的和要求.doc
  2. AAAI2020-图神经网络(GNN)过去、现在、应用和未来最新研究进展分享
  3. Spring MVC - Hello World示例
  4. 如何设计一个安全的登录流程
  5. java 获取当前classpath的绝对路径
  6. 远程桌面端口修改批处理
  7. 用 Python 识别图片中的文字
  8. ShaderLab学习小结(四)简单产生阴影
  9. [Springboot]SpringCache + Redis实现数据缓存
  10. 苹果“撞上”反垄断,围墙花园能否坚挺?
  11. 本地提交spark_spark快速入门(三)-------spark部署及运行模式
  12. 验证堆表(heap table)存储方式
  13. 双足机器人重心在头部_双足行走机器人及其重心调节装置制造方法及图纸
  14. DNS 智能解析功能评测之华为云篇~
  15. RoaringBitmap应用场景
  16. CA签名是报的错误及解决方法
  17. 2021-11-25 统计学-基于R(第四版)第八章课后习题记录及总结
  18. 使用ffmpeg调整图像大小
  19. 歪写数学史(当之无愧的数学王子)
  20. python 修饰符和装饰器_将两个python装饰器组合到on中

热门文章

  1. 互联网医院在线问诊系统-医院远程问诊,守护您的健康
  2. 算法笔记CodeUp第一至第六章刷题记录
  3. 1-50可复制到word文档外带圈的数生成方法
  4. 黑马旅游网学习笔记之分页展示(六)
  5. Robot Framework robot命令
  6. 5月书讯(下)| 5天小长假,一起读新书
  7. C++ 内存中堆栈讲解
  8. 还有多少时间可以挥霍!
  9. 局域网搭建视频服务器
  10. 使用navicat连接远程linux mysql数据库出现10061