跳到主要内容

微博账号信任登录

概述

  1. 微博账号信任登录包含:PC网站应用登录和H5网站应用登录
  2. 每种登录方式都需要配置不同的应用ID和应用秘钥
  3. 总体逻辑为:发起登录 -> 用户授权 -> 获取用户信息 -> 根据用户信息进行登录

PC网站应用登录

逻辑概述

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

详细逻辑可参考官方文档:授权机制微博API

流程图

image-20230804145334059

重点步骤说明:

  • 步骤3:构建的授权URL格式如下:

    https://open.weibo.cn/oauth2/authorize?client_id=APPID&redirect_uri=REDIRECT_URI&scope=SCOPE

    client_id:第三方应用在微博开放平台注册的APPKEY

    redirect_uri:成功授权后的回调地址,传的值需与在微博开放平台网站填写的回调地址一致

    scope:申请scope权限所需参数,程序中我们使用的是all,也就是获取全部权限。(可参考官方使用文档

  • 步骤10-11:

    调用的接口为通过code获取access_token接口,如下:

    https://api.weibo.com/oauth2/access_token?client_id=AppID&client_secret=AppSecret&grant_type=authorization_code&code=AuthorizationCode&redirect_uri=REDIRECT_URI

    请求参数说明:

    参数说明
    client_id第三方应用在微博开放平台注册的APPKEY
    client_secret在微博开放平台注册的应用所对应的AppSecret
    grant_type请求的类型,需填写 authorization_code
    code调用第一步 authorize 接口所获得的授权 code
    redirect_uri授权回调地址,传的值需与在开放平台网站填写的回调地址一致

    接口返回的数据格式如下:

    {
    "access_token": "ACCESS_TOKEN",
    "expires_in": "7200",
    "remind_in": "7200",
    "uid": "1404376560"
    }

    返回参数说明:

    参数描述
    access_token授权令牌,Access_Token
    expires_in该access token的有效期,单位为秒
    uid授权用户的UID
  • 步骤12:根据uid查询是否已经绑定注册了会员信息(查询es_connect表,此表存放的是第三方账号和系统会员的关联信息),如果未查询到数据,需要调用会员注册业务接口注册一条会员数据

  • 步骤13:会员登录后会创建会员token信息,由于是服务端进行的重定向跳转页面,因此跳转之前要将token信息存入cookie中方便前端读取

代码展示

Controller代码展示:

@Tag(name = "微博统一登陆接口")
@RestController
@RequestMapping("/buyer/connect/weibo")
public class LoginByWeiboController {

@Operation(summary = "PC发起信任登录")
@GetMapping("/pc/login")
public void pcLogin(){
//信任登录插件基类
AbstractConnectLoginPlugin connectionLogin = connectManager.getConnectionLogin(ConnectTypeEnum.WEIBO);
String loginUrl = connectionLogin.getLoginUrl(ClientTypeEnum.PC.name());
try {
ThreadContextHolder.getHttpResponse().sendRedirect(loginUrl);
} catch (IOException e) {
this.logger.error(e.getMessage(), e);
throw new ServiceException(MemberErrorCode.E131.name(), "联合登录失败");
}
}

}

构建授权登录URL代码展示:

@Component
public class WeiboAbstractConnectLoginPlugin extends AbstractConnectLoginPlugin {

/**
* 获取授权登录的url
*
* @param clientTypeEnum 客户端类型 {@link ClientTypeEnum}
* @return
*/
@Override
public String getLoginUrl(String clientTypeEnum) {
//获取参数
Map map = initConnectSetting(ThirdPlatformEnum.WEIBO, ClientTypeEnum.valueOf(clientTypeEnum));

//将回调地址存入redis中
String callBack = this.getCallBackUrl(ConnectTypeEnum.WEIBO.value(), clientTypeEnum);
return "https://api.weibo.com/oauth2/authorize?" +
"client_id=" + StringUtil.toString(map.get("app_key")) +
"&redirect_uri=" + callBack +
"&scope=all";
}

}

回调获取微博用户accessToken和uid代码展示:

@Component
public class WeiboAbstractConnectLoginPlugin extends AbstractConnectLoginPlugin {

/**
* 登录成功后的回调方法
*
* @param client 客户端类型 {@link ClientTypeEnum}
* @return
*/
@Override
public Auth2Token loginCallback(String client) {

debugger.log("进入 WeiboAbstractConnectLoginPlugin 回调");

//获取参数
Map map = initConnectSetting(ThirdPlatformEnum.WEIBO, ClientTypeEnum.valueOf(client));
HttpServletRequest request = ThreadContextHolder.getHttpRequest();

//获取code
String code = request.getParameter("code");
//通过code获取access_token及openid
String callBack = this.getCallBackUrl(ConnectTypeEnum.WEIBO.value(), client);
String url = "https://api.weibo.com/oauth2/access_token";

Map newMap = new HashMap<>(8);
newMap.put("client_id", StringUtil.toString(map.get("app_key")));
newMap.put("client_secret", StringUtil.toString(map.get("app_secret")));
newMap.put("grant_type", "authorization_code");
newMap.put("code", code);
newMap.put("redirect_uri", callBack);

debugger.log("向weibo发出请求,请求地址为:",url);

String content = HttpUtils.doPost(url, newMap, "UTF-8", 1000, 1000);

debugger.log("返回结果为:",content);

//获取openid
JSONObject json = JSONObject.fromObject(content);

String openid = json.getString("uid");
String accessToken = json.getString("access_token");

Auth2Token auth2Token = new Auth2Token();
auth2Token.setUnionid(openid);
auth2Token.setAccessToken(accessToken);

return auth2Token;
}

}

H5网站应用登录

逻辑概述

  1. 前端调用API获取微博H5授权地址
  2. 用户确认授权跳转至回调页面后,获取code
  3. 通过code进行接口调用获取access_token和获取用户基本数据资源或帮助用户实现基本操作

具体逻辑可参考官方文档:授权机制微博API

流程图

image-20230804154455553

重点步骤说明:

  • 步骤2:获取的授权URL格式如下:

    https://open.weibo.cn/oauth2/authorize?client_id=APPID&redirect_uri=REDIRECT_URI&display=mobile&state=weibo

    client_id:第三方应用在微博开放平台注册的APPKEY

    redirect_uri:成功授权后的回调地址,传的值需与在微博开放平台网站填写的回调地址一致

    display:授权确认页面可适配的终端类型,取值为:default - 默认的授权确认页,适用于PC端的网页浏览器,mobile - 适配移动端的授权确认页,适用于手机浏览器场景

    state:用于保持请求和回调的状态,在回调时,会在Query Parameter中回传该参数。开发者可以用这个参数验证请求有效性,也可以记录用户请求授权页前的位置,这个参数可用于防止跨站请求伪造(CSRF)攻击。

  • 步骤7:在步骤5跳转至回调页面时,回调地址上会携带code

  • 步骤9-10:

    调用的接口为通过code获取access_token接口,如下:

    https://api.weibo.com/oauth2/access_token?client_id=AppID&client_secret=AppSecret&grant_type=authorization_code&code=AuthorizationCode&redirect_uri=REDIRECT_URI

    请求参数说明:

    参数说明
    client_id第三方应用在微博开放平台注册的APPKEY
    client_secret在微博开放平台注册的应用所对应的AppSecret
    grant_type请求的类型,需填写 authorization_code
    code调用第一步 authorize 接口所获得的授权 code
    redirect_uri授权回调地址,传的值需与在开放平台网站填写的回调地址一致

    接口返回的数据格式如下:

    {
    "access_token": "ACCESS_TOKEN",
    "expires_in": "7200",
    "remind_in": "7200",
    "uid": "1404376560"
    }

    返回参数说明:

    参数描述
    access_token授权令牌,Access_Token
    expires_in该access token的有效期,单位为秒
    uid授权用户的UID
  • 步骤11-12:

    调用的接口为获取微博用户信息接口,如下:

    https://api.weibo.com/2/users/show.json?
    access_token=AccessToken&uid=UID

    接口返回的数据格式如下:

    {
    "id": 1404376560,
    "screen_name": "zaku",
    "name": "zaku",
    "province": "11",
    "city": "5",
    "location": "北京 朝阳区",
    "description": "人生五十年,乃如梦如幻;有生斯有死,壮士复何憾。",
    "url": "http://blog.sina.com.cn/zaku",
    "profile_image_url": "http://tp1.sinaimg.cn/1404376560/50/0/1",
    "domain": "zaku",
    "gender": "m",
    "followers_count": 1204,
    "friends_count": 447,
    "statuses_count": 2908,
    "favourites_count": 0,
    "created_at": "Fri Aug 28 00:00:00 +0800 2009",
    "following": false,
    "allow_all_act_msg": false,
    "geo_enabled": true,
    "verified": false,
    "status": {
    "created_at": "Tue May 24 18:04:53 +0800 2011",
    "id": 11142488790,
    "text": "我的相机到了。",
    "source": "<a href="http://weibo.com" rel="nofollow">新浪微博</a>",
    "favorited": false,
    "truncated": false,
    "in_reply_to_status_id": "",
    "in_reply_to_user_id": "",
    "in_reply_to_screen_name": "",
    "geo": null,
    "mid": "5610221544300749636",
    "annotations": [],
    "reposts_count": 5,
    "comments_count": 8
    },
    "allow_all_comment": true,
    "avatar_large": "http://tp1.sinaimg.cn/1404376560/180/0/1",
    "verified_reason": "",
    "follow_me": false,
    "online_status": 0,
    "bi_followers_count": 215
    }

    返回参数说明:

    image-20230804155352831

代码展示

Controller代码展示:

@Tag(name = "微博统一登陆接口")
@RestController
@RequestMapping("/buyer/connect/weibo")
public class LoginByWeiboController {

@Operation(summary = "获取授权页地址")
@Parameter(name = "redirectUri", description = "授权成功跳转地址(需要urlEncode整体加密)", required = true, in=ParameterIn.QUERY)
@GetMapping("/h5/getLoginUrl")
public String getLoginUrl(@RequestParam("redirectUri") String redirectUri) {
return loginWeiboManager.getLoginUrl(redirectUri);
}

@Operation(summary = "H5登陆")
@Parameters({
@Parameter(name = "code", description = "授权登陆返回的code", required = true , in=ParameterIn.QUERY),
@Parameter(name = "uuid", description = "此次登陆的随机数", required = true , in=ParameterIn.QUERY),
@Parameter(name = "redirect_uri", description = "授权成功跳转地址(需要urlEncode整体加密)", required = true, in=ParameterIn.QUERY)
})
@PostMapping("/h5/login")
public Map h5Login(@NotEmpty(message = "code不能为空") String code,
@NotEmpty(message = "uuid不能为空") String uuid,
@NotEmpty(message = "redirect_uri不能为空") String redirect_uri) {
return loginWeiboManager.wapLogin(code, uuid, redirect_uri);
}

}

业务接口代码展示:

@Service
public class LoginWeiboManagerImpl implements LoginWeiboManager {

/**
* 获取H5授权登录页面地址链接
*
* @param redirectUri 回调页面地址链接
* @return
*/
@Override
public String getLoginUrl(String redirectUri) {
Map<String, String> map = this.thirdPlatformAppManager.getConfigOnConnect(ThirdPlatformEnum.WEIBO, ClientTypeEnum.H5);
String appId = map.get("app_id");
StringBuffer loginBuffer = new StringBuffer("https://open.weibo.cn/oauth2/authorize?");
loginBuffer.append("client_id=").append(appId);
loginBuffer.append("&redirect_uri=").append(redirectUri);
loginBuffer.append("&display=mobile");
loginBuffer.append("&state=weibo");
return loginBuffer.toString();
}

/**
* H5端微博账号登录
*
* @param code 获取access_token的凭证
* @param uuid 此次登陆的随机数
* @param redirectUri 回调页面地址链接
* @return
*/
@Override
public Map wapLogin(String code, String uuid, String redirectUri) {
JSONObject jsonObject = getAccessToken(code,redirectUri);
LoginUserDTO loginUserDTO = getUserInfo(jsonObject.getString("access_token"),jsonObject.getString("uid"));
loginUserDTO.setUuid(uuid);
loginUserDTO.setTokenOutTime(null);
loginUserDTO.setRefreshTokenOutTime(null);
return loginManager.loginByUnionId(loginUserDTO);
}

/**
* 获取微博授权登录access_token
*
* @param code 获取access_token的凭证
* @param redirectUri 回调页面地址链接
* @return
*/
private JSONObject getAccessToken(String code, String redirectUri) {
Map<String, String> map = this.thirdPlatformAppManager.getConfigOnConnect(ThirdPlatformEnum.WEIBO, ClientTypeEnum.H5);
String appId = map.get("weibo_pc_app_key");
String secret = map.get("weibo_pc_app_secret");
String url = "https://api.weibo.com/oauth2/access_token";
Map<String, Object> dataMap = Maps.newHashMap();
dataMap.put("client_id",appId);
dataMap.put("client_secret",secret);
dataMap.put("grant_type","authorization_code");
dataMap.put("code",code);
try {
dataMap.put("redirect_uri", URLDecoder.decode(redirectUri,"utf-8"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
String post = HttpUtil.post(url, dataMap);
JSONObject jsonObject = JSONObject.fromObject(post);
if (jsonObject.get("access_token")==null){
throw new ServiceException("403","fail_to_get_access_token");
}
return jsonObject;
}

/**
* 获取微博用户信息
*
* @param accessToken 接口调用凭证
* @param uid 微博用户id
* @return
*/
private LoginUserDTO getUserInfo(String accessToken,String uid){
StringBuffer accessTokenBuffer = new StringBuffer("https://api.weibo.com/2/users/show.json?");
accessTokenBuffer.append("access_token=").append(accessToken);
accessTokenBuffer.append("&uid=").append(uid);
String user_json = HttpUtils.doGet(accessTokenBuffer.toString(),"UTF-8", 1000, 1000);
JSONObject jsonObject = JSONObject.fromObject(user_json);
LoginUserDTO loginUserDTO = new LoginUserDTO();
loginUserDTO.setUnionType(ConnectTypeEnum.WEIBO);
loginUserDTO.setUnionid(uid);
loginUserDTO.setHeadimgurl(jsonObject.getString("avatar_hd"));
loginUserDTO.setNickName(jsonObject.getString("screen_name"));
loginUserDTO.setSex("m".equals(jsonObject.getString("gender"))?1:0);
return loginUserDTO;
}

}