一、微信登录介绍
1、准备工作
网站应用微信登录是基于 OAuth2.0 协议标准构建的微信 OAuth2.0 授权登录系统。如果想了解 OAuth2.0 可以参考我的另一篇博客:【编程开发】之 OAuth2 。
在进行微信 OAuth2.0 授权登录接入之前,需要完成以下步骤:
(1)、在微信开放平台注册开发者帐号:https://open.weixin.qq.com,注册时需要注意的是:
- 目前只支持企业类型注册,也就是说是对企业开放的,不对个人开放
- 注册之后会得到相应的微信 id(AppID) 和微信密钥(AppSecret)
(2)、使用邮箱激活账号
(3)、完善开发者资料
(4)、开发者资质认证:准备营业执照,1-2个工作日审批、300元
(5)、创建一个已审核通过的网站应用域名,这个网站域名将会用于微信授权登录后的地址回调
2、微信登录授权流程
微信 OAuth2.0 授权登录让微信用户使用微信身份安全登录第三方应用或网站,在微信用户授权登录已接入微信 OAuth2.0 的第三方应用后,第三方可以获取到用户的接口调用凭证(access_token)。
获取 access_token
时序图如下:
其中:
- 微信用户指的是进行微信登录的用户;
- 微信开放平台指的是微信授权方,即微信官方;
- 而第三方应用则是相对于第一方和第二方来说的,在这里,第一方和第二方指的是微信用户和微信开放平台,只有他们才能进行微信授权操作的,而我们的网站应用只是请求授权而已,是无法进行微信授权操作的,所以我们的应用在这里就是第三方应用
下面我们解释一下这个获取 access_token
时序图的流程:
- 1、微信用户使用微信扫描我们的微信登录授权二维码,请求登录我们的应用;
- 2、扫描成功后,我们的应用就会请求微信 OAuth2.0 授权登录;
- 3、此时,微信平台等待微信用户进行授权确认;
- 4、当用户点击确认授权之后,微信平台将会重定向到我们指定的回调地址,并且带上授权临时票据(code)参数;
- 5、我们拿到这个 code 之后,通过 code + 微信 id(appid) + 微信密钥(appsecreset)来请求微信平台获取
access_token
。
获取到 access_token
之后,通过 access_token
可以进行微信开放平台授权关系接口调用,从而可实现获取微信用户基本开放信息和帮助用户实现基础开放功能等.
总结整个登录流程为如下三个步骤:
- 第三方发起微信授权登录请求,微信用户允许授权第三方应用后,微信会拉起应用或重定向到第三方网站,并且带上授权临时票据code参数;
- 通过code参数加上AppID和AppSecret等,通过API换取access_token;
- 通过access_token进行接口调用,获取用户基本数据资源或帮助用户实现基本操作。
二、微信登录实战案例
1、创建用户登录服务模块
比如我们新建一个用于用户登录的 Spring Boot 微服务模块。
2、添加配置
application.properties 添加相关配置信息
# 微信开放平台 appid
wx.open.app_id=你的appid
# 微信开放平台 appsecret
wx.open.app_secret=你的appsecret
# 微信开放平台 重定向url
wx.open.redirect_url=http://你的服务器名称/api/ucenter/wx/callback
3、创建常量类
创建 util 包,创建 ConstantWxUtils.java 常量类:
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class ConstantWxUtils implements InitializingBean {
@Value("${wx.open.app_id}")
private String appId;
@Value("${wx.open.app_secret}")
private String appSecret;
@Value("${wx.open.redirect_url}")
private String redirectUrl;
public static String WX_OPEN_APP_ID;
public static String WX_OPEN_APP_SECRET;
public static String WX_OPEN_REDIRECT_URL;
@Override
public void afterPropertiesSet() throws Exception {
WX_OPEN_APP_ID = appId;
WX_OPEN_APP_SECRET = appSecret;
WX_OPEN_REDIRECT_URL = redirectUrl;
}
}
4、开发生成授权 URL
创建用于微信登录的 Controller 层,并添加生成微信二维码请求接口:
@CrossOrigin
@Controller // 只是请求地址不需要返回数据
@RequestMapping("/api/ucenter/wx")
public class WxApiController {
// 生成微信扫描二维码
@GetMapping("login")
public String getWxCode() {
// 微信开放平台授权baseUrl %s相当于?代表占位符
String baseUrl = "https://open.weixin.qq.com/connect/qrconnect" +
"?appid=%s" +
"&redirect_uri=%s" +
"&response_type=code" +
"&scope=snsapi_login" +
"&state=%s" +
"#wechat_redirect";
// 对redirect_url进行URLEncoder编码
String redirectUrl = ConstantWxUtils.WX_OPEN_REDIRECT_URL;
try {
redirectUrl = URLEncoder.encode(redirectUrl, "utf-8");
} catch (Exception e) {
e.printStackTrace();
}
// 设置%s里面值
String url = String.format(
baseUrl,
ConstantWxUtils.WX_OPEN_APP_ID,
redirectUrl,
"EduShop"
);
// 重定向到请求微信地址中
return "redirect:" + url;
}
}
说明如下:
- 请求生成授权 url 的地址是固定的,就是 “https://open.weixin.qq.com/connect/qrconnect” 拼接上授权 url 参数:
- appid:应用唯一标识,即微信id
- redirect_uri:重定向地址,一般需要使用 URLEncoder 对链接地址进行编码处理
- response_type:相应类型,其值固定为 code
- scope:应用授权作用域,拥有多个作用域用逗号(,)分隔,网页应用目前仅填写
snsapi_login
- state:非必须参数,用于保持请求和回调的状态,授权请求后原样带回给第三方。该参数可用于防止
csrf
攻击(跨站请求伪造攻击),建议第三方带上该参数,可设置为简单的随机数加 session 进行校验
- 由于请求授权 url 地址成功之后将会跳转到微信扫码登录页面,所以我们不用使用
@RestController
注解,而是使用@Controller
注解,然后接口返回 String 类型的地址进行重定向跳转。
成功访问生成微信扫描二维码接口之后,前端页面将会跳转到微信授权登录二维码扫码页面,比如:
并且用户手机微信将会出现登录授权页面,等待用户点击确认登录。
5、开发回调 URL
前面我们请求微信登录授权 URL 时填写了一个回调地址 redirect_uri
参数,当微信用户点击确认登录时,微信平台将会调用这个回调地址,并且带上授权临时票据 code 参数(如果授权 URL 中填写了 state 参数,将会原样返回这个参数值)。
在回调接口中,我们需要使用到 http 请求工具 httpclient 和 JSon 处理工具,所以需要添加如下依赖:
<!--httpclient-->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<!--commons-io-->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</dependency>
<!--gson-->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
下面是我们编写的回调接口
@CrossOrigin
@Controller // 只是请求地址不需要返回数据
@RequestMapping("/api/ucenter/wx")
public class WxApiController {
@Autowired
private UcenterMemberService memberService;
// 微信扫码回调请求
@GetMapping("/callback")
public String callback(String code, String state) {
// 从 redis 中将 state 获取出来,和当前传入的 state 作比较
// 如果一致则放行,如果不一致则抛出异常:非法访问
try {
// 微信扫描回调返回的code值为临时票据,相当于验证码
// 使用code去请求微信固定的地址获取 accsess_token 和 openid
String baseAccessTokenUrl = "https://api.weixin.qq.com/sns/oauth2/access_token" +
"?appid=%s" +
"&secret=%s" +
"&code=%s" +
"&grant_type=authorization_code";
// 拼接三个参数:微信id、微信秘钥和code值
String accessTokenUrl = String.format(
baseAccessTokenUrl,
ConstantWxUtils.WX_OPEN_APP_ID,
ConstantWxUtils.WX_OPEN_APP_SECRET,
code
);
// 使用httpclient发送请求获取 accsess_token 和 openid
String accessTokenInfo = HttpClientUtils.get(accessTokenUrl);
// 提取 accsess_token 和 openid
Gson gson = new Gson();
HashMap mapAccessToken = gson.fromJson(accessTokenInfo, HashMap.class);
String access_token = (String)mapAccessToken.get("access_token");
String openid = (String)mapAccessToken.get("openid");
// 根据openid查询数据库是否已存在,不存在则添加到数据库里面
UcenterMember member = memberService.getMemberByOpenId(openid);
if (member == null) { //memeber是空,则表示没有相同的微信数据
// 根据accsess_token和openid去请求微信提供固定的地址,获取到扫描人信息
String baseUserInfoUrl = "https://api.weixin.qq.com/sns/userinfo" +
"?access_token=%s" +
"&openid=%s";
// 填充accsess_token和openid参数
String userInfoUrl = String.format(
baseUserInfoUrl,
access_token,
openid
);
// 发送请求
String userInfo = HttpClientUtils.get(userInfoUrl);
// 提取用于信息
HashMap userInfoMap = gson.fromJson(userInfo, HashMap.class);
String nickname = (String) userInfoMap.get("nickname"); // 昵称
String headimgurl = (String) userInfoMap.get("headimgurl"); // 头像地址
// 保存用户信息
member = new UcenterMember();
member.setNickname(nickname);
member.setOpenid(openid);
member.setAvatar(headimgurl);
memberService.save(member);
}
// 生产token字符串
String jwtToken = JwtUtils.getJwtToken(member.getId(), member.getNickname());
// 返回首页面并通过路径传递token字符串
return "redirect:http://localhost:3000?token=" + jwtToken;
} catch (Exception e) {
throw new EduShopException(20001, "登录失败");
}
}
}
回调过程如下:
- 1、微信平台回调我们的接口时,传递了授权临时票据 code 和 校验参数 state,我们使用 state 来进行校验防止
csrf
攻击(跨站请求伪造攻击); - 2、检验成功之后,使用
code + 微信id + 微信秘钥
向微信平台发送请求获取accsess_token
和openid
,这个请求地址也是固定的,只需填充参数即可,地址可以参考微信开发平台; - 3、这里使用的 http 请求发送工具使用的是自己写的一个 HttpClientUtils,由于代码比较多,可以参考博客:【编程开发】之 Java 开发常用工具 Utils 类;
- 4、由于请求
accsess_token
时返回的是一个 JSon 字符串对象,所以我们使用 gson 工具类把它转换为 HashMap,再进行参数获取; - 有了
accsess_token
和openid
我们就可以向微信开发平台获取用户相关信息,比如昵称、头像等等;
在最后,由于微信登录也涉及到单点登录问题,所以我们也使用了 jwt
工具生成 token 字符串。