快速接入 GitHub、QQ 第三方登录方式

本文提及第三方登录涉及到 OAuth2.0,关于 OAuth2.0 的理论基础参考阮一峰老师的《理解 OAuth 2.0》,其中关于授权码模式就是本篇文章的重点,如想看这篇理论基础自行百度即可。

本文着重于代码,关于理论不再赘述,关于不同公司的三方登录流程,只要遵循 OAuth2.0

一、GitHub 登录 1.1 注册应用

进入 Github 的 Setting 页面,点击 Developer settings,如图所示:

进入后点击 New Oauth App,如图所示:

在其中填写主页 URL 和 回调 URL,回调 URL 尤为重要,如果不太明白可以先和我一致。

点击注册后,上方会生成 Client ID 和 Client Secret,这两个后面要用到。

1.2 HTML 页面

页面十分简单,只有两个跳转链接:


<html lang="en">
<head>
<meta charset="UTF-8">
<title>三方登录title>

head>
<body>
<h1>三方登录Demoh1>
<div>
<a href="/githubLogin">GitHub登录a>
<a href="/qqLogin">QQ登录a>
div>
body>
html>

1.3 Github 登录方法

在这个方法中,我们需要访问 GitHub 的认证服务器,使用 Get 请求,这里使用重定向来实现。

遵循 Oauth 2.0 规范,需要携带以下参数:

这里的 state 参数我要额外说明下,因为该参数会在后面的回调 URL 中被原样携带回来,绝大多数的开发者会忽略该字段,阮一峰老师的文章也没有着重提及这一点。但是忽略该参数是会导致 CSRF攻击的,在回调函数中应当对该字段进行校验!

关于如何校验,我一开始的想法是使用 session 来存储 state 进行校验的,但是我发现使用重定向后 session 不是同一个 session,方案一失败。

然后我想通过 ajax 请求,在页面中使用 window.location.href 方法跳转到认证服务器,使用 session 存储,但是很不幸这样也不是同一个 session,方案二失败。

最后我的解决办法是使用 redis 缓存,使用 set 存储,回调时判断是否存在。当然你也可以用 HashMap 来存储,这也是一个解决办法。

关于 Redis,可以参考:

private static String GITHUB_CLIENT_ID = "0307dc634e4c5523cef2";
private static String GITHUB_CLIENT_SECRET = "707647176eb3bef1d4c2a50fcabf73e0401cc877";
private static String GITHUB_REDIRECT_URL = "http://127.0.0.1:8080/githubCallback";

@RequestMapping("/githubLogin")
public void githubLogin(HttpServletResponse response) throws Exception {
// Github认证服务器地址
String url = "https://github.com/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;

// 1、请求Github认证服务器
response.sendRedirect(url + "?" + param);
}

1.4 Github 回调方法

在上一步中,浏览器会被跳转到 Github 的授权页,当用户登录并点击确认后,GitHub认证服务器会跳转到我们填写的回调URL中,我们在程序中处理回调。

在回调方法中,步骤如下:

1. 首先验证 state 与发送时是否一致,如果不一致,可能遭遇了 CSRF 攻击。

2. 得到 code,向 GitHub 认证服务器申请令牌(token)

这一步使用模拟的 POST 请求,携带参数包括:

3. 得到令牌(access_token)和令牌类型(token_type),向GitHub资源服务器获取资源(以 user_info 为例)

这一步使用模拟的 GET 请求,携带参数包括:

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,如果不一致,可能被CSRF攻击
if(!oauthService.checkState(state)) {
throw new Exception("State验证失败");
}

// 2、向GitHub认证服务器申请令牌
String url = "https://github.com/login/oauth/access_token";
// 传递参数grant_type、code、redirect_uri、client_id
String param = "grant_type=authorization_code&code=" + code + "&redirect_uri=" +
GITHUB_REDIRECT_URL + "&client_id=" + GITHUB_CLIENT_ID + "&client_secret=" + GITHUB_CLIENT_SECRET;

// 申请令牌,注意此处为post请求
String result = HttpClientUtils.sendPostRequest(url, param);

/*
* result示例:
* 失败:error=incorrect_client_credentials&error_description=The+client_id+and%2For+client_secret+passed+are+incorrect.&
* error_uri=https%3A%2F%2Fdeveloper.github.com%2Fapps%2Fmanaging-oauth-apps%2Ftroubleshooting-oauth-app-access-token-request-errors%2F%23incorrect-client-credentials
* 成功:access_token=7c76186067e20d6309654c2bcc1545e41bac9c61&scope=&token_type=bearer
*/

Map<String, String> resultMap = HttpClientUtils.params2Map(result);
// 如果返回的map中包含error,表示失败,错误原因存储在error_description
if(resultMap.containsKey("error")) {
throw new Exception(resultMap.get("error_description"));
}

// 如果返回结果中包含access_token,表示成功
if(!resultMap.containsKey("access_token")) {
throw new Exception("获取token失败");
}

// 得到token和token_type
String accessToken = resultMap.get("access_token");
String tokenType = resultMap.get("token_type");

// 3、向资源服务器请求用户信息,携带access_token和tokenType
String userUrl = "https://api.github.com/user";
String userParam = "access_token=" + accessToken + "&token_type=" + tokenType;

// 申请资源
String userResult = HttpClientUtils.sendGetRequest(userUrl, userParam);

// 4、输出用户信息
response.setContentType("text/html;charset=utf-8");
response.getWriter().write(userResult);
}

二、QQ 登录

2.1 注册应用

进入 QQ 互联管理中心:,创建一个新应用(需要先审核个人身份):

然后注册应用信息,和 GitHub 的步骤大差不差:

注册后,可以看到应用的 APP ID、APP Key,以及你被允许的接口,当然只有一个获取用户信息。

官方开发文档点击这里:

%E5%BC%80%E5%8F%91%E6%94%BB%E7%95%A5_server-side

注意:审核状态为审核中和审核失败也是可以使用的,不用担心(只是无法实际上线而已,作为 Demo 足够了)。

2.2 QQ 登录方法

private static String QQ_APP_ID = "101474821";
private static String QQ_APP_KEY = "00d91cc7f636d71faac8629d559f9fee";
private static String QQ_REDIRECT_URL = "http://127.0.0.1:8080/qqCallback";

@RequestMapping("/qqLogin")
public void qqLogin(HttpServletResponse response) throws Exception {
// QQ认证服务器地址
String url = "https://graph.qq.com/oauth2.0/authorize";
// 生成并保存state,忽略该参数有可能导致CSRF攻击
String state = oauthService.genState();
// 传递参数response_type、client_id、state、redirect_uri
String param = "response_type=code&" + "client_id=" + QQ_APP_ID + "&state=" + state
+ "&redirect_uri=" + QQ_REDIRECT_URL;

// 1、请求QQ认证服务器
response.sendRedirect(url + "?" + param);
}

2.3 QQ 回调方法

/**
* QQ回调方法
* @param code 授权码
* @param state 应与发送时一致
* @author jitwxs
* @since 2018/5/21 15:24
*/

@RequestMapping("/qqCallback")
public void qqCallback(String code, String state, HttpServletResponse response) throws Exception {
// 验证state,如果不一致,可能被CSRF攻击
if(!oauthService.checkState(state)) {
throw new Exception("State验证失败");
}

// 2、向QQ认证服务器申请令牌
String url = "https://graph.qq.com/oauth2.0/token";
// 传递参数grant_type、code、redirect_uri、client_id
String param = "grant_type=authorization_code&code=" + code + "&redirect_uri=" +
QQ_REDIRECT_URL + "&client_id=" + QQ_APP_ID + "&client_secret=" + QQ_APP_KEY;

// 申请令牌,注意此处为post请求
// QQ获取到的access token具有3个月有效期,用户再次登录时自动刷新。
String result = HttpClientUtils.sendPostRequest(url, param);

/*
* result示例:
* 成功:access_token=A24B37194E89A0DDF8DDFA7EF8D3E4F8&expires_in=7776000&refresh_token=BD36DADB0FE7B910B4C8BBE1A41F6783
*/

Map<String, String> resultMap = HttpClientUtils.params2Map(result);
// 如果返回结果中包含access_token,表示成功
if(!resultMap.containsKey("access_token")) {
throw new Exception("获取token失败");
}
// 得到token
String accessToken = resultMap.get("access_token");

// 3、使用Access Token来获取用户的OpenID
String meUrl = "https://graph.qq.com/oauth2.0/me";
String meParams = "access_token=" + accessToken;
String meResult = HttpClientUtils.sendGetRequest(meUrl, meParams);
// 成功返回如下:callback( {"client_id":"YOUR_APPID","openid":"YOUR_OPENID"} );
// 取出openid
String openid = getQQOpenid(meResult);

// 4、使用Access Token以及OpenID来访问和修改用户数据
String userInfoUrl = "https://graph.qq.com/user/get_user_info";
String userInfoParam = "access_token=" + accessToken + "&oauth_consumer_key=" + QQ_APP_ID + "&openid=" + openid;
String userInfo = HttpClientUtils.sendGetRequest(userInfoUrl, userInfoParam);

// 5、输出用户信息
response.setContentType("text/html;charset=utf-8");
response.getWriter().write(userInfo);
}

/**
* 提取Openid
* @param str 形如:callback( {"client_id":"YOUR_APPID","openid":"YOUR_OPENID"} );
* @author jitwxs
* @since 2018/5/22 21:37
*/

private String getQQOpenid(String str) {
// 获取花括号内串
String json = str.substring(str.indexOf("{"), str.indexOf("}") + 1);
// 转为Map
Map<String, String> map = JsonUtils.jsonToPojo(json, Map.class);
return map.get("openid");
}

三、项目源码

QQ 登录的具体流程我就不啰嗦了,都差不多。代码只列出了关键方法,具体程序还包含工具类和 redis 的配置。具体请参考文章开头源码,该项目采用 SpringBoot 搭建,需要 Redis 支持。

前端如何控制表格布局

前端控制表格布局的方法有:使用CSS样式、利用Flexbox布局、使用Grid布局、结合JavaScript进行动态操作。 其中,使用CSS样式是最基础也是最广泛应用的方法,能够快速实现对表格布局的基本控制。下面将详细介绍如何通过这些方法来控制表格布局,并提供一些实际的代码示例和应用场景。

一、使用CSS样式

使用CSS样式控制表格布局是最基础也是最常见的方法。通过CSS,你可以设置表格的宽度、高度、边框、间距等属性,从而实现对表格外观和布局的精细控制。

1. 基本样式设置

CSS可以设置表格的边框、间距、宽度和高度等基本样式。例如:

Header 1 Header 2 Header 3
Data 1 Data 2 Data 3

2. 控制列宽

通过CSS设置列宽可以确保表格中的内容按预期显示。例如:

.styled-table th:nth-child(1),

.styled-table td:nth-child(1) {

width: 50%;

}

.styled-table th:nth-child(2),

.styled-table td:nth-child(2) {

width: 25%;

}

.styled-table th:nth-child(3),

.styled-table td:nth-child(3) {

width: 25%;

}

二、利用Flexbox布局

Flexbox是一种非常强大的CSS布局工具,可以用来控制表格布局,特别是对于需要响应式设计的表格非常有用。

1. Flexbox基本设置

通过将表格的容器设为Flex容器,可以实现灵活的布局控制:

Header 1

Header 2

Header 3

Data 1

Data 2

Data 3

2. 控制Flex属性

通过调整flex属性,可以控制表格单元格的大小和排列方式:

.flex-cell {

flex: 1 1 auto;

}

.flex-cell:nth-child(1) {

flex: 2 1 auto; /* 使第一列宽度是其他列的两倍 */

}

三、使用Grid布局

CSS Grid布局是一种更强大的布局工具,特别适用于复杂的表格布局。

1. Grid布局基本设置

通过将表格设为Grid容器,可以实现精细的布局控制:

Header 1

Header 2

Header 3

Data 1

Data 2

Data 3

2. 控制Grid属性

通过调整grid-template-columns和grid-template-rows属性,可以实现对表格布局的精细控制:

.grid-table {

display: grid;

grid-template-columns: 2fr 1fr 1fr; /* 定义列宽 */

grid-template-rows: auto; /* 自动调整行高 */

}

四、结合JavaScript进行动态操作

在实际开发中,往往需要根据用户交互或数据变化动态调整表格布局,这时可以结合JavaScript进行操作。

1. 动态调整列宽

通过JavaScript,可以动态调整表格列宽。例如:

Header 1 Header 2 Header 3
Data 1 Data 2 Data 3

2. 动态添加/移除行列

通过JavaScript,可以动态添加或移除表格中的行列。例如:

五、推荐使用项目管理系统

在实际开发中,管理和协作是非常重要的。特别是对于涉及多个开发人员的项目,使用项目管理系统可以大大提高效率和质量。这里推荐两个系统:

1. 研发项目管理系统PingCode

PingCode是一款专为研发团队设计的项目管理系统,支持需求、任务、缺陷、版本等全生命周期管理,帮助团队高效协作。

2. 通用项目协作软件Worktile

Worktile是一款通用的项目协作软件,支持任务管理、文档协作、日程安排等功能,适用于各种类型的团队和项目。

结论

通过本文的介绍,我们详细探讨了前端如何控制表格布局的多种方法,包括使用CSS样式、利用Flexbox布局、使用Grid布局以及结合JavaScript进行动态操作。每种方法都有其独特的优势和适用场景,可以根据具体需求选择最合适的方法。同时,推荐使用项目管理系统PingCode和Worktile来提升项目管理和团队协作效率。希望本文能为你在前端开发中提供有价值的参考和帮助。

相关问答FAQs:

1. 如何在前端控制表格的列宽?

在前端中,可以使用CSS的属性width来控制表格中列的宽度。通过为表格的或元素添加样式,可以指定每个列的宽度,例如:

td {
  width: 100px; /* 设置每个列的宽度为100像素 */
}

这样,所有的列都会有相同的宽度。如果要为不同的列设置不同的宽度,可以通过为每个列的或元素添加不同的类名或选择器,并在CSS中分别设置宽度。

2. 如何使表格自动适应页面宽度?

要使表格自动适应页面宽度,可以使用CSS的属性table-layout来控制表格的布局方式。将table-layout属性设置为auto,表格将根据内容自动调整列宽度以适应页面宽度,例如:

table {
  table-layout: auto; /* 表格自动适应页面宽度 */
}

这样,当表格的内容超出页面宽度时,表格会自动调整列宽度,确保内容不会被截断。

3. 如何使表格具有固定布局?

如果希望表格具有固定的布局,可以使用CSS的属性table-layout来控制表格的布局方式。将table-layout属性设置为fixed,表格将根据指定的宽度来分配列宽度,例如:

table {
  table-layout: fixed; /* 表格具有固定布局 */
}

然后,通过为每个列的或元素添加相应的宽度样式,来指定每个列的宽度,例如:

td {
  width: 100px; /* 设置每个列的宽度为100像素 */
}

这样,表格的列宽度将根据指定的宽度进行分配,无论内容是否超出列宽度。

本站内容来自用户投稿,如果侵犯了您的权利,请与我们联系删除。联系邮箱:835971066@qq.com

本文链接:http://news.xiuzhanwang.com/post/1600.html

发表评论

评论列表

还没有评论,快来说点什么吧~

友情链接: