Java 实现 QQ 登陆

2023-11-02

点击上方蓝色字体,选择“标星公众号”
优质文章,第一时间送达
1. 前言
个人网站最近增加了评论功能,为了方便用户不用注册就可以评论,对接了 QQ 和微博这 2 大常用软件的一键登录,总的来说其实都挺简单的,可能会有一点小坑,但不算多,完整记录下来方便后来人快速对接。
2. 后台设计
在真正开始对接之前,我们先来聊一聊后台的方案设计。既然是对接第三方登录,那就免不了如何将用户信息保存。首先需要明确一点的是,用户在第三方登录成功之后,我们能拿到的仅仅是一个代表用户唯一身份的ID(微博是真实 uid ,QQ是加密的 openId )以及用来识别身份的 accessToken ,当然还有昵称、头像、性别等有限资料,对接第三方登录的关键就是如何确定用户是合法登录,如果确定这次登录的和上次登录的是同一个人并且不是假冒的。
其实这个并不用我们特别操心,就以微博登录为例,用户登录成功之后会回调一个code 给我们,然后我们再拿code去微博那换取 accessToken ,如果这个code是用户乱填的,那这一关肯定过不了,所以,前面的担心有点多余。
另外一个问题就是如何和现有用户系统打通,有的网站在用户已经登录成功之后还要用户输入手机号和验证码,或者要用户重新注册账号和密码来绑定第三方账户,感觉这种实现用户体验非常差,碰到这种网站我一般都是直接关掉,都已经登录了还让用户注册,什么鬼!由于我做的是评论功能,我并不希望评论用户和现有用户表打通,所以就不存在这件事了,如果想打通的话,我觉得无非就是登录成功之后默认往老用户表插入一条数据,然后和 OpenUser 表关联起来,判断用户是否登录时把 OpenUser 的鉴权也加进去就OK了。
本文的后台以Java为例。

2.1. 数据库设计

再来说说 OpenUser 表用来存放第三方登录用户,主要字段如下:
这样设计理论上就可以无限扩展了。

2.2. 鉴权流程

这里我只是说说我的方案,把 accessToken 写入cookie肯定是不安全的,因为 accessToken 相当于是第三方网站的临时密码,被别人窃取了就可以随意拿来干坏事了。可以在用户登录成功之后我们自己生成一个token,这样的token即使泄露了顶多就是被人拿来随意评论,损失不大,但是如果accessToken被泄露了,以微博为例,人家可以利用这个 accessToken 随意发微博、删微博、加关注等等,很危险。当然,如果不想token泄露的话也可以通过绑定IP等方式来限制。
鉴权的话就是首先判断cookie中是否有我们自己的token,然后判断是否合法,合法再判断第三方授权是否已过期等等。
QQ登陆

3.1. 实名认证

QQ登录我们对接的是QQ互联,地址:https://connect.qq.com ,首先需要注册成为开发者并实名认证,需要手持身份证照片,具体就不讲了。

3.2. 创建应用

进入应用管理页面(https://connect.qq.com/manage.html#/)创建应用,根据实际需要是创建网站应用还是移动应用,我这里是网站应用:

640

第一步:

640

第二步:

640

提交完之后会自动提交审核,基本上就是审核你的资料和备案的资料是否一致,所有资料必须和备案资料一模一样,否则审核不会通过:

640

当然,这些资料后面还是可以修改的。申请成功之后你会得到 appIdappKey

3.3. 引导用户登录

这里可以下载一些视觉素材,在页面合适位置放一个QQ登录按钮,点击时引导用户进入授权页面:

640

代码:
function openWindow(url, width, height)	
{	
    width = width || 600;	
    height = height || 400;	
    var left = (window.screen.width - width) / 2;	
    var top = (window.screen.height - height) / 2;	
    window.open(url, "_blank", "toolbar=yes, location=yes, directories=no, status=no, menubar=yes, scrollbars=yes, resizable=no, copyhistory=yes, left="+left+", top="+top+", width="+width+", height="+height);	
}	

	
function qqLogin()	
{	
    var qqAppId = '424323422'; // 上面申请得到的appid	
    var qqAuthPath = 'http://www.test.com/auth'; // 前面设置的回调地址	
    var state = 'fjdslfjsdlkfd'; // 防止CSRF攻击的随机参数,必传,登录成功之后会回传,最好后台自己生成然后校验合法性	
    openWindow(`https://graph.qq.com/oauth2.0/authorize?response_type=token&client_id=${qqAppId}&redirect_uri=${encodeURIComponent(qqAuthPath)}&state=${state}`);	
}
然后会打开一个授权页面,这个页面大家应该都熟悉:

640

然后到了这里我就碰到一个问题了,官方文档(https://wiki.connect.qq.com)写的是登录成功之后首先会回传一个code,然后再拿code调接口换取accessToken,然后我试了很多次也换过2个账号发现每次都是直接返回了accessToken,帮我省了一步了,不知道是什么情况,郁闷。微信搜索 Web项目聚集地 获取更多实战教程。

3.4. 拿到accessToken

现在假设我们都是直接拿到accessToken(因为我暂时还没搞明白QQ为啥会直接返回,跟文档说的不一样),但是授权回调时accessToken会被放在  后面,URL地址中的hash值好像不会被传到后台(貌似是这样,如有不正确欢迎评论指正),所以只能写一个下面这样的临时页面:
@RequestMapping("/authqq")	
public void authQQ(HttpServletRequest request, HttpServletResponse response) throws Exception	
{	
    // QQ登录有点特殊,参数放在#后面,后台无法获取#后面的参数,只能用JS做中间转换	
    String html =   "<!DOCTYPE html>" +	
                    "<html lang=\"zh-cn\">" +	
                    "<head>" +	
                    "   <title>QQ登录重定向页</title>" +	
                    "   <meta charset=\"utf-8\"/>" +	
                    "</head>" +	
                    "<body>" +	
                    "   <script type=\"text/javascript\">" +	
                    "   location.href = location.href.replace('#', '&').replace('auth_qq', 'auth_qq_redirect');" +	
                    "   	
</script>	
" +	
                    "</body>" +	
                    "</html>";	
    response.getWriter().print(html);	
}
3.5. 获取openId
根据accessToken调接口获取用户的openId,特别注意这个openId是相对于 QQ号+appId 唯一的,换句话说同一个QQ号登录2个不同appId时获取到的openId是不同的。顺便说一句,QQ登录的相关接口做的还真够“随便”的,全部都是最简单的get请求,所以对接起来非常顺利。 微信搜索 Web项目聚集地 获取更多实战教程。
直接看代码:
// 根据accessToken换取openId	
// 错误示例:callback( {"error":100016,"error_description":"access token check failed"} );	
// 正确示例:callback( {"client_id":"10XXXXX49","openid":"CF2XXXXXXXX9F4C"} );	
String result = HttpsUtil.get("https://graph.qq.com/oauth2.0/me?access_token=" + accessToken);	
Map<String, Object> resp = parseQQAuthResponse(result); // 这个方法就是把结果转Map	
// 欢迎关注 Web项目聚集地 获取更多实战教程	
Integer errorCode = (Integer)resp.get("error");	
String errorMsg = (String)resp.get("error_description");	
String openId = (String)resp.get("openid");	
if(errorCode != null) return new ErrorResult(errorCode, "获取QQ用户openId失败:"+errorMsg);
3.6. 获取用户头像昵称等信息
// 获取用户昵称、头像等信息,{ret: 0, msg: '', nickname: '', ...} ret不为0表示失败	
result = HttpsUtil.get("https://graph.qq.com/user/get_user_info?access_token="+accessToken+"&oauth_consumer_key="+appId+"&openid="+openId);	
resp = JsonUtil.parseJsonToMap(result);	
// 欢迎关注 Web项目聚集地 获取更多实战教程	
Integer ret = (Integer)resp.get("ret");	
String msg = (String)resp.get("msg");	
if(ret != 0) return new ErrorResult("获取用户QQ信息失败:"+msg);	

	
// 用户昵称可能存在4个字节的utf-8字符,MySQL默认不支持,直接插入会报错,所以过滤掉	
String nickname = StringUtil.filterUtf8Mb4((String)resp.get("nickname")).trim(); // 这个方法可以自行百度	
// figureurl_qq_2=QQ的100*100头像,figureurl_2=QQ 100&100空间头像,QQ头像不一定有,空间头像一定有	
String avatar = (String)resp.get("figureurl_qq_2");	
if(StringUtil.isBlank(avatar)) avatar = (String)resp.get("figureurl_2");	
String gender = (String)resp.get("gender");
3.7. 注意事项
到了这一步基本上涉及第三方的就结束了,是不是很简单?后面无非就是如何插入数据库、如何保存token、写入session等。
有几点注意事项:
  • 需要注意数据库中是否已经有改用户,没有的添加,有的修改,不要重复添加了;
  • QQ昵称昵称有各种奇奇怪怪的字符,包括emoji,MySQL默认没有开启 utf8mb4 ,直接插入会报错,所以需要过滤掉;
  • 需要做好对各种错误的兼容;
  • 接口会同时返回QQ头像和空间头像,QQ头像不一定有,空间头像一定有;
  • 回调地址必须和申请的域名一致,否则会报错。
  • QQ互联有个特大的bug,有时候显示已登录但是点击授权管理一直报错,此时只需要退出重新登录即可;
  • 授权之后用户可能会在过期之前提前取消授权;
  • 微信搜索 Web项目聚集地 获取更多实战教程。
相关文档官网已经写得比较细了,但是比较乱:http://wiki.connect.qq.com/

对接微博登陆

4.1. 实名认证

这个我就不具体讲了,登录 http://open.weibo.com/ 很容易找到相关入口,注册成为开发者,实名认证,一模一样的。

4.2. 创建应用

点击链接 http://open.weibo.com/apps/new?sort=web 创建web应用:

640

创建成果后完善相关信息,主要是下面这些:

640

我就不一一介绍了,都看得懂。
微博登录不需要网站一定要备案,但对网站本身有一定要求,不能弄一个空壳网站让人家去审核,肯定审核不通过的。

有关微博的对接可以参考我好几年前写的一篇文章:

http://www.cnblogs.com/liuxianan/archive/2012/11/11/2765123.html

4.3. 引导用户登录

微博视觉素材(https://open.weibo.com/wiki/微博标识下载)下载在这里,页面合适位置放一个登录按钮:
function weiboLogin()	
{	
    let weiboAppId = '432432';	
    let weiboAuthPath = 'http://www.test.com/authweibo';	
    openWindow(`https://api.weibo.com/oauth2/authorize?client_id=${weiboAppId}&response_type=code&redirect_uri=${encodeURIComponent(weiboAuthPath)}`);	
}
微博登录有一个好处,第一次登录需要授权,后面第二次登录时只会一闪而过自动就登录成功了,都不需要点一下,用户体验非常好,看下图:

640

4.4. 获取accessToken

登录成功会返回一个code,根据code换取accessToken:
String params = "client_id=" + appId	
        + "&client_secret=" + appSecret	
        + "&grant_type=authorization_code"	
        + "&redirect_uri=" + URLUtil.encode(authPath)	
        + "&code=" + code;	
// 用code换取accessToken	
String result = HttpsUtil.post("https://api.weibo.com/oauth2/access_token", params);	
Map<String, Object> resp = JsonUtil.toObject(result, new TypeReference<Map<String, Object>>(){});	

	
Integer errorCode = (Integer)resp.get("error_code");	
String error = (String)resp.get("error");	
String errorMsg = (String)resp.get("error_description");	
if(errorCode != null && errorCode != 0) return new ErrorResult(errorCode, error + (errorMsg==null?"":errorMsg));	
String accessToken = (String)resp.get("access_token");	
String uid = (String)resp.get("uid"); // 这个uid就是微博用户的唯一用户ID,可以通过这个id直接访问到用户微博主页	
int expires = (Integer)resp.get("expires_in"); // 有效期,单位秒

4.5. 获取用户头像等信息

  • 微博接口都有频率限制,不过一般不会超过;
  • 需做好错误兼容;
  • 微博直接返回的uid,可以根据这个uid直达用户微博主页 https://weibo.com/u/xxxxx ,所以可以把用户头像链接到这里;
  • 其实也有现成的js-sdk,可以根据自己实际需要选择是否使用;
  • 微博的接口是https,并且是post,需要注意;
相关链接
  • 微博开放平台:open.weibo.com/

  • 微博登录授权机制:open.weibo.com/wiki/授权机制
  • QQ互联:connect.qq.com/
  • QQ授权管理页面:connect.qq.com/manage.html#/appauth/user
作者:我是小茗同学
链接:www.cnblogs.com/liuxianan.html

END

喜欢本文的朋友们,欢迎长按下图关注订阅号 成猿之路 ,收看更多精彩内容!

推荐阅读:

640?
点在看再走呗! 640?wx_fmt=gif

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java 实现 QQ 登陆 的相关文章

  • IDEA如何导出导入配置文件

    导出配置 打开工具 找到 file gt export setting 选择路径即可 导出的是setting jar文件 导入配置 file gt import setttings gt 选则jar文件 gt 一路确认 gt 重启
  • 多线程常见面试题

    常见的锁策略 这里讨论的锁策略 不仅仅局限于 Java 乐观锁 vs 悲观锁 锁冲突 两个线程尝试获取一把锁 一个线程能获取成功 另一个线程阻塞等待 乐观锁 预该场景中 不太会出现锁冲突的情况 后续做的工作会更少 悲观锁 预测该场景 非常容
  • 优雅的代码命名规范,代码如诗

    优雅的代码命名规范 管理类命名 传播类命名 回调类命名 监控类命名 内存管理类命名 过滤检测类命名 结构类命名 常见设计模式命名 解析类命名 网络类命名 CRUD命名 其他 END 日常编码中 代码的命名是个大的学问 能快速的看懂开源软件的
  • canvas详解05-变形

    几何变换 canvas现在被大量地运用于游戏等动画领域 最主要的归功于它提供的一系列几何变换方法 使得动画更加地容易 所以其几何变换是非常重要的一节 在本教程前面的部分中 我们已经了解了 Canvas 网格和坐标空间 到目前为止 我们只是根
  • 解决:The Apache Tomcat Native library which allows optimal performance in production environments was

    在启动Tomcat 6 0时发现第一条信息便是 The Apache Tomcat Native library which allows optimal performance in production environments was
  • 用C++编写一个猜数字游戏

    系统随机生成一个1到100之间的数字 玩家进行猜测 如果猜错 提示玩家数字过大或过小 如果猜对恭喜玩家胜利 并且推出游戏 include
  • RocketMQ rocketmq_client.log日志文件配置

    1 默认路径 项目添加RocketMQ以后启动项目时会在user home下创建一个rocketmq client log日志文件 文件全路径是 user home logs rocketmqlogs rocketmq client log
  • 日语动词变形(概念明确篇)

    首先 日语中的动词我们可以这样看 这是几种不同的分法 相互之间没有关联 A 一段动词 五段动词 変动词 変动词 B 自动词 他动词 按照属性来区分 C 意志动词 非意志动词 只包含部分日语动词 在动词变形上 我们只涉及第一种分法 其他两种这
  • UE4 开房 多人在线动作游戏------学习笔记

    先创建好UI界面 这个UI是用来创建房间 查找房间 进入房间和退出的 这个UI是用来显示搜索出来的房间 然后点击进入房间的 用了监听 别人才能搜索到服务器 点击开始搜索 显示搜索图标 开始对查找Listen的关卡 查找成功生成ServerB
  • 本地笔记软件_笔记软件obsidian重大更新

    obsidian是一个笔记软件 之前写了篇obsidian的介绍 https zhuanlan zhihu com p 212204160 zhuanlan zhihu com 这次obsidian发布了0 91版本 真正变得实用了 首先说
  • springmvc

    1 SpringMVC简介 1 1 什么是MVC MVC是一种软件架构的思想 将软件按照模型 视图 控制器来划分 M Model 模型层 指工程中的JavaBean 作用是处理数据 JavaBean分为两类 一类称为实体类Bean 专门存储
  • Camunda流程引擎笔记(四):Send Task,Receive Task

    流程引擎中 Send Task和 Service Task拥有相同的行为 都是通过回调Java代码完成相应逻辑 通常 Send Task和 Receive Task配合使用 一 Send Task 绘制一个Send Task流程 配置过程和
  • 网页服务器请求响应,网页的请求响应,你们所看到的网页跳转长这样子

    Servlet的认识在上一篇文章有介绍过 如有不同看法或者其他理解的话可以下方留言给我 我肯定虚心学习 说一说Servlet 网友说 有点料 我们生活在这一个互联网时代 每天都点击数据 交互数据 响应数据和请求数据 以上几个技术点 你能想到
  • nodejs使用websocket

    一 websocket简介 首先要知道什么是几个常用的互联网传输协议 http https tcp udp 1 http 超文本传输协议 HyperText Transfer Protocol 是一种无状态协议 就是说客户端发送一次请求 服
  • linux centos7配置网卡信息

    1 配置网卡 查看网卡信息 ifconfig 这里看到的网卡名是 ens33 ip 名称是 172 16 28 31 准备改成 172 16 28 226 2 进入 配置文件目录 cd etc sysconfig network scrip
  • CISP 相关知识点梳理

    第一章 1 1 信息安全保障基础 v信息安全视角 了解国家视角对信息安全关注点 网络战 关键基础设施保护 法律建设与标准化 相关概念 了解企业视角对信息安全关注点 业务连续性管理 资产保护 合规性 相关概念 了解个人视角对信息安全关注点 隐
  • 心音信号特征提取Matlab系统

    心音信号特征提取Matlab系统 心脏疾病是一种常见的疾病 如果能够通过自己开发的程序来对心音信号进行分析 检测就更加方便快捷了 本文章将介绍如何使用 Matlab 提取心音信号的频域特征值 包括频谱 能量谱 功率谱和倒频谱等 同时 将通过
  • 10个免费的web压力测试工具

    转自 http apps hi baidu com share detail 53794908 当一套程序写完或者一台服务器配置完成后 相必很多朋友会像我一样 非常想知道它到底能够承受多大的负载压力 那在本文中 就给大家介绍十个免费的可以用
  • Ubuntu安装*.deb程序,用gdebi

    Ubuntu 安装 deb程序的时候 用安装源 gdebi 命令 sudo gdebi deb 如果 Ubuntu 没有安装gdebi的 shell term 运行命令 sudo apt get install gdebi
  • PHP代码审计12—反序列化漏洞

    文章目录 一 PHP反序列化漏洞基础 1 序列化与反序列化 2 反序列化漏洞类型 3 常见的一些魔法函数 4 漏洞利用与防御 二 MRCTF2020 Ezpop 分析与利用 三 Phar反序列化例题分析 四 PHPmyadmin 2 x 反

随机推荐

  • 软件测试面试必问的几个问题,拿好标准答案,有备无患~

    在今年这个特殊的情况下 竞争压力增大 各大企业对于求职者的要求也随之增高 很多小伙伴都面临着这样的情况 千辛万苦拿到了面试机会 却因种种原因翻车 在面试的时候不能将自己的真实实力表现出来 在回答面试官问题时 抓不到重点 紧张 说话结巴 不知
  • leetcode312 戳气球

    题目 https leetcode cn com problems burst balloons 思路 动态规划 状态 dp i j 表示戳破 i j 范围内这些气球所能获得的最大数量的硬币 转移方程 dp i j max dp i j d
  • 给自己的软件添加数字签名&数字签名格式转换

    工具链接 解压密码为 解压密码 ziyuanxiaozhan outlook com 废话不说 先上图 添加数字签名前 添加正规数字签名后 数字签名相关文件的后缀 pfx一定包含或可以转换为所有文件 pem可以包含或可以转换为所有文件但不一
  • orcad caputre里面Off-Page connect和port的区别

    1 下图即为orcad caputre里面Off Page connect和port的符号 1 在同一张page里面想要实现信号的连接可以之间将两个端口连接起来 如下图 也可以将两个端口的网络符号改为一致 软件也认为是连在一起的 2 在不同
  • 单片机(ISIS 7 Professional):简易数码管显示0~99计数代码项目

    上一篇文章主要介绍用C语言制作一个按钮的简易0 9控制计数器 单片机 ISIS 7 Professional 简易数码管显示0 9计数代码项目https blog csdn net MOKI36 article details 122810
  • 2023年计算机毕业设计选题大全 计算机毕业设计选题推荐Java、Python、Android、小程序等

    2023年计算机毕业设计选题大全 计算机毕业设计选题推荐Java Python Android 小程序等 在已经迎来2023年的毕业季 很多同学咨询关于计算机毕业设计选题方面的问题 例如计算机毕设选题什么好 计算机毕设选题选什么新颖一些 计
  • AAudio进行音频采集的实现

    使用AAudio进行音频采集 介绍 AAudio 是在Android 8 0版本中引入的一种基于C语言的本地音频API Android 8 1版本具有增强功能 可在支持MMAP的HAL和驱动程序结合使用时缩短延迟时间 AAUdio提供 Op
  • redis连接数合理配置_redis如何进行合理配置,这10种配置参数你必须知道

    redis参数如何配置 redis数据库的使用 关键一步是对redis进行合理的参数配置 redis的配置文件都在安装目录下的redis conf文件中进行相关参数配置 redis参数的配置可以通过config get命令来获取redis参
  • Java实现最长公共子序列

    动态规划做最长公共子序列 最重要的是求出状态转移方程 理论的就不多说了 用语言太难描述了 直接去看视频吧 我们直接上代码 对这里来说 他的状态转移方程如下 if a i 1 b j 1 c i j c i 1 j 1 1 d i j 1 e
  • 服务器里的文件启动失败404,云表服务器启动失败问题汇总

    这是在安装本地版时经常发生的问题 10个人 就有7个人都会出现这样的问题 出现这样的问题 怎么解决呢 首先 想要连接服务器 必须要开启数据库的服务 其次 服务器其实是 锁 住的 那么你要连接服务器 肯定得有钥匙 这里的钥匙可以看成是授权文件
  • Qt之布局管理——停靠窗口

    QDockWidget类继承与QWidget类 用于停靠窗口的管理 在主窗口中 先设置中心控件 然后实例化QDockWidget对象 通过setFeatures 设置停靠窗口的窗体特性 通过 setAllowedAreas 设置窗体可停靠的
  • 1200*C. Stripe

    题意翻译 给定一整数n 下面有n个数a i 求将该数列分割成两个非空数列且两个数列内数字的和相等的方案数 1 lt n lt 10 5 a i 的绝对值不大于10000 解析 前缀和 include
  • QT 实现16进制与字符串互转

    QT 实现16进制与字符串互转 文章目录 QT 实现16进制与字符串互转 前言 一 字符串QString转换16进制 二 16进制转换为字符串QString 三 正则表达式限制输入16进制 四 文本自动补全空格功能的实现 前言 QT在界面时
  • matlab图像处理——直方图及直方图均衡化

    imhist 对rice png和增强亮度后的 增强对比度后的图进行直方图展示 imhist 对于brightness 和 contrast的不同 clear all brightness I imread rice png J imadd
  • 洛谷Java入门代码之分苹果

    题目描述 八尾勇喜欢吃苹果 她现在有 m m 100 m m le 100 m m 100 个苹果 吃完一个苹果需要花费 t t 100 t t le100 t t 100 分钟 吃完一个后立刻开始吃下一个 现在时间过去了 s s 1000
  • LevelDB简介

    LevelDB简介 综述 leveldb整体架构 提供接口 db h 技术 memtable WAL sstable Manifest cache LRU cache LRU的优缺点 filter levelDB初始化 compaction
  • 专治疑难系列 - 无法激活网络的解决方法

    博客主页 Passerby Wang的博客 CSDN博客 系统运维 云计算 Linux基础领域博主 所属专栏 转治疑难系列 上期文章 无 如觉得博主文章写的不错或对你有所帮助的话 还望大家多多支持呀 关注 点赞 收 藏 评论 目录 一 问题
  • Python将txt文件内容转换成列表

    参考 Python将txt文件内容转换成列表 云 社区 腾讯云 方法一 coding utf 8 f open r ip txt r a list f print a f close 方法二 coding utf 8 f open r ip
  • Sublime Text 3 安装Go语言相关插件gosublime《小白也能学会的教程》

    Sublime Text 3 安装Go语言相关插件gosublime 序言 这篇文章是自己的亲身体会 今天为了安装gosublime可是找了一堆教程 但大部分都无功于返 有些甚至点开后都是直接复制粘贴过来的 一度心灰意冷 就在我快要暴躁的时
  • Java 实现 QQ 登陆

    点击上方蓝色字体 选择 标星公众号 优质文章 第一时间送达 1 前言 个人网站最近增加了评论功能 为了方便用户不用注册就可以评论 对接了 QQ 和微博这 2 大常用软件的一键登录 总的来说其实都挺简单的 可能会有一点小坑 但不算多 完整记录