跳转至

使用AuthController发行访问令牌

使用Aqueduct的Auth框架的应用程序必须具有端点才能交换访问令牌的凭据。 尽管开发人员可以自己实现这些端点并直接与AuthServer对话,但OAuth 2.0规范才是幸福的终点。 因此,Aqueduct中有两个用于处理授予和刷新授权令牌的Controller,即AuthControllerAuthCodeController

使用AuthController发行,刷新和交换令牌

AuthController授予访问令牌并刷新它们。它还可以将从AuthCodeController中获得的授权码交换为访问令牌。

在应用程序中使用AuthController很简单,将其连接到一个 Router并传递给它一个AuthServer

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

  router
    .route("/auth/token")
    .link(() => AuthController(authServer));

  return router;
}

要授予一个访问令牌,客户端应用程序会向控制器发送一个HTTP POST。该请求必须有:

  • 一个带有客户端ID和客户端Secret (如果有的话)的授权头,并且:
  • 一个带有身份验证用户名和密码的x-www-form-urlencoded主体。

该正文还必须包含key-value对grant_type=password。例如,下面的Dart代码将启动成功认证:

var clientID = "com.app.demo";
var clientSecret = "mySecret";
var body = "username=bob@stablekernel.com&password=foobar&grant_type=password";
var clientCredentials = Base64Encoder().convert("$clientID:$clientSecret".codeUnits);

var response = await http.post(
  "https://stablekernel.com/auth/token",
  headers: {
    "Content-Type": "application/x-www-form-urlencoded",
    "Authorization": "Basic $clientCredentials"
  },
  body: body);

如果OAuth 2.0客户端ID是公开的,也就是说,它没有客户端的Secret,这个秘密被省略在授权头中:

// Notice that the separating colon (:) is still present.
var clientCredentials = Base64Encoder().convert("$clientID:".codeUnits);

对密码令牌请求的响应是一个遵循OAuth 2.0规范的JSON体:

{
  "access_token": "..."
  "refresh_token": "...",
  "expires_in": 3600,
  "token_type": "bearer"
}

expires_in字段是一个根据发行日期和到期日的增量的计算的属性。单位是秒。你应该避免手动编辑issuedateexpirationdate列的值。

令牌通过同一端点刷新,但其有效载荷只包含刷新令牌和grant_type=refresh_token

grant_type=refresh_token&refresh_token=kjasdiuz9u3namnsd

有关创建OAuth 2.0客户端标识符和机密的更多详细信息,请参见Aqueduct Auth CLI

如果Aqueduct应用程序正在使用范围,则附加的scope参数可以包含以空格分隔的请求授权范围的列表。 仅返回和授予允许的范围,如果不允许范围,则请求失败。 如果提供了范围,则授予的范围将在响应正文中可用。

重要的是, Authorizer不得保护AuthController的实例。 Authorization标头是由AuthController解析和验证的。

授予访问令牌后,即可使用该访问令牌在应用程序通道中传递Authorizer.bearer()

使用AuthCodeController发行授权代码

AuthCodeController管理OAuth 2.0授权代码流。当Aqueduct应用程序允许第三方应用程序访问授权资源时,就会使用授权代码流。

假设你建立了一个Aqueduct应用程序,允许人们为自己存储笔记。现在,一个朋友向你提出了他们的应用是一个待办事项列表。你的朋友没有建立自己的笔记功能,而是希望他们的应用的用户能够访问用户在你的应用中存储的笔记。虽然值得信赖,但你不希望你的朋友能够访问你的用户的用户名和密码。

你的朋友会在他们的应用程序中添加一个链接,将用户带到一个由你的服务器托管的HTML页面。用户在这个页面中输入他们的凭证,然后向您的服务器发送一个 POST请求。您的服务器会通过将用户的浏览器重定向到您朋友的应用程序来响应。在重定向URL的查询字符串中包含了一个授权代码

你朋友的应用程序从URL中解析代码,并将其发送至他们的服务器。在幕后,他们的服务器与你的服务器交换这个代码以获得访问令牌。

AuthCodeController响应GETPOST请求。当发出 GET请求时,它将提供一个带有登录表单的HTML页面。这个登录表单的提交操作会发送一个 POST到相同的端点,其中包含用户的用户名和密码。成功后,来自 POST的响应是一个带有授权码的302重定向。

设置AuthCodeController和设置 AuthController几乎一样简单,但需要一个函数来渲染HTML登录表单。下面是一个例子:

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

  router
    .route("/auth/code")
    .link(() => AuthCodeController(
      authServer, renderAuthorizationPageHTML: renderLogin));

  return router;
}

Future<String> renderLogin(
    AuthCodeController requestingController,
    URI requestURI,
    Map<String, String> queryParameters) {
  var html = HTMLRenderer.templateWithSubstitutions(
    "web/login.html", requestURI, queryParameters);

  return html;
}

重要的是,所有传递给HTML渲染函数的值都要在表单的查询参数中发送,它们包含必要的安全组件和范围信息。

当你朋友的应用程序链接到你的登录页面时GET /auth/code他们必须包含三个查询参数。stateclient_idresponse_type。它们可以选择包含scope

https://stablekernel.com/auth/code?client_id=friend.app&response_type=code&state=87uijn3rkja

client_id的值必须专门为你的朋友的应用程序创建,并存储在你的数据库中。(请参阅Aqueduct Auth CLI中关于使用aqueduct auth生成客户端标识符的更多信息。) response_type必须始终是codestate必须是你朋友的应用程序创建的值,通常是一些随机值,比如会话cookie。

当你朋友的应用程序的用户经过这个过程时,他们会被重定向到你朋友的应用程序中。生成的授权代码和state的值都将成为URL中的查询参数。这个重定向的URL会是这样的:

https://friends.app/code_callback?code=abcd672kk&state=87uijn3rkja

重定向URL是在用aqueduct auth生成客户端标识符时预先确定的。

你朋友的应用程序会验证state与他们在GET /auth/code中发送的state匹配。然后他们将code发送到他们的服务器上。然后服务器通过向一个 AuthController(而不是 AuthCodeController)发出一个 POST来和你的服务器交换这个代码,并带有以下的 application/x-www-form-urlencoded体:

grant_type=authorization_code&code=abcd672kk

一个访问令牌将被传回服务器,然后你的朋友会把这个令牌存储在他们的数据库中。每当他们的用户中的一个用户提出需要访问您的应用程序数据的请求时,他们就会用该访问令牌执行请求。