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实现微信登录(网页授权)的更多相关文章

  1. 第八篇 :微信公众平台开发实战Java版之如何网页授权获取用户基本信息

    第一部分:微信授权获取基本信息的介绍 我们首先来看看官方的文档怎么说: 如果用户在微信客户端中访问第三方网页,公众号可以通过微信网页授权机制,来获取用户基本信息,进而实现业务逻辑. 关于网页授权回调域 ...

  2. 微信公众平台开发实战Java版之如何网页授权获取用户基本信息

    第一部分:微信授权获取基本信息的介绍 我们首先来看看官方的文档怎么说: 如果用户在微信客户端中访问第三方网页,公众号可以通过微信网页授权机制,来获取用户基本信息,进而实现业务逻辑. 关于网页授权回调域 ...

  3. 微信 OAuth2 网页授权获取用户信息

    文档:http://mp.weixin.qq.com/wiki/17/c0f37d5704f0b64713d5d2c37b468d75.html !!! 微信跟用户没有关系类接口采用了OAUTH2 [ ...

  4. .NET Core中 实现H5微信登录(静默授权方式)

    需求 假设现在有一个H5需要有微信登录.手机号登录.邮箱登录 三种登录方式.让我们一起来看看微信登录如何实现吧 界面: 最终实现的效果图(登录成功后返回个人页): 因为微信登录目前没有实现移动端的其他 ...

  5. 【微信公众号】微信关于网页授权access_token和普通access_token的区别及两种不同方式授权

    微信官网网址:https://mp.weixin.qq.com/wiki/17/c0f37d5704f0b64713d5d2c37b468d75.html#.E9.99.84.EF.BC.9A.E6. ...

  6. 微信关于网页授权access_token和普通access_token的区别

    微信官网网址:https://mp.weixin.qq.com/wiki/17/c0f37d5704f0b64713d5d2c37b468d75.html#.E9.99.84.EF.BC.9A.E6. ...

  7. Java对接微信登录

    今天我们来对接微信开放平台的网站应用登录 首先上文档链接:https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login ...

  8. 微信OAuth2网页授权

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using YTO.WeiX ...

  9. 微信开发网页授权OAuth2.0注意事项

    如图所示

  10. NodeJs 开发微信公众号(四)微信网页授权

    微信的网页授权指的是在微信公众号中访问第三方网页时获取用户地理.个人等信息的权限.对于开发了自己的网页app应用时,获取个人的信息非常重要.上篇博客讲到了注册时可以获取用户的信息,很多人会问为什么还需 ...

随机推荐

  1. Java对象转Map<String,String>

    Java对象转Map<String,String> import org.springframework.beans.BeanUtils; import org.springframewo ...

  2. @Async异步方法对异常的处理,从内层向外层抛出机制

    @Async异步方法对异常的处理,从内层向外层抛出机制 @RequestMapping(value = "/test", method = RequestMethod.GET) p ...

  3. git客户端安装和使用

    需要安装三个软件 1.git客户端 点击下载 下载完成后一只next就行了. 2.git右键属性的扩展程序 点击下载 下载完成后一只next就行了 3.git中文包 点击下载 下载完成后一只next就 ...

  4. MapInfo 12.0 及 mapbasic 12.0 安装过程当中遇到的问题的汇总

    目录 MapInfo 12.0 及 mapbasic 12.0 安装过程当中遇到的问题的汇总 C++ 运行时库 Unable to load the CLR (-2147467263) 1) .NET ...

  5. Bloom Filter布隆过滤器

    简介 本质上布隆过滤器是一种数据结构,比较巧妙的概率型数据结构(probabilistic data structure),特点是高效地插入和查询,可以用来告诉你 "某样东西一定不存在或者可 ...

  6. selenium窗口之间的切换

    import time from selenium.webdriver import Edge from selenium.webdriver.common.by import By from sel ...

  7. NXP i.MX 8M Mini视频开发案例分享 (上)

    本文主要介绍i.MX 8M Mini的视频开发案例,包含基于GStreamer的视频采集.编解码.算法处理.显示以及存储案例,GigE工业相机测试说明,H.265视频硬件解码功能演示说明等. 注:本案 ...

  8. typora markdown笔记

    前言 此为个人markdown笔记,不定时更新. 正文 1. mermaid 使用 横向流程图 源码 graph LR a(起始)-->b(中间) b-->c1<-->d b- ...

  9. Lambda表达式常见用法

    Lambda介绍 Lambda,别名函数式编程 函数式编程是一种编程范式.它把计算当成是数学函数的求值,从而避免改变状态和使用可变数据.它是一种声明式的编程范式,通过表达式和声明而不是语句来编程. L ...

  10. var、let、const 区别?

    var 存在变量提升.let 只能在块级作用域内访问.const 用来定义常量,必须初始化,不能修改(对象特殊) 1.var[声明变量] var 没有块的概念,可以跨块访问,无法跨函数访问: 2.le ...