微信第三方登录 -- (PC端+移动端)
一、前言
一. 什么是第三方登录
所谓的第三方登录,是说基于用户在第三方平台上已有的账号和密码来快速完成己方应用的登录或者注册的功能。而这里的第三方平台,一般是已经拥有大量用户的平台,国外的比如Facebook,Twitter等,国内的比如微博、微信、QQ等。
二. 为什么要用第三方登录
第三方登录之所以会被较为广泛地在产品设计上使用,是因为它有以下几个优点:
(1)对普通用户
相比于本地注册,第三方登录一般来说比较方便、快捷,能够显著降低用户的注册和登录成本,方便用户实现快捷登录或注册。不用费尽心思地应付本地注册对账户名和密码的各种限制,如果不考虑昵称的重复性要求,几乎可以直接一个账号走遍天下,再也不用在大脑或者什么地方记住N多不同的网站或App的账号和密码,整个世界一下子清静了。在第一次绑定成功之后,之后用户便可以实现一键登录,使得后续的登录操作比起应用内的登录来容易了很多。对于某些喜欢社交,并希望将更多自己的生活内容展示给朋友的人来说,第三方登录可以实现把用户在应用内的活动同步到第三方平台上,省去了用户手动发布动态的麻烦。但对于某些比较注重个人隐私的用户来说,则会有一些担忧,所以说这个优点是有前提的。
(2)对应用
因为降低了用户的注册或登录成本,从而减少由于本地注册的繁琐性而带来的隐形用户流失,最终提高注册转化率。对于某些应用来说,使用第三方登录完全可以满足自己的需要,因此不必要设计和开发一套自己的账户体系。通过授权,可以通过在第三方平台上分享用户在应用内的活动在第三方平台上宣传自己,从而增加产品知名度。通过授权,可以获得该用户在第三方平台上的好友或粉丝等社交信息,从而后续可以针对用户的社交关系网进行有目的性的营销宣传,为产品的市场推广提供另一种渠道。
(3)对第三方
增加用户对平台的依赖,用户越多使用本平台的第三方登录,就代表着平台对该用户的粘性越高。获得更广泛的影响力,只要用户使用提供第三方登录的应用,那么这个提供第三方登录的品牌就会被用户浏览,有利于对平台的拉新和促活。
三. 使用第三方登录要注意的地方
任何事物都是有两面性的,尽管第三方登录具有以上所述的优点,但同时也存在着一定的问题需要注意,供大家参考:
(1)对用户
一旦自己的第三方账户出现问题,比如被第三方平台封号,或者账号被盗,则会发生相应的应用内数据丢失或者数据泄露。这个时候即使注册一个新账户,之前在应用内所有的记录也是无法恢复的。
(2)对应用
对于有自己本地注册需求,并且提供第三方登录的应用而言,需要考虑第三方账号和本地账号的对接问题,产品需要设计对接方案,研发也要正确实现这个对接方案,会带来一定的额外工作量;此外,如果这个问题处理不好,很容易导致同一个用户在应用上存在多个账号的情况,为用户在平台上的操作带来了困扰。一旦第三方登录出现问题,比如出现服务宕机,或者停止提供登录服务,将会对应用的后续发展造成一定的风险。
四. 第三方登录的实现方式
目前第三方登录的实现方式一般来讲有两种:
纯登录登录+账号绑定
下面来看看这两种实现方式的细节:
1. 纯登录方式
(1)实现策略
使用第三方账号直接登录,即可拥有完整的同本地注册用户相同的待遇。
(2)优势
简单、快捷,用户第一次只需要登录第三方平台并将登录许可授权给应用即可,只要成功,后续就能像应用注册用户一样使用应用内所有服务。
(3)劣势
账号体系在别人手里,一旦第三方登录出现问题,会面临用户及用户数据丢失的风险,给应用的可持续发展带来一定的隐患,只是,考虑到目前提供第三方登录的平台的实力,这种隐患的发生是一个小概率事件。
(4)适用场景
如果你所开发的应用定位与分享、评论、社交,并不涉及必须进行创建应用账号的复杂功能,是比较轻量级的应用,那么你可以选择放弃自主的应用账号体系。比如今日头条、一点资讯等资讯类应用,还有网易云音乐等音乐类应用。
2. 登录+绑定方式
使用第三方登录后,要求绑定应用内账户(如果用户有,则直接绑定,否则需要走应用内的账号注册流程)。
这种实现方式总体上来讲,对应用和用户都是有一定好处的。
对应用来讲:
可以将用户信息牢牢抓在自己手里,防止被第三方拒绝提供服务后丢失本应用积累的用户;可以拿到更多用户的联系信息,比如邮箱、手机号码等,可以为后续的持续营销打下基础;可以避免一个用户多个账号同时存在的情况发生。
对用户来讲:主要是可以防止第三方登录关闭登录服务之后,无法找到自己在应用内的信息的风险。
至于具体的绑定方式,有两种常规的做法:立即绑定和延时绑定。接下来笔者将分别聊聊。
1)实现方式
使用第三方登录后立即需要绑定应用内账号。
2)优势
使得应用内的账号体系比较规整,避免同一个用户产生不同的账号,也进一步避免了同一个用户不同账号之间的数据合并工作。
3)劣势
这种方式给用户的体验不太好,尤其是用户没有本地账号的时候,真的很让人讨厌啊,不但么有减轻用户的注册成本,反而增加了,给用户的感觉是还不如直接注册,多此一举。
4)适用场景
对于店大的应用,你完全可以这么干,比如京东、唯品会这种级别的,因为你对用户的吸引力足够,他为了获得应用的服务,能够忍受这样的额外麻烦。
但对于刚起步的应用,拉新是比较重要的任务和业绩指标,这么干则有一定的风险,因为这样的用户体验是非常糟糕的,用户很可能因为这个设计而骂娘,然后弃你而去。
(2)登录+延时绑定账号
1)实现策略
将第三方登录和账号绑定进行解耦,用户在第三方登录后会获得部分本地注册用户的权限,但在一些关键点上卡住,引导用户绑定一个本地账号,这种做法在用户的便捷性和应用的安全性上取得了一个最好的平衡。
2)优势
降低注册成本,迅速将第三方登录带来的流量留在应用内,提高应用的注册转化率;给用户的体验更贴近于用户的期望(因为登录之后再登录用户很难理解的),第三方登录后可以使用应用了,哪怕是部分功能。
3)劣势
会造成一个用户在绑定应用内账号之前会拥有多个的个人账户:一来给用户的使用造成一定的困扰,二来会因为后续的账号合并带来一定的产品和研发工作量。
4)适用场景
比较适合于刚起步阶段,同时仅仅第三方登录满足不了全部功能需要的应用,比如电商或者O2O类型的应用,因为这样的应用一般会需要用户的手机号,而这个信息第三方登录是提供不了的。
结论
最后结论就是应用账号登录与第三方登录均有利弊,无论何种选择,力求尊重用户,为用户带来方便这个大方向总是不错的。至于具体选择,则要结合自己产品的形态、定位、风格以及愿景来进行选择,不同场景使用不同的实现方式,具体问题具体分析,任何试图用一个方子处理所有问题的企图都是有风险的。
二、请求code (GET请求)
注:请求code仅限于PC端。移动端(APP)由于java无法控制用户手机 拉起(用户手机中)微信应用。所以移动端code由Android和iOS请求、做回调获取code,然后作为参数传给java后台!(仅个人理解,如有不对的地方。望指正、并忽略)
接口地址:https://open.weixin.qq.com/connect/qrconnect?appid=wx4530e35e8a9fc5cd&redirect_uri=http://www.mogujie.com/oauth/callback/weixin/mogujie?referer_key=5E6BA887B6F2988918AD730D730D8D69&response_type=code&scope=snsapi_login&state=STATE#wechat_redirect
(点击链接:可拉起蘑菇街网站‘微信二维码‘登录页面)
一、接下来就借助这个链接中的几个参数,做详细解释:
appid:是在微信开方平台申请的‘应用唯一标识,在微信开放平台提交应用审核通过后获得‘;
redirect_uri:是在微信开方平台申请时填写‘返回code的回调地址‘;
关于redirect_uri回调地址后面的referer_key参数说明:
这个参数可以在后台拼接code请求地址时:加上一个参数,对这个参数生成一个时间戳或uuid、随机数(存入session)。然后拼接到redirect_uri回调地址的后面,这样当你写回调方法的时候可以获取下你在请求code时生成的参数。然后(和session的参数)做下校验用于保持请求和回调的状态,防止跨站请求伪造攻击。
**state参数和referer_key是一样的作用**
response_type:是需要返回的类型,固定填code;
scope:为授权作用域,有两种‘snsapi_base‘和‘snsapi_userinfo‘
关于网页授权的两种scope的区别说明
1、以snsapi_base为scope发起的网页授权,是用来获取进入页面的用户的openid的,并且是静默授权并自动跳转到回调页的。用户感知的就是直接进入了回调页(往往是业务页面) - - 静默授权
2、以snsapi_userinfo为scope发起的网页授权,是用来获取用户的基本信息的。但这种授权需要用户手动同意,并且由于用户同意过,所以无须关注,就可在授权后获取该用户的基本信息。 - - 手动授权
state:生成一个时间戳或uuid、随机数用于保持请求和回调的状态,授权请求后原样带回给第三方(此参数也可以忽略不传);
二、微信官方对这几个参数的解释:
参数 | 是否必须 | 说明 |
---|---|---|
appid | 是 | 应用唯一标识 |
redirect_uri | 是 | 请使用urlEncode对链接进行处理 |
response_type | 是 | 填code |
scope | 是 | 应用授权作用域,拥有多个作用域用逗号(,)分隔,网页应用目前仅填写snsapi_login即 |
state | 否 | 用于保持请求和回调的状态,授权请求后原样带回给第三方。该参数可用于防止csrf攻击(跨站请求伪造攻击),建议第三方带上该参数,可设置为简单的随机数加session进行校验 |
三、请求code代码
/**微信请求地址 */
//获取code 接口地址 Get
public final static String WEB_CODE_URL = "https://open.weixin.qq.com/connect/qrconnect?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect";
//微信回调地址(获取code)
public final static String REDIRECT_URI = "http://服务器域名:端口号(如为80,可忽略端口号)/*****/weixin/*****";
/**
* 拼接获取code的:接口地址
* @param state 状态码
* @return
*/
public static String getCodeURL(String state) {
return WEB_CODE_URL.replace("APPID",APPID).replace("REDIRECT_URI",REDIRECT_URI).replace("SCOPE",SCOPE).replace("STATE",state);
}
四、回调方法:
controller层:
@RestController
@RequestMapping("/*****")
public class WXController {
@Autowired
private WXService weXService;
@RequestMapping(value = "/weixin/*****")
public JsonResult WXLogin(HttpServletRequest request) {
return weXService.WXLogin(request);
}
}
service层:
public interface WXService {
JsonResult WXLogin(HttpServletRequest request);
}
serviceImpl层:
@Service
public class WXServiceImpl {
@Override
public JsonResult WXLogin(HttpServletRequest request) {
//创建一个统一返回对象
JsonResult jsonResult = new JsonResult();
/**
*微信请求回调格式:
* 允许授权:
* redirect_uri?code=CODE&state=STATE
* 禁止授权:
* redirect_uri?state=STATE
*/
//获取回调地址中的参数code
String code = request.getParameter("code");
//获取回调地址中的参数state
String state = request.getParameter("state");
//从session中取出state状态码
Object sessionState = request.getSession().getAttribute("state");
//校验回调中的state和session中的sessionState是否一致(防止:跨站请求伪造攻击)
if (sessionState.toString().equals(state)) {
//校验回调中的code,是否为空
if (ObjectUtil.isNotEmpty(code)) {
//根据code向微信服务器发送请求,获取返回参数
AccessToken accessToken = WeChatUtils.getAccessToken(code);
//判断微信服务器返回的access_token是否为空
if (ObjectUtil.isNotEmpty(accessToken.getAccess_token())) {
//根据access_token和openid向微信服务器发送请求,获取微信用户信息
WeChatUser weChatUserInfo = WeChatUtils.getWeChatUserInfo(accessToken.getAccess_token(), accessToken.getOpenid());
//根据返回的openid是否为空,判断‘微信用户信息‘是否成功获取
if (ObjectUtil.isNotEmpty(weChatUserInfo.getOpenid())) {
/*
* 在此处编写具体业务的逻辑!!!
*
* 在此处编写具体业务的逻辑!!!
*
* 在此处编写具体业务的逻辑!!!
*/
}
} else {
System.out.println("微信用户信息获取失败:错误编码 >> "+weChatUserInfo.getErrcode()+"。错误信息 >> "+weChatUserInfo.getErrmsg());
jsonResult.setStatus(2);
jsonResult.setData("微信用户信息获取失败");
}
} else {
System.out.println("微信授权异常:错误编码 >> "+accessToken.getErrcode()+"。错误信息 >> "+accessToken.getErrmsg());
jsonResult.setStatus(2);
jsonResult.setMsg("微信授权异常");
}
} else {
jsonResult.setMsg("用户未授权");
jsonResult.setStatus(2);
}
} else {
jsonResult.setMsg("恶意请求");
jsonResult.setStatus(2);
}
return jsonResult;
}
}
五、请求code完成,总结需要注意的点:
1.appid、redirect_uri、scope是必传的,缺一不可!
***********************************************
***********************************************
***********************************************
2.在上面请求code中所用到的参数,appid、redirect_uri是需要在微信开放平台上申请的。(换言之,就是这两个参数是要公司提供的)
三、根据code,获取access_token(GET请求)
接口地址:https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
一、接下来就借助这个链接中的几个参数,做详细解释:
appid:同上;
secret:同上;
code:用户授权后,微信服务器返回临时凭证,用于换取access_token;(五分钟有效期)
grant_type:在获取access_token时,这是一个固定值的参数authorization_code;
二、微信官方对这几个参数的解释:
参数 | 是否必须 | 说明 |
---|---|---|
appid | 是 | 应用唯一标识,在微信开放平台提交应用审核通过后获得 |
secret | 是 | 应用密钥AppSecret,在微信开放平台提交应用审核通过后获得 |
code | 是 | 填写第一步获取的code参数 |
grant_type | 是 | 填authorization_code |
三、请求access_token代码
//获取access_token 接口地址 Get
public final static String ACCESS_TOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code";
/**
* 获取授权登录access_token
* @param code 临时授权凭证
* @return
*/
public static AccessToken getAccessToken(String code) {
//拼接获取access_token的请求地址
String accessTokenUrl = ACCESS_TOKEN_URL.replace("APPID", APPID).replace("SECRET", APPSECRET).replace("CODE", code);
//发送请求,获取返回结果
JSONObject jsonObject = HttpsUtils.sendRequest(accessTokenUrl, "GET", null);
//获取返回的参数,并且封装成AccessToken对象
AccessToken accessToken = new AccessToken();
if (jsonObject.getString("access_token") != null) {
/**
* 官方返回格式:
* "access_token":"ACCESS_TOKEN",
* "expires_in":7200,
* "refresh_token":"REFRESH_TOKEN",
* "openid":"OPENID",
* "scope":"SCOPE",
* "unionid":"o6_bmasdasdsad6_2sgVt7hMZOPfL"
*/
accessToken.setAccess_token(jsonObject.getString("access_token"));
accessToken.setExpires_in(jsonObject.getString("expires_in"));
accessToken.setRefresh_token(jsonObject.getString("refresh_token"));
accessToken.setOpenid(jsonObject.getString("openid"));
accessToken.setScope(jsonObject.getString("scope"));
//UnionID机制
accessToken.setUnionid(jsonObject.getString("unionid"));
} else {
/**
* 错误返回样例:
* "errcode":40029,
* "errmsg":"invalid code"
*/
accessToken.setErrcode(jsonObject.getString("errcode"));
accessToken.setErrmsg(jsonObject.getString("errmsg"));
}
return accessToken;
}
四、根据access_token,获取用户信息(GET请求)
接口地址:https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID
一、接下来就借助这个链接中的几个参数,做详细解释:
access_token:根据临时凭证code,获取到的用户有效凭证token,(有效期2小时);
openid:普通用户的标识,对当前开发者帐号(也就是应用、网站)唯一。
二、微信官方对这几个参数的解释:
参数 | 是否必须 | 说明 |
---|---|---|
access_token | 是 | 调用凭证 |
openid | 是 | 普通用户的标识,对当前开发者帐号唯一 |
三、获取用户个人信息的代码
//获取userinfo 接口地址 Get
public final static String WECHAT_USERINFO_URL = "https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID";
/**
* 获取用户信息
* @param accessToken
* @param openid
* @return
*/
public static WeChatUser getWeChatUserInfo(String accessToken,String openid) {
//拼接获取userinfo的请求地址
String userinfoUrl = WECHAT_USERINFO_URL.replace("ACCESS_TOKEN",accessToken).replace("OPENID",openid);
//发送请求,获取返回结果
JSONObject jsonObject = HttpsUtils.sendRequest(userinfoUrl, "GET", null);
//获取返回的参数,并且封装成WeChatUser对象
WeChatUser weChatUser = new WeChatUser();
if (jsonObject.getString("openid") != null) {
/**
* 官方返回格式:
* {
* "openid":"OPENID",
* "nickname":"NICKNAME",
* "sex":1,
* "province":"PROVINCE",
* "city":"CITY",
* "country":"COUNTRY",
* "headimgurl": "http://wx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/0",
* "privilege":[PRIVILEGE1","PRIVILEGE2"],
* "unionid": " o6_bmasdasdsad6_2sgVt7hMZOPfL"
* }
*/
weChatUser.setOpenid(jsonObject.getString("openid"));
weChatUser.setNickname(jsonObject.getString("nickname"));
weChatUser.setSex(jsonObject.getString("sex"));
weChatUser.setProvince(jsonObject.getString("province"));
weChatUser.setCity(jsonObject.getString("city"));
weChatUser.setCountry(jsonObject.getString("country"));
weChatUser.setHeadimgurl(jsonObject.getString("headimgurl"));
weChatUser.setPrivilege(jsonObject.getString("privilege"));
weChatUser.setUnionid(jsonObject.getString("unionid"));
} else {
/**
* 错误返回样例:
* "errcode":40003,
* "errmsg":"invalid openid"
*/
weChatUser.setErrcode(jsonObject.getString("errcode"));
weChatUser.setErrmsg(jsonObject.getString("errmsg"));
}
return weChatUser;
}
五、结尾
在本篇博客中所用的到所有工具类均在下面链接中可以找到,后续有时间会对代码进行更新和加入其它的第三方登录!
在下面链接中有在项目中的实际案例!可供参考...
项目源码:https://gitee.com/qcxdld/open_logon
原文地址:https://www.cnblogs.com/2019gdiceboy/p/11384312.html