【微信登录】Java实现微信网页授权与APP授权

5,007 阅读6分钟

前言

微信登录网页授权与APP授权
微信JSAPI支付
微信APP支付
微信APP和JSAPI退款
支付宝手机网站支付
支付宝APP支付
支付宝退款
以上我都放到个人公众号,搜一搜:JAVA大贼船,文末有公众号二维码!觉得个人以后开发会用到的可以关注一下哦!少走点弯路…

官方文档

H5微信登录授权文档地址

developers.weixin.qq.com/doc/offiacc…

APP微信登录授权

developers.weixin.qq.com/doc/oplatfo…

iOS 接入指南

developers.weixin.qq.com/doc/oplatfo…

Android 接入指南

developers.weixin.qq.com/doc/oplatfo…

流程步骤

H5微信登录授权
  • 引导用户进入授权页面同意授权,获取code
  • 通过code换取网页授权access_token(与基础支持中的access_token不同)
  • 如果需要,开发者可以刷新网页授权access_token,避免过期
  • 通过网页授权access_token和openid获取用户基本信息(支持UnionID机制)

**疑问1:**scope为snsapi_base和scope为snsapi_userinfo的区别?

snsapi_base是静默授权并自动跳转到回调页的,snsapi_userinfo是需要用户手动同意。

**疑问2:**网页授权access_token和普通access_token的区别?

普通access_token获取用户信息时,如果用户未关注,信息获取就为空。而网页授权access_token的获取,只要用户许可,就可以获得,不论用户是否关注。

**疑问3:**UnionID和openid的区别?

unionid对同一个微信开放平台下的不同应用(移动应用、网站应用和公众帐号)都是相同的。

而openid对同一个微信开放平台下的不同应用都是不相同的,如用户授权应用A和应用B,那么用户的两个openid是不相同的,并且一个应用对应一个openid,如用户在次授权给应用A,openid不变。

**疑问4:**关于UnionID机制

即如果开发者有多个公众号,或在公众号、移动应用之间统一用户帐号的需求,需要前往微信开放平台(open.weixin.qq.com)绑定公众号后,才可利用UnionID机制来满足上述需求。

踩过的坑
  • redirect_uri域名与后台配置不一致

解决:在公众号设置-功能设置-网页授权,配置前端存放txt文件的路径,如www.xxx.com/static,然后点击提交,可以事先测一下能不能访问到txt的内容。

  • 获取微信用户信息,返回的unionID为空

解决:前往微信开放平台,绑定该公众号,大功告成

APP微信登录授权
  • 第三方发起微信授权登录请求,微信用户允许授权第三方应用后,微信会拉起应用或重定向到第三方网站,并且带上授权临时票据code参数;
  • 通过code参数加上AppID和AppSecret等,通过API换取access_token;
  • 通过access_token进行接口调用,获取用户基本数据资源或帮助用户实现基本操作。

准备工作

natapp 内网穿透工具

开发阶段可用此工具获取域名

获取AppID和AppSecret

前往微信开放平台(open.weixin.qq.com)查看对应的应用详情

修改授权回调域名

前往公众平台官网中的“公众号功能-功能设置”的配置选项中,根据说明规范配置网页授权回调域名

代码实现(含APP和H5)

配置参数

application.yml

# 微信相关配置																		
wx:
  #商户 ID
  MCH_ID: 
  # 项目基础域名
  BASEURL: 
  #微信登录-用户同意后回调域名(前端域名)
  URL: 
  # 公众号APP_ID
  H_APP_ID: 
  # 公众号秘钥
  H_APP_SECRET: 
  # app的APP_ID
  A_APP_ID: 
  # APP的 秘钥
  A_APP_SECRET: 
  #微信登录-微信授权基本地址
  LOGIN_AUTH_BASE_URL: https://open.weixin.qq.com/connect/oauth2/authorize?
  #微信登录-获取ACCESS_TOKEN的URL
  LOGIN_ACCESS_TOKEN_URL: https://api.weixin.qq.com/sns/oauth2/access_token?
  #微信登录-获取登录人信息的url
  LOGIN_USER_INFO_URL: https://api.weixin.qq.com/sns/userinfo?
  #微信登录-用户同意后回调地址(前端地址)
  LOGIN_RETURN_URL: ${wx.URL}/static/weixinShouQuan.html
  #微信登录-应用授权作用域,snsapi_base (不弹出授权页面,直接跳转,只能获取用户openid),      snsapi_userinfo
  #(弹出授权页面,可通过openid拿到昵称、性别、所在地。并且,即使在未关注的情况下,只要用户授权,也能获取其信息 )
  SCOPE: snsapi_userinfo
读取参数

YmlParament

/**
 * 获取yml参数实体
 */
@Component
@Data
public class YmlParament {
	/*微信相关字段*/
	@Value("${wx.BASEURL}")
	private String baseurl;

	@Value("${wx.H_APP_ID}")
	private String h_app_id;
	
    @Value("${wx.A_APP_ID}")
	private String a_app_id;
    
	@Value("${wx.H_APP_SECRET}")
	private String h_app_secret;
    
    @Value("${wx.A_APP_SECRET}")
	private String a_app_secret;

	@Value("${wx.LOGIN_ACCESS_TOKEN_URL}")
	private String login_access_token_url;

	@Value("${wx.LOGIN_USER_INFO_URL}")
	private String login_user_info_url;

	@Value("${wx.LOGIN_AUTH_BASE_URL}")
	private String login_auth_base_url;

	@Value("${wx.LOGIN_RETURN_URL}")
	private String login_return_url;

	@Value("${wx.SCOPE}")
	private String scope;
}
获取code
  • H5获取code

前端通过后台拿到授权url,然后前端请求该url得到code再请求后台

WxController

	@ApiOperation("获取授权url")
	@PostMapping("/getWeiXinLoginUrl")
	public R getWeiXinLoginUrl() throws Exception {
		String url = ymlParament.getLogin_auth_base_url() + "appid=" + ymlParament.getH_app_id()+ "&redirect_uri=" + ymlParament.getLogin_return_url()
+ "&response_type=code"+ "&scope=snsapi_userinfo" + "&state=STATE#wechat_redirect";	
        //这里的R是自己自定义的,用于返回结果给前端
		return R.ok().data("redirectUrl", url);
	}
  • APP获取code

APP则是前端使用 SDK 请求授权登录,用户同意授权后得到code再去请求后台

iOS 平台应用授权登录接入代码示例(请参考 iOS 接入指南):

-(void)sendAuthRequest
{
	//构造SendAuthReq结构体
	SendAuthReq* req =[[[SendAuthReq alloc]init]autorelease];
	req.scope = @"snsapi_userinfo";
	req.state = @"123";
	//第三方向微信终端发送一个SendAuthReq消息结构
	[WXApi sendReq:req];
}

Android 平台应用授权登录接入代码示例(请参考 Android 接入指南):

{
	// send oauth request
	Final SendAuth.Req req = new SendAuth.Req();
	req.scope = "snsapi_userinfo";
	req.state = "wechat_sdk_demo_test";
	api.sendReq(req);
}
通过code换取网页授权access_token,然后通过access_token和openid拉取用户信息

WxController

/*H5和app都可以调用*/
	@ApiOperation("获取微信用户信息")
	@PostMapping(value = "/getWxUserInFo")
	public R getWxUserInFo(@RequestBody String body) throws Exception {
		String state = JacksonUtil.parseString(body, "state");
		String code = JacksonUtil.parseString(body, "code");
        //标志哪一个应用,用来获取对应的appid和appsecret
		Integer openIdType = JacksonUtil.parseInteger(body, "openIdType");
		//1、获取code
		if(IsNull.isNull(code) || IsNull.isNull(state)) {
			return R.error("参数不能为空");
		}
		//2、通过code获取accesstoken,UserWxOpenidEums是用来记录应用的,如type1是xxAPP,type2是xx服务号
		JSONObject accessToken=WxUtils.getAccessTokenByCode(code, 
 openIdType==UserWxOpenidEums.TYPE_1.getKey()?ymlParament.getA_app_id():ymlParament.getH_app_id(),
openIdType==UserWxOpenidEums.TYPE_1.getKey()?ymlParament.getA_app_secret():ymlParament.getH_app_secret(),
ymlParament.getLogin_access_token_url());
		//3、获取用户信息
		JSONObject userinfo=WxUtils.getUserinfo(openIdType==UserWxOpenidEums.TYPE_1.getKey()?ymlParament.getA_app_id():ymlParament.getH_app_id(),
accessToken.getString("openid"), accessToken.getString("access_token"), ymlParament.getLogin_user_info_url());
		if(!IsNull.isNull(userinfo.getString("errcode"))){
			return R.error(userinfo.getString("errmsg"));
	}
		return R.ok().data("token", token).data("userinfo",userinfo);
}

WxUtils

	/**
	 * =============>>登录<<=============
	 * 第二步
	 * 通过code获取access_token
	 * 正确时返回的JSON数据包如下:
	 * {
		  "access_token":"ACCESS_TOKEN",
		  "expires_in":7200,
		  "refresh_token":"REFRESH_TOKEN",
		  "openid":"OPENID",
		  "scope":"SCOPE" 
		}
	 */
	public static JSONObject getAccessTokenByCode(String code,String appId,String appSecret,String login_access_token_url) throws Exception {
		Map<String, String> map = new HashMap<String, String>();
		map.put("appid",appId);
		map.put("secret",appSecret);
		map.put("code",code);
		map.put("grant_type","authorization_code");
		return (JSONObject)JSON.parse(HttpUtil.get(login_access_token_url, map));
	}
	
	/**
	 * =============>>登录<<=============
	 * 第三步:刷新access_token(如果需要)
	 * 正确时返回的JSON数据包如下:
	 * { 
		  "access_token":"ACCESS_TOKEN",
		  "expires_in":7200,
		  "refresh_token":"REFRESH_TOKEN",
		  "openid":"OPENID",
		  "scope":"SCOPE" 
		}
	 */
	public static JSONObject refreshAccessToken(String appid,String refresh_token) throws Exception {
		Map<String, String> map = new HashMap<>();
		map.put("appid",appid);
		map.put("grant_type","refresh_token");
		map.put("refresh_token",refresh_token);
		return (JSONObject)JSON.parse(HttpUtil.get("https://api.weixin.qq.com/sns/oauth2/refresh_token?", map));
	}
	
	/**
	 * =============>>登录<<=============
	 * 第四部,获取用户信息
	 * 通过code获取access_token
	 * 正确时返回的JSON数据包如下:
	 * {   
		  "openid":" OPENID",
		  "nickname": NICKNAME,
		  "sex":"1",
		  "province":"PROVINCE",
		  "city":"CITY",
		  "country":"COUNTRY",
		  "headimgurl":       "http://thirdwx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/46",
		  "privilege":[ "PRIVILEGE1" "PRIVILEGE2"     ],
		  "unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
		}
	 * @throws Exception 
	 */
	public static JSONObject getUserinfo(String appid,String openid,String accessToken,String login_user_info_url) throws Exception {
		//先判断下accessToken是否有效
		HashMap<String, String> param =new HashMap<String, String>();
		param.put("access_token", accessToken);
		param.put("openid", openid);
		JSONObject check=(JSONObject)JSON.parse(HttpUtil.get("https://api.weixin.qq.com/sns/auth?access_token="+accessToken+"&openid="+openid, param));
		//检验授权凭证(access_token)是否有效,如果accessToken失效了,则刷新accessToken
		if(!check.getString("errcode").equals("0")) {
			accessToken=refreshAccessToken(appid, accessToken).getString("refresh_token");
		}
		param =new HashMap<String, String>();
		param.put("openid",openid);
	param.put("access_token",accessToken);
		param.put("lang","zh_CN");
	return (JSONObject) JSON.parse(HttpUtil.get(login_user_info_url, param));
	}
工具类

HttpUtil

public static String get(String urlStr, Map<String, String> parameters) throws IOException {
		URL url = new URL(urlStr);
		HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
		httpURLConnection.setDoInput(true);
		httpURLConnection.setDoOutput(true); // 设置该连接是可以输出的
		httpURLConnection.setRequestMethod("GET"); // 设置请求方式
		httpURLConnection.setRequestProperty("charset", "utf-8");
		PrintWriter pw = new PrintWriter(new BufferedOutputStream(httpURLConnection.getOutputStream()));

		StringBuffer parameter = new StringBuffer();
		parameter.append("1=1");
		for (Entry<String, String> entry : parameters.entrySet()) {
			parameter.append("&" + entry.getKey() + "=" + entry.getValue());
		}
		pw.write(parameter.toString());// 向连接中写数据(相当于发送数据给服务器)
		pw.flush();
		pw.close();
    
		BufferedReader br = new BufferedReader(new InputStreamReader(httpURLConnection.getInputStream(), "utf-8"));
		String line = null;
		StringBuilder sb = new StringBuilder();
		while ((line = br.readLine()) != null) { // 读取数据
			sb.append(line + "\n");
	}
		br.close();
	return sb.toString();
	}

JacksonUtil

public class JacksonUtil {
    public static String parseString(String body, String field) {
        ObjectMapper mapper = new ObjectMapper();
        JsonNode node;
        try {
            node = mapper.readTree(body);
            JsonNode leaf = node.get(field);
            if (leaf != null) {
                return leaf.asText();
            }
        } catch (IOException e) {
        	e.printStackTrace();
        }
        return null;
    }
    
    public static Integer parseInteger(String body, String field) {
        ObjectMapper mapper = new ObjectMapper();
        JsonNode node;
        try {
            node = mapper.readTree(body);
            JsonNode leaf = node.get(field);
            if (leaf != null) {
                return leaf.asInt();
            }
        } catch (IOException e) {
        	e.printStackTrace();
      }
        return null;
    }
}
感谢您赏脸收看,喜欢可以关注一下哦!一个分享java学习资源,技术文章,实战经验的公众号~

欢迎关注公众号哦~