之前文章讲了JWT的登陆校验,SpringCloud等微服务下登录校验解决方案JWT
今天再讲一下微信的扫一扫登陆,并结合一下之前写的JWT
首先我们需要了解一下微信开发的官方文档
https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Wechat_Login.html
这是微信官方给的一张图
初一看感觉和微信对接流程还是挺复杂,但是仔细看完上边链接里的文档,然后再实践操作一下就会发现不是想象的那么难
这个流程简单的来说就是:首先需要给前端拼装生成一个微信扫一扫登录url,然后前端控制跳转打开这个url,给用户展示一个二维码,用户扫一扫确认授权后,微信回调我们服务器的接口,这个回调接口里的逻辑应该是拿到微信回调我们时回传的授权code码,再去调用微信的服务获取access_token,获取到的access_token的返回值里有openId,用openId再次调微信服务去拿用户的信息。下面代码实现一下
首先我们需要在微信开放平台注册一个账号,然后在管理中心添加自己或者公司的网站应用,等待通过审核,就会出现下图所示的页面,我们主要需要里边的三个信息:appid、appsecret和授权回调域(redirect_url)
有了这个几个参数后,我们就可以开发了
准备一下配置文件和其他的配置
上边说的appid、appsecret和授权回调域(redirect_url)需要我们配置到配置文件里
package com.cj.wx_pay.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
/**
* 微信配置类
*/
@Configuration
@PropertySource(value="classpath:application.properties")
public class WeChatConfig {
/**
* 公众号appid
*/
@Value("${wxpay.appid}")
private String appId;
/**
* 公众号秘钥
*/
@Value("${wxpay.appsecret}")
private String appsecret;
/**
* 开放平台appid
*/
@Value("${wxopen.appid}")
private String openAppid;
/**
* 开放平台appsecret
*/
@Value("${wxopen.appsecret}")
private String openAppsecret;
/**
* 开放平台回调url
*/
@Value("${wxopen.redirect_url}")
private String openRedirectUrl;
/**
* 微信开放平台二维码连接
*/
private final static String OPEN_QRCODE_URL= "https://open.weixin.qq.com/" +
"connect/qrconnect?appid=%s&redirect_uri=%s&response_type=code&scope=snsapi_login&state=%s#wechat_redirect";
/**
* 开放平台获取access_token地址
*/
private final static String OPEN_ACCESS_TOKEN_URL="https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code";
/**
* 获取用户信息
*/
private final static String OPEN_USER_INFO_URL ="https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s&lang=zh_CN";
//......
//set get方法太占地方就不贴上来了
}
1、拼装微信扫一扫登录url的接口
/**
* 拼装微信扫一扫登录url
* accessPage是指用户当前所在的页面,就是用户扫码时当前所在页面
* @return
*/
@GetMapping("login_url")
@ResponseBody
public JsonData loginUrl(@RequestParam(value = "access_page",required = true)String accessPage) throws UnsupportedEncodingException {
String redirectUrl = weChatConfig.getOpenRedirectUrl(); //获取开放平台重定向地址,就是用户扫完码微信回调我们的地址
String callbackUrl = URLEncoder.encode(redirectUrl,"GBK"); //进行编码
String qrcodeUrl = String.format(weChatConfig.getOpenQrcodeUrl(),weChatConfig.getOpenAppid(),callbackUrl,accessPage);
return JsonData.buildSuccess(qrcodeUrl);
}
2、微信回调我们服务器的接口
大致逻辑是:用户扫码后,微信回调我们这个接口,它回调的时候会带着一个授权code过来
我们再带着这个code用httpClient去请求微信的服务,获取access_token,返回值里有openId,根据这个openId去我们库里查看看是否已经存在该用户,如果存在,则直接把user的信息返回,并用JWT加密生成token返回给前端;如果我们库里不存在该用户则我们需要用openId再次调微信服务去拿用户的信息,然后把拿到的用户信息入库并通过JWT加密生成token返回给前端。
/**
* 微信扫码登录,用户确认授权后,微信回调我们服务器的接口
* 这个接口里的逻辑应该是拿到微信回传的授权code码调用微信的服务获取access_token
* 获取的access_token的返回值里有openId,用openId再次调微信服务去拿用户的信息
* @param code
* @param state 用户扫码时当前所在页面
* @param response
* @throws IOException
*/
@GetMapping("/user/callback")
public void wechatUserCallback(@RequestParam(value = "code",required = true) String code,
String state, HttpServletResponse response) throws IOException {
User user = userService.saveWeChatUser(code);
if(user != null){
//生成jwt
String token = JwtUtils.geneJsonWebToken(user);
// state 当前用户的页面地址,需要拼接 http:// 这样才不会站内跳转
response.sendRedirect(state+"?token="+token+"&head_img="+user.getHeadImg()+"&name="+URLEncoder.encode(user.getName(),"UTF-8"));
}
}
package com.cj.wx_pay.service.impl;
import com.cj.wx_pay.config.WeChatConfig;
import com.cj.wx_pay.domain.User;
import com.cj.wx_pay.mapper.UserMapper;
import com.cj.wx_pay.service.UserService;
import com.cj.wx_pay.utils.HttpUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.Map;
@Service
public class UserServiceImpl implements UserService {
@Autowired
private WeChatConfig weChatConfig;
@Autowired
private UserMapper userMapper;
@Override
public User saveWeChatUser(String code) {
String accessTokenUrl = String.format(WeChatConfig.getOpenAccessTokenUrl(),weChatConfig.getOpenAppid(),weChatConfig.getOpenAppsecret(),code);
//获取access_token
Map<String ,Object> baseMap = HttpUtils.doGet(accessTokenUrl);
if(baseMap == null || baseMap.isEmpty()){ return null; }
String accessToken = (String)baseMap.get("access_token");
String openId = (String) baseMap.get("openid");
User dbUser = userMapper.findByopenid(openId);
if(dbUser!=null) { //更新用户,直接返回
return dbUser;
}
//获取用户基本信息
String userInfoUrl = String.format(WeChatConfig.getOpenUserInfoUrl(),accessToken,openId);
Map<String ,Object> baseUserMap = HttpUtils.doGet(userInfoUrl);
if(baseUserMap == null || baseUserMap.isEmpty()){
return null;
}
String nickname = (String)baseUserMap.get("nickname");
Double sexTemp = (Double) baseUserMap.get("sex");
int sex = sexTemp.intValue();
String province = (String)baseUserMap.get("province");
String city = (String)baseUserMap.get("city");
String country = (String)baseUserMap.get("country");
String headimgurl = (String)baseUserMap.get("headimgurl");
StringBuilder sb = new StringBuilder(country).append("||").append(province).append("||").append(city);
String finalAddress = sb.toString();
try {
//解决乱码
nickname = new String(nickname.getBytes("ISO-8859-1"), "UTF-8");
finalAddress = new String(finalAddress.getBytes("ISO-8859-1"), "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
User user = new User();
user.setName(nickname);
user.setHeadImg(headimgurl);
user.setCity(finalAddress);
user.setOpenid(openId);
user.setSex(sex);
user.setCreateTime(new Date());
userMapper.save(user);
return user;
}
}
package com.cj.wx_pay.utils;
import com.google.gson.Gson;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import java.util.HashMap;
import java.util.Map;
/**
* 封装http get post
*/
public class HttpUtils {
private static final Gson gson = new Gson();
/**
* get方法
* @param url
* @return
*/
public static Map<String,Object> doGet(String url){
Map<String,Object> map = new HashMap<>();
CloseableHttpClient httpClient = HttpClients.createDefault();
RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(5000) //连接超时
.setConnectionRequestTimeout(5000)//请求超时
.setSocketTimeout(5000)
.setRedirectsEnabled(true) //允许自动重定向
.build();
HttpGet httpGet = new HttpGet(url);
httpGet.setConfig(requestConfig);
try{
HttpResponse httpResponse = httpClient.execute(httpGet);
if(httpResponse.getStatusLine().getStatusCode() == 200){
String jsonResult = EntityUtils.toString( httpResponse.getEntity());
map = gson.fromJson(jsonResult,map.getClass());
}
}catch (Exception e){
e.printStackTrace();
}finally {
try {
httpClient.close();
}catch (Exception e){
e.printStackTrace();
}
}
return map;
}
/**
* 封装post
* @return
*/
public static String doPost(String url, String data,int timeout){
CloseableHttpClient httpClient = HttpClients.createDefault();
//超时设置
RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(timeout) //连接超时
.setConnectionRequestTimeout(timeout)//请求超时
.setSocketTimeout(timeout)
.setRedirectsEnabled(true) //允许自动重定向
.build();
HttpPost httpPost = new HttpPost(url);
httpPost.setConfig(requestConfig);
httpPost.addHeader("Content-Type","text/html; chartset=UTF-8");
if(data != null && data instanceof String){ //使用字符串传参
StringEntity stringEntity = new StringEntity(data,"UTF-8");
httpPost.setEntity(stringEntity);
}
try{
CloseableHttpResponse httpResponse = httpClient.execute(httpPost);
HttpEntity httpEntity = httpResponse.getEntity();
if(httpResponse.getStatusLine().getStatusCode() == 200){
String result = EntityUtils.toString(httpEntity);
return result;
}
}catch (Exception e){
e.printStackTrace();
}finally {
try{
httpClient.close();
}catch (Exception e){
e.printStackTrace();
}
}
return null;
}
}