Vert.x Common Authentication and Authorization 翻译

1,226 阅读8分钟

Common Authentication and Authorization 翻译

这个vertx组件为鉴权和授权提供了一组接口,可以将其用于vertx应用程序而且可以由不同的供应方提供支持

Vert.x auth也可以用于vertx-web处理授权鉴权

为了使用这个项目,请将以下的依赖添加到你的依赖文件里面

  • Maven (in your pom.xml):
<dependency>
 <groupId>io.vertx</groupId>
 <artifactId>vertx-auth-common</artifactId>
 <version>4.0.2</version>
</dependency>
  • Gradle (in your build.gradle file):
compile 'io.vertx:vertx-auth-common:4.0.2'

基本概念

验权是指验证用户的身份。

授权指验证用户是否被授权执行特定的任务

为了支持多种模型并且保持其的灵活性,所有授权的操作都是在授权类型上执行

在某些情况下,授权可能表示权限,例如访问所有打印机或对特定打印机的授权。在其他情况下,一个授权可能是一个角色(例如:'admin', 'manager'等),为了提供一个小的实现集合,可以使用以下实现的工厂

  • RoleBasedAuthorization 基于角色的授权
  • PermissionBasedAuthorization基于许可的授权
  • WildcardPermissionBasedAuthorization Role based authorization matched as a wildcard.基于角色的通配符匹配授权
  • AndAuthorization 逻辑授权
  • OrAuthorization 逻辑授权
  • NotAuthorization 逻辑授权

这组授权表示任何类型的授权,例如:

  • 基于角色的授权
  • 基于许可的授权
  • 逻辑授权 (AND, OR, NOT)
  • 基于时间的授权(例如:允许在本月最后五天访问,从上午8点到上午10点)
  • 基于上下文的授权 (例如:允许ip为“xxx.xxx.xx”的用户访问)
  • 自定义授权(例如:为应用程序指定的脚本或硬编码代码)
  • 等等…

要了解特定的AuthorizationProvider所提供的内容,请参阅该Authorization提供者的文档。

验权

你可以使用 authenticate验证用户身份

第一个参数是一个包含验证信息的JSON object,其到底包含哪些取决于具体的实现,举个例子基于用户名/密码的验证可能会包含以下内容

{
 "username": "tim"
 "password": "mypassword"
}

对于基于JWT或者OAuth token的实现则可能包括token信息

身份验证以异步方式进行,结果通过调用中提供的结果处理程序传递给用户。asynç结果包含一个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);

授权

一旦您有了一个user实例,您就可以调用authorizations来获得它的授权。新创建的用户将不包含任何授权。您可以直接在user本身或通过AuthorizationProvider添加授权。

上述所有结果都是在处理程序中异步提供的。

下面是一个通过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没有做任何事情。

授权列表

user对象持有一个授权列表,因此随后的调用应该检查它是否具有相同的授权,这样就能避免多余的IO操作导致底层的授权提供方加载授权服务

为了清除授权列表,您可以使用clear

用户主体和属性

您可以获取通过携带principal已授权的用户获取到主体

具体会返回什么取决于底层的实现。主体(principal)的map是被用于创建user实例的源数据,而属性(attributes)则是在创建阶段没有提供而是用户数据处理后添加的额外属性。这种区别是为了处理主体时不会篡改或者覆盖已有的数据

为了简化使用,有两个方法可以帮助您寻找和读取这两个源中的值

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");
}

创建你的授权与鉴权的实现

如果你想创建你自己的auth provider,你可以实现下面一个或者两个接口

  • AuthenticationProvider
  • AuthorizationProvider

user工厂可以用给定的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.

前三个属性控制如何计算用户的过期时间,最后一个可以允许计算过期时间的时候添加时钟漂移补偿

伪随机数生成器

由于java的安全随机数在从系统获取熵的过程中可能会阻塞,因此我们提供了一个简单的包装器,可以使用它而不会阻塞事件循环。

默认情况下,这个PRNG(译者注:pseudorandom number generator 伪随机数生成器)使用混合模式,生成种子时阻塞,生成数字时不阻塞。PRNG也将以64位的新熵每5分钟重新生成种子一次。然而,这些都可以使用系统属性来配置:

  • io.vertx.ext.auth.prng.algorithm e.g.: SHA1PRNG
  • io.vertx.ext.auth.prng.seed.interval e.g.: 1000 (every second)
  • io.vertx.ext.auth.prng.seed.bits e.g.: 128

大多数用户不需要配置这些值,除非您注意到应用程序的性能正受到PRNG算法的影响。

共享伪随机数生成器

由于伪随机数生成器对象从资源角度来看上是昂贵的,它们消耗系统熵,这是一种稀缺的资源,明智的做法是在所有的处理程序中共享PRNG。为了做到这一点,并让Vert支持的所有语言都能使用。你应该看一下VertxContextPRNG

This interface relaxes the lifecycle management of PRNG’s for the end user and ensures it can be reused across all your application, for example:

例如,这个接口减轻了终端用户的PRNG的生命周期管理的压力,并确保它可以在所有vertx应用程序中重用

举个例子

String token = VertxContextPRNG.current(vertx).nextString(32);
// Generate a secure random integer
int randomInt = VertxContextPRNG.current(vertx).nextInt();

使用密钥

在处理安全问题时,您将面临加载安全密钥的需要。安全密钥有许多格式和标准,这使得它成为一项相当复杂的任务。为了简化开发人员的工作,这个模块包含了2个抽象:

  1. KeyStoreOptions 抽象JVM密钥存储库公共格式。
  2. PubSecKeyOptions 抽象PEM通用格式。

为了加载本地密钥存储模块,你应当提供一个属性实例,例如:

KeyStoreOptions options = new KeyStoreOptions()
  .setPath("/path/to/keystore/file")
  .setType("pkcs8")
  .setPassword("keystore-password")
  .putPasswordProtection("key-alias", "alias-password");

Type属性非常重要,因为它随所使用的JVM版本而变化。在9之前,默认是iks,它是特定于JVM的,而pkcs12是一个公共标准

非JVM存储密钥可以导入到pkscs12文件,甚至不需要keytool命令,下面是个如何使用OpenSSL例子

openssl pkcs12 -export -in mykeycertificate.pem -out mykeystore.pkcs12 -name myAlias -noiter -nomaciter

上面的命令将现有的pem文件转换为pkcs12密钥存储库,并将给定的密钥放在名称myAlias下。为了使文件与IVM加载器兼容,需要额外的参数-noiter -nomaciter

为了加载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

JWKs are a standard used by OpenID connect and JWT providers. They represent a key as a JSON object. Usually these JSON documents are provided by an identity provider server like Google, Microsoft, etc… but you can also generate your own keys using the online application mkjwk.org%3C/a%3E;. For an offline experience there is also the tool: connect2id.com/products/ni…;.

JWKS 是一个被用于OpenID connect和JWT提供方的标准。他是一种json格式的密钥。

通常这些JSON文档是由身份提供服务器提供的,如谷歌,微软等。但是您也可以使用在线应用程序mkjwk.org%3C/a%3E/于离线体验这个功能则可以使用这个工具

connect2id.com/products/ni…;

链接多个认证提供方

There are cases where it might be interesting to have support for chaining authentication providers, for example look up users on LDAP or properties files. This can be achieved with the ChainAuth.

在某些情况下,您可能对支持链接多个身份验证提供者感兴趣,例如在LDAP或属性文件上查找用户。这可以通过ChainAuth实现。

ChainAuth.any()
  .add(ldapAuthProvider)
  .add(propertiesAuthProvider);

它也可以使用全匹配模式,一个用户必须匹配LDAP和属性文件,例如:

ChainAuth.all()
  .add(ldapAuthProvider)
  .add(propertiesAuthProvider);

翻译:我 审校:你