要配置 GitHub 的 OAuth 应用,首先请访问 GitHub 的设置页面,并点击 Developer settings,如下图所示:
接着,在页面中选择 New OAuth App,操作如图所示:
在弹出的页面中,你需要填写主页 URL 和回调 URL。尤其要注意回调 URL 的设置,如果不清楚可以参考我的说明。
注册成功后,你会看到生成了 Client ID 和 Client Secret,这两个信息在后续操作中非常重要。
1.2 HTML 页面
该 HTML 页面结构简单,仅包含两个链接:
<!DOCTYPE html>
<html lang
"en"
>
<head>
<meta charset
"UTF-8"
>
<title>
三方登录
</title></head><body>
<h1>
三方登录Demo
</h1>
<div>
<a href
"/githubLogin"
>GitHub登录
</a>
<a href
"/qqLogin"
>QQ登录
</a>
</div></body></html>
在实现中,我们需要向 GitHub 的认证服务器发送请求,使用 GET 方法,通常是通过重定向来完成这一过程。
按照 OAuth 2.0 的规范,需要传递以下参数:
response_type:对于授权码模式,该值固定为 code
client_id:在注册应用时获得的 Client ID
state:回调时会原样返回
redirect_uri:注册应用时填写的回调 URL
需要特别注意的是 state 参数,它会在回调时被原样传回。如果忽视这一参数,可能会导致 CSRF 攻击。在回调处理过程中必须对该参数进行验证。
最初,我打算通过 session 存储 state 来进行校验,但由于重定向后 session 不一致,这个方法失败了。
之后,我尝试用 ajax 请求和 window.location.href 方法来处理重定向,结果也因为 session 不一致而失败。
最终,我采用了 Redis 缓存来存储 state,回调时检查其存在性。另一种可行的方法是使用 HashMap 来进行存储。
有关 Redis 的更多信息,可以参考这里:/e331e26a.html
private
static
String
GITHUB_CLIENT_ID =
"0307dc634e4c5523cef2"
private
static
String
GITHUB_CLIENT_SECRET =
"707647176eb3bef1d4c2a50fcabf73e0401cc877"
private
static
String
GITHUB_REDIRECT_URL =
"127.0.0.1:8080/githubCallback"
@RequestMapping
"/githubLogin"
public
void
githubLogin(HttpServletResponse response) throws Exception {
// 认证服务器的地址
String
url =
"/login/oauth/authorize"
;
// 生成并保存state,忽略该参数可能会导致CSRF攻击
String
state = oauthService.genState();
// 构建请求参数 response_type、client_id、state、redirect_uri
String
param =
"response_type=code&"
+
"client_id="
+ GITHUB_CLIENT_ID +
"&state="
+ state +
"&redirect_uri="
+ GITHUB_REDIRECT_URL;
// 发送请求到GitHub认证服务器
response.sendRedirect(url +
"?"
+ param);}
在用户被重定向到 GitHub 的授权页面后,登录并确认授权后,GitHub 认证服务器会跳转到你设置的回调 URL,我们在程序中处理这个回调。
回调处理的步骤如下:
1. 首先验证 state 是否与最初发送的一致。如果不一致,可能发生了 CSRF 攻击。
2. 获取到 code 后,向 GitHub 认证服务器请求令牌 (token)。
这一步使用模拟的 POST 请求,携带的参数包括:
grant_type:对于授权码模式,值固定为 authorization_code
code:在上一步中获得的 code
redirect_uri:回调 URL
client_id:注册应用时的 Client ID
client_secret:注册应用时的 Client Secret
3. 获取令牌(access_token)及令牌类型(token_type),然后使用这些信息向 GitHub 资源服务器请求资源(例如用户信息)。
这一步使用模拟的 GET 请求,携带的参数包括:
access_token:令牌
token_type:令牌类型
4. 最后输出结果。
/ * GitHub 回调方法 * @param code 授权码 * @param state 应与发送时一致 * @author jitwxs * @since 2018/5/21 15:24 */
@RequestMapping
"/githubCallback"
public
void
githubCallback(
String
code,
String
state, HttpServletResponse response) throws Exception {
// 验证 state 的一致性
if
state.equals(oauthService.getState()))
) {
// 如果不一致,可能遭遇了 CSRF 攻击
response.sendError(HttpServletResponse.SC_FORBIDDEN, "Invalid state");