Java实现微信登录(网页授权)
1.背景
实际开发中,使用第三方登录是非常常见的业务...
这样可以大提高用户体验,没必要一来就要注册,或者登录之类的...
并且开发一个登录或者注册严格来说也是非常麻烦的(各种防止攻击、机器操作等)
2.准备公众号和测试环境
需要准备的如下
1.appid
2.appSecret
3.外网可以访问的映射地址
如果你有服务号、并且是认证了的(这些认证需要企业资质),当然很好,通常来时如果你是学习应该没有
即使没有也没关系,微信提供了测试账号,并且拥有很多权限,开发好后,只要替换为公司的生产公众号就可以使用了
获取测试公众号步骤如下:
打开链接:https://developers.weixin.qq.com/doc/offiaccount/Getting_Started/Overview.html
建议初学者认证读一下微信的开发文档,正常情况下如果你是做开发很大概率会经常用到微信相关的接口

点击测试号申请界面如下:

点击微信登陆,扫码即可快速获得一个微信公众号测试

页面下方有接口权限,设置网页回调地址

如下,注意只写域名,不要写http之类的
如果没有外网地址可以使用外网映射:https://www.cnblogs.com/newAndHui/p/14241177.html (免费、简单、三步搞定)

到此公众号配置已经完成
3.实现网页授权(微信登陆)
1.微信登陆的本质就是,通过用户授权获得用户的 信息,然后保存到数据库,就像用户注册时保存用户信息是一个道理,只是数据来源不同
2.具体实现步骤,微信文档已经写得非常清楚
官方文档地址:https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html
一共4个步骤,其实不论是微信授权登录,还是QQ授权登录,或者支付宝授权登录.....等只要是OAuth2.0协议都是这逻辑
换句话说,OAuth2.0是一种三方授权登录的协议,大部分授权登录都是遵循这个协议的,使用开发思路都是一样的
那么如果你要开一个系统,然后允许别的系统使用你的三方授权登录是不是也可以安装这个思路设计
1 第一步:用户同意授权,获取code 2 第二步:通过code换取网页授权access_token 3 第三步:刷新access_token(如果需要) 4 第四步:拉取用户信息(需scope为 snsapi_userinfo)
具体实现代码:

package com.ldp.user.controller; import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.ldp.user.common.base.BaseResponse;
import com.ldp.user.common.base.ResponseBuilder;
import com.ldp.user.common.exception.ParamException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import java.util.Date;
import java.util.HashMap;
import java.util.Map; /**
* @Copyright (C) 四川千行你我科技股份技有限公司
* @Author: lidongping
* @Date: 2021-01-04 16:16
* @Description: <p>
* 微信网页授权
* https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html
* </p>
*/
@Slf4j
@RestController
@RequestMapping("/wc")
public class WeChatLoginController {
// 模拟存放(实际开发中应该存放在数据库和Redis)
private static Map<String, String> mapData = new HashMap<>();
// appId\appSecret redirectUri 实际生产中应该配置到数据库
private static String appId = "wxeb91796d8fbb1";
private static String appSecret = "e7aeb6cb4be6fe3388cfd4580f36";
// 微信授权code后的回调地址
private static String redirectUri = "http://lidongping.free.idcfengye.com"; /**
* 请求CODE
*/
@GetMapping("/codeUrl")
public BaseResponse getCodeUrl() {
String url = "https://open.weixin.qq.com/connect/qrconnect?appid=%s&redirect_uri=%s&response_type=code&scope=SCOPE&state=STATE#wechat_redirec";
url = String.format(url, appId, redirectUri);
return ResponseBuilder.success(url);
} /**
* 第二步:通过code换取网页授权access_token
* code=011NQuFa1OiTgA0spVGa1Cvyff1NQuFC&state=STATE
* <p>
* {
* "access_token":"ACCESS_TOKEN",
* "expires_in":7200,
* "refresh_token":"REFRESH_TOKEN",
* "openid":"OPENID",
* "scope":"SCOPE"
* }
*/
@GetMapping("/notify/code")
public BaseResponse notifyCode(String code, String state) {
log.info("code={},state={}", code, state);
if (StrUtil.isEmpty(code)) {
return ResponseBuilder.failed("获取code失败");
}
String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code";
url = String.format(url, appId, appSecret, code);
log.info("第二步:通过code换取网页授权access_token,请求url={}", url);
String response = HttpUtil.get(url, 60000);
log.info("响应结果:{}", response); // 模拟数据入库,便于下次使用
JSONObject object = JSON.parseObject(response);
String openId = object.getString("openid");
// 设置token失效时间
Long timeOutAccessToken = DateUtil.offset(new Date(), DateField.SECOND, object.getInteger("expires_in") - 120).getTime();
object.put("timeOutAccessToken", timeOutAccessToken);
// refresh_token有效期为30天
object.put("timeOutRefreshToken", DateUtil.offsetDay(new Date(), 30));
mapData.put(openId, JSON.toJSONString(object)); // 将openid返回给调用者便于,下次使用openid获取用户信息
return ResponseBuilder.success(openId);
} /**
* 拉取用户信息(需scope为 snsapi_userinfo)
*/
@GetMapping("/userInfo")
public BaseResponse userInfo(String openId) {
String obj = mapData.get(openId);
if (obj == null) {
return ResponseBuilder.failed("未授权");
}
JSONObject object = JSON.parseObject(obj);
String accessToken = object.getString("access_token");
Long timeOutAccessToken = object.getLong("timeOutAccessToken");
if (timeOutAccessToken < System.currentTimeMillis()) {
// 重新获取 access_token
accessToken = refreshToken(openId);
}
String url = "https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s&lang=zh_CN";
url = String.format(url, accessToken, openId); log.info("拉取用户信息 请求url={}", url);
String response = HttpUtil.get(url, 60000);
log.info("响应结果:{}", response);
return ResponseBuilder.success(response);
} /**
* 刷新access_token(如果需要)
* <p>
* {
* "access_token":"ACCESS_TOKEN",
* "expires_in":7200,
* "refresh_token":"REFRESH_TOKEN",
* "openid":"OPENID",
* "scope":"SCOPE"
* }
*
* @return
*/
public String refreshToken(String openId) {
String obj = mapData.get(openId);
if (obj == null) {
throw new ParamException("用户没有授权");
}
JSONObject objectMap = JSON.parseObject(obj);
Long timeOutAccessTokenOld = objectMap.getLong("timeOutRefreshToken");
// 判定refresh_token是否过期
if (timeOutAccessTokenOld < System.currentTimeMillis()) {
throw new ParamException("授权已过期,请重新授权");
}
String refreshToken = objectMap.getString("refresh_token");
String url = "https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=%s&grant_type=refresh_token&refresh_token=%s";
url = String.format(url, appId, refreshToken);
log.info("拉取用户信息 请求url={}", url);
String response = HttpUtil.get(url, 60000);
log.info("响应结果:{}", response); // 模拟数据入库,便于下次使用
JSONObject object = JSON.parseObject(response);
// 设置token失效时间
Long timeOutAccessToken = DateUtil.offset(new Date(), DateField.SECOND, object.getInteger("expires_in") - 120).getTime();
object.put("timeOutAccessToken", timeOutAccessToken);
// refresh_token有效期为30天
object.put("timeOutRefreshToken", DateUtil.offsetDay(new Date(), 30));
mapData.put(openId, JSON.toJSONString(object)); return object.getString("access_token");
} }
4.测试
测试代码

package com.ldp.user.controller; import org.junit.jupiter.api.Test; /**
* @Copyright (C) 四川千行你我科技股份技有限公司
* @Author: lidongping
* @Date: 2021-01-04 17:16
* @Description:
*/
class WeChatLoginControllerTest {
// 个人测试
private static String appId = "wxeb91796d8f74dbb1";
private static String redirectUri = "http://lidongping.free.idcfengye.com/api/wc/notify/code"; /**
* 通过code获取openid (微信通知地址)
* http://192.168.5.195:8080/api/wc/notify/code (http://lidongping.free.idcfengye.com/api/wc/notify/code)
* <p>
* 通过openid获取用户信息
* http://192.168.5.195:8080/api/wc/userInfo?openId=oNHe35yo1LCRfTd5TGytemISl4xs
*/
/**
* 获取授权链接(注意链接只能在微信公众号里面打开)
*/
@Test
void getCodeUrl() {
String url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect";
url = String.format(url, appId, redirectUri);
System.out.println(url);
}
}
获取code的链接:
https://open.weixin.qq.com/connect/oauth2/authorize?appid=wxeb91hh798f74dbb1&redirect_uri=http://lidongping.free.idcfengye.com/api/wc/notify/code&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect
通过code获取token日志如下
2021-01-06 16:01:11.733-[a8a444bc-705]-[ INFO ] [ c.l.u.c.i.HttpServletRequestWrapperFilter ] - ContentType: null
2021-01-06 16:01:11.734-[a8a444bc-705]-[ INFO ] [ c.l.u.c.i.HttpServletRequestWrapperFilter ] - 请求地址: http://lidongping.free.idcfengye.com/api/wc/notify/code
2021-01-06 16:01:11.734-[a8a444bc-705]-[ INFO ] [ c.l.u.c.i.HttpServletRequestWrapperFilter ] - 请求方法: GET
2021-01-06 16:01:11.945-[a8a444bc-705]-[ INFO ] [ com.ldp.user.controller.WeChatLoginController ] - code=011wGO0w3fGCCV2Q5f3w3F92W02wGO08,state=STATE
2021-01-06 16:01:11.945-[a8a444bc-705]-[ INFO ] [ com.ldp.user.controller.WeChatLoginController ] - 第二步:通过code换取网页授权access_token,请求url=https://api.weixin.qq.com/sns/oauth2/access_token?appid=wxeb996ddd74dbb1&secret=e7aeb4be6dd33d7fe33cfd4580f36&code=011wGO0w3fGCCV2Q5f3w3F92W02wGO08&grant_type=authorization_code
2021-01-06 16:01:12.681-[a8a444bc-705]-[ INFO ] [ com.ldp.user.controller.WeChatLoginController ] - 响应结果:{"access_token":"40_rg_AbGORcVygaz45XxihaF1Qzd5HCZaO0FbEssxhCAxwgoBajWEtozl1GLFtEPQ3YI-Gir-KMjwzkUPcE--SnhEicwwd1P3W8w0e3FXe8lg","expires_in":7200,"refresh_token":"40_PDo-sss9H6Shvh6LRX6VgU2wFWfKxlAevJ5879ij9uYqlSKunrxiPKX9S16INvlTp5jczRw-Nu9bSrHzLKrj0lzNdmE9I68Hg4vG_Wz3je-iU","openid":"oNHe35yo1LCRfTd5TsssGytemISl4xs","scope":"snsapi_userinfo"}
2021-01-06 16:01:12.732-[a8a444bc-705]-[ INFO ] [ c.l.u.c.i.HttpServletRequestWrapperFilter ] - 响应结果: {"message":"success","code":100,"data":"oNHe35ysso1LCR5TGytemISl4xs"}
2021-01-06 16:01:12.732-[a8a444bc-705]-[ INFO ] [ c.l.u.c.i.HttpServletRequestWrapperFilter ] - HTTP状态: 200
2021-01-06 16:01:12.732-[a8a444bc-705]-[ INFO ] [ c.l.u.c.i.HttpServletRequestWrapperFilter ] - 处理时长: 998毫秒
通过openid获取用户行测试日志如下:
测试地址:http://127.0.0.1:8080/api/wc/userInfo?openId=oNHe35yo1LCRfTd5TGytemIxs
2021-01-06 16:04:29.835-[b7caeebc-7ef]-[ INFO ] [ c.l.u.c.i.HttpServletRequestWrapperFilter ] - ContentType: null
2021-01-06 16:04:29.835-[b7caeebc-7ef]-[ INFO ] [ c.l.u.c.i.HttpServletRequestWrapperFilter ] - 请求地址: http://127.0.0.1:8080/api/wc/userInfo
2021-01-06 16:04:29.835-[b7caeebc-7ef]-[ INFO ] [ c.l.u.c.i.HttpServletRequestWrapperFilter ] - 请求方法: GET
2021-01-06 16:04:29.838-[b7caeebc-7ef]-[ INFO ] [ com.ldp.user.controller.WeChatLoginController ] - 拉取用户信息 请求url=https://api.weixin.qq.com/sns/userinfo?access_token=40_rg_AbGORcVyg45XxigggghaF1Qzd5HCZaO0FbExhCAxwgoBarFjWEtozl1GLFtEPQ3YI-Gir-KMjwzkUPcE--SnhEicwwd1P3W8w0e3FXe8lg&openid=oNHe35yoggg1LCRfTd5TGytemISl4xs&lang=zh_CN
2021-01-06 16:04:30.197-[b7caeebc-7ef]-[ INFO ] [ com.ldp.user.controller.WeChatLoginController ] - 响应结果:{"openid":"oNHe35yo1LggCRfTd5TGytemISl4xs","nickname":"阳光飞阳","sex":1,"language":"zh_CN","city":"成都","province":"四川","country":"中国","headimgurl":"https:\/\/thirdwx.qlogo.cn\/mmopen\/vi_32\/yaZDgUs7xJHcxMsCcbLbQgU2cJvn9iajDeW8Dj2gic9UfHgBggWgshNiaIWUcpsVqz4RTLEl5aJ3FtQHKoMicicNVQVRw\/132","privilege":[]}
2021-01-06 16:04:30.202-[b7caeebc-7ef]-[ INFO ] [ c.l.u.c.i.HttpServletRequestWrapperFilter ] - 响应结果: {"message":"success","code":100,"data":"{\"openid\":\"oNHe35yo1ggLCRfTd5TGemISl4xs\",\"nickname\":\"阳光飞阳\",\"sex\":1,\"language\":\"zh_CN\",\"city\":\"成都\",\"province\":\"四川\",\"country\":\"中国\",\"headimgurl\":\"https:\\/\\/thirdwx.qlogo.cn\\/mmopen\\/vi_32\\/yaZDgUs7xJHcxMsCcbLbQgU2cJvn9iajDeW8Dj2gic9UfHgBWgshNiaIWUcpsVqz4RTLEl5aJ3FtQHKoMicicNVQVRw\\/132\",\"privilege\":[]}"}
2021-01-06 16:04:30.203-[b7caeebc-7ef]-[ INFO ] [ c.l.u.c.i.HttpServletRequestWrapperFilter ] - HTTP状态: 200gg
2021-01-06 16:04:30.203-[b7caeebc-7ef]-[ INFO ] [ c.l.u.c.i.HttpServletRequestWrapperFilter ] - 处理时长: 367毫秒
从日志可以看出已经获得了用户的基本信息(昵称、性别、地区、头像等)
到这里微信登陆的主要逻辑就已经完成了,如果还是不理解可以看视频,该博客已录制成视频讲解,或者单独问我
更多的微信开发相关可以看之前的微信公众号开发教程。
完美!
Java实现微信登录(网页授权)的更多相关文章
- 第八篇 :微信公众平台开发实战Java版之如何网页授权获取用户基本信息
第一部分:微信授权获取基本信息的介绍 我们首先来看看官方的文档怎么说: 如果用户在微信客户端中访问第三方网页,公众号可以通过微信网页授权机制,来获取用户基本信息,进而实现业务逻辑. 关于网页授权回调域 ...
- 微信公众平台开发实战Java版之如何网页授权获取用户基本信息
第一部分:微信授权获取基本信息的介绍 我们首先来看看官方的文档怎么说: 如果用户在微信客户端中访问第三方网页,公众号可以通过微信网页授权机制,来获取用户基本信息,进而实现业务逻辑. 关于网页授权回调域 ...
- 微信 OAuth2 网页授权获取用户信息
文档:http://mp.weixin.qq.com/wiki/17/c0f37d5704f0b64713d5d2c37b468d75.html !!! 微信跟用户没有关系类接口采用了OAUTH2 [ ...
- .NET Core中 实现H5微信登录(静默授权方式)
需求 假设现在有一个H5需要有微信登录.手机号登录.邮箱登录 三种登录方式.让我们一起来看看微信登录如何实现吧 界面: 最终实现的效果图(登录成功后返回个人页): 因为微信登录目前没有实现移动端的其他 ...
- 【微信公众号】微信关于网页授权access_token和普通access_token的区别及两种不同方式授权
微信官网网址:https://mp.weixin.qq.com/wiki/17/c0f37d5704f0b64713d5d2c37b468d75.html#.E9.99.84.EF.BC.9A.E6. ...
- 微信关于网页授权access_token和普通access_token的区别
微信官网网址:https://mp.weixin.qq.com/wiki/17/c0f37d5704f0b64713d5d2c37b468d75.html#.E9.99.84.EF.BC.9A.E6. ...
- Java对接微信登录
今天我们来对接微信开放平台的网站应用登录 首先上文档链接:https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login ...
- 微信OAuth2网页授权
using System; using System.Collections.Generic; using System.Linq; using System.Text; using YTO.WeiX ...
- 微信开发网页授权OAuth2.0注意事项
如图所示
- NodeJs 开发微信公众号(四)微信网页授权
微信的网页授权指的是在微信公众号中访问第三方网页时获取用户地理.个人等信息的权限.对于开发了自己的网页app应用时,获取个人的信息非常重要.上篇博客讲到了注册时可以获取用户的信息,很多人会问为什么还需 ...
随机推荐
- Jenkins发布服务报错Fatal error: put encountered an exception while uploading磁盘空间不足处理 No space left on device
Jenkins发布服务报错Fatal error: put encountered an exception while uploading磁盘空间不足处理 No space left on devi ...
- jdk17+spring6下打jar包
由于特定情况,本机下有多个jdk,而JAVA_HOME又只有一个. 本人习惯在命令行下一个命令编译打包程序,如何解决这个问题? 研究了不少时间,得到了两个解决方案: 1.使用bat -- 非常烂 ...
- 10-Python进程与线程
Python进程 创建新进程 from multiprocessing import Process import time def run_proc(name): #子进程要执行的代码 for i ...
- 开启PHP-GD库
话不多说,上教程 环境 CentOS7 1. 安装php-gd yum install php-gd 2. 定位gd.so位置 rpm -qal | grep gd.so #第一行即是 3. 定位配置 ...
- python重拾第九天-进程、线程、协程
本节内容 操作系统发展史介绍 进程.与线程区别 python GIL全局解释器锁 线程 语法 join 线程锁之Lock\Rlock\信号量 将线程变为守护进程 Event事件 queue队列 生产者 ...
- ClickHouse介绍(二)MergeTree引擎
MergeTree引擎 ClickHouse中有多种表引擎,包括MergeTree.外部存储.内存.文件.接口等,6大类,20多种表引擎.其中最强大的当属MergeTree(及其同一家族中)引擎.我们 ...
- 高通Android UEFI XBL 代码流程分析
高通Android UEFI XBL 代码流程分析 背景 之前学习的lk阶段点亮LCD的流程算是比较经典,但是高通已经推出了很多种基于UEFI方案的启动架构. 所以需要对这块比较新的技术进行学习.在学 ...
- Ubuntu 22.04单机部署K3s
安装docker 从docker官网获取最新的一键安装脚本,安装docker运行环境 curl -fsSL https://get.docker.com -o get-docker.sh sudo s ...
- k8s实战 ---- pod 基础
如果你对k8s还不了解,可以看下前文 k8s 实战 1 ---- 初识 (https://www.cnblogs.com/jilodream/p/18245222) 什么是pod,pod在 ...
- VUE手稿1