通用认证(鉴权)和授权
该Vert.x组件提供了用于身份验证和授权的接口, 这些接口可以从Vert.x应用程序中使用,并且可以由其他提供程序支持。
vertx-web同样也使用了Vert.x auth来处理其身份验证和授权。
要使用这个项目,在构建描述的 dependencies 部分添加以下依赖项:
-
Maven (在
pom.xml
文件中):
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-auth-common</artifactId>
<version>4.2.7</version>
</dependency>
-
Gradle (在
build.gradle
文件中):
compile 'io.vertx:vertx-auth-common:4.2.7'
基本概念
(Authentication)鉴权 是指验证用户的身份。
(Authorization)授权 是指验证用户是否有权执行特定任务。
为了支持多种授权模型以及保持其灵活性, 所有的授权操作都在
Authorization
上进行。
在某些情况下,一个授权可能代表一种权限,
比如,访问所有打印机或某个打印机的权限。
在其他情况下,授权可能是一个角色(Role)(例如:admin
,manager
等)。
为了提供少量实现,可以使用下面这些工厂方法:
-
RoleBasedAuthorization
基于角色(Role)的授权。 -
PermissionBasedAuthorization
基于权限(Permission)的授权。 -
WildcardPermissionBasedAuthorization
以通配符匹配的基于角色的授权。 -
AndAuthorization
逻辑授权。 -
OrAuthorization
逻辑授权。 -
NotAuthorization
逻辑授权。
这组授权代表任何类型的授权,例如:
-
基于角色的授权
-
基于权限的授权
-
逻辑授权(与,或,非)
-
基于时间的授权(即:允许访问该月的最后5天,从上午8点到上午10点,依此类推)
-
基于上下文的授权(即:如果IP地址为’xxx.xxx.xxx.xxx',则允许访问)
-
基于自定义的授权(即:基于特定于应用程序的脚本或硬编码代码)
-
等等
要找到你期望的特定 AuthorizationProvider
实现,
请查阅该身份验证提供程序的文档。
鉴权/身份认证(Authentication)
要对用户进行身份认证,请使用 authenticate
.
第一个参数是一个JSON对象,其中包含身份验证信息。实际包含的内容取决于具体的实现方式。 对于基于用户名/密码的简单身份验证,它可能包含以下内容:
{ "username": "tim" "password": "mypassword" }
对于基于JWT token或OAuth bearer token的实现,它可能包含token信息。
身份验证是异步进行的,并且结果会通过参数中提供的handler传递给用户。
异步结果包含一个 User
实例,
该实例代表已认证的用户。
上述得到的认证用户对象不包含该对象被授权的授权信息或上下文。
授权与身份认证分离的原因是,
身份认证和授权是两个区分的操作,不需要在同一程序上执行。
一个简单的示例是,使用 OAuth2.0
进行身份认证的用户可以使用 JWT
授权提供者为给定的权限匹配令牌,
但其他情况也适用,例如使用 LDAP
进行身份认证和使用 MongoDB
进行授权。
这是一个使用简单的用户名/密码实现对用户进行身份验证的示例:
JsonObject authInfo = new JsonObject()
.put("username", "tim").put("password", "mypassword");
authProvider.authenticate(authInfo)
.onSuccess(user -> {
System.out.println("User " + user.principal() + " is now authenticated");
})
.onFailure(Throwable::printStackTrace);
授权(Authorization)
当你得到一个 User
实例后,你可以调用其 authorizations
方法来获取其授权。
一个新创建的用户不会包含授权。你可以直接在 User
上添加授权或者通过 AuthorizationProvider
来添加。
上面所有的结果都是在handler中异步返回的。
这是一个通过 AuthorizationProvider
添加授权的示例:
authorizationProvider.getAuthorizations(user)
.onSuccess(done -> {
// cache is populated, perform query
if (PermissionBasedAuthorization.create("printer1234").match(user)) {
System.out.println("User has the authority");
} else {
System.out.println("User does not have the authority");
}
});
以及另一个基于角色的授权示例,该模型使用了接口 RoleBasedAuthorization
。
请注意,如上所述, 授权字符串的解释方式完全由基础实现决定,Vert.x在此不做任何假设。
用户主体和属性
您可以使用获得与已认证用户相对应的 主体(principal)
。
返回的内容取决于具体的底层实现。主体映射是用于创建的源数据用户实例。 属性是额外的属性,在实例创建的过程中 不会 提供, 但是它们是以处理用户数据的结果出现的。 区别就在于有确保处理的主体不会修改或重写现有数据。
为了简化用法,可以使用两种方法在两个源上查找和读取值:
if (user.containsKey("sub")) {
// the check will first assert that the attributes contain
// the given key and if not assert that the principal contains
// the given key
// just like the check before the get will follow the same
// rules to retrieve the data, first "attributes" then "principal"
String sub = user.get("sub");
}
创建自定义的身份认证或授权提供程序
如果希望创建自己的身份验证提供程序,则应实现一个或两个接口:
用户工厂方法可以使用给定的 principal
JSON内容创建一个 User
对象。
第二个参数 attributes
是可选的,它可以提供额外的元数据供以后使用。
以下属性是一个示例:
-
exp
- Expires at in seconds. -
iat
- Issued at in seconds. -
nbf
- Not before in seconds. -
leeway
- clock drift leeway in seconds.
前3个控制 expired
方法如何计算用户的过期时间,
最后一个可以用于在计算过期时间时允许事件偏移补偿。
伪随机数生成器(PRNG)
由于来自Java的Secure Random会在从系统获取熵的过程中阻塞, 因此我们提供了它的简单封装,可以使用该封装,而不会阻塞事件循环。
默认情况下,此PRNG使用混合模式,播种(seeding)过程是阻塞的,生成过程是非阻塞的。 同时,每5分钟PRNG也将重新设置新的64位的熵。但是,所有这些都可以使用系统属性进行配置:
-
io.vertx.ext.auth.prng.algorithm 示例: SHA1PRNG
-
io.vertx.ext.auth.prng.seed.interval 示例: 1000 (每秒)
-
io.vertx.ext.auth.prng.seed.bits 示例: 128
除非你注意到应用程序的性能受到PRNG算法的影响, 否则大多数用户都不需要配置这些值。
共享伪随机数生成器
由于伪随机数生成器对象的资源昂贵,它们消耗系统熵,这是一种稀缺资源,
因此明智的做法是在所有处理程序之间共享PRNG。为了做到这一点并使它适用于Vert.x支持的所有语言,您应该查看
VertxContextPRNG
。
此接口使得用户对PRNG的生命周期管理变得轻松,并确保可以在所有应用程序中复用它, 例如:
String token = VertxContextPRNG.current(vertx).nextString(32);
// Generate a secure random integer
int randomInt = VertxContextPRNG.current(vertx).nextInt();
使用密钥
在处理安全性时,您需要加载安全密钥。 安全密钥有多种格式和标准,这使其成为一项非常复杂的任务。 为了简化开发人员方面的工作,此模块包含2个抽象类:
-
KeyStoreOptions
JVM keystore通用格式。 -
PubSecKeyOptions
PEM通用格式。
要加载本地密钥库模块,应提供一个options对象,例如:
KeyStoreOptions options = new KeyStoreOptions()
.setPath("/path/to/keystore/file")
.setType("pkcs8")
.setPassword("keystore-password")
.putPasswordProtection("key-alias", "alias-password");
该类型非常重要,因为它随所使用的JVM版本而异。
Java 9之前的默认值是特定于JVM的默认值 jks
,而之后 pkcs12
是通用标准。
pkcs12
即使不需要 keytool
命令,也可以将非JVM密钥库密钥导入到文件中,
例如,可以通过以下方式实现 OpenSSL
:
openssl pkcs12 -export -in mykeycertificate.pem -out mykeystore.pkcs12 -name myAlias -noiter -nomaciter
上面的命令会将现有的pem文件转换为pkcs12密钥库,并将给定的密钥使用 myAlias
命名。
-noiter -nomaciter
为了使文件与JVM加载程序兼容,需要额外的参数。
要加载 PEM
文件,您应该注意一些限制。
默认的JVM类仅支持 PKCS8
格式的密钥 ,因此,如果您有其他PEM文件,则需要使用以下方式转换 OpenSSL
:
openssl pkcs8 -topk8 -inform PEM -in private.pem -out private_key.pem -nocrypt
在此之后,使用这样的文件很简单:
PubSecKeyOptions options = new PubSecKeyOptions()
.setAlgorithm("RS256")
.setBuffer(
vertx.fileSystem()
.readFileBlocking("/path/to/pem/file")
.toString());
PEM文件是常见且易于使用的,但不受密码保护,因此可以轻松嗅探私钥。
JSON Web Keys(JWK)
JWK是OpenID connect和JWT提供程序使用的标准。它们用JSON对象表示密钥。 通常这些JSON文档由Google,Microsoft等身份提供商服务器提供, 但是您也可以使用在线应用程序 <a href="https://mkjwk.org/">https://mkjwk.org</a> 来生成自己的密钥。 要想离线体验,还可以使用该工具: <a href="https://connect2id.com/products/nimbus-jose-jwt/generator">https://connect2id.com/products/nimbus-jose-jwt/generator</a> 。
链接多个身份验证提供程序
在某些情况下,支持链接多个身份验证提供程序可能会很有意思,例如,在LDAP或属性文件上查找用户。
这可以用 ChainAuth
来实现。
ChainAuth.any()
.add(ldapAuthProvider)
.add(propertiesAuthProvider);
执行 全部 匹配也是可以的,例如,必须在LDAP和属性上匹配用户:
ChainAuth.all()
.add(ldapAuthProvider)
.add(propertiesAuthProvider);