跳转至

使用授权器确保路由安全

Authorizer的实例被添加到应用程序通道中,以验证HTTP请求的授权信息,然后再将请求传递下去。它们保护通道访问,通常是在route之后。下面是一个例子:

@override
Controller get entryPoint {
  final router = Router();

  router
    .route("/protected")
    .link(() => Authorizer.bearer(authServer))
    .link(() => ProtectedController());

  router
    .route("/other")
    .link(() => Authorizer.basic(authServer))
    .link(() => OtherProtectedController());

  return router;
}

Authorizer解析HTTP请求的授权头。Authorizer的命名构造函数指示了授权头所需的格式。Authorization.bearinger()构造函数期望在头中包含一个OAuth 2.0 bearer token,它的格式如下:

Authorization: Bearer 768iuzjkx82jkasjkd9z9

Authorizer.basic期望HTTP基本认证,其中用户名和密码用冒号字符(:)和Base 64编码。

// 'dXNlcjpwYXNzd29yZA==' is 'user:password'
Authorization: Basic dXNlcjpwYXNzd29yZA==

如果头不能被解析,不存在或格式错误,Authorizer将以401状态码响应请求,并阻止下一个控制器接收请求。

一旦被解析后,Authorizer会将信息(无论是不记名令牌,还是用户名和密码)发送至其 AuthServer进行验证。如果 AuthServer拒绝了授权信息,Authorizer将以401状态码响应请求,并阻止下一个控制器接收请求。否则,请求将继续到下一个控制器。

对于Authorizer.bearer,请求头中的值必须是有效的、未过期的访问令牌。当一个端点需要登录用户时,这些类型的授权器会被使用。

对于 Authorizer.basic授权器,通过找到一个OAuth 2.0客户端标识符并确保其客户端密钥匹配来验证凭证。使用这种类型的授权器的路由被称为客户端认证的路由。当一个端点需要一个有效的客户端应用程序,但不需要登录的用户时,这些类型的授权器会被使用。

授权器和OAuth 2.0范围

Authorizer可以根据请求的承载令牌的范围限制对控制器的访问。默认情况下,Authorizer.bearer允许任何有效的承载令牌通过它。如果需要的话,Authorizer会被初始化为需要的范围列表。只有当一个请求能够访问Authorizer中列出的所有范围时,它才能通过 Authorizer。例如,下面这个请求至少需要user:postlocation范围。

router
  .route("/checkin")
  .link(() => Authorizer.bearer(authServer, scopes: ["user:posts", "location"]))
  .link(() => CheckInController());

注意,你不必使用 Authorizer来限制基于范围的访问。在请求通过Authorizer后,控制器可以访问作用域信息,因此它可以使用范围做出更细化的授权决定。

授权对象

不记名令牌代表了一个被授予的授权,在过去的某个时刻,用户提供了他们的凭证,而令牌就是证明。当一个不记名令牌在HTTP请求的授权头中发送时,应用程序可以查询该令牌是为哪个用户和为哪个客户端应用程序发出的。在令牌被验证后,这些信息被存储在 Authorization实例中,并被分配到Request.authorization

Authorizer保护的控制器可以访问这些信息来进一步确定他们的行为。例如,一个社交网络应用可能有一个受Authorizer保护的/news_feed端点。当一个经过认证的用户对/news_feed提出请求时,控制器将返回该用户的“news feed”。它可以通过使用 Authorization来确定:

class NewsFeedController extends ResourceController {
  NewsFeedController(this.context);

  ManagedContext context;

  @Operation.get()
  Future<Response> getNewsFeed() async {
    var forUserID = request.authorization.ownerID;

    var query = Query<Post>(context)
      ..where((p) => p.author).identifiedBy(forUserID);

    return Response.ok(await query.fetch());
  }
}

在上述控制器中,一个用户不可能访问另一个用户的帖子。

Authorization 对象还保留了访问令牌的范围,这样控制器就可以对端点中的信息/操作做出更细化的决定。检查 Authorization对象是否有访问特定范围的权限,可以通过查看其scopes列表或使用 authorizedForScope来完成:

class NewsFeedController extends ResourceController {
  NewsFeedController(this.context);

  ManagedContext context;

  @Operation.get()
  Future<Response> getNewsFeed() async {
    if (!request.authorization.authorizedForScope("user:feed")) {
      return Response.unauthorized();
    }

    var forUserID = request.authorization.ownerID;

    var query = Query<Post>(context)
      ..where((p) => p.author).identifiedBy(forUserID);

    return Response.ok(await query.fetch());
  }
}

在没有AuthServer的情况下使用授权器

在本指南中,Authorizer实例的参数被称为AuthServer。 的确如此,但这只是因为AuthServer实现了AuthValidatorAuthValidator是一个用于验证承载令牌和用户名/密码凭证的接口。

你可以在不使用AuthServer的情况下使用Authorizer。例如,一个不使用OAuth 2.0的应用程序可以提供自己的AuthValidator接口来验证每个请求的用户名和密码:

class BasicValidator implements AuthValidator {
  @override
  FutureOr<Authorization> validate<T>(AuthorizationParser<T> parser, T authorizationData, {List<AuthScope> requiredScope}) {}
    var user = await userForName(usernameAndPassword.username);
    if (user.password == hash(usernameAndPassword.password, user.salt)) {
      return Authorization(...);
    }

    // Will end up creating a 401 Not Authorized Response
    return null;
  }
}

如果凭据有效,则validate方法必须返回Authorization;否则,则返回null。。parser让验证器知道授权头的格式(例如,BasicBearer),而authorizationData是该头中的有意义的信息。AuthorizationParser<T>有两种具体类型:AuthorizationBasicParserAuthorizationBearerParser。基本解析器的授权数据是包含用户名和密码的 AuthBasicCredentials的实例,而不记名解析器的授权数据是不记名令牌字符串。