[JAVA实现]微信公众号网页授权登录

网上搜资料时,网友都说官方文档太垃圾了不易看懂,如何如何的。现在个人整理了一个通俗易懂易上手的,希望可以帮助到刚接触微信接口的你。

请看流程图!看懂图,就懂了一半了:

其实整体流程大体只需三步:用户点击登录按钮(其实就相当于一个链接) ---》  用户点击授权登录  ----》  实现获取用户信息代码。

然后获取用户信息代码只需三步:获取code  ----》 通过code获取access_token和openId  ---》 通过access_token和openId获取用户信息(包含union)。

 

以上便是整体套路,当然官网上也有,但具体如何实现呢?

不着急,咱们一步一步来!

 

第一步:微信登录按钮

它其实就是一个连接,不过想得到这个链接,有一点点麻烦。

1、设置。 微信公众平台---》接口权限---》网页授权---》修改 ---》设置网页授权域名(域名,不含http://),其实就是微信调你的java方法的项目路径或项目域名,如:www.zzff.net/pp ---》点击设置后弹出页面(大致意思,将MP_verify_31qRIDcjN8ZD1lVJ.txt放在你项目路径下面,如:www.ffzz.net/pp/MP_verify_31qRIDcjN8ZD1lVJ.txt 能访问到) ---》点击确认,授权回调页面域名设置成功!

2、拼链接。   https://open.weixin.qq.com/connect/oauth2/authorize?appid=xxx公众号IDxxxxx   &  redirect_uri = 授权回调页面域名/你的action(即微信授权后跳向的地址)

& response_type=code(固定的) & scope = snsapi_userinfo(或者snsapi_base默认授权)  & state=STATE#wechat_redirect

 如:https://open.weixin.qq.com/connect/oauth2/authorize?appid=wxf0e81c3bee622d60&redirect_uri=http%3A%2F%2Fnba.bluewebgame.com%2Foauth_response.php&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect

这个链接中参数的具体含义,官方解释如下:

参数是否必须说明
appid 公众号的唯一标识
redirect_uri 授权后重定向的回调链接地址,请使用urlencode对链接进行处理
response_type 返回类型,请填写code
scope 应用授权作用域,snsapi_base (不弹出授权页面,直接跳转,只能获取用户openid),snsapi_userinfo (弹出授权页面,可通过openid拿到昵称、性别、所在地。并且,即使在未关注的情况下,只要用户授权,也能获取其信息
state 重定向后会带上state参数,开发者可以填写a-zA-Z0-9的参数值,最多128字节 
#wechat_redirect 无论直接打开还是做页面302重定向时候,必须带此参数

 

第二步:授权确认登录 

这一步最简单,第一步登录链接拼好后,在手机微信中打开,微信便会跳转到确认授权页面,点击确认授权即可。(这一步,不用开发者做处理!)

 

第三步:获取用户信息 (重点)
这一步便是真正的代码实现的地方。开篇便讲了,它只需三步:获取code  ----》 通过code获取access_token和openId  ---》 通过access_token和openId获取用户信息。

First:   获取code

Second:  获取网页授权access_token和openId  

Third:通过access_token和openId获取用户信息。

关于union机制的细节:如果开发者需要公众号微信登录和APP微信登录共用一个微信ID,那个就需要union机制了。其实很简单,只需在微信开放平台(open.weixin.qq.com)绑定公众号,获取用户信息时就会返回union字段。

 

具体代码实现为:实体 ----  方法  --- 工具

实体Oauth2Token:

package com.wfcm.wxUitls;

/**
* 类名: WeixinOauth2Token </br>
* 描述: 网页授权信息 </br>
* 创建时间: 2015-11-27 </br>
* 发布版本:V1.0 </br>
*/
public class Oauth2Token {
// 网页授权接口调用凭证
private String accessToken;
// 凭证有效时长
private int expiresIn;
// 用于刷新凭证
private String refreshToken;
// 用户标识
private String openId;
// 用户授权作用域
private String scope;

public String getAccessToken() {
return accessToken;
}

public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
}

public int getExpiresIn() {
return expiresIn;
}

public void setExpiresIn(int expiresIn) {
this.expiresIn = expiresIn;
}

public String getRefreshToken() {
return refreshToken;
}

public void setRefreshToken(String refreshToken) {
this.refreshToken = refreshToken;
}

public String getOpenId() {
return openId;
}

public void setOpenId(String openId) {
this.openId = openId;
}

public String getScope() {
return scope;
}

public void setScope(String scope) {
this.scope = scope;
}
}

 

实体SNSUserInfo:

package com.wfcm.wxUitls;

import java.util.List;

/**
* 类名: SNSUserInfo </br>
* 描述: 通过网页授权获取的用户信息 </br>
* 开发人员: wzf </br>
* 创建时间: 2015-11-27 </br>
* 发布版本:V1.0 </br>
*/
public class SNSUserInfo {
// 用户标识
private String openId;
// 用户昵称
private String nickname;
// 性别(1是男性,2是女性,0是未知)
private int sex;
// 国家
private String country;
// 省份
private String province;
// 城市
private String city;
// 用户头像链接
private String headImgUrl;
// 用户特权信息
private List<String> privilegeList;

private String unionid;

public String getUnionid() {
return unionid;
}

public void setUnionid(String unionid) {
this.unionid = unionid;
}

public String getOpenId() {
return openId;
}

public void setOpenId(String openId) {
this.openId = openId;
}

public String getNickname() {
return nickname;
}

public void setNickname(String nickname) {
this.nickname = nickname;
}

public int getSex() {
return sex;
}

public void setSex(int sex) {
this.sex = sex;
}

public String getCountry() {
return country;
}

public void setCountry(String country) {
this.country = country;
}

public String getProvince() {
return province;
}

public void setProvince(String province) {
this.province = province;
}

public String getCity() {
return city;
}

public void setCity(String city) {
this.city = city;
}

public String getHeadImgUrl() {
return headImgUrl;
}

public void setHeadImgUrl(String headImgUrl) {
this.headImgUrl = headImgUrl;
}

public List<String> getPrivilegeList() {
return privilegeList;
}

public void setPrivilegeList(List<String> privilegeList) {
this.privilegeList = privilegeList;
}
}

 

方法WxController(其中authorize() 方法就是请求第一步获取的链接):

package com.wfcm.controller;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Formatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.http.client.utils.URLEncodedUtils;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.util.IOUtils;
import com.wfcm.annotation.IgnoreSign;
import com.wfcm.annotation.IgnoreToken;
import com.wfcm.entity.WfMemberEntity;
import com.wfcm.service.WfMemberService;
import com.wfcm.service.WfMemberSessionService;
import com.wfcm.utils.FastJSONUtils;
import com.wfcm.utils.NetUtil;
import com.wfcm.utils.R;
import com.wfcm.wxUitls.AccessToken;
import com.wfcm.wxUitls.JsapiTicket;
import com.wfcm.wxUitls.Oauth2Token;
import com.wfcm.wxUitls.SNSUserInfo;

@Controller
@RequestMapping("/wx")
@ResponseBody
public class WxController {

private static Logger log = LoggerFactory.getLogger(WxController.class);


/**
* 向指定URL发送GET方法的请求
*
* @param url
* 发送请求的URL
* @param param
* 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
* @return URL 所代表远程资源的响应结果
*
* 用户同意授权,获取code
*/
@RequestMapping("/authorize")
@ResponseBody
@IgnoreToken
public static R authorize() {
String appid = "wxbb000000000e";
//String uri ="wftest.zzff.net/wx/weixinLogin";
String uri = urlEncodeUTF8("wftest.zzff.net/api/wx/weixinLogin");
String result = "";
BufferedReader in = null;
try {
String urlNameString = "https://open.weixin.qq.com/connect/oauth2/authorize?appid="+appid+"&redirect_uri="+uri+"&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect";

URL realUrl = new URL(urlNameString);
// 打开和URL之间的连接
URLConnection connection = realUrl.openConnection();
// 设置通用的请求属性
connection.setRequestProperty("accept", "*/*");
connection.setRequestProperty("connection", "Keep-Alive");
connection.setRequestProperty("user-agent",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
// 建立实际的连接
connection.connect();
// 获取所有响应头字段
Map<String, List<String>> map = connection.getHeaderFields();
// 遍历所有的响应头字段
for (String key : map.keySet()) {
System.out.println(key + "--->" + map.get(key));
}
// 定义 BufferedReader输入流来读取URL的响应
in = new BufferedReader(new InputStreamReader(
connection.getInputStream()));
String line =null;
while ((line = in.readLine()) != null) {
result += line;
}
/* com.alibaba.fastjson.JSONObject jsonObj= FastJSONUtils.getJSONObject(result);
String access_token = jsonObj.getString("access_token");
long expires_in = Long.valueOf(jsonObj.getString("expires_in"));
*/
} catch (Exception e) {
System.out.println("发送GET请求出现异常!" + e);
e.printStackTrace();
}
// 使用finally块来关闭输入流
finally {
try {
if (in != null) {
in.close();
}
} catch (Exception e2) {
e2.printStackTrace();
}
}
return R.ok(result);
}

@RequestMapping("/weixinLogin")
@ResponseBody
@IgnoreToken
@IgnoreSign
public void weixinLogin(HttpServletRequest request,HttpServletResponse response) throws Exception {
// 用户同意授权后,能获取到code
Map<String, String[]> params = request.getParameterMap();//针对get获取get参数
String[] codes = params.get("code");//拿到code的值
String code = codes[0];//code
//String[] states = params.get("state");
//String state = states[0];//state

System.out.println("****************code:"+code);
// 用户同意授权
if (!"authdeny".equals(code)) {
// 获取网页授权access_token
Oauth2Token oauth2Token = getOauth2AccessToken("wxb0000000000e", "4c22222233333335555a9", code);
System.out.println("***********************************oauth2Token信息:"+oauth2Token.toString());
// 网页授权接口访问凭证
String accessToken = oauth2Token.getAccessToken();
// 用户标识
String openId = oauth2Token.getOpenId();
// 获取用户信息
SNSUserInfo snsUserInfo = getSNSUserInfo(accessToken, openId);
System.out.println("***********************************用户信息unionId:"+snsUserInfo.getUnionid()+"***:"+snsUserInfo.getNickname());
// 设置要传递的参数

//具体业务start

//具体业务end

String url = "http://wftest.zzff.net/#/biddd?from=login&tokenId="+snsUserInfo.getUnionid();

response.sendRedirect(url);
return ;
}
}


/**
* 获取网页授权凭证
*
* @param appId 公众账号的唯一标识
* @param appSecret 公众账号的密钥
* @param code
* @return WeixinAouth2Token
*/
public static Oauth2Token getOauth2AccessToken(String appId, String appSecret, String code) {
Oauth2Token wat = null;
// 拼接请求地址
String requestUrl = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code";
requestUrl = requestUrl.replace("APPID", appId);
requestUrl = requestUrl.replace("SECRET", appSecret);
requestUrl = requestUrl.replace("CODE", code);
// 获取网页授权凭证
com.alibaba.fastjson.JSONObject jsonObject = JSON.parseObject(NetUtil.get(requestUrl));
if (null != jsonObject) {
try {
wat = new Oauth2Token();
wat.setAccessToken(jsonObject.getString("access_token"));
wat.setExpiresIn(jsonObject.getInteger("expires_in"));
wat.setRefreshToken(jsonObject.getString("refresh_token"));
wat.setOpenId(jsonObject.getString("openid"));
wat.setScope(jsonObject.getString("scope"));
} catch (Exception e) {
wat = null;
int errorCode = jsonObject.getInteger("errcode");
String errorMsg = jsonObject.getString("errmsg");
log.error("获取网页授权凭证失败 errcode:{} errmsg:{}", errorCode, errorMsg);
}
}
return wat;
}

/**
* 通过网页授权获取用户信息
*
* @param accessToken 网页授权接口调用凭证
* @param openId 用户标识
* @return SNSUserInfo
*/
public static SNSUserInfo getSNSUserInfo(String accessToken, String openId) {
SNSUserInfo snsUserInfo = null;
// 拼接请求地址
String requestUrl = "https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID";
requestUrl = requestUrl.replace("ACCESS_TOKEN", accessToken).replace("OPENID", openId);
// 通过网页授权获取用户信息
com.alibaba.fastjson.JSONObject jsonObject = JSON.parseObject(NetUtil.get(requestUrl));

if (null != jsonObject) {
try {
snsUserInfo = new SNSUserInfo();
// 用户的标识
snsUserInfo.setOpenId(jsonObject.getString("openid"));
// 昵称
snsUserInfo.setNickname(jsonObject.getString("nickname"));
// 性别(1是男性,2是女性,0是未知)
snsUserInfo.setSex(jsonObject.getInteger("sex"));
// 用户所在国家
snsUserInfo.setCountry(jsonObject.getString("country"));
// 用户所在省份
snsUserInfo.setProvince(jsonObject.getString("province"));
// 用户所在城市
snsUserInfo.setCity(jsonObject.getString("city"));
// 用户头像
snsUserInfo.setHeadImgUrl(jsonObject.getString("headimgurl"));
// 用户特权信息
List<String> list = JSON.parseArray(jsonObject.getString("privilege"),String.class);
snsUserInfo.setPrivilegeList(list);
//与开放平台共用的唯一标识,只有在用户将公众号绑定到微信开放平台帐号后,才会出现该字段。
snsUserInfo.setUnionid(jsonObject.getString("unionid"));
} catch (Exception e) {
snsUserInfo = null;
int errorCode = jsonObject.getInteger("errcode");
String errorMsg = jsonObject.getString("errmsg");
log.error("获取用户信息失败 errcode:{} errmsg:{}", errorCode, errorMsg);
}
}
return snsUserInfo;
}

/**
* URL编码(utf-8)
*
* @param source
* @return
*/
public static String urlEncodeUTF8(String source) {
String result = source;
try {
result = java.net.URLEncoder.encode(source, "utf-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return result;
}

private static String byteToHex(final byte[] hash) {
Formatter formatter = new Formatter();
for (byte b : hash)
{
formatter.format("%02x", b);
}
String result = formatter.toString();
formatter.close();
return result;
}

private static String create_nonce_str() {
return UUID.randomUUID().toString();
}

private static String create_timestamp() {
return Long.toString(System.currentTimeMillis() / 1000);
}
}

工具NetUtil:

package com.wfcm.utils;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import org.apache.commons.httpclient.NameValuePair;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.message.BasicNameValuePair;

/**
* Created by Song on 2016/11/28.
* 基于HttpClient提供网络访问工具
*/
public final class NetUtil {
public static CloseableHttpClient httpClient = HttpClientBuilder.create().build();

/**
* get请求获取String类型数据
* @param url 请求链接
* @return
*/
public static String get(String url){
StringBuffer sb = new StringBuffer();
HttpGet httpGet = new HttpGet(url);
try {
HttpResponse response = httpClient.execute(httpGet); //1

HttpEntity entity = response.getEntity();
InputStreamReader reader = new InputStreamReader(entity.getContent(),"utf-8");
char [] charbufer;
while (0<reader.read(charbufer=new char[10])){
sb.append(charbufer);
}
}catch (IOException e){//1
e.printStackTrace();
}finally {
httpGet.releaseConnection();
}
return sb.toString();
}

/**
* post方式请求数据
* @param url 请求链接
* @param data post数据体
* @return
*/
@SuppressWarnings("unchecked")
public static String post(String url, Map<String,String> data){
StringBuffer sb = new StringBuffer();
HttpPost httpPost = new HttpPost(url);
List<NameValuePair> valuePairs = new ArrayList<NameValuePair>();
if(null != data) {
for (String key : data.keySet()) {
valuePairs.addAll((Collection<? extends NameValuePair>) new BasicNameValuePair(key, data.get(key)));
}
}
try {
httpPost.setEntity(new UrlEncodedFormEntity((List<? extends org.apache.http.NameValuePair>) valuePairs));
HttpResponse response = httpClient.execute(httpPost);
HttpEntity httpEntity = response.getEntity();
BufferedInputStream bis = new BufferedInputStream(httpEntity.getContent());
byte [] buffer;
while (0<bis.read(buffer=new byte[128])){
sb.append(new String(buffer,"utf-8"));
}
}catch (UnsupportedEncodingException e){//数据格式有误
e.printStackTrace();
}catch (IOException e){//请求出错
e.printStackTrace();
}finally {
httpPost.releaseConnection();
}
return sb.toString();
}


}

 R类:

package com.wfcm.utils;

import java.util.HashMap;
import java.util.Map;

/**
* 返回数据
*
* @author xlf
* @email xlfbe696@gmail.com
* @date 2017年4月19日 上午11:58:56
*/
public class R extends HashMap<String, Object> {
private static final long serialVersionUID = 1L;

public static final String SUCCESS = "success";

public static final String EXCEPTION = "exception";

public static final Integer SUCCESSCODE = 0;

public static final Integer EXCEPTIONCODE = 500;

public R() {
put("errCode", 0);
put("msg", SUCCESS);
}

public R(int code, String msg){
put("errCode", code);
put("msg", msg);
}

public static R error() {
return error(500, "未知异常,请联系管理员");
}

public static R error(String msg) {
return error(500, msg);
}

public static R error(int code, String msg) {
R r = new R();
r.put("errCode", code);
r.put("msg", msg);
return r;
}

public static R ok(String msg) {
R r = new R();
r.put("msg", msg);
return r;
}

public static R ok(Map<String, Object> map) {
R r = new R();
r.putAll(map);
return r;
}

public static R ok() {
return new R();
}

public R put(String key, Object value) {
super.put(key, value);
return this;
}
}

 

OK,大功告成!整体流程已经搭建起来,读懂了这些代码差不多就明白了整个流程了,然后再看官方文档,你会觉得读起来很顺畅,而不是刚开始那种味同嚼蜡的感觉。你只需再根据官方文档仔细检查检查流程,有没有需要完善的地方,就可以了。

还等什么呢,赶快敲实现功能吧!!!

posted @ 2020-05-07 15:11  YoungDeng  阅读(5927)  评论(1编辑  收藏  举报