每个线上系统几乎都是离不开认证和授权的,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 { @Override
public void start() throws Exception { // 创建认证的Provider
AuthProvider 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 { @Override
public 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调用模式
*/
@Override
public User isAuthorized(String authority, Handler<AsyncResult<Boolean>> resultHandler) {
// 一直返回成功
resultHandler.handle(Future.succeededFuture(true));
return this;
} @Override
public User clearCache() {
return null;
} @Override
public JsonObject principal() {
return authInfo;
} @Override
public 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; @Override
public 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); @Override
public 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 { @Override
public 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("认证失败");
}
});
} @Override
public 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(vertx) 认证和授权的更多相关文章

  1. OAuth2.0认证和授权原理

    什么是OAuth授权?   一.什么是OAuth协议 OAuth(开放授权)是一个开放标准. 允许第三方网站在用户授权的前提下访问在用户在服务商那里存储的各种信息. 而这种授权无需将用户提供用户名和密 ...

  2. MVC 登录认证与授权及读取登录错误码

    最近悟出来一个道理,在这儿分享给大家:学历代表你的过去,能力代表你的现在,学习代表你的将来. 十年河东十年河西,莫欺少年穷 学无止境,精益求精    最近在自学MVC,遇到的问题很多,索性一点点总结下 ...

  3. Python+Django+SAE系列教程17-----authauth (认证与授权)系统1

    通过session,我们能够在多次浏览器请求中保持数据,接下来的部分就是用session来处理用户登录了. 当然,不能仅凭用户的一面之词,我们就相信,所以我们须要认证. 当然了,Django 也提供了 ...

  4. hOAuth2.0认证和授权原理

    原文地址: http://www.6zou.net/tech/what_is_oauth.html http://www.phpddt.com/%E4%BA%8C%E6%AC%A1%E5%BC%80% ...

  5. Open开发平台,认证,授权,计费

    1.申请appid和appkeyhttp://wiki.connect.qq.com/%E5%87%86%E5%A4%87%E5%B7%A5%E4%BD%9C_oauth2-0 appid:应用的唯一 ...

  6. 用户登录安全框架shiro—用户的认证和授权(一)

     ssm整合shiro框架,对用户的登录操作进行认证和授权,目的很纯粹就是为了增加系统的安全线,至少不要输在门槛上嘛. 这几天在公司独立开发一个供公司内部人员使用的小管理系统,客户不多但是登录一直都是 ...

  7. Asp.Net MVC-4-过滤器1:认证与授权

    基础 过滤器体现了MVC框架中的Aop思想,虽然这种实现并不完美但在实际的开发过程中一般也足以满足需求了. 过滤器分类 依据上篇分析的执行时机的不同可以把过滤器按照实现不同的接口分为下面五类: IAu ...

  8. ASP.NET Core 认证与授权[1]:初识认证

    在ASP.NET 4.X 中,我们最常用的是Forms认证,它既可以用于局域网环境,也可用于互联网环境,有着非常广泛的使用.但是它很难进行扩展,更无法与第三方认证集成,因此,在 ASP.NET Cor ...

  9. ASP.NET Core 认证与授权[3]:OAuth & OpenID Connect认证

    在上一章中,我们了解到,Cookie认证是一种本地认证方式,通常认证与授权都在同一个服务中,也可以使用Cookie共享的方式分开部署,但局限性较大,而如今随着微服务的流行,更加偏向于将以前的单体应用拆 ...

随机推荐

  1. Ubuntu 16.04/18.04 安装Shutter截图工具

    在安装Shutter软件之前,先安装依赖包,下载地址: 1.libgoocanvas-common_1.0.0-1_all.deb                 http://launchpadli ...

  2. 3.2 Spark运行架构

    一.基本概念 1.RDD Resillient Distributed Dataset 弹性分布式数据集 2.DAG 反映RDD之间的依赖关系 3.Executor 进程驻守在机器上面,由进程派生出很 ...

  3. c# 第10节 表达式

    本节内容: 1:表达式是什么 2:表达式实例 1:表达式是什么 2:表达式实例 3:运算符分类

  4. Codeforces Round #606 (Div. 2, based on Technocup 2020 Elimination Round 4) 题解

    Happy Birthday, Polycarp! Make Them Odd As Simple as One and Two Let's Play the Words? Two Fairs Bea ...

  5. [LeetCode] 2. Add Two Numbers 两个数字相加

    You are given two non-empty linked lists representing two non-negative integers. The digits are stor ...

  6. Windows搭建FTP/Http文件共享(利用IIS)

    控制面板——程序——添加功能 勾选Ftp服务器.万维网服务.IIS管理控制台 然后,计算机——右键管理——服务和应用程序,添加网站和添加Ftp IP设置为 未分配 或 本机获取到的静态IP即可. 然后 ...

  7. nginx 配置实例(ssl、proxy、cache、gzip、upstream等优化)

    [root@xxxxxxxxxxxxxx ~]# cat /usr/local/nginx/conf/nginx.conf user nobody; worker_processes ; worker ...

  8. C++ 中文乱码的问题

    乱码的根本原因就是字符串编码的方式也字符串解码方式不一致导致的, 而在我们平常用的编码编码方式一般都是utf-8以gbk之间的相互转换, 下面给出编码方式的转换代码 string UtfToStrin ...

  9. 获取oracle中某张表的各个字段名称

    select column_name from all_tab_columns where table_name='AT2_SSIS_CHNDB_CLIENT_INFO'

  10. 在 Queue 中 poll()和 remove()有什么区别?

    remove() ,如果队列为空的时候,则会抛出异常 而poll()只会返回null