最近因项目需要,需要在项目中实现微信扫码这一功能,微信扫码登陆是现在非常常用的一种技术,很多地方都会用到扫码,不仅方便快捷而且也比较安全,在参考了一些大佬的文章后把我自己的理解分享给大家,欢迎大家指正其中的不足之处,谢谢。
准备工作
1.使用微信登录是要在微信开放平台注册一个开发者账户并经行验证,验证通过后需要创建网站应用,并且需要一个域名来作为为微信的回调,创建好网站应用后会获取到一个appid和appsecret,配置好回调域,到此一些前置需求就结束了。
微信开发平台链接 https://open.weixin.qq.com/
2.了解微信登录的流程及相关技术
这些在开发文档中都有介绍,这里我向大家讲述一下我自己理解的微信登录流程
首先,官方给出了基本的流程图,但其中的细节并没有细说,下图是官方给出的流程图
1.获取code
首先,用户需要在第三方应用发起微信扫码登录的请求,然后第三方应用通过OAuth2.0向微信开放平台发起请求拉起扫码登陆请求,微信开发平台会通过网络应用的回调域返回给第三方应用一个扫码界面,用户扫码确认登录,微信开发平台会拉起或重定向到第三方并携带临时票据(code)
2.通过code获取令牌(access_token)
第三方应用获取到微信开发平台返回的code后,向微信开放平台根据其提供的连接获取token
获取第一步的 code 后,请求以下链接获取 access_token:
https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
因为appid和其他参数都非常冗长,为了避免出错,将其设置为常量
/**
* 获取令牌常量类
* @author
*/
public class WxConstants {
public static final String APPID = "自己网站应用的APPID ";
public static final String SECRET = "自己网站应用的SECRET ";
/**
* 通过code appid secret 获取token令牌
*/
public static final String GET_TOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code";
/**
* 获取用户信息
*/
public static final String GET_USER_URL = "https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID";
}
返回说明:
{
"access_token": "ACCESS_TOKEN",
"expires_in": 7200,
"refresh_token": "REFRESH_TOKEN",
"openid": "OPENID",
"scope": "SCOPE",
"unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
}
参数 说明
access_token 接口调用凭证
expires_in access_token接口调用凭证超时时间,单位(秒)
refresh_token 用户刷新access_token
openid 授权用户唯一标识
scope 用户授权的作用域,使用逗号(,)分隔
刷新access_token有效期
access_token是调用授权关系接口的调用凭证,由于access_token有效期(目前为2个小时)较短,当access_token超时后,可以使用refresh_token进行刷新,access_token刷新结果有两种:
1. 若access_token已超时,那么进行refresh_token会获取一个新的access_token,新的超时时间;
2. 若access_token未超时,那么进行refresh_token不会改变access_token,但超时时间会刷新,相当于续期access_token。
refresh_token拥有较长的有效期(30天),当refresh_token失效的后,需要用户重新授权。
请求方法
获取第一步的code后,请求以下链接进行refresh_token:
https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=APPID&grant_type=refresh_token&refresh_token=REFRESH_TOKEN
参数说明
参数 是否必须 说明
appid 是 应用唯一标识
grant_type 是 填refresh_token
refresh_token 是 填写通过access_token获取到的refresh_token参数
3.实现微信扫码登录
拿到access_token后我们就可以获取到登陆用户的相关信息并进行操作,拿到用户数据才是这一功能的开始,如何实现用户扫码登录就需要我们自己完成数据的操作。
在该项目中,有一个表用来存储用户扫码登录的所有数据,还有一些表与其关联,例如用户信息表,用户登录表等一些需要用到该数据的表。
用户扫码后,拿到用户数据后对其经行判断,是第一次登陆还是已有账号,如果是已有账号,只需要将微信登录的相关信息与其对应的用户信息关联起来即可,如果是第一次登录则需要在系统中注册一个账号用来存储该用户的信息。
/**
* 微信登录
* @param map
* @return
*/
@Override
public Ajaxresult wechat(Map<String, String> map) {
String code=map.get("code");
System.out.println(code);
if (StringUtils.isEmpty(code)){
throw new MyExpection("系统错误");
}
//通过code appid secret 获取token令牌
String url= WxConstants.GET_TOKEN_URL.replace("APPID", WxConstants.APPID)
.replace("SECRET", WxConstants.SECRET)
.replace("CODE",code);
//发送请求,获取token
String obj = HttpClientUtils.httpGet(url);
System.out.println(obj);
// 把json字符串转成json对象
JSONObject jsonObject = JSONObject.parseObject(obj);
//获取token和openid
String token = jsonObject.getString("access_token");
String openid = jsonObject.getString("openid");
System.out.println(openid+"==========================================");
//通过openid和userid查询对象,如果对象不为空则说明登陆过,对象为空则说明没有登陆过
WxUser wxUser = wxUserMapper.findByOpenId(openid);
// 第一次扫描的时候 获取用户信息
String param = "?token="+token+"&openid="+openid;
//判断对象是否存在和userId是存在
if (wxUser !=null && wxUser.getUser_id()!=null){
//获取用户信息
LoginInfo loginInfo=loginInfoMapper.findByUserId(wxUser.getUser_id());
// 4,1 获取token
String userToken = UUID.randomUUID().toString();
// 把用户信息存入redis中,过期时间30分钟
redisTemplate.opsForValue().set(userToken,
loginInfo,
30,
TimeUnit.MINUTES);
Map<String,Object> wxMap=new HashMap<>();
wxMap.put("token", userToken);
// 把用户名和密码设置为空
loginInfo.setPassword("");
loginInfo.setSalt("");
// 对象 存对象,要序列化
wxMap.put("loginInfo",loginInfo);
return Ajaxresult.me().setResultObj(wxMap);
}
return Ajaxresult.me().setSuccess().setResultObj(param);
}
/**
* 微信注册-绑定
* @param map
* @return
*/
@Override
public Map<String, Object> binder(Map<String, String> map) {
String phone = map.get("phone");
String verifyCode = map.get("verifyCode");
String accessToken = map.get("accessToken");
String openId = map.get("openId");
//1.空校验
if(StringUtils.isEmpty(phone)){
throw new MyExpection("电话号码不能为空,请输入电话号码");
}
//2.验证码校验
//获取验证码
Object obj = redisTemplate.opsForValue().get("binder:"+phone);
String time = obj.toString().split(":")[1];
//1.验证码是否过期
if(System.currentTimeMillis() - Long.valueOf(time)>180*1000){
throw new MyExpection("验证码过期,请重新获取验证码");
}else {
// 2,判断验证码是否一致
//获取验证码
String code = obj.toString().split(":")[0];
if (! verifyCode.equals(code)){
throw new MyExpection("验证码错误,请重新输入");
}
}
//3.判断手机号是否被注册过
User user = userMapper.findByPhone(phone);
LoginInfo loginInfo=null;
User userTemp=null;
//注册过
if (user!=null){
loginInfo=loginInfoMapper.findByUserId(userTemp.getId());
System.out.println(loginInfo);
userTemp=user;
}else {
//没注册过,保存三张表 loginInfo user wxUser
userTemp=initUser(phone);
loginInfo=initLoginInfo(userTemp);
//将数据存储到对应的表中
loginInfo.setType(1);
loginInfoMapper.add(loginInfo);
//将信息存储到user表中
userTemp.setLogininfo(loginInfo);
userMapper.add(userTemp);
}
//将信息保存到wxUser中
String replace = HttpClientUtils.httpGet(WxConstants.GET_USER_URL
.replace("ACCESS_TOKEN", accessToken)
.replace("OPENID", openId));
// System.out.println(obj+"=======================================");
//将信息添加到wxUser中
WxUser wxUser=initWxuser(replace);
//绑定
wxUser.setUser_id(userTemp.getId());
//保存到表中
wxUserMapper.add(wxUser);
//将数据保存到redis中
String userToken = UUID.randomUUID().toString();
// 把用户信息存入redis中,过期时间30分钟
redisTemplate.opsForValue().set(userToken,
loginInfo,
30,
TimeUnit.MINUTES);
Map<String,Object> binderMap=new HashMap<>();
binderMap.put("token", userToken);
// 把用户名和密码设置为空
loginInfo.setPassword("");
loginInfo.setSalt("");
// 对象 存对象,要序列化
binderMap.put("loginInfo",loginInfo);
return binderMap;
}