第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
登录,让你的用户有第三个选择,使用外部认证服务来注册你的应用程序。