第23章:GitHub认证¶
在上一章中,你学到了如何用谷歌来认证用户。在本章中,你将看到如何在此基础上,让用户用他们的GitHub账户来登录。
用GitHub设置你的应用程序¶
为了能够在你的应用程序中使用GitHub OAuth,你必须首先在GitHub上注册该应用程序。在浏览器中,转到 https://github.com/settings/developers。点击Register a new application。

Note
你必须有一个GitHub账户才能完成本章。如果你没有,请访问 https://github.com/join 来创建一个。本章还假定你在前一章中把Imperial作为依赖项添加到你的项目中。
在表格中填写一个合适的名字,例如:Vapor TIL。为这个应用程序设置主页URL为http://localhost:8080,并提供一个合理的描述。将授权回调URL设置为http://localhost:8080/oauth/github。一旦用户允许你的应用程序访问他们的数据,GitHub就会重定向到这个URL:

点击Register application。创建应用程序后,该网站将带你回到应用程序的信息页面。该页面提供了客户ID。点击Generate a new client secret以获得客户秘密:

Note
你必须保持这些东西的安全和可靠。你的秘密允许你访问GitHub的API,你不应该分享或检查秘密到源代码控制。你应该像对待密码一样对待它。
与Imperial集成¶
现在你已经在GitHub上注册了你的应用程序,你可以开始整合Imperial。首先,在Xcode中打开Package.swift并替换:
.product(name: "ImperialGoogle", package: "Imperial")
为以下内容:
.product(name: "ImperialGoogle", package: "Imperial"),
.product(name: "ImperialGitHub", package: "Imperial")
这就把Imperial的GitHub库作为一个依赖项添加进去。接下来,打开ImperialController.swift,在import Fluent下面添加以下内:
import ImperialGitHub
这样你的代码就可以看到Imperial的GitHub功能。接下来,在processGoogleLogin(request:token:)下添加以下内容:
func processGitHubLogin(request: Request, token: String)
throws -> EventLoopFuture<ResponseEncodable> {
return request.eventLoop.future(request.redirect(to: "/"))
}
这定义了一个处理GitHub登录的方法,类似于Google登录的初始处理程序。这个处理方法只是将用户重定向到主页。一旦处理完GitHub的重定向,Imperial会使用这个方法作为最后的回调。
接下来,通过在boot(routes:)的底部添加以下内容来设置Imperial路由:
guard let githubCallbackURL =
Environment.get("GITHUB_CALLBACK_URL") else {
fatalError("GitHub callback URL not set")
}
try routes.oAuth(
from: GitHub.self,
authenticate: "login-github",
callback: githubCallbackURL,
completion: processGitHubLogin)
下面是这个的作用:
- 从环境变量中获取回调
URL- 这是你在GitHub注册应用程序时设置的URL。 - 将
Imperial的GitHub OAuth路由器与你的应用程序的路由注册。 - 告诉
Imperial使用GitHub处理程序。 - 将
/login-github请求设置为触发OAuth流程的路由。这是应用程序用来允许用户通过GitHub登录的路由。 - 提供回调
URL给Imperial。 - 将完成处理程序设置为
processGitHubLogin(request:token:)- 你在上面创建的方法。
和以前一样,你需要用环境变量向Imperial提供GitHub给你的客户ID和客户秘密。你还必须提供重定向的URL。在文本编辑器中打开.env,在文件的底部添加以下内容:
GITHUB_CALLBACK_URL=http://localhost:8080/oauth/github
GITHUB_CLIENT_ID=<YOUR_GITHUB_CLIENT_ID>
GITHUB_CLIENT_SECRET=<YOUR_GITHUB_CLIENT_SECRET>
添加之前由GitHub生成的客户端ID和密码。
Note
请确保你仍然为GOOGLE_CALLBACK_URL、GOOGLE_CLIENT_ID和GOOGLE_CLIENT_SECRET设置了环境变量,否则你的应用程序将无法启动。
与网络认证整合¶
和上一章一样,与常规登录的体验相匹配是很重要的。同样,当用户第一次使用GitHub登录时,你会创建一个新用户。你可以用用户的OAuth令牌来使用GitHub的API。
在ImperialController.swift的底部,添加一个新类型来解码来自GitHub的API的数据:
struct GitHubUserInfo: Content {
let name: String
let login: String
}
对GitHub的API的请求会返回许多字段。然而,你只关心登录,也就是用户名,以及名字。
接下来,在GitHubUserInfo下,添加以下内容:
extension GitHub {
// 1
static func getUser(on request: Request)
throws -> EventLoopFuture<GitHubUserInfo> {
// 2
var headers = HTTPHeaders()
try headers.add(
name: .authorization,
value: "token \(request.accessToken())")
headers.add(name: .userAgent, value: "vapor")
// 3
let githubUserAPIURL: URI = "https://api.github.com/user"
// 4
return request
.client
.get(githubUserAPIURL, headers: headers)
.flatMapThrowing { response in
// 5
guard response.status == .ok else {
// 6
if response.status == .unauthorized {
throw Abort.redirect(to: "/login-github")
} else {
throw Abort(.internalServerError)
}
}
// 7
return try response.content
.decode(GitHubUserInfo.self)
}
}
}
下面是这个的作用:
- 为
Imperial的GitHub服务添加一个新方法,从GitHub的API中获取用户的详细信息。 - 通过在授权头中添加
OAuth令牌来设置请求的头信息。注意,GitHub没有使用标准的承载授权头,所以你必须手动定义该头。还要设置user-agent头,因为GitHub的API需要这个。 - 设置请求的
URL--这是GitHub的API,用于获取用户的信息。这使用了Vapor的URI,而Client需要这个。 - 使用
request.client发送一个HTTP请求。get()向提供的URL发送一个HTTP GET请求。解除返回的未来响应。 - 确保响应状态是
200 OK。 - 否则,如果响应是
401 Unauthorized,则返回到登录页面,或者返回一个错误。 - 对
GitHub响应中的数据进行解码并返回结果。
接下来,将processGitHubLogin(request:token:)的主体替换为以下内容:
// 1
return try GitHub
.getUser(on: request)
.flatMap { userInfo in
// 2
return User
.query(on: request.db)
.filter(\.$username == userInfo.login)
.first()
.flatMap { foundUser in
guard let existingUser = foundUser else {
// 3
let user = User(
name: userInfo.name,
username: userInfo.login,
password: UUID().uuidString)
// 4
return user
.save(on: request.db)
.flatMap {
// 5
request.session.authenticate(user)
return generateRedirect(on: request, for: user)
}
}
// 6
request.session.authenticate(existingUser)
return generateRedirect(on: request, for: existingUser)
}
}
以下是新代码的作用:
- 从
GitHub获取用户信息。 - 通过查找用户名的登录属性,查看该用户是否存在于数据库中。
- 如果用户不存在,用
GitHub上的用户信息中的名字和用户名创建一个新的User。将密码设置为UUID,因为你不需要它。 - 保存用户并解开返回的未来。
- 在
Request上调用session.authenticate(_:)以在会话中保存创建的用户,这样网站就允许访问。使用前一章的generateRedirect(on:for:)来重定向到主页。 - 如果用户已经存在,在会话中验证用户并重定向到主页。同样,使用
generateRedirect(on:for:)来创建重定向。
最后要做的是在网站上添加一个按钮,让用户使用新的功能! 打开login.leaf,在</form>下,添加以下内容:
<a href="/login-github">
<img class="mt-3" src="/images/sign-in-with-github.png"
alt="Sign In With GitHub">
</a>
本章的示例项目包含一个新的图片,sign-in-with-github.png,用于显示Sign in with GitHub按钮。这将图像添加为/login-github的链接--提供给Imperial开始登录的路线。构建并运行该应用程序,然后在浏览器中访问http://localhost:8080。点击Create An Acronym,应用程序就会带你到登录页面。你会看到新的Sign in with GitHub按钮在Sign in with Google按钮旁边:

点击新按钮,应用程序会带你到一个GitHub页面,允许TIL应用程序访问你的信息:

点击你在那里看到的Authorize按钮,应用程序会将你重定向到主页。进入All Users屏幕,你会看到你的新用户账户。如果你创建了一个缩写,应用程序也会使用这个新用户。
与iOS整合¶
就像用谷歌登录一样,你也应该提供在iOS上用GitHub登录的功能。在上一章中,你已经完成了大部分的工作 :] 。
在iOSGoogleLogin(_:)下面,创建一个新的路由处理程序,用于在iOS上用GitHub登录:
func iOSGitHubLogin(_ req: Request) -> Response {
// 1
req.session.data["oauth_login"] = "iOS"
// 2
return req.redirect(to: "/login-github")
}
这个新的路由处理程序做两件事:
- 在请求的会话中设置一个标志,将其标记为
iOS登录的尝试。 - 重定向到
/login-github以触发与GitHub的OAuth流程。
在boot(routes:)的底部注册新路由:
routes.get("iOS", "login-github", use: iOSGitHubLogin)
这就把对/iOS/login-github的GET请求路由到iosGitHubLogin(_:)。这就是你在TILApp中需要做的所有事情,以支持iOS与GitHub的登录! 构建并运行该项目,并打开本章的iOS启动项目。
本章的iOS启动项目在GitHub的登录页面上包含一个新的按钮。在LoginTableViewController.swift中有一个相应的方法,在用户点击新按钮时调用。在signInWithGithubButtonTapped(_:)中加入以下内容:
// 1
guard let githubAuthURL =
URL(string: "http://localhost:8080/iOS/login-github")
else {
return
}
// 2
let scheme = "tilapp"
// 3
let session = ASWebAuthenticationSession(
url: githubAuthURL,
callbackURLScheme: scheme) { callbackURL, error in
// 4
guard
error == nil,
let callbackURL = callbackURL
else {
return
}
let queryItems = URLComponents(
string: callbackURL.absoluteString
)?.queryItems
let token = queryItems?.first { $0.name == "token" }?.value
// 5
Auth().token = token
// 6
DispatchQueue.main.async {
let appDelegate =
UIApplication.shared.delegate as? AppDelegate
appDelegate?.window?.rootViewController =
UIStoryboard(
name: "Main",
bundle: Bundle.main).instantiateInitialViewController()
}
}
// 7
session.presentationContextProvider = self
session.start()
以下是新代码的作用:
- 创建一个用于登录
GitHub的URL。这是你之前在TILApp中创建的URL。 - 将方案设置为
tilapp--这是你重定向到的方案。更多信息见第22章,"谷歌认证"。 - 使用该方案和URL创建一个`ASWebAuthenticationSession'的实例。
- 确保有一个回调的
URL并且没有错误。从回调URL中提取令牌。 - 使用
Auth在钥匙串中设置令牌。 - 通过改变根视图控制器到主导航视图控制器来完成用户的登录。
- 将
presentationContextProvider设置为LoginViewController并开始会话。
构建并运行应用程序,如有必要,在Users标签中注销。在登录页面,你会看到新的Sign in with GitHub按钮:

点击新按钮,应用程序会要求你确认你想使用TILApp来登录:

点击Continue。如果你已经在模拟器上登录了GitHub,该应用会直接登录。否则,你会看到GitHub的OAuth界面,允许访问:

登录GitHub,如果你已经批准了TILApp,应用程序就会将你登入。否则,GitHub会要求你确认对TIL应用程序的访问,就像网站一样。一旦确认,应用程序就会登录。
接下来去哪?¶
在本章中,你学到了如何使用Imperial和OAuth将GitHub登录整合到你的网站中。这是对谷歌和第一方登录体验的补充,让你的用户可以选择一系列的认证方式。
在下一章中,你将学习如何实现Apple登录,让你的用户有第三个选择,使用外部认证服务来注册你的应用程序。