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应用时,获取个人的信息非常重要.上篇博客讲到了注册时可以获取用户的信息,很多人会问为什么还需 ...
随机推荐
- 一款.NET开源、功能强大、跨平台的绘图库 - OxyPlot
前言 今天大姚给大家分享一款.NET开源(MIT License).免费.跨平台.功能强大的绘图库,支持多平台使用(包括:WPF.UWP.WinForm.Silverlight.Xamarin.iOS ...
- 实验2.ARP实验
# 实验2.ARP实验 本实验用于验证arp以及arp表,arp缓存的使用,测试ping包时arp表的更新机制. 实验组 PC1 10.68.57.10 255.255.255.0 00-00-00- ...
- C++判断当前程序是否运行在Windows展台(Kiosk)模式下
Windows有一个展台(Kiosk)模式.展台模式可以使Windows作为数字标牌进行使用.具体请参考Windows 展台 配置完展台模式,重启设备后,Windows会以全屏的方式运行展台应用,无法 ...
- 面试官:你了解git cherry-pick吗?
事情要从一次不规范的代码开发开始说起 背景故事 时间 2024年某个风平浪静的周五晚上 地点 中国,北京,西二旗,某互联网大厂会议室 人物 小杰,小A,小B,老K 对话 老K:昨天提交的代码被测试打回 ...
- 【VMware vSAN】vSAN Data Protection Part 2:配置管理。
上篇文章"vSAN Data Protection Part 1:安装部署."介绍了如何安装及部署 VMware Snapshot Service Appliance 设备,并在 ...
- vue3 'alex' is defined but never used
解决方法 在package.json中的rules下加入 "no-unused-vars":"off" 即可
- 基于微信小程序+Springboot校园二手商城系统设计和实现
\n文末获取源码联系 感兴趣的可以先收藏起来,大家在毕设选题,项目以及论文编写等相关问题都可以给我加好友咨询 一. 前言介绍: 在当今社会的高速发展过程中,产生的劳动力越来越大,提高人们的生活水平和质 ...
- 利用FastAPI和OpenAI-Whisper打造高效的语音转录服务
最近好久没有写博客了,浅浅记录下如何将OpenAI-Whisper做成Web服务吧 介绍 在这篇指导性博客中,我们将探讨如何在Python中结合使用FastAPI和OpenAI-Whisper.Ope ...
- oeasy教您玩转vim - 005 - # 程序本质
程序本质 回忆上次内容 py 的程序是按照顺序 一行行挨排解释执行的 我们可以 python3 -m pdb hello.py 来对程序调试 调试的目的是去除 bug 别害怕 bug bug 会有 ...
- 配置Sprig security后Post请求无法使用
在学习过程中发现在配置完Spring security后,Post请求失效,无法增删改数据,这里可以通过在Spring Security 的Config类中增加 也可以自定义csrf,不过目前还不是很 ...