【微信登录】一文看懂,Java实现网页授权与APP授权登录流程【详细版】

前言

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

官方文档

网页授权

https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html

APP授权

https://developers.weixin.qq.com/doc/oplatform/Mobile_App/WeChat_Login/Development_Guide.html

iOS 接入指南

https://developers.weixin.qq.com/doc/oplatform/Mobile_App/Access_Guide/iOS.html

Android 接入指南

https://developers.weixin.qq.com/doc/oplatform/Mobile_App/Access_Guide/Android.html

流程步骤说明(具体说明参考官方文档,这里只描述大体)

  • H5 微信授权步骤

1、引导用户进入授权页面同意授权,获取 code

2、通过 code 换取网页授权 access_token(与基础支持中的 access_token 不同)

3、如果需要,开发者可以刷新网页授权 access_token,避免过期

4、通过网页授权 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 微信授权步骤

  1. 第三方发起微信授权登录请求,微信用户允许授权第三方应用后,微信会拉起应用或重定向到第三方网站,并且带上授权临时票据 code 参数;

  2. 通过 code 参数加上 AppID 和 AppSecret 等,通过 API 换取 access_token;

  3. 通过 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

    APP 与 H5 获取 code 的区别:h5 是前端通过后台拿到授权 url,然后前端请求该 url 得到 code 再请求后台;APP 则是前端配合使用微信开放平台提供的 SDK 进行授权登录请求,用户同意授权后得到 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

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;
    }
}

 

         扫码二维码

      关注更多精彩内容

 

 

 

        

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值