添加项目文件。
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
using JNPF.Extras.CollectiveOAuth.Config;
|
||||
using JNPF.Extras.CollectiveOAuth.Models;
|
||||
using JNPF.Extras.CollectiveOAuth.Utils;
|
||||
using JNPF.Extras.CollectiveOAuth.Cache;
|
||||
using Aop.Api;
|
||||
using Aop.Api.Request;
|
||||
using Aop.Api.Response;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace JNPF.Extras.CollectiveOAuth.Request;
|
||||
|
||||
public partial class AlipayMpAuthRequest : DefaultAuthRequest
|
||||
{
|
||||
private IAopClient aopClient;
|
||||
public AlipayMpAuthRequest(ClientConfig config) : base(config, new AlipayMPAuthSource())
|
||||
{
|
||||
aopClient = new DefaultAopClient(source.accessToken(), config.clientId, config.clientSecret, "json", "1.0", "RSA2", config.alipayPublicKey, "GBK", false);
|
||||
}
|
||||
|
||||
public AlipayMpAuthRequest(ClientConfig config, IAuthStateCache authStateCache) : base(config, new AlipayMPAuthSource(), authStateCache)
|
||||
{
|
||||
aopClient = new DefaultAopClient(source.accessToken(), config.clientId, config.clientSecret, "json", "1.0", "RSA2", config.alipayPublicKey, "GBK", false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 微信的特殊性,此时返回的信息同时包含 openid 和 access_token
|
||||
*
|
||||
* @param authCallback 回调返回的参数
|
||||
* @return 所有信息
|
||||
*/
|
||||
protected override AuthToken getAccessToken(AuthCallback authCallback)
|
||||
{
|
||||
AlipaySystemOauthTokenRequest request = new AlipaySystemOauthTokenRequest();
|
||||
request.GrantType = "authorization_code";
|
||||
request.Code = authCallback.auth_code;
|
||||
AlipaySystemOauthTokenResponse response = null;
|
||||
try
|
||||
{
|
||||
response = this.aopClient.Execute(request);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new Exception(e.Message);
|
||||
}
|
||||
if (response.IsError)
|
||||
{
|
||||
throw new Exception(response.SubMsg);
|
||||
}
|
||||
|
||||
var authToken = new AuthToken();
|
||||
authToken.accessToken = response.AccessToken;
|
||||
authToken.uid = response.UserId;
|
||||
authToken.expireIn = Convert.ToInt32(response.ExpiresIn);
|
||||
authToken.refreshToken = response.RefreshToken;
|
||||
authToken.userId = response.AlipayUserId;
|
||||
|
||||
return authToken;
|
||||
}
|
||||
|
||||
protected override AuthUser getUserInfo(AuthToken authToken)
|
||||
{
|
||||
string accessToken = authToken.accessToken;
|
||||
AlipayUserInfoShareRequest request = new AlipayUserInfoShareRequest();
|
||||
AlipayUserInfoShareResponse response = null;
|
||||
try
|
||||
{
|
||||
response = this.aopClient.Execute(request, accessToken);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new Exception(e.Message, e);
|
||||
}
|
||||
if (response.IsError)
|
||||
{
|
||||
throw new Exception(response.SubMsg);
|
||||
}
|
||||
|
||||
string province = response.Province, city = response.City;
|
||||
string location = string.Format("{0} {1}", !province.IsNullOrWhiteSpace() ? "" : province, !city.IsNullOrWhiteSpace() ? "" : city);
|
||||
|
||||
var authUser = new AuthUser();
|
||||
authUser.username = response.UserName.IsNullOrWhiteSpace() ? response.NickName : response.UserName;
|
||||
authUser.nickname = response.NickName;
|
||||
authUser.avatar = response.Avatar;
|
||||
authUser.location = location;
|
||||
authUser.uuid = response.UserId;
|
||||
authUser.gender = GlobalAuthUtil.getRealGender(response.Gender);
|
||||
authUser.token = authToken;
|
||||
authUser.source = source.getName();
|
||||
authUser.originalUser = response;
|
||||
authUser.originalUserStr = JsonConvert.SerializeObject(response);
|
||||
return authUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state}
|
||||
*
|
||||
* @param state state 验证授权流程的参数,可以防止csrf
|
||||
* @return 返回授权地址
|
||||
* @since 1.9.3
|
||||
*/
|
||||
public override string authorize(string state)
|
||||
{
|
||||
return UrlBuilder.fromBaseUrl(source.authorize())
|
||||
.queryParam("app_id", config.clientId)
|
||||
.queryParam("scope", config.scope.IsNullOrWhiteSpace() ? "auth_user" : config.scope)
|
||||
.queryParam("redirect_uri", config.redirectUri)
|
||||
.queryParam("state", getRealState(state))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
using JNPF.Extras.CollectiveOAuth.Cache;
|
||||
using JNPF.Extras.CollectiveOAuth.Config;
|
||||
using JNPF.Extras.CollectiveOAuth.Enums;
|
||||
using JNPF.Extras.CollectiveOAuth.Models;
|
||||
using JNPF.Extras.CollectiveOAuth.Utils;
|
||||
|
||||
namespace JNPF.Extras.CollectiveOAuth.Request;
|
||||
|
||||
public class BaiduAuthRequest : DefaultAuthRequest
|
||||
{
|
||||
public BaiduAuthRequest(ClientConfig config) : base(config, new BaiduAuthSource())
|
||||
{
|
||||
}
|
||||
|
||||
public BaiduAuthRequest(ClientConfig config, IAuthStateCache authStateCache)
|
||||
: base(config, new BaiduAuthSource(), authStateCache)
|
||||
{
|
||||
}
|
||||
|
||||
protected override AuthToken getAccessToken(AuthCallback authCallback)
|
||||
{
|
||||
string response = doPostAuthorizationCode(authCallback.code);
|
||||
var accessTokenObject = response.parseObject();
|
||||
this.checkResponse(accessTokenObject);
|
||||
|
||||
var authToken = new AuthToken();
|
||||
authToken.accessToken = accessTokenObject.getString("access_token");
|
||||
authToken.refreshToken = accessTokenObject.getString("refresh_token");
|
||||
authToken.expireIn = accessTokenObject.getInt32("expires_in");
|
||||
authToken.scope = accessTokenObject.getString("scope");
|
||||
|
||||
return authToken;
|
||||
}
|
||||
|
||||
protected override AuthUser getUserInfo(AuthToken authToken)
|
||||
{
|
||||
string response = doGetUserInfo(authToken);
|
||||
var userObj = response.parseObject();
|
||||
this.checkResponse(userObj);
|
||||
|
||||
var authUser = new AuthUser();
|
||||
authUser.uuid = userObj.getString("userid");
|
||||
authUser.username = userObj.getString("username");
|
||||
authUser.nickname = userObj.getString("username");
|
||||
|
||||
string protrait = userObj.getString("portrait");
|
||||
authUser.avatar = protrait.IsNullOrWhiteSpace() ? null : string.Format("http://himg.bdimg.com/sys/portrait/item/{0}.jpg", protrait);
|
||||
|
||||
authUser.remark = userObj.getString("userdetail");
|
||||
authUser.gender = GlobalAuthUtil.getRealGender(userObj.getString("sex"));
|
||||
|
||||
authUser.token = authToken;
|
||||
authUser.source = source.getName();
|
||||
authUser.originalUser = userObj;
|
||||
authUser.originalUserStr = response;
|
||||
return authUser;
|
||||
}
|
||||
|
||||
public override AuthResponse revoke(AuthToken authToken)
|
||||
{
|
||||
string response = doGetRevoke(authToken);
|
||||
var revokeObj = response.parseObject();
|
||||
this.checkResponse(revokeObj);
|
||||
// 返回1表示取消授权成功,否则失败
|
||||
AuthResponseStatus status = revokeObj.getInt32("result") == 1 ? AuthResponseStatus.SUCCESS : AuthResponseStatus.FAILURE;
|
||||
return new AuthResponse(status.GetCode(), status.GetDesc());
|
||||
}
|
||||
|
||||
public override AuthResponse refresh(AuthToken authToken)
|
||||
{
|
||||
string refreshUrl = UrlBuilder.fromBaseUrl(this.source.refresh())
|
||||
.queryParam("grant_type", "refresh_token")
|
||||
.queryParam("refresh_token", authToken.refreshToken)
|
||||
.queryParam("client_id", this.config.clientId)
|
||||
.queryParam("client_secret", this.config.clientSecret)
|
||||
.build();
|
||||
string response = HttpUtils.RequestGet(refreshUrl);
|
||||
var accessTokenObject = response.parseObject();
|
||||
this.checkResponse(accessTokenObject);
|
||||
|
||||
var newAuthToken = new AuthToken();
|
||||
newAuthToken.accessToken = accessTokenObject.getString("access_token");
|
||||
newAuthToken.refreshToken = accessTokenObject.getString("refresh_token");
|
||||
newAuthToken.expireIn = accessTokenObject.getInt32("expires_in");
|
||||
newAuthToken.scope = accessTokenObject.getString("scope");
|
||||
|
||||
return new AuthResponse(AuthResponseStatus.SUCCESS.GetCode(), AuthResponseStatus.SUCCESS.GetDesc(), newAuthToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state}
|
||||
*
|
||||
* @param state state 验证授权流程的参数,可以防止csrf
|
||||
* @return 返回授权地址
|
||||
* @since 1.9.3
|
||||
*/
|
||||
public override string authorize(string state)
|
||||
{
|
||||
return UrlBuilder.fromBaseUrl(source.authorize())
|
||||
.queryParam("response_type", "code")
|
||||
.queryParam("client_id", config.clientId)
|
||||
.queryParam("redirect_uri", config.redirectUri)
|
||||
.queryParam("display", "page")
|
||||
.queryParam("scope", "basic")
|
||||
.queryParam("state", getRealState(state))
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验请求结果
|
||||
*
|
||||
* @param response 请求结果
|
||||
* @return 如果请求结果正常,则返回JSONObject
|
||||
*/
|
||||
private void checkResponse(Dictionary<string, object> dic)
|
||||
{
|
||||
if (dic.ContainsKey("error") || dic.ContainsKey("error_code"))
|
||||
{
|
||||
throw new Exception($@"error_code: {dic.getString("error_code")}," +
|
||||
$" error_description: {dic.getString("error_description")}," +
|
||||
$" error_msg: {dic.getString("error_msg")}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
using JNPF.Extras.CollectiveOAuth.Cache;
|
||||
using JNPF.Extras.CollectiveOAuth.Config;
|
||||
using JNPF.Extras.CollectiveOAuth.Models;
|
||||
using JNPF.Extras.CollectiveOAuth.Utils;
|
||||
using System.Net;
|
||||
|
||||
namespace JNPF.Extras.CollectiveOAuth.Request;
|
||||
|
||||
public class CodingAuthRequest : DefaultAuthRequest
|
||||
{
|
||||
public CodingAuthRequest(ClientConfig config) : base(config, new CodingAuthSource())
|
||||
{
|
||||
}
|
||||
|
||||
public CodingAuthRequest(ClientConfig config, IAuthStateCache authStateCache)
|
||||
: base(config, new CodingAuthSource(), authStateCache)
|
||||
{
|
||||
}
|
||||
|
||||
protected override AuthToken getAccessToken(AuthCallback authCallback)
|
||||
{
|
||||
string response = doGetAuthorizationCode(authCallback.code);
|
||||
var accessTokenObject = response.parseObject();
|
||||
this.checkResponse(accessTokenObject);
|
||||
|
||||
var authToken = new AuthToken();
|
||||
authToken.accessToken = accessTokenObject.getString("access_token");
|
||||
authToken.expireIn = accessTokenObject.getInt32("expires_in");
|
||||
authToken.refreshToken = accessTokenObject.getString("refresh_token");
|
||||
return authToken;
|
||||
}
|
||||
|
||||
protected override AuthUser getUserInfo(AuthToken authToken)
|
||||
{
|
||||
string response = doGetUserInfo(authToken);
|
||||
var resData = response.parseObject();
|
||||
this.checkResponse(resData);
|
||||
|
||||
var userObj = resData.getString("data").parseObject();
|
||||
|
||||
var authUser = new AuthUser();
|
||||
authUser.uuid = userObj.getString("id");
|
||||
authUser.username = userObj.getString("name");
|
||||
authUser.nickname = userObj.getString("name");
|
||||
authUser.avatar = $"{"https://coding.net/"}{userObj.getString("avatar")}";
|
||||
authUser.blog = $"{"https://coding.net/"}{userObj.getString("path")}";
|
||||
authUser.company = userObj.getString("company");
|
||||
authUser.location = userObj.getString("location");
|
||||
authUser.email = userObj.getString("email");
|
||||
authUser.remark = userObj.getString("slogan");
|
||||
authUser.gender = GlobalAuthUtil.getRealGender(userObj.getString("sex"));
|
||||
|
||||
authUser.token = authToken;
|
||||
authUser.source = source.getName();
|
||||
authUser.originalUser = resData;
|
||||
authUser.originalUserStr = response;
|
||||
return authUser;
|
||||
}
|
||||
|
||||
protected override string doGetUserInfo(AuthToken authToken)
|
||||
{
|
||||
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
|
||||
return HttpUtils.RequestJsonGet(userInfoUrl(authToken));
|
||||
}
|
||||
|
||||
protected override string doGetAuthorizationCode(String code)
|
||||
{
|
||||
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
|
||||
return HttpUtils.RequestJsonGet(accessTokenUrl(code));
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state}
|
||||
*
|
||||
* @param state state 验证授权流程的参数,可以防止csrf
|
||||
* @return 返回授权地址
|
||||
* @since 1.9.3
|
||||
*/
|
||||
public override string authorize(string state)
|
||||
{
|
||||
return UrlBuilder.fromBaseUrl(source.authorize())
|
||||
.queryParam("response_type", "code")
|
||||
.queryParam("client_id", config.clientId)
|
||||
.queryParam("redirect_uri", config.redirectUri)
|
||||
.queryParam("scope", config.scope.IsNullOrWhiteSpace() ? "user" : config.scope)
|
||||
.queryParam("state", getRealState(state))
|
||||
.build();
|
||||
}
|
||||
/**
|
||||
* 校验请求结果
|
||||
*
|
||||
* @param response 请求结果
|
||||
* @return 如果请求结果正常,则返回Exception
|
||||
*/
|
||||
private void checkResponse(Dictionary<string, object> dic)
|
||||
{
|
||||
if (dic.ContainsKey("code") && dic.getInt32("code") != 0)
|
||||
{
|
||||
throw new Exception($"{dic.getString("msg")}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
using JNPF.Extras.CollectiveOAuth.Cache;
|
||||
using JNPF.Extras.CollectiveOAuth.Config;
|
||||
using JNPF.Extras.CollectiveOAuth.Models;
|
||||
using JNPF.Extras.CollectiveOAuth.Utils;
|
||||
using JNPF.Extras.CollectiveOAuth.Enums;
|
||||
using Newtonsoft.Json;
|
||||
using DingTalk.Api;
|
||||
using DingTalk.Api.Response;
|
||||
using DingTalk.Api.Request;
|
||||
using JNPF.Common.Security;
|
||||
|
||||
namespace JNPF.Extras.CollectiveOAuth.Request;
|
||||
|
||||
public class DingTalkScanAuthRequest : DefaultAuthRequest
|
||||
{
|
||||
public DingTalkScanAuthRequest(ClientConfig config) : base(config, new DingTalkScanAuthSource())
|
||||
{
|
||||
}
|
||||
|
||||
public DingTalkScanAuthRequest(ClientConfig config, IAuthStateCache authStateCache)
|
||||
: base(config, new DingTalkScanAuthSource(), authStateCache)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
protected override AuthToken getAccessToken(AuthCallback authCallback)
|
||||
{
|
||||
var map = new Dictionary<string, object>();
|
||||
map.Add("clientId", this.config.clientId);
|
||||
map.Add("clientSecret", this.config.clientSecret);
|
||||
map.Add("code", authCallback.code);
|
||||
map.Add("refreshToken", authCallback.code);
|
||||
map.Add("grantType", "authorization_code");
|
||||
var head = new Dictionary<string, object>() { { "Content-Type", "application/json" } };
|
||||
var response = HttpUtils.RequestPost(source.accessToken(), map.ToJsonString(), head);
|
||||
var accessTokenObject = response.parseObject();
|
||||
this.checkResponse(accessTokenObject);
|
||||
var res = new AuthToken();
|
||||
res.accessToken = accessTokenObject["accessToken"].ToString();
|
||||
res.refreshToken = accessTokenObject["refreshToken"].ToString();
|
||||
res.expireIn = Convert.ToInt16(accessTokenObject["expireIn"]);
|
||||
res.openId = accessTokenObject.ContainsKey("openId") ? accessTokenObject["openId"].ToString() : string.Empty;
|
||||
return res;
|
||||
}
|
||||
|
||||
protected override AuthUser getUserInfo(AuthToken authToken)
|
||||
{
|
||||
var httpHeader = new Dictionary<string, object>();
|
||||
httpHeader.Add("x-acs-dingtalk-access-token", authToken.accessToken);
|
||||
httpHeader.Add("Content-Type", "application/json");
|
||||
|
||||
var response = HttpUtils.RequestGet(userInfoUrl(authToken), httpHeader);
|
||||
|
||||
var data = response.parseObject();
|
||||
this.checkResponse(data);
|
||||
|
||||
var authUser = new AuthUser();
|
||||
authUser.uuid = data.getString("unionId");
|
||||
authUser.username = data.getString("nick");
|
||||
authUser.nickname = data.getString("nick");
|
||||
authUser.email = data.getString("email");
|
||||
authUser.token = authToken;
|
||||
authUser.source = source.getName();
|
||||
|
||||
authUser.originalUserStr = response;
|
||||
return authUser;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验请求结果
|
||||
*
|
||||
* @param response 请求结果
|
||||
* @return 如果请求结果正常,则返回JSONObject
|
||||
*/
|
||||
private void checkResponse(Dictionary<string, object> dic)
|
||||
{
|
||||
if (dic.ContainsKey("errcode") && dic.getInt32("errcode") != 0)
|
||||
{
|
||||
throw new Exception($"errcode: {dic.getString("errcode")}, errmsg: {dic.getString("errmsg")}");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state}
|
||||
*
|
||||
* @param state state 验证授权流程的参数,可以防止csrf
|
||||
* @return 返回授权地址
|
||||
* @since 1.9.3
|
||||
*/
|
||||
public override string authorize(string state)
|
||||
{
|
||||
return UrlBuilder.fromBaseUrl(source.authorize())
|
||||
.queryParam("response_type", "code")
|
||||
.queryParam("client_id", config.clientId)
|
||||
.queryParam("scope", "openid")
|
||||
.queryParam("redirect_uri", config.redirectUri)
|
||||
.queryParam("prompt", "consent")
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
using JNPF.Extras.CollectiveOAuth.Cache;
|
||||
using JNPF.Extras.CollectiveOAuth.Config;
|
||||
using JNPF.Extras.CollectiveOAuth.Models;
|
||||
using JNPF.Extras.CollectiveOAuth.Utils;
|
||||
using JNPF.Extras.CollectiveOAuth.Enums;
|
||||
|
||||
namespace JNPF.Extras.CollectiveOAuth.Request;
|
||||
|
||||
public class DouyinAuthRequest : DefaultAuthRequest
|
||||
{
|
||||
public DouyinAuthRequest(ClientConfig config) : base(config, new DouyinAuthSource())
|
||||
{
|
||||
}
|
||||
|
||||
public DouyinAuthRequest(ClientConfig config, IAuthStateCache authStateCache)
|
||||
: base(config, new DouyinAuthSource(), authStateCache)
|
||||
{
|
||||
}
|
||||
|
||||
protected override AuthToken getAccessToken(AuthCallback authCallback)
|
||||
{
|
||||
return this.getToken(accessTokenUrl(authCallback.code));
|
||||
}
|
||||
|
||||
protected override AuthUser getUserInfo(AuthToken authToken)
|
||||
{
|
||||
string response = doGetUserInfo(authToken);
|
||||
var userInfoObject = response.parseObject();
|
||||
this.checkResponse(userInfoObject);
|
||||
var userObj = userInfoObject.getString("data").parseObject();
|
||||
|
||||
var location = $"{userObj.getString("country")}-{userObj.getString("province")}-{userObj.getString("city")}";
|
||||
var authUser = new AuthUser();
|
||||
authUser.uuid = userObj.getString("union_id");
|
||||
authUser.username = userObj.getString("nickname");
|
||||
authUser.nickname = userObj.getString("nickname");
|
||||
authUser.avatar = userObj.getString("avatar");
|
||||
authUser.location = location;
|
||||
authUser.remark = userObj.getString("description");
|
||||
authUser.gender = GlobalAuthUtil.getRealGender(userObj.getString("gender"));
|
||||
|
||||
authUser.token = authToken;
|
||||
authUser.source = source.getName();
|
||||
authUser.originalUser = userObj;
|
||||
authUser.originalUserStr = response;
|
||||
return authUser;
|
||||
}
|
||||
|
||||
public override AuthResponse refresh(AuthToken oldToken)
|
||||
{
|
||||
var data = getToken(refreshTokenUrl(oldToken.refreshToken));
|
||||
return new AuthResponse(AuthResponseStatus.SUCCESS.GetCode(), AuthResponseStatus.SUCCESS.GetDesc(), data);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取token,适用于获取access_token和刷新token
|
||||
*
|
||||
* @param accessTokenUrl 实际请求token的地址
|
||||
* @return token对象
|
||||
*/
|
||||
private AuthToken getToken(string accessTokenUrl)
|
||||
{
|
||||
var response = HttpUtils.RequestPost(accessTokenUrl);
|
||||
string accessTokenStr = response;
|
||||
var tokenObj = accessTokenStr.parseObject();
|
||||
this.checkResponse(tokenObj);
|
||||
var accessTokenObject = tokenObj.getString("data").parseObject();
|
||||
|
||||
var authToken = new AuthToken
|
||||
{
|
||||
accessToken = accessTokenObject.getString("access_token"),
|
||||
openId = accessTokenObject.getString("open_id"),
|
||||
expireIn = accessTokenObject.getInt32("token_type"),
|
||||
refreshToken = accessTokenObject.getString("refresh_token"),
|
||||
scope = accessTokenObject.getString("scope")
|
||||
};
|
||||
|
||||
return authToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state}
|
||||
*
|
||||
* @param state state 验证授权流程的参数,可以防止csrf
|
||||
* @return 返回授权地址
|
||||
* @since 1.9.3
|
||||
*/
|
||||
public override string authorize(string state)
|
||||
{
|
||||
return UrlBuilder.fromBaseUrl(source.authorize())
|
||||
.queryParam("response_type", "code")
|
||||
.queryParam("client_key", config.clientId)
|
||||
.queryParam("redirect_uri", config.redirectUri)
|
||||
.queryParam("scope", config.scope.IsNullOrWhiteSpace() ? "user_info" : config.scope)
|
||||
.queryParam("state", getRealState(state))
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回获取accessToken的url
|
||||
*
|
||||
* @param code oauth的授权码
|
||||
* @return 返回获取accessToken的url
|
||||
*/
|
||||
protected override string accessTokenUrl(string code)
|
||||
{
|
||||
return UrlBuilder.fromBaseUrl(source.accessToken())
|
||||
.queryParam("code", code)
|
||||
.queryParam("client_key", config.clientId)
|
||||
.queryParam("client_secret", config.clientSecret)
|
||||
.queryParam("grant_type", "authorization_code")
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回获取userInfo的url
|
||||
*
|
||||
* @param authToken oauth返回的token
|
||||
* @return 返回获取userInfo的url
|
||||
*/
|
||||
protected override string userInfoUrl(AuthToken authToken)
|
||||
{
|
||||
return UrlBuilder.fromBaseUrl(source.userInfo())
|
||||
.queryParam("access_token", authToken.accessToken)
|
||||
.queryParam("open_id", authToken.openId)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回获取accessToken的url
|
||||
*
|
||||
* @param refreshToken oauth返回的refreshtoken
|
||||
* @return 返回获取accessToken的url
|
||||
*/
|
||||
protected override string refreshTokenUrl(string refreshToken)
|
||||
{
|
||||
return UrlBuilder.fromBaseUrl(source.refresh())
|
||||
.queryParam("client_key", config.clientId)
|
||||
.queryParam("refresh_token", refreshToken)
|
||||
.queryParam("grant_type", "refresh_token")
|
||||
.build();
|
||||
}
|
||||
/**
|
||||
* 校验请求结果
|
||||
*
|
||||
* @param response 请求结果
|
||||
* @return 如果请求结果正常,则返回Exception
|
||||
*/
|
||||
private void checkResponse(Dictionary<string, object> dic)
|
||||
{
|
||||
string message = dic.getString("message");
|
||||
var data = dic.getString("data").parseObject();
|
||||
int errorCode = data.getInt32("error_code");
|
||||
if ("error".Equals(message) || errorCode != 0)
|
||||
{
|
||||
throw new Exception(data.getString("description"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,280 @@
|
||||
using JNPF.Extras.CollectiveOAuth.Cache;
|
||||
using JNPF.Extras.CollectiveOAuth.Config;
|
||||
using JNPF.Extras.CollectiveOAuth.Models;
|
||||
using JNPF.Extras.CollectiveOAuth.Utils;
|
||||
using JNPF.Extras.CollectiveOAuth.Enums;
|
||||
using System.Text;
|
||||
using System.Security.Cryptography;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace JNPF.Extras.CollectiveOAuth.Request;
|
||||
|
||||
public class ElemeAuthRequest : DefaultAuthRequest
|
||||
{
|
||||
public ElemeAuthRequest(ClientConfig config) : base(config, new ElemeAuthSource())
|
||||
{
|
||||
}
|
||||
|
||||
public ElemeAuthRequest(ClientConfig config, IAuthStateCache authStateCache)
|
||||
: base(config, new ElemeAuthSource(), authStateCache)
|
||||
{
|
||||
}
|
||||
|
||||
protected override AuthToken getAccessToken(AuthCallback authCallback)
|
||||
{
|
||||
var reqParams = new Dictionary<string, object>
|
||||
{
|
||||
{ "client_id", config.clientId },
|
||||
{ "redirect_uri", config.clientSecret },
|
||||
{ "code", authCallback.code },
|
||||
{ "grant_type", "authorization_code" },
|
||||
};
|
||||
|
||||
var reqHeaders = this.getSpecialHeader(this.getRequestId());
|
||||
|
||||
var response = HttpUtils.RequestFormPost(source.accessToken(), reqParams.spellParams(), reqHeaders);
|
||||
var accessTokenObject = response.parseObject();
|
||||
|
||||
this.checkResponse(accessTokenObject);
|
||||
|
||||
var authToken = new AuthToken
|
||||
{
|
||||
accessToken = accessTokenObject.getString("access_token"),
|
||||
expireIn = accessTokenObject.getInt32("expires_in"),
|
||||
refreshToken = accessTokenObject.getString("refresh_token"),
|
||||
tokenType = accessTokenObject.getString("token_type"),
|
||||
code = authCallback.code
|
||||
};
|
||||
|
||||
return authToken;
|
||||
}
|
||||
|
||||
|
||||
protected override AuthUser getUserInfo(AuthToken authToken)
|
||||
{
|
||||
// 获取商户账号信息的API接口名称
|
||||
String action = "eleme.user.getUser";
|
||||
// 时间戳,单位秒。API服务端允许客户端请求最大时间误差为正负5分钟。
|
||||
long timestamp = DateTime.Now.Ticks;
|
||||
// 公共参数
|
||||
var metasHashMap = new Dictionary<string, object>();
|
||||
metasHashMap.Add("app_key", config.clientId);
|
||||
metasHashMap.Add("timestamp", timestamp);
|
||||
string signature = this.generateElemeSignature(timestamp, action, authToken.accessToken);
|
||||
string requestId = this.getRequestId();
|
||||
|
||||
var paramsMap = new Dictionary<string, object>
|
||||
{
|
||||
{ "nop", "1.0.0" },
|
||||
{ "id", requestId },
|
||||
{ "action", action },
|
||||
{ "token", authToken.accessToken },
|
||||
{ "metas", JsonConvert.SerializeObject(metasHashMap) },
|
||||
{ "params", "{}" },
|
||||
{ "signature", signature }
|
||||
};
|
||||
|
||||
var reqHeaders = new Dictionary<string, object>
|
||||
{
|
||||
{ "Content-Type", "application/json; charset=utf-8" },
|
||||
{ "Accept", "text/xml,text/javascript,text/html" },
|
||||
{ "Accept-Encoding", "gzip" },
|
||||
{ "User-Agent", "eleme-openapi-java-sdk"},
|
||||
{ "x-eleme-requestid", requestId},
|
||||
{ "Authorization", this.spliceBasicAuthStr()}
|
||||
};
|
||||
var response = HttpUtils.RequestPost(source.userInfo(), JsonConvert.SerializeObject(paramsMap), reqHeaders);
|
||||
|
||||
var resObj = response.parseObject();
|
||||
|
||||
// 校验请求
|
||||
if (resObj.ContainsKey("name"))
|
||||
{
|
||||
throw new Exception(resObj.getString("message"));
|
||||
}
|
||||
if (resObj.ContainsKey("error") && !resObj.getString("error").IsNullOrWhiteSpace())
|
||||
{
|
||||
throw new Exception(resObj.getJSONObject("error").getString("message"));
|
||||
}
|
||||
|
||||
var userObj = resObj.getJSONObject("result");
|
||||
|
||||
var authUser = new AuthUser
|
||||
{
|
||||
uuid = userObj.getString("userId"),
|
||||
username = userObj.getString("userName"),
|
||||
nickname = userObj.getString("userName"),
|
||||
gender = AuthUserGender.UNKNOWN,
|
||||
token = authToken,
|
||||
source = source.getName(),
|
||||
originalUser = resObj,
|
||||
originalUserStr = response
|
||||
};
|
||||
return authUser;
|
||||
}
|
||||
|
||||
public override AuthResponse refresh(AuthToken oldToken)
|
||||
{
|
||||
var reqParams = new Dictionary<string, object>
|
||||
{
|
||||
{ "refresh_token", oldToken.refreshToken },
|
||||
{ "grant_type", "refresh_token" },
|
||||
};
|
||||
|
||||
var reqHeaders = this.getSpecialHeader(this.getRequestId());
|
||||
|
||||
var response = HttpUtils.RequestFormPost(source.accessToken(), reqParams.spellParams(), reqHeaders);
|
||||
var accessTokenObject = response.parseObject();
|
||||
|
||||
this.checkResponse(accessTokenObject);
|
||||
|
||||
var authToken = new AuthToken
|
||||
{
|
||||
accessToken = accessTokenObject.getString("access_token"),
|
||||
refreshToken = accessTokenObject.getString("refresh_token"),
|
||||
expireIn = accessTokenObject.getInt32("expires_in"),
|
||||
tokenType = accessTokenObject.getString("token_type")
|
||||
};
|
||||
|
||||
return new AuthResponse(AuthResponseStatus.SUCCESS.GetCode(), AuthResponseStatus.SUCCESS.GetDesc(), authToken);
|
||||
}
|
||||
|
||||
public override string authorize(string state)
|
||||
{
|
||||
return UrlBuilder.fromBaseUrl(base.authorize(state))
|
||||
.queryParam("scope", config.scope.IsNullOrWhiteSpace() ? "all" : config.scope)
|
||||
.build();
|
||||
}
|
||||
|
||||
private string spliceBasicAuthStr()
|
||||
{
|
||||
string encodeToString = encodeBase64($"{config.clientId}:{config.clientSecret}");
|
||||
return $"Basic {encodeToString}";
|
||||
}
|
||||
|
||||
private Dictionary<string, object> getSpecialHeader(string requestId)
|
||||
{
|
||||
var headers = new Dictionary<string, object>
|
||||
{
|
||||
{ "Content-Type", "application/x-www-form-urlencoded;charset=UTF-8" },
|
||||
{ "Accept", "text/xml,text/javascript,text/html" },
|
||||
{ "Accept-Encoding", "gzip" },
|
||||
{ "User-Agent", "eleme-openapi-java-sdk"},
|
||||
{ "x-eleme-requestid", requestId},
|
||||
{ "Authorization", this.spliceBasicAuthStr()}
|
||||
};
|
||||
return headers;
|
||||
}
|
||||
|
||||
|
||||
private string getRequestId()
|
||||
{
|
||||
return (Guid.NewGuid().ToString() + "|" + DateTime.Now.Ticks.ToString()).ToUpper();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 校验请求结果
|
||||
*
|
||||
* @param response 请求结果
|
||||
* @return 如果请求结果正常,则返回Exception
|
||||
*/
|
||||
private void checkResponse(Dictionary<string, object> dic)
|
||||
{
|
||||
if (dic.ContainsKey("error"))
|
||||
{
|
||||
throw new Exception($"{dic.getString("error_description")}");
|
||||
}
|
||||
}
|
||||
|
||||
///编码
|
||||
public string encodeBase64(string contentStr, string encodeType = "utf-8")
|
||||
{
|
||||
string encode = "";
|
||||
byte[] bytes = Encoding.GetEncoding(encodeType).GetBytes(contentStr);
|
||||
try
|
||||
{
|
||||
encode = Convert.ToBase64String(bytes);
|
||||
}
|
||||
catch
|
||||
{
|
||||
encode = contentStr;
|
||||
}
|
||||
return encode;
|
||||
}
|
||||
///解码
|
||||
public string decodeBase64(string contentStr, string encodeType = "utf-8")
|
||||
{
|
||||
string decode = "";
|
||||
byte[] bytes = Convert.FromBase64String(contentStr);
|
||||
try
|
||||
{
|
||||
decode = Encoding.GetEncoding(encodeType).GetString(bytes);
|
||||
}
|
||||
catch
|
||||
{
|
||||
decode = contentStr;
|
||||
}
|
||||
return decode;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 生成饿了么请求的Signature
|
||||
* <p>
|
||||
* 代码copy并修改自:https://coding.net/u/napos_openapi/p/eleme-openapi-java-sdk/git/blob/master/src/main/java/eleme/openapi/sdk/utils/SignatureUtil.java
|
||||
*
|
||||
* @param appKey 平台应用的授权key
|
||||
* @param secret 平台应用的授权密钥
|
||||
* @param timestamp 时间戳,单位秒。API服务端允许客户端请求最大时间误差为正负5分钟。
|
||||
* @param action 饿了么请求的api方法
|
||||
* @param token 用户授权的token
|
||||
* @param parameters 加密参数
|
||||
* @return Signature
|
||||
*/
|
||||
public string generateElemeSignature(long timestamp, string action, string token)
|
||||
{
|
||||
Dictionary<string, object> dicList = new Dictionary<string, object>();
|
||||
dicList.Add("app_key", config.clientId);
|
||||
dicList.Add("timestamp", timestamp);
|
||||
|
||||
var signStr = dicList.Sort().spellParams();
|
||||
string splice = $"{action}{token}{signStr}{config.clientSecret}";
|
||||
string calculatedSignature = hashMd5String(splice);
|
||||
return calculatedSignature;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 对字符串进行Md5加密,isUpper为True时返回大写,反之小写
|
||||
/// </summary>
|
||||
/// <param name="willMd5Str"></param>
|
||||
/// <param name="isUpper"></param>
|
||||
public static string hashMd5String(string willMd5Str, bool isUpper = true)
|
||||
{
|
||||
//就是比string往后一直加要好的优化容器
|
||||
StringBuilder sb = new StringBuilder();
|
||||
using (MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider())
|
||||
{
|
||||
//将输入字符串转换为字节数组并计算哈希。
|
||||
byte[] data = md5.ComputeHash(Encoding.UTF8.GetBytes(willMd5Str));
|
||||
|
||||
//X为 十六进制 X都是大写 x都为小写
|
||||
//2为 每次都是两位数
|
||||
//假设有两个数10和26,正常情况十六进制显示0xA、0x1A,这样看起来不整齐,为了好看,可以指定"X2",这样显示出来就是:0x0A、0x1A。
|
||||
//遍历哈希数据的每个字节
|
||||
//并将每个字符串格式化为十六进制字符串。
|
||||
int length = data.Length;
|
||||
for (int i = 0; i < length; i++)
|
||||
sb.Append(data[i].ToString("X2"));
|
||||
|
||||
}
|
||||
if (isUpper)
|
||||
{
|
||||
return sb.ToString().ToUpper();
|
||||
}
|
||||
else
|
||||
{
|
||||
return sb.ToString().ToLower();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
using JNPF.Extras.CollectiveOAuth.Cache;
|
||||
using JNPF.Extras.CollectiveOAuth.Config;
|
||||
using JNPF.Extras.CollectiveOAuth.Models;
|
||||
using JNPF.Extras.CollectiveOAuth.Utils;
|
||||
|
||||
namespace JNPF.Extras.CollectiveOAuth.Request;
|
||||
|
||||
public class FackbookAuthRequest : DefaultAuthRequest
|
||||
{
|
||||
public FackbookAuthRequest(ClientConfig config) : base(config, new FackbookAuthSource())
|
||||
{
|
||||
}
|
||||
|
||||
public FackbookAuthRequest(ClientConfig config, IAuthStateCache authStateCache)
|
||||
: base(config, new FackbookAuthSource(), authStateCache)
|
||||
{
|
||||
}
|
||||
|
||||
protected override AuthToken getAccessToken(AuthCallback authCallback)
|
||||
{
|
||||
var response = doPostAuthorizationCode(authCallback.code);
|
||||
var accessTokenObject = response.parseObject();
|
||||
this.checkResponse(accessTokenObject);
|
||||
|
||||
var authToken = new AuthToken
|
||||
{
|
||||
accessToken = accessTokenObject.getString("access_token"),
|
||||
expireIn = accessTokenObject.getInt32("expires_in"),
|
||||
tokenType = accessTokenObject.getString("token_type"),
|
||||
code = authCallback.code
|
||||
};
|
||||
return authToken;
|
||||
}
|
||||
|
||||
protected override AuthUser getUserInfo(AuthToken authToken)
|
||||
{
|
||||
var response = doGetUserInfo(authToken);
|
||||
var userObj = response.parseObject();
|
||||
this.checkResponse(userObj);
|
||||
|
||||
var authUser = new AuthUser
|
||||
{
|
||||
uuid = userObj.getString("id"),
|
||||
username = userObj.getString("name"),
|
||||
nickname = userObj.getString("name"),
|
||||
avatar = getUserPicture(userObj),
|
||||
location = userObj.getString("locale"),
|
||||
email = userObj.getString("email"),
|
||||
gender = GlobalAuthUtil.getRealGender(userObj.getString("gender")),
|
||||
token = authToken,
|
||||
source = source.getName(),
|
||||
originalUser = userObj,
|
||||
originalUserStr = response
|
||||
};
|
||||
return authUser;
|
||||
}
|
||||
|
||||
private string getUserPicture(Dictionary<string, object> userObj)
|
||||
{
|
||||
string picture = null;
|
||||
if (userObj.ContainsKey("picture"))
|
||||
{
|
||||
var pictureObj = userObj.getString("picture").parseObject();
|
||||
pictureObj = pictureObj.getString("data").parseObject();
|
||||
if (null != pictureObj)
|
||||
{
|
||||
picture = pictureObj.getString("url");
|
||||
}
|
||||
}
|
||||
return picture;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回获取userInfo的url
|
||||
*
|
||||
* @param authToken 用户token
|
||||
* @return 返回获取userInfo的url
|
||||
*/
|
||||
protected override string userInfoUrl(AuthToken authToken)
|
||||
{
|
||||
return UrlBuilder.fromBaseUrl(source.userInfo())
|
||||
.queryParam("access_token", authToken.accessToken)
|
||||
.queryParam("fields", "id,name,birthday,gender,hometown,email,devices,picture.width(400)")
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 检查响应内容是否正确
|
||||
*
|
||||
* @param object 请求响应内容
|
||||
*/
|
||||
private void checkResponse(Dictionary<string, object> dic)
|
||||
{
|
||||
if (dic.ContainsKey("error"))
|
||||
{
|
||||
throw new Exception($"{dic.getString("error").parseObject().getString("message")}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
using JNPF.Common.Security;
|
||||
using JNPF.Extras.CollectiveOAuth.Cache;
|
||||
using JNPF.Extras.CollectiveOAuth.Config;
|
||||
using JNPF.Extras.CollectiveOAuth.Models;
|
||||
using JNPF.Extras.CollectiveOAuth.Utils;
|
||||
using System.Web;
|
||||
|
||||
namespace JNPF.Extras.CollectiveOAuth.Request;
|
||||
|
||||
public class FeiShuAuthRequest : DefaultAuthRequest
|
||||
{
|
||||
public FeiShuAuthRequest(ClientConfig config) : base(config, new FeiShuAuthSource())
|
||||
{
|
||||
}
|
||||
|
||||
public FeiShuAuthRequest(ClientConfig config, IAuthStateCache authStateCache)
|
||||
: base(config, new FeiShuAuthSource(), authStateCache)
|
||||
{
|
||||
}
|
||||
|
||||
private string getAppAccessToken()
|
||||
{
|
||||
var cacheKey = this.source.getName() + this.config.clientId;
|
||||
var cacheAppAccessToken = this.authStateCache.get(cacheKey);
|
||||
if (!cacheAppAccessToken.IsNullOrEmpty())
|
||||
{
|
||||
return cacheAppAccessToken;
|
||||
}
|
||||
else
|
||||
{
|
||||
var url = "https://open.feishu.cn/open-apis/auth/v3/app_access_token/internal/";
|
||||
var requestObject = new Dictionary<string, object>();
|
||||
requestObject.Add("app_id", this.config.clientId);
|
||||
requestObject.Add("app_secret", this.config.clientSecret);
|
||||
var response = HttpUtils.RequestPost(url, requestObject.ToJsonString(), new Dictionary<string, object>() { { "Content-Type", "application/json" } });
|
||||
var jsonObject = response.parseObject();
|
||||
this.checkResponse(jsonObject);
|
||||
var appAccessToken = jsonObject.getString("app_access_token");
|
||||
this.authStateCache.cache(cacheKey, appAccessToken, 9 * 1000);
|
||||
return appAccessToken;
|
||||
}
|
||||
}
|
||||
|
||||
protected override AuthToken getAccessToken(AuthCallback authCallback)
|
||||
{
|
||||
var requestObject = new Dictionary<string, object>();
|
||||
requestObject.Add("app_access_token", this.getAppAccessToken());
|
||||
requestObject.Add("grant_type", "authorization_code");
|
||||
requestObject.Add("code", authCallback.code);
|
||||
|
||||
return this.getToken(requestObject, this.source.accessToken());
|
||||
}
|
||||
|
||||
private AuthToken getToken(Dictionary<string, object> param, string url)
|
||||
{
|
||||
var response = HttpUtils.RequestPost(url, param.ToJsonString(), new Dictionary<string, object>() { { "Content-Type", "application/json" } });
|
||||
var jsonObject = response.parseObject();
|
||||
this.checkResponse(jsonObject);
|
||||
var data = jsonObject["data"].ToObject<Dictionary<string, object>>();
|
||||
var authToken = new AuthToken();
|
||||
authToken.accessToken = data.getString("access_token");
|
||||
authToken.refreshToken = data.getString("refresh_token");
|
||||
authToken.expireIn = data.getInt32("expires_in");
|
||||
authToken.tokenType = data.getString("token_type");
|
||||
authToken.openId = data.getString("open_id");
|
||||
return authToken;
|
||||
}
|
||||
|
||||
protected override AuthUser getUserInfo(AuthToken authToken)
|
||||
{
|
||||
var accessToken = authToken.accessToken;
|
||||
string response = doGetUserInfo(authToken);
|
||||
var jsonObject = response.parseObject();
|
||||
this.checkResponse(jsonObject);
|
||||
var data = jsonObject["data"].ToObject<Dictionary<string, object>>();
|
||||
|
||||
var authUser = new AuthUser();
|
||||
authUser.uuid = data.getString("union_id");
|
||||
authUser.username = data.getString("name");
|
||||
authUser.nickname = data.getString("name");
|
||||
authUser.avatar = data.getString("avatar_url");
|
||||
authUser.email = data.getString("email");
|
||||
//authUser.location = location;
|
||||
//authUser.remark = data.getString("bio");
|
||||
//authUser.gender = GlobalAuthUtil.getRealGender(userObj.getString("gender"));
|
||||
authUser.token = authToken;
|
||||
authUser.source = source.getName();
|
||||
|
||||
authUser.originalUserStr = response;
|
||||
return authUser;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state}
|
||||
*
|
||||
* @param state state 验证授权流程的参数,可以防止csrf
|
||||
* @return 返回授权地址
|
||||
* @since 1.9.3
|
||||
*/
|
||||
public override string authorize(string state)
|
||||
{
|
||||
config.redirectUri = HttpUtility.UrlEncode(config.redirectUri);
|
||||
config.redirectUri = config.redirectUri.Replace("+", "%20").Replace("*", "%2A").Replace("~", "%7E").Replace("/", "%2F");
|
||||
|
||||
return UrlBuilder.fromBaseUrl(source.authorize())
|
||||
.queryParam("app_id", this.config.clientId)
|
||||
.queryParam("redirect_uri", config.redirectUri)
|
||||
.queryParam("state", getRealState(state))
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验请求结果
|
||||
*
|
||||
* @param response 请求结果
|
||||
* @return 如果请求结果正常,则返回JSONObject
|
||||
*/
|
||||
private void checkResponse(Dictionary<string, object> dic)
|
||||
{
|
||||
if (dic.ContainsKey("errcode") && dic.getInt32("errcode") != 0)
|
||||
{
|
||||
throw new Exception($"errcode: {dic.getString("errcode")}, errmsg: {dic.getString("errmsg")}");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回获取accessToken的url
|
||||
*
|
||||
* @param code 授权码
|
||||
* @return 返回获取accessToken的url
|
||||
*/
|
||||
protected override string accessTokenUrl(string code)
|
||||
{
|
||||
return UrlBuilder.fromBaseUrl(source.accessToken())
|
||||
.queryParam("app_id", config.clientId)
|
||||
.queryParam("app_secret", config.clientSecret)
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
using JNPF.Extras.CollectiveOAuth.Cache;
|
||||
using JNPF.Extras.CollectiveOAuth.Config;
|
||||
using JNPF.Extras.CollectiveOAuth.Enums;
|
||||
using JNPF.Extras.CollectiveOAuth.Models;
|
||||
using JNPF.Extras.CollectiveOAuth.Utils;
|
||||
|
||||
namespace JNPF.Extras.CollectiveOAuth.Request;
|
||||
|
||||
public class GiteeAuthRequest : DefaultAuthRequest
|
||||
{
|
||||
public GiteeAuthRequest(ClientConfig config) : base(config, new GiteeAuthSource())
|
||||
{
|
||||
}
|
||||
|
||||
public GiteeAuthRequest(ClientConfig config, IAuthStateCache authStateCache)
|
||||
: base(config, new GiteeAuthSource(), authStateCache)
|
||||
{
|
||||
}
|
||||
|
||||
protected override AuthToken getAccessToken(AuthCallback authCallback)
|
||||
{
|
||||
string response = doPostAuthorizationCode(authCallback.code);
|
||||
var accessTokenObject = response.parseObject();
|
||||
this.checkResponse(accessTokenObject);
|
||||
|
||||
var authToken = new AuthToken();
|
||||
authToken.accessToken = accessTokenObject.getString("access_token");
|
||||
authToken.refreshToken = accessTokenObject.getString("refresh_token");
|
||||
authToken.tokenType = accessTokenObject.getString("token_type");
|
||||
authToken.expireIn = accessTokenObject.getInt32("expires_in");
|
||||
authToken.scope = accessTokenObject.getString("scope");
|
||||
|
||||
return authToken;
|
||||
}
|
||||
|
||||
protected override AuthUser getUserInfo(AuthToken authToken)
|
||||
{
|
||||
string response = doGetUserInfo(authToken);
|
||||
var userObj = response.parseObject();
|
||||
this.checkResponse(userObj);
|
||||
|
||||
var authUser = new AuthUser();
|
||||
authUser.uuid = userObj.getString("id");
|
||||
authUser.username = userObj.getString("login");
|
||||
authUser.nickname = userObj.getString("name");
|
||||
authUser.avatar = userObj.getString("avatar_url");
|
||||
authUser.blog = userObj.getString("blog");
|
||||
authUser.company = userObj.getString("company");
|
||||
authUser.location = userObj.getString("address");
|
||||
authUser.email = userObj.getString("email");
|
||||
authUser.remark = userObj.getString("bio");
|
||||
authUser.gender = AuthUserGender.UNKNOWN;
|
||||
authUser.token = authToken;
|
||||
authUser.source = source.getName();
|
||||
authUser.originalUser = userObj;
|
||||
authUser.originalUserStr = response;
|
||||
return authUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验请求结果
|
||||
*
|
||||
* @param response 请求结果
|
||||
* @return 如果请求结果正常,则返回Exception
|
||||
*/
|
||||
private void checkResponse(Dictionary<string, object> dic)
|
||||
{
|
||||
if (dic.ContainsKey("error"))
|
||||
{
|
||||
throw new Exception($"{dic.getString("error_description")}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
using JNPF.Extras.CollectiveOAuth.Cache;
|
||||
using JNPF.Extras.CollectiveOAuth.Config;
|
||||
using JNPF.Extras.CollectiveOAuth.Models;
|
||||
using JNPF.Extras.CollectiveOAuth.Utils;
|
||||
using JNPF.Extras.CollectiveOAuth.Enums;
|
||||
|
||||
namespace JNPF.Extras.CollectiveOAuth.Request;
|
||||
|
||||
public class GithubAuthRequest : DefaultAuthRequest
|
||||
{
|
||||
public GithubAuthRequest(ClientConfig config) : base(config, new GithubAuthSource())
|
||||
{
|
||||
}
|
||||
|
||||
public GithubAuthRequest(ClientConfig config, IAuthStateCache authStateCache)
|
||||
: base(config, new GithubAuthSource(), authStateCache)
|
||||
{
|
||||
}
|
||||
|
||||
protected override AuthToken getAccessToken(AuthCallback authCallback)
|
||||
{
|
||||
string response = doPostAuthorizationCode(authCallback.code);
|
||||
var accessTokenObject = response.parseStringObject();
|
||||
this.checkResponse(accessTokenObject);
|
||||
|
||||
var authToken = new AuthToken();
|
||||
authToken.accessToken = accessTokenObject.getString("access_token");
|
||||
authToken.tokenType = accessTokenObject.getString("token_type");
|
||||
authToken.scope = accessTokenObject.getString("scope");
|
||||
authToken.code = authCallback.code;
|
||||
|
||||
return authToken;
|
||||
}
|
||||
|
||||
protected override AuthUser getUserInfo(AuthToken authToken)
|
||||
{
|
||||
string response = doGetUserInfo(authToken);
|
||||
var userObj = response.parseObject();
|
||||
this.checkResponse(userObj);
|
||||
|
||||
var authUser = new AuthUser();
|
||||
authUser.uuid = userObj.getString("id");
|
||||
authUser.username = userObj.getString("login");
|
||||
authUser.nickname = userObj.getString("name");
|
||||
authUser.avatar = userObj.getString("avatar_url");
|
||||
authUser.blog = userObj.getString("blog");
|
||||
authUser.company = userObj.getString("company");
|
||||
authUser.location = userObj.getString("location");
|
||||
authUser.email = userObj.getString("email");
|
||||
authUser.remark = userObj.getString("bio");
|
||||
authUser.gender = AuthUserGender.UNKNOWN;
|
||||
authUser.token = authToken;
|
||||
authUser.source = source.getName();
|
||||
authUser.originalUser = userObj;
|
||||
authUser.originalUserStr = response;
|
||||
return authUser;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重写获取用户信息方法
|
||||
/// </summary>
|
||||
/// <param name="authToken"></param>
|
||||
/// <returns></returns>
|
||||
protected override string doGetUserInfo(AuthToken authToken)
|
||||
{
|
||||
return HttpUtils.RequestJsonGet(userInfoUrl(authToken));
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state}
|
||||
*
|
||||
* @param state state 验证授权流程的参数,可以防止csrf
|
||||
* @return 返回授权地址
|
||||
* @since 1.9.3
|
||||
*/
|
||||
public override string authorize(string state)
|
||||
{
|
||||
return UrlBuilder.fromBaseUrl(source.authorize())
|
||||
.queryParam("client_id", config.clientId)
|
||||
.queryParam("response_type", "code")
|
||||
.queryParam("redirect_uri", config.redirectUri)
|
||||
.queryParam("scope", config.scope.IsNullOrWhiteSpace() ? "user" : config.scope)
|
||||
.queryParam("state", getRealState(state) + "#wechat_redirect")
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验请求结果
|
||||
*
|
||||
* @param response 请求结果
|
||||
* @return 如果请求结果正常,则返回Exception
|
||||
*/
|
||||
private void checkResponse(Dictionary<string, object> dic)
|
||||
{
|
||||
if (dic.ContainsKey("error"))
|
||||
{
|
||||
throw new Exception($"{dic.getString("error_description")}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
using JNPF.Extras.CollectiveOAuth.Cache;
|
||||
using JNPF.Extras.CollectiveOAuth.Config;
|
||||
using JNPF.Extras.CollectiveOAuth.Models;
|
||||
using JNPF.Extras.CollectiveOAuth.Utils;
|
||||
using JNPF.Extras.CollectiveOAuth.Enums;
|
||||
|
||||
namespace JNPF.Extras.CollectiveOAuth.Request;
|
||||
|
||||
public class GitlabAuthRequest : DefaultAuthRequest
|
||||
{
|
||||
public GitlabAuthRequest(ClientConfig config) : base(config, new GitlabAuthSource())
|
||||
{
|
||||
}
|
||||
|
||||
public GitlabAuthRequest(ClientConfig config, IAuthStateCache authStateCache)
|
||||
: base(config, new GitlabAuthSource(), authStateCache)
|
||||
{
|
||||
}
|
||||
|
||||
protected override AuthToken getAccessToken(AuthCallback authCallback)
|
||||
{
|
||||
var response = doPostAuthorizationCode(authCallback.code);
|
||||
var accessTokenObject = response.parseObject();
|
||||
|
||||
this.checkResponse(accessTokenObject);
|
||||
|
||||
var authToken = new AuthToken();
|
||||
authToken.accessToken = accessTokenObject.getString("access_token");
|
||||
authToken.refreshToken = accessTokenObject.getString("refresh_token");
|
||||
authToken.idToken = accessTokenObject.getString("id_token");
|
||||
authToken.tokenType = accessTokenObject.getString("token_type");
|
||||
authToken.scope = accessTokenObject.getString("scope");
|
||||
authToken.code = authCallback.code;
|
||||
|
||||
return authToken;
|
||||
}
|
||||
|
||||
|
||||
protected override AuthUser getUserInfo(AuthToken authToken)
|
||||
{
|
||||
var response = doGetUserInfo(authToken);
|
||||
var userObj = response.parseObject();
|
||||
|
||||
this.checkResponse(userObj);
|
||||
|
||||
var authUser = new AuthUser();
|
||||
authUser.uuid = userObj.getString("id");
|
||||
authUser.username = userObj.getString("username");
|
||||
authUser.nickname = userObj.getString("name");
|
||||
authUser.avatar = userObj.getString("avatar_url");
|
||||
authUser.blog = userObj.getString("web_url");
|
||||
authUser.company = userObj.getString("organization");
|
||||
authUser.location = userObj.getString("location");
|
||||
authUser.email = userObj.getString("email");
|
||||
authUser.remark = userObj.getString("bio");
|
||||
authUser.gender = AuthUserGender.UNKNOWN;
|
||||
|
||||
authUser.token = authToken;
|
||||
authUser.source = source.getName();
|
||||
authUser.originalUser = userObj;
|
||||
authUser.originalUserStr = response;
|
||||
return authUser;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state}
|
||||
*
|
||||
* @param state state 验证授权流程的参数,可以防止csrf
|
||||
* @return 返回授权地址
|
||||
* @since 1.11.0
|
||||
*/
|
||||
public override string authorize(string state)
|
||||
{
|
||||
return UrlBuilder.fromBaseUrl(source.authorize())
|
||||
.queryParam("response_type", "code")
|
||||
.queryParam("client_id", config.clientId)
|
||||
.queryParam("redirect_uri", config.redirectUri)
|
||||
.queryParam("state", getRealState(state))
|
||||
.queryParam("scope", config.scope.IsNullOrWhiteSpace() ? "read_user+openid+profile+email" : config.scope)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验请求结果
|
||||
*
|
||||
* @param response 请求结果
|
||||
* @return 如果请求结果正常,则返回Exception
|
||||
*/
|
||||
private void checkResponse(Dictionary<string, object> dic)
|
||||
{
|
||||
if (dic.ContainsKey("error"))
|
||||
{
|
||||
throw new Exception($"{dic.getString("error_description")}");
|
||||
}
|
||||
// user 验证异常
|
||||
if (dic.ContainsKey("message"))
|
||||
{
|
||||
throw new Exception($"{dic.getString("message")}");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
using JNPF.Extras.CollectiveOAuth.Cache;
|
||||
using JNPF.Extras.CollectiveOAuth.Config;
|
||||
using JNPF.Extras.CollectiveOAuth.Models;
|
||||
using JNPF.Extras.CollectiveOAuth.Utils;
|
||||
using JNPF.Extras.CollectiveOAuth.Enums;
|
||||
|
||||
namespace JNPF.Extras.CollectiveOAuth.Request;
|
||||
|
||||
public class GoogleAuthRequest : DefaultAuthRequest
|
||||
{
|
||||
public GoogleAuthRequest(ClientConfig config) : base(config, new GoogleAuthSource())
|
||||
{
|
||||
}
|
||||
|
||||
public GoogleAuthRequest(ClientConfig config, IAuthStateCache authStateCache)
|
||||
: base(config, new GoogleAuthSource(), authStateCache)
|
||||
{
|
||||
}
|
||||
protected override AuthToken getAccessToken(AuthCallback authCallback)
|
||||
{
|
||||
var response = doPostAuthorizationCode(authCallback.code);
|
||||
var accessTokenObject = response.parseObject();
|
||||
this.checkResponse(accessTokenObject);
|
||||
|
||||
var authToken = new AuthToken();
|
||||
authToken.accessToken = accessTokenObject.getString("access_token");
|
||||
authToken.expireIn = accessTokenObject.getInt32("expires_in");
|
||||
authToken.idToken = accessTokenObject.getString("id_token");
|
||||
authToken.tokenType = accessTokenObject.getString("token_type");
|
||||
authToken.scope = accessTokenObject.getString("scope");
|
||||
|
||||
return authToken;
|
||||
}
|
||||
|
||||
protected override AuthUser getUserInfo(AuthToken authToken)
|
||||
{
|
||||
var reqParams = new Dictionary<string, object>
|
||||
{
|
||||
{ "Authorization", "Bearer " + authToken.accessToken }
|
||||
};
|
||||
var response = HttpUtils.RequestPost(userInfoUrl(authToken), null, reqParams);
|
||||
var userInfo = response;
|
||||
var userObj = userInfo.parseObject();
|
||||
this.checkResponse(userObj);
|
||||
|
||||
var authUser = new AuthUser();
|
||||
authUser.uuid = userObj.getString("sub");
|
||||
authUser.username = userObj.getString("email");
|
||||
authUser.nickname = userObj.getString("name");
|
||||
authUser.avatar = userObj.getString("picture");
|
||||
authUser.location = userObj.getString("locale");
|
||||
authUser.email = userObj.getString("email");
|
||||
authUser.gender = AuthUserGender.UNKNOWN;
|
||||
|
||||
authUser.token = authToken;
|
||||
authUser.source = source.getName();
|
||||
authUser.originalUser = userObj;
|
||||
authUser.originalUserStr = response;
|
||||
return authUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state}
|
||||
*
|
||||
* @param state state 验证授权流程的参数,可以防止csrf
|
||||
* @return 返回授权地址
|
||||
* @since 1.9.3
|
||||
*/
|
||||
public override string authorize(string state)
|
||||
{
|
||||
return UrlBuilder.fromBaseUrl(source.authorize())
|
||||
.queryParam("response_type", "code")
|
||||
.queryParam("client_id", config.clientId)
|
||||
.queryParam("scope", config.scope.IsNullOrWhiteSpace() ? "openid%20email%20profile" : config.scope)
|
||||
.queryParam("redirect_uri", config.redirectUri)
|
||||
.queryParam("state", getRealState(state))
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回获取userInfo的url
|
||||
*
|
||||
* @param authToken 用户授权后的token
|
||||
* @return 返回获取userInfo的url
|
||||
*/
|
||||
protected override string userInfoUrl(AuthToken authToken)
|
||||
{
|
||||
return UrlBuilder.fromBaseUrl(source.userInfo())
|
||||
.queryParam("access_token", authToken.accessToken)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验请求结果
|
||||
*
|
||||
* @param response 请求结果
|
||||
* @return 如果请求结果正常,则返回Exception
|
||||
*/
|
||||
private void checkResponse(Dictionary<string, object> dic)
|
||||
{
|
||||
if (dic.ContainsKey("error") || dic.ContainsKey("error_description"))
|
||||
{
|
||||
throw new Exception($"{dic.getString("error")}: {dic.getString("error_description")}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
using JNPF.Extras.CollectiveOAuth.Cache;
|
||||
using JNPF.Extras.CollectiveOAuth.Config;
|
||||
using JNPF.Extras.CollectiveOAuth.Models;
|
||||
using JNPF.Extras.CollectiveOAuth.Utils;
|
||||
using JNPF.Extras.CollectiveOAuth.Enums;
|
||||
|
||||
namespace JNPF.Extras.CollectiveOAuth.Request;
|
||||
|
||||
public class HuaweiAuthRequest : DefaultAuthRequest
|
||||
{
|
||||
public HuaweiAuthRequest(ClientConfig config) : base(config, new HuaweiAuthSource())
|
||||
{
|
||||
}
|
||||
|
||||
public HuaweiAuthRequest(ClientConfig config, IAuthStateCache authStateCache)
|
||||
: base(config, new HuaweiAuthSource(), authStateCache)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取access token
|
||||
*
|
||||
* @param authCallback 授权成功后的回调参数
|
||||
* @return token
|
||||
* @see AuthDefaultRequest#authorize()
|
||||
* @see AuthDefaultRequest#authorize(String)
|
||||
*/
|
||||
protected override AuthToken getAccessToken(AuthCallback authCallback)
|
||||
{
|
||||
var reqParams = new Dictionary<string, object>
|
||||
{
|
||||
{ "grant_type", "authorization_code" },
|
||||
{ "code", authCallback.authorization_code },
|
||||
{ "client_id", config.clientId },
|
||||
{ "client_secret", config.clientSecret },
|
||||
{ "redirect_uri", config.redirectUri },
|
||||
};
|
||||
|
||||
var response = HttpUtils.RequestFormPost(source.accessToken(), reqParams.spellParams());
|
||||
|
||||
return getAuthToken(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用token换取用户信息
|
||||
*
|
||||
* @param authToken token信息
|
||||
* @return 用户信息
|
||||
* @see AuthDefaultRequest#getAccessToken(AuthCallback)
|
||||
*/
|
||||
protected override AuthUser getUserInfo(AuthToken authToken)
|
||||
{
|
||||
var reqParams = new Dictionary<string, object>
|
||||
{
|
||||
{ "nsp_ts", DateTime.Now.Ticks },
|
||||
{ "access_token", authToken.accessToken },
|
||||
{ "nsp_fmt", "JS" },
|
||||
{ "nsp_svc", "OpenUP.User.getInfo" },
|
||||
};
|
||||
|
||||
var response = HttpUtils.RequestFormPost(source.userInfo(), reqParams.spellParams());
|
||||
var userObj = response.parseObject();
|
||||
|
||||
this.checkResponse(userObj);
|
||||
|
||||
AuthUserGender gender = getRealGender(userObj);
|
||||
|
||||
var authUser = new AuthUser();
|
||||
authUser.uuid = userObj.getString("userID");
|
||||
authUser.username = userObj.getString("userName");
|
||||
authUser.nickname = userObj.getString("userName");
|
||||
authUser.gender = gender;
|
||||
authUser.avatar = userObj.getString("headPictureURL");
|
||||
|
||||
authUser.token = authToken;
|
||||
authUser.source = source.getName();
|
||||
authUser.originalUser = userObj;
|
||||
authUser.originalUserStr = response;
|
||||
return authUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新access token (续期)
|
||||
*
|
||||
* @param authToken 登录成功后返回的Token信息
|
||||
* @return AuthResponse
|
||||
*/
|
||||
public override AuthResponse refresh(AuthToken authToken)
|
||||
{
|
||||
var reqParams = new Dictionary<string, object>
|
||||
{
|
||||
{ "client_id", config.clientId },
|
||||
{ "client_secret", config.clientSecret },
|
||||
{ "refresh_token", authToken.refreshToken },
|
||||
{ "grant_type", "refresh_token" },
|
||||
};
|
||||
var response = HttpUtils.RequestFormPost(source.refresh(), reqParams.spellParams());
|
||||
|
||||
return new AuthResponse(AuthResponseStatus.SUCCESS.GetCode(), AuthResponseStatus.SUCCESS.GetDesc(), getAuthToken(response));
|
||||
}
|
||||
|
||||
private AuthToken getAuthToken(string response)
|
||||
{
|
||||
var authTokenObj = response.parseObject();
|
||||
|
||||
this.checkResponse(authTokenObj);
|
||||
|
||||
var authToken = new AuthToken();
|
||||
authToken.accessToken = authTokenObj.getString("access_token");
|
||||
authToken.refreshToken = authTokenObj.getString("refresh_token");
|
||||
authToken.expireIn = authTokenObj.getInt32("expires_in");
|
||||
return authToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state}
|
||||
*
|
||||
* @param state state 验证授权流程的参数,可以防止csrf
|
||||
* @return 返回授权地址
|
||||
* @since 1.9.3
|
||||
*/
|
||||
public override string authorize(string state)
|
||||
{
|
||||
return UrlBuilder.fromBaseUrl(source.authorize())
|
||||
.queryParam("response_type", "code")
|
||||
.queryParam("client_id", config.clientId)
|
||||
.queryParam("redirect_uri", config.redirectUri)
|
||||
.queryParam("access_type", "offline")
|
||||
.queryParam("scope", config.scope.IsNullOrWhiteSpace() ? "https%3A%2F%2Fwww.huawei.com%2Fauth%2Faccount%2Fbase.profile" : config.scope)
|
||||
.queryParam("state", getRealState(state))
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回获取accessToken的url
|
||||
*
|
||||
* @param code 授权码
|
||||
* @return 返回获取accessToken的url
|
||||
*/
|
||||
protected override string accessTokenUrl(string code)
|
||||
{
|
||||
return UrlBuilder.fromBaseUrl(source.accessToken())
|
||||
.queryParam("grant_type", "authorization_code")
|
||||
.queryParam("code", code)
|
||||
.queryParam("client_id", config.clientId)
|
||||
.queryParam("client_secret", config.clientSecret)
|
||||
.queryParam("redirect_uri", config.redirectUri)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回获取userInfo的url
|
||||
*
|
||||
* @param authToken token
|
||||
* @return 返回获取userInfo的url
|
||||
*/
|
||||
protected override string userInfoUrl(AuthToken authToken)
|
||||
{
|
||||
return UrlBuilder.fromBaseUrl(source.userInfo())
|
||||
.queryParam("nsp_ts", DateTime.Now.Ticks)
|
||||
.queryParam("access_token", authToken.accessToken)
|
||||
.queryParam("nsp_fmt", "JS")
|
||||
.queryParam("nsp_svc", "OpenUP.User.getInfo")
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户的实际性别。华为系统中,用户的性别:1表示女,0表示男
|
||||
*
|
||||
* @param object obj
|
||||
* @return AuthUserGender
|
||||
*/
|
||||
private AuthUserGender getRealGender(Dictionary<string, object> userObj)
|
||||
{
|
||||
int genderCodeInt = userObj.getInt32("gender");
|
||||
string genderCode = genderCodeInt == 1 ? "0" : (genderCodeInt == 0) ? "1" : genderCodeInt + "";
|
||||
return GlobalAuthUtil.getRealGender(genderCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验响应结果
|
||||
*
|
||||
* @param object 接口返回的结果
|
||||
*/
|
||||
private void checkResponse(Dictionary<string, object> dic)
|
||||
{
|
||||
if (dic.ContainsKey("NSP_STATUS"))
|
||||
{
|
||||
throw new Exception(dic.getString("error"));
|
||||
}
|
||||
if (dic.ContainsKey("error"))
|
||||
{
|
||||
throw new Exception(dic.getString("sub_error") + ":" + dic.getString("error_description"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
using JNPF.Extras.CollectiveOAuth.Cache;
|
||||
using JNPF.Extras.CollectiveOAuth.Config;
|
||||
using JNPF.Extras.CollectiveOAuth.Models;
|
||||
using JNPF.Extras.CollectiveOAuth.Utils;
|
||||
using JNPF.Extras.CollectiveOAuth.Enums;
|
||||
|
||||
namespace JNPF.Extras.CollectiveOAuth.Request;
|
||||
|
||||
public class KujialeAuthRequest : DefaultAuthRequest
|
||||
{
|
||||
public KujialeAuthRequest(ClientConfig config) : base(config, new KujialeAuthSource())
|
||||
{
|
||||
}
|
||||
|
||||
public KujialeAuthRequest(ClientConfig config, IAuthStateCache authStateCache)
|
||||
: base(config, new KujialeAuthSource(), authStateCache)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state}
|
||||
* 默认只向用户请求用户信息授权
|
||||
*
|
||||
* @param state state 验证授权流程的参数,可以防止csrf
|
||||
* @return 返回授权地址
|
||||
* @since 1.11.0
|
||||
*/
|
||||
public override string authorize(string state)
|
||||
{
|
||||
var urlBuilder = UrlBuilder.fromBaseUrl(source.authorize())
|
||||
.queryParam("response_type", "code")
|
||||
.queryParam("client_id", config.clientId)
|
||||
.queryParam("redirect_uri", config.redirectUri)
|
||||
.queryParam("state", getRealState(state))
|
||||
.queryParam("scope", config.scope.IsNullOrWhiteSpace() ? "get_user_info" : config.scope)
|
||||
.build();
|
||||
return urlBuilder;
|
||||
}
|
||||
|
||||
protected override AuthToken getAccessToken(AuthCallback authCallback)
|
||||
{
|
||||
var response = doPostAuthorizationCode(authCallback.code);
|
||||
return getAuthToken(response);
|
||||
}
|
||||
|
||||
private AuthToken getAuthToken(string response)
|
||||
{
|
||||
var accessTokenObject = response.parseObject();
|
||||
this.checkResponse(accessTokenObject);
|
||||
|
||||
var resultObject = accessTokenObject.getJSONObject("d");
|
||||
|
||||
var authToken = new AuthToken();
|
||||
authToken.accessToken = resultObject.getString("accessToken");
|
||||
authToken.refreshToken = resultObject.getString("refreshToken");
|
||||
authToken.expireIn = resultObject.getInt32("expiresIn");
|
||||
return authToken;
|
||||
}
|
||||
|
||||
|
||||
protected override AuthUser getUserInfo(AuthToken authToken)
|
||||
{
|
||||
string openId = this.getOpenId(authToken);
|
||||
|
||||
var userInfoUrl = UrlBuilder.fromBaseUrl(source.userInfo())
|
||||
.queryParam("access_token", authToken.accessToken)
|
||||
.queryParam("open_id", openId)
|
||||
.build();
|
||||
|
||||
var response = HttpUtils.RequestGet(userInfoUrl);
|
||||
var resObj = response.parseObject();
|
||||
this.checkResponse(resObj);
|
||||
|
||||
var userObj = resObj.getJSONObject("d");
|
||||
|
||||
var authUser = new AuthUser();
|
||||
authUser.uuid = userObj.getString("openId");
|
||||
authUser.username = userObj.getString("userName");
|
||||
authUser.nickname = userObj.getString("userName");
|
||||
authUser.avatar = userObj.getString("avatar");
|
||||
authUser.gender = AuthUserGender.UNKNOWN;
|
||||
|
||||
authUser.token = authToken;
|
||||
authUser.source = source.getName();
|
||||
authUser.originalUser = resObj;
|
||||
authUser.originalUserStr = response;
|
||||
return authUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取酷家乐的openId,此id在当前client范围内可以唯一识别授权用户
|
||||
*
|
||||
* @param authToken 通过{@link AuthKujialeRequest#getAccessToken(AuthCallback)}获取到的{@code authToken}
|
||||
* @return openId
|
||||
*/
|
||||
private string getOpenId(AuthToken authToken)
|
||||
{
|
||||
var openIdUrl = UrlBuilder.fromBaseUrl("https://oauth.kujiale.com/oauth2/auth/user")
|
||||
.queryParam("access_token", authToken.accessToken)
|
||||
.build();
|
||||
var response = HttpUtils.RequestGet(openIdUrl);
|
||||
var accessTokenObject = response.parseObject();
|
||||
this.checkResponse(accessTokenObject);
|
||||
return accessTokenObject.getString("d");
|
||||
}
|
||||
|
||||
public override AuthResponse refresh(AuthToken authToken)
|
||||
{
|
||||
var refreshUrl = refreshTokenUrl(authToken.refreshToken);
|
||||
var response = HttpUtils.RequestPost(refreshUrl);
|
||||
return new AuthResponse(AuthResponseStatus.SUCCESS.GetCode(), AuthResponseStatus.SUCCESS.GetDesc(), getAuthToken(response));
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验请求结果
|
||||
*
|
||||
* @param response 请求结果
|
||||
* @return 如果请求结果正常,则返回Exception
|
||||
*/
|
||||
private void checkResponse(Dictionary<string, object> dic)
|
||||
{
|
||||
if (dic.Count == 0)
|
||||
{
|
||||
throw new Exception("请求所返回的数据为空!");
|
||||
}
|
||||
|
||||
if (!"0".Equals(dic.getString("c")))
|
||||
{
|
||||
throw new Exception($"{dic.getString("m")}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
using JNPF.Extras.CollectiveOAuth.Cache;
|
||||
using JNPF.Extras.CollectiveOAuth.Config;
|
||||
using JNPF.Extras.CollectiveOAuth.Models;
|
||||
using JNPF.Extras.CollectiveOAuth.Utils;
|
||||
using JNPF.Extras.CollectiveOAuth.Enums;
|
||||
|
||||
namespace JNPF.Extras.CollectiveOAuth.Request;
|
||||
|
||||
public class LinkedInAuthRequest : DefaultAuthRequest
|
||||
{
|
||||
public LinkedInAuthRequest(ClientConfig config) : base(config, new LinkedInAuthSource())
|
||||
{
|
||||
}
|
||||
|
||||
public LinkedInAuthRequest(ClientConfig config, IAuthStateCache authStateCache)
|
||||
: base(config, new LinkedInAuthSource(), authStateCache)
|
||||
{
|
||||
}
|
||||
|
||||
protected override AuthToken getAccessToken(AuthCallback authCallback)
|
||||
{
|
||||
return this.getToken(accessTokenUrl(authCallback.code));
|
||||
}
|
||||
|
||||
protected override AuthUser getUserInfo(AuthToken authToken)
|
||||
{
|
||||
var accessToken = authToken.accessToken;
|
||||
var reqParams = new Dictionary<string, object>
|
||||
{
|
||||
{ "Host", "api.linkedin.com" },
|
||||
{ "Connection", "Keep-Alive" },
|
||||
{ "Authorization", "Bearer " + accessToken }
|
||||
};
|
||||
var response = HttpUtils.RequestGet(userInfoUrl(authToken), reqParams);
|
||||
|
||||
var userObj = response.parseObject();
|
||||
|
||||
this.checkResponse(userObj);
|
||||
|
||||
string userName = getUserName(userObj);
|
||||
|
||||
// 获取用户头像
|
||||
string avatar = this.getAvatar(userObj);
|
||||
|
||||
// 获取用户邮箱地址
|
||||
string email = this.getUserEmail(accessToken);
|
||||
|
||||
var authUser = new AuthUser
|
||||
{
|
||||
uuid = userObj.getString("id"),
|
||||
username = userName,
|
||||
nickname = userName,
|
||||
avatar = avatar,
|
||||
email = email,
|
||||
gender = AuthUserGender.UNKNOWN,
|
||||
token = authToken,
|
||||
source = source.getName(),
|
||||
originalUser = userObj,
|
||||
originalUserStr = response
|
||||
};
|
||||
return authUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户的真实名
|
||||
*
|
||||
* @param userInfoObject 用户json对象
|
||||
* @return 用户名
|
||||
*/
|
||||
private string getUserName(Dictionary<string, object> userInfoObject)
|
||||
{
|
||||
string firstName, lastName;
|
||||
// 获取firstName
|
||||
if (userInfoObject.ContainsKey("localizedFirstName"))
|
||||
{
|
||||
firstName = userInfoObject.getString("localizedFirstName");
|
||||
}
|
||||
else
|
||||
{
|
||||
firstName = getUserName(userInfoObject, "firstName");
|
||||
}
|
||||
// 获取lastName
|
||||
if (userInfoObject.ContainsKey("localizedLastName"))
|
||||
{
|
||||
lastName = userInfoObject.getString("localizedLastName");
|
||||
}
|
||||
else
|
||||
{
|
||||
lastName = getUserName(userInfoObject, "lastName");
|
||||
}
|
||||
return firstName + " " + lastName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户的头像
|
||||
*
|
||||
* @param userInfoObject 用户json对象
|
||||
* @return 用户的头像地址
|
||||
*/
|
||||
private string getAvatar(Dictionary<string, object> userInfoObject)
|
||||
{
|
||||
string avatar = null;
|
||||
var profilePictureObject = userInfoObject.getJSONObject("profilePicture");
|
||||
if (profilePictureObject.ContainsKey("displayImage~"))
|
||||
{
|
||||
var displayImageElements = profilePictureObject.getJSONObject("displayImage~")
|
||||
.getJSONArray("elements");
|
||||
if (null != displayImageElements && displayImageElements.Count > 0)
|
||||
{
|
||||
var largestImageObj = displayImageElements[displayImageElements.Count - 1];
|
||||
avatar = largestImageObj.getJSONArray("identifiers")[0].getString("identifier");
|
||||
}
|
||||
}
|
||||
return avatar;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户的email
|
||||
*
|
||||
* @param accessToken 用户授权后返回的token
|
||||
* @return 用户的邮箱地址
|
||||
*/
|
||||
private string getUserEmail(string accessToken)
|
||||
{
|
||||
string email = null;
|
||||
var reqParams = new Dictionary<string, object>();
|
||||
reqParams.Add("Host", "api.linkedin.com");
|
||||
reqParams.Add("Connection", "Keep-Alive");
|
||||
reqParams.Add("Authorization", "Bearer " + accessToken);
|
||||
var emailResponse = HttpUtils.RequestGet("https://api.linkedin.com/v2/emailAddress?q=members&projection=(elements*(handle~))", reqParams);
|
||||
|
||||
var emailObj = emailResponse.parseObject();
|
||||
this.checkResponse(emailObj);
|
||||
var listObject = emailObj.getJSONArray("elements");
|
||||
if (listObject != null && listObject.Count > 0)
|
||||
{
|
||||
email = listObject[listObject.Count - 1].getJSONObject("handle~").getString("emailAddress");
|
||||
}
|
||||
|
||||
return email;
|
||||
}
|
||||
|
||||
private string getUserName(Dictionary<string, object> userInfoObject, string nameKey)
|
||||
{
|
||||
string firstName;
|
||||
var firstNameObj = userInfoObject.getJSONObject(nameKey);
|
||||
var localizedObj = firstNameObj.getJSONObject("localized");
|
||||
var preferredLocaleObj = firstNameObj.getJSONObject("preferredLocale");
|
||||
firstName = localizedObj.getString(preferredLocaleObj.getString("language") + "_" + preferredLocaleObj.getString("country"));
|
||||
return firstName;
|
||||
}
|
||||
|
||||
public override AuthResponse refresh(AuthToken oldToken)
|
||||
{
|
||||
string refreshToken = oldToken.refreshToken;
|
||||
if (refreshToken.IsNullOrWhiteSpace())
|
||||
{
|
||||
throw new Exception(AuthResponseStatus.REQUIRED_REFRESH_TOKEN.GetDesc());
|
||||
}
|
||||
string refreshTokenUrl = this.refreshTokenUrl(refreshToken);
|
||||
|
||||
return new AuthResponse(AuthResponseStatus.SUCCESS.GetCode(), AuthResponseStatus.SUCCESS.GetDesc(), this.getToken(refreshTokenUrl));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取token,适用于获取access_token和刷新token
|
||||
*
|
||||
* @param accessTokenUrl 实际请求token的地址
|
||||
* @return token对象
|
||||
*/
|
||||
private AuthToken getToken(string accessTokenUrl)
|
||||
{
|
||||
var reqParams = new Dictionary<string, object>();
|
||||
reqParams.Add("Host", "www.linkedin.com");
|
||||
reqParams.Add("Content-Type", "application/x-www-form-urlencoded");
|
||||
|
||||
string response = HttpUtils.RequestPost(accessTokenUrl, null, reqParams);
|
||||
string accessTokenStr = response;
|
||||
var accessTokenObject = accessTokenStr.parseObject();
|
||||
|
||||
this.checkResponse(accessTokenObject);
|
||||
|
||||
var authToken = new AuthToken();
|
||||
authToken.accessToken = accessTokenObject.getString("access_token");
|
||||
authToken.refreshToken = accessTokenObject.getString("refresh_token");
|
||||
authToken.expireIn = accessTokenObject.getInt32("expire_in");
|
||||
|
||||
return authToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state}
|
||||
*
|
||||
* @param state state 验证授权流程的参数,可以防止csrf
|
||||
* @return 返回授权地址
|
||||
* @since 1.9.3
|
||||
*/
|
||||
public override string authorize(string state)
|
||||
{
|
||||
return UrlBuilder.fromBaseUrl(source.authorize())
|
||||
.queryParam("response_type", "code")
|
||||
.queryParam("client_id", config.clientId)
|
||||
.queryParam("redirect_uri", config.redirectUri)
|
||||
.queryParam("scope", config.scope.IsNullOrWhiteSpace() ? "r_liteprofile%20r_emailaddress%20w_member_social" : config.scope)
|
||||
.queryParam("state", getRealState(state))
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回获取userInfo的url
|
||||
*
|
||||
* @param authToken 用户授权后的token
|
||||
* @return 返回获取userInfo的url
|
||||
*/
|
||||
protected override string userInfoUrl(AuthToken authToken)
|
||||
{
|
||||
return UrlBuilder.fromBaseUrl(source.userInfo())
|
||||
.queryParam("projection", "(id,firstName,lastName,profilePicture(displayImage~:playableStreams))")
|
||||
.build();
|
||||
}
|
||||
/**
|
||||
* 校验请求结果
|
||||
*
|
||||
* @param response 请求结果
|
||||
* @return 如果请求结果正常,则返回Exception
|
||||
*/
|
||||
private void checkResponse(Dictionary<string, object> dic)
|
||||
{
|
||||
if (dic.ContainsKey("error"))
|
||||
{
|
||||
throw new Exception($"{dic.getString("error_description")}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
using JNPF.Extras.CollectiveOAuth.Cache;
|
||||
using JNPF.Extras.CollectiveOAuth.Config;
|
||||
using JNPF.Extras.CollectiveOAuth.Models;
|
||||
using JNPF.Extras.CollectiveOAuth.Utils;
|
||||
using JNPF.Extras.CollectiveOAuth.Enums;
|
||||
|
||||
namespace JNPF.Extras.CollectiveOAuth.Request;
|
||||
|
||||
public class MeituanAuthRequest : DefaultAuthRequest
|
||||
{
|
||||
public MeituanAuthRequest(ClientConfig config) : base(config, new MeituanAuthSource())
|
||||
{
|
||||
}
|
||||
|
||||
public MeituanAuthRequest(ClientConfig config, IAuthStateCache authStateCache)
|
||||
: base(config, new MeituanAuthSource(), authStateCache)
|
||||
{
|
||||
}
|
||||
protected override AuthToken getAccessToken(AuthCallback authCallback)
|
||||
{
|
||||
var reqParams = new Dictionary<string, object>
|
||||
{
|
||||
{ "app_id", config.clientId },
|
||||
{ "secret", config.clientSecret },
|
||||
{ "code", authCallback.code },
|
||||
{ "grant_type", "authorization_code" },
|
||||
};
|
||||
|
||||
var response = HttpUtils.RequestFormPost(source.accessToken(), reqParams.spellParams());
|
||||
var accessTokenObject = response.parseObject();
|
||||
|
||||
this.checkResponse(accessTokenObject);
|
||||
|
||||
var authToken = new AuthToken
|
||||
{
|
||||
accessToken = accessTokenObject.getString("access_token"),
|
||||
expireIn = accessTokenObject.getInt32("expires_in"),
|
||||
refreshToken = accessTokenObject.getString("refresh_token"),
|
||||
code = authCallback.code
|
||||
};
|
||||
|
||||
return authToken;
|
||||
}
|
||||
|
||||
protected override AuthUser getUserInfo(AuthToken authToken)
|
||||
{
|
||||
var reqParams = new Dictionary<string, object>
|
||||
{
|
||||
{ "app_id", config.clientId },
|
||||
{ "secret", config.clientSecret },
|
||||
{ "access_token", authToken.accessToken },
|
||||
};
|
||||
|
||||
var response = HttpUtils.RequestFormPost(source.userInfo(), reqParams.spellParams());
|
||||
var userObj = response.parseObject();
|
||||
|
||||
this.checkResponse(userObj);
|
||||
|
||||
var authUser = new AuthUser
|
||||
{
|
||||
uuid = userObj.getString("openid"),
|
||||
username = userObj.getString("nickname"),
|
||||
nickname = userObj.getString("nickname"),
|
||||
avatar = userObj.getString("avatar"),
|
||||
gender = AuthUserGender.UNKNOWN,
|
||||
token = authToken,
|
||||
source = source.getName(),
|
||||
originalUser = userObj,
|
||||
originalUserStr = response
|
||||
};
|
||||
return authUser;
|
||||
}
|
||||
|
||||
public override AuthResponse refresh(AuthToken oldToken)
|
||||
{
|
||||
var reqParams = new Dictionary<string, object>
|
||||
{
|
||||
{ "app_id", config.clientId },
|
||||
{ "secret", config.clientSecret },
|
||||
{ "refresh_token", oldToken.refreshToken },
|
||||
{ "grant_type", "refresh_token" },
|
||||
};
|
||||
|
||||
var response = HttpUtils.RequestFormPost(source.refresh(), reqParams.spellParams());
|
||||
var accessTokenObject = response.parseObject();
|
||||
|
||||
this.checkResponse(accessTokenObject);
|
||||
|
||||
var authToken = new AuthToken
|
||||
{
|
||||
accessToken = accessTokenObject.getString("access_token"),
|
||||
refreshToken = accessTokenObject.getString("refresh_token"),
|
||||
expireIn = accessTokenObject.getInt32("expires_in")
|
||||
};
|
||||
|
||||
return new AuthResponse(AuthResponseStatus.SUCCESS.GetCode(), AuthResponseStatus.SUCCESS.GetDesc(), authToken);
|
||||
}
|
||||
|
||||
|
||||
public override string authorize(string state)
|
||||
{
|
||||
return UrlBuilder.fromBaseUrl(source.authorize())
|
||||
.queryParam("response_type", "code")
|
||||
.queryParam("app_id", config.clientId)
|
||||
.queryParam("redirect_uri", config.redirectUri)
|
||||
.queryParam("state", getRealState(state))
|
||||
.queryParam("scope", config.scope)
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 校验请求结果
|
||||
*
|
||||
* @param response 请求结果
|
||||
* @return 如果请求结果正常,则返回Exception
|
||||
*/
|
||||
private void checkResponse(Dictionary<string, object> dic)
|
||||
{
|
||||
if (dic.ContainsKey("error_code"))
|
||||
{
|
||||
throw new Exception($"{dic.getString("error_msg")}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
using JNPF.Extras.CollectiveOAuth.Cache;
|
||||
using JNPF.Extras.CollectiveOAuth.Config;
|
||||
using JNPF.Extras.CollectiveOAuth.Models;
|
||||
using JNPF.Extras.CollectiveOAuth.Utils;
|
||||
using JNPF.Extras.CollectiveOAuth.Enums;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace JNPF.Extras.CollectiveOAuth.Request;
|
||||
|
||||
public class MicrosoftAuthRequest : DefaultAuthRequest
|
||||
{
|
||||
public MicrosoftAuthRequest(ClientConfig config) : base(config, new MicrosoftAuthSource())
|
||||
{
|
||||
}
|
||||
|
||||
public MicrosoftAuthRequest(ClientConfig config, IAuthStateCache authStateCache)
|
||||
: base(config, new MicrosoftAuthSource(), authStateCache)
|
||||
{
|
||||
}
|
||||
protected override AuthToken getAccessToken(AuthCallback authCallback)
|
||||
{
|
||||
return getToken(accessTokenUrl(authCallback.code));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取token,适用于获取access_token和刷新token
|
||||
*
|
||||
* @param accessTokenUrl 实际请求token的地址
|
||||
* @return token对象
|
||||
*/
|
||||
private AuthToken getToken(string accessTokenUrl)
|
||||
{
|
||||
var reqParams = new Dictionary<string, object>
|
||||
{
|
||||
{ "Host", "https://login.microsoftonline.com" },
|
||||
{ "Content-Type", "application/x-www-form-urlencoded" },
|
||||
};
|
||||
|
||||
var reqParamDic = GlobalAuthUtil.parseUrlObject(accessTokenUrl);
|
||||
var response = HttpUtils.RequestPost(accessTokenUrl, JsonConvert.SerializeObject(reqParamDic), reqParams);
|
||||
var accessTokenObject = response.parseObject();
|
||||
|
||||
this.checkResponse(accessTokenObject);
|
||||
|
||||
var authToken = new AuthToken();
|
||||
authToken.accessToken = accessTokenObject.getString("access_token");
|
||||
authToken.tokenType = accessTokenObject.getString("token_type");
|
||||
authToken.expireIn = accessTokenObject.getInt32("expires_in");
|
||||
authToken.refreshToken = accessTokenObject.getString("refresh_token");
|
||||
authToken.scope = accessTokenObject.getString("scope");
|
||||
|
||||
return authToken;
|
||||
}
|
||||
|
||||
|
||||
protected override AuthUser getUserInfo(AuthToken authToken)
|
||||
{
|
||||
var token = authToken.accessToken;
|
||||
var tokenType = authToken.tokenType;
|
||||
var jwt = tokenType + " " + token;
|
||||
var reqParams = new Dictionary<string, object>
|
||||
{
|
||||
{ "Authorization", jwt },
|
||||
};
|
||||
|
||||
var response = HttpUtils.RequestGet(userInfoUrl(authToken), reqParams);
|
||||
var userObj = response.parseObject();
|
||||
this.checkResponse(userObj);
|
||||
|
||||
var authUser = new AuthUser();
|
||||
authUser.uuid = userObj.getString("id");
|
||||
authUser.username = userObj.getString("userPrincipalName");
|
||||
authUser.nickname = userObj.getString("displayName");
|
||||
authUser.location = userObj.getString("officeLocation");
|
||||
authUser.email = userObj.getString("mail");
|
||||
authUser.gender = AuthUserGender.UNKNOWN;
|
||||
|
||||
authUser.token = authToken;
|
||||
authUser.source = source.getName();
|
||||
authUser.originalUser = userObj;
|
||||
authUser.originalUserStr = response;
|
||||
return authUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新access token (续期)
|
||||
*
|
||||
* @param authToken 登录成功后返回的Token信息
|
||||
* @return AuthResponse
|
||||
*/
|
||||
public override AuthResponse refresh(AuthToken authToken)
|
||||
{
|
||||
var token = getToken(refreshTokenUrl(authToken.refreshToken));
|
||||
return new AuthResponse(AuthResponseStatus.SUCCESS.GetCode(), AuthResponseStatus.SUCCESS.GetDesc(), token);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state}
|
||||
*
|
||||
* @param state state 验证授权流程的参数,可以防止csrf
|
||||
* @return 返回授权地址
|
||||
* @since 1.9.3
|
||||
*/
|
||||
public override string authorize(string state)
|
||||
{
|
||||
return UrlBuilder.fromBaseUrl(source.authorize())
|
||||
.queryParam("response_type", "code")
|
||||
.queryParam("client_id", config.clientId)
|
||||
.queryParam("redirect_uri", config.redirectUri)
|
||||
.queryParam("response_mode", "query")
|
||||
.queryParam("scope", "offline_access%20" + (config.scope.IsNullOrWhiteSpace() ? "user.read%20mail.read" : config.scope))
|
||||
.queryParam("state", getRealState(state))
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回获取accessToken的url
|
||||
*
|
||||
* @param code 授权code
|
||||
* @return 返回获取accessToken的url
|
||||
*/
|
||||
protected override string accessTokenUrl(string code)
|
||||
{
|
||||
return UrlBuilder.fromBaseUrl(source.accessToken())
|
||||
.queryParam("code", code)
|
||||
.queryParam("client_id", config.clientId)
|
||||
.queryParam("client_secret", config.clientSecret)
|
||||
.queryParam("grant_type", "authorization_code")
|
||||
.queryParam("scope", config.scope.IsNullOrWhiteSpace() ? "user.read%20mail.read" : config.scope)
|
||||
.queryParam("redirect_uri", config.redirectUri)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回获取userInfo的url
|
||||
*
|
||||
* @param authToken 用户授权后的token
|
||||
* @return 返回获取userInfo的url
|
||||
*/
|
||||
protected override string userInfoUrl(AuthToken authToken)
|
||||
{
|
||||
return UrlBuilder.fromBaseUrl(source.userInfo()).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回获取accessToken的url
|
||||
*
|
||||
* @param refreshToken 用户授权后的token
|
||||
* @return 返回获取accessToken的url
|
||||
*/
|
||||
protected override string refreshTokenUrl(string refreshToken)
|
||||
{
|
||||
return UrlBuilder.fromBaseUrl(source.refresh())
|
||||
.queryParam("client_id", config.clientId)
|
||||
.queryParam("client_secret", config.clientSecret)
|
||||
.queryParam("refresh_token", refreshToken)
|
||||
.queryParam("grant_type", "refresh_token")
|
||||
.queryParam("scope", config.scope.IsNullOrWhiteSpace() ? "user.read%20mail.read" : config.scope)
|
||||
.queryParam("redirect_uri", config.redirectUri)
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 检查响应内容是否正确
|
||||
*
|
||||
* @param object 请求响应内容
|
||||
*/
|
||||
private void checkResponse(Dictionary<string, object> dic)
|
||||
{
|
||||
if (dic.ContainsKey("error"))
|
||||
{
|
||||
throw new Exception(dic.getString("error_description"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
using JNPF.Extras.CollectiveOAuth.Cache;
|
||||
using JNPF.Extras.CollectiveOAuth.Config;
|
||||
using JNPF.Extras.CollectiveOAuth.Models;
|
||||
using JNPF.Extras.CollectiveOAuth.Utils;
|
||||
|
||||
namespace JNPF.Extras.CollectiveOAuth.Request;
|
||||
|
||||
public class OschinaAuthRequest : DefaultAuthRequest
|
||||
{
|
||||
public OschinaAuthRequest(ClientConfig config) : base(config, new OschinaAuthSource())
|
||||
{
|
||||
}
|
||||
|
||||
public OschinaAuthRequest(ClientConfig config, IAuthStateCache authStateCache)
|
||||
: base(config, new OschinaAuthSource(), authStateCache)
|
||||
{
|
||||
}
|
||||
|
||||
protected override AuthToken getAccessToken(AuthCallback authCallback)
|
||||
{
|
||||
string response = doPostAuthorizationCode(authCallback.code);
|
||||
var accessTokenObject = response.parseObject();
|
||||
this.checkResponse(accessTokenObject);
|
||||
|
||||
var authToken = new AuthToken();
|
||||
authToken.accessToken = accessTokenObject.getString("access_token");
|
||||
authToken.refreshToken = accessTokenObject.getString("refresh_token");
|
||||
authToken.uid = accessTokenObject.getString("uid");
|
||||
authToken.expireIn = accessTokenObject.getInt32("expires_in");
|
||||
authToken.code = authCallback.code;
|
||||
return authToken;
|
||||
}
|
||||
|
||||
protected override AuthUser getUserInfo(AuthToken authToken)
|
||||
{
|
||||
string response = doGetUserInfo(authToken);
|
||||
|
||||
var userObj = response.parseObject();
|
||||
this.checkResponse(userObj);
|
||||
|
||||
var authUser = new AuthUser();
|
||||
authUser.uuid = userObj.getString("id");
|
||||
authUser.username = userObj.getString("name");
|
||||
authUser.nickname = userObj.getString("name");
|
||||
authUser.avatar = userObj.getString("avatar");
|
||||
authUser.blog = userObj.getString("url");
|
||||
authUser.location = userObj.getString("location");
|
||||
authUser.email = userObj.getString("email");
|
||||
authUser.gender = GlobalAuthUtil.getRealGender(userObj.getString("gender"));
|
||||
|
||||
authUser.token = authToken;
|
||||
authUser.source = source.getName();
|
||||
authUser.originalUser = userObj;
|
||||
authUser.originalUserStr = response;
|
||||
return authUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回获取accessToken的url
|
||||
*
|
||||
* @param code 授权回调时带回的授权码
|
||||
* @return 返回获取accessToken的url
|
||||
*/
|
||||
protected override string accessTokenUrl(string code)
|
||||
{
|
||||
return UrlBuilder.fromBaseUrl(source.accessToken())
|
||||
.queryParam("code", code)
|
||||
.queryParam("client_id", config.clientId)
|
||||
.queryParam("client_secret", config.clientSecret)
|
||||
.queryParam("grant_type", "authorization_code")
|
||||
.queryParam("redirect_uri", config.redirectUri)
|
||||
.queryParam("dataType", "json")
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回获取userInfo的url
|
||||
*
|
||||
* @param authToken 用户授权后的token
|
||||
* @return 返回获取userInfo的url
|
||||
*/
|
||||
protected override string userInfoUrl(AuthToken authToken)
|
||||
{
|
||||
return UrlBuilder.fromBaseUrl(source.userInfo())
|
||||
.queryParam("access_token", authToken.accessToken)
|
||||
.queryParam("dataType", "json")
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 校验请求结果
|
||||
*
|
||||
* @param response 请求结果
|
||||
* @return 如果请求结果正常,则返回Exception
|
||||
*/
|
||||
private void checkResponse(Dictionary<string, object> dic)
|
||||
{
|
||||
if (dic.ContainsKey("error"))
|
||||
{
|
||||
throw new Exception($"{dic.getString("error_description")}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
using JNPF.Extras.CollectiveOAuth.Cache;
|
||||
using JNPF.Extras.CollectiveOAuth.Config;
|
||||
using JNPF.Extras.CollectiveOAuth.Models;
|
||||
using JNPF.Extras.CollectiveOAuth.Utils;
|
||||
using JNPF.Extras.CollectiveOAuth.Enums;
|
||||
|
||||
namespace JNPF.Extras.CollectiveOAuth.Request;
|
||||
|
||||
public class PinterestAuthRequest : DefaultAuthRequest
|
||||
{
|
||||
public PinterestAuthRequest(ClientConfig config) : base(config, new PinterestAuthSource())
|
||||
{
|
||||
}
|
||||
|
||||
public PinterestAuthRequest(ClientConfig config, IAuthStateCache authStateCache)
|
||||
: base(config, new PinterestAuthSource(), authStateCache)
|
||||
{
|
||||
}
|
||||
|
||||
protected override AuthToken getAccessToken(AuthCallback authCallback)
|
||||
{
|
||||
var response = doPostAuthorizationCode(authCallback.code);
|
||||
var accessTokenObject = response.parseObject();
|
||||
this.checkResponse(accessTokenObject);
|
||||
|
||||
var authToken = new AuthToken
|
||||
{
|
||||
accessToken = accessTokenObject.getString("access_token"),
|
||||
tokenType = accessTokenObject.getString("token_type"),
|
||||
code = authCallback.code
|
||||
};
|
||||
|
||||
return authToken;
|
||||
}
|
||||
|
||||
protected override AuthUser getUserInfo(AuthToken authToken)
|
||||
{
|
||||
string userinfoUrl = userInfoUrl(authToken);
|
||||
var response = HttpUtils.RequestGet(userinfoUrl);
|
||||
var responseObj = response.parseObject();
|
||||
this.checkResponse(responseObj);
|
||||
|
||||
var userObj = responseObj.getString("data").parseObject();
|
||||
|
||||
var authUser = new AuthUser();
|
||||
authUser.uuid = userObj.getString("id");
|
||||
authUser.username = userObj.getString("username");
|
||||
authUser.nickname = userObj.getString("first_name") + userObj.getString("last_name");
|
||||
authUser.avatar = getAvatarUrl(userObj);
|
||||
authUser.remark = userObj.getString("bio");
|
||||
authUser.gender = AuthUserGender.UNKNOWN;
|
||||
authUser.token = authToken;
|
||||
authUser.source = source.getName();
|
||||
authUser.originalUser = responseObj;
|
||||
authUser.originalUserStr = response;
|
||||
return authUser;
|
||||
}
|
||||
|
||||
|
||||
private string getAvatarUrl(Dictionary<string, object> userObj)
|
||||
{
|
||||
// image is a map data structure
|
||||
var jsonObject = userObj.getString("image").parseObject();
|
||||
if (jsonObject.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return jsonObject.getString("60x60").parseObject().getString("url");
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state}
|
||||
*
|
||||
* @param state state 验证授权流程的参数,可以防止csrf
|
||||
* @return 返回授权地址
|
||||
* @since 1.9.3
|
||||
*/
|
||||
public override string authorize(string state)
|
||||
{
|
||||
return UrlBuilder.fromBaseUrl(source.authorize())
|
||||
.queryParam("response_type", "code")
|
||||
.queryParam("client_id", config.clientId)
|
||||
.queryParam("redirect_uri", config.redirectUri)
|
||||
.queryParam("scope", config.scope.IsNullOrWhiteSpace() ? "read_public" : config.scope)
|
||||
.queryParam("state", getRealState(state))
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回获取userInfo的url
|
||||
*
|
||||
* @param authToken token
|
||||
* @return 返回获取userInfo的url
|
||||
*/
|
||||
protected override string userInfoUrl(AuthToken authToken)
|
||||
{
|
||||
return UrlBuilder.fromBaseUrl(source.userInfo())
|
||||
.queryParam("access_token", authToken.accessToken)
|
||||
.queryParam("fields", "id,username,first_name,last_name,bio,image")
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 检查响应内容是否正确
|
||||
*
|
||||
* @param object 请求响应内容
|
||||
*/
|
||||
private void checkResponse(Dictionary<string, object> dic)
|
||||
{
|
||||
if (dic.ContainsKey("status") && "failure".Equals(dic.getString("status")))
|
||||
{
|
||||
throw new Exception($"{dic.getString("message")}");
|
||||
}
|
||||
}
|
||||
}
|
||||
132
common/Tnb.CollectiveOAuth/Request/AuthRequests/QQAuthRequest.cs
Normal file
132
common/Tnb.CollectiveOAuth/Request/AuthRequests/QQAuthRequest.cs
Normal file
@@ -0,0 +1,132 @@
|
||||
using JNPF.Extras.CollectiveOAuth.Cache;
|
||||
using JNPF.Extras.CollectiveOAuth.Config;
|
||||
using JNPF.Extras.CollectiveOAuth.Models;
|
||||
using JNPF.Extras.CollectiveOAuth.Utils;
|
||||
using JNPF.Extras.CollectiveOAuth.Enums;
|
||||
|
||||
namespace JNPF.Extras.CollectiveOAuth.Request;
|
||||
|
||||
public class QQAuthRequest : DefaultAuthRequest
|
||||
{
|
||||
public QQAuthRequest(ClientConfig config) : base(config, new QQAuthSource())
|
||||
{
|
||||
}
|
||||
|
||||
public QQAuthRequest(ClientConfig config, IAuthStateCache authStateCache)
|
||||
: base(config, new QQAuthSource(), authStateCache)
|
||||
{
|
||||
}
|
||||
|
||||
protected override AuthToken getAccessToken(AuthCallback authCallback)
|
||||
{
|
||||
string response = doGetAuthorizationCode(authCallback.code);
|
||||
return getAuthToken(response);
|
||||
}
|
||||
|
||||
public override AuthResponse refresh(AuthToken authToken)
|
||||
{
|
||||
string response = HttpUtils.RequestGet(refreshTokenUrl(authToken.refreshToken));
|
||||
return new AuthResponse(AuthResponseStatus.SUCCESS.GetCode(), AuthResponseStatus.SUCCESS.GetDesc(), getAuthToken(response));
|
||||
}
|
||||
|
||||
protected override AuthUser getUserInfo(AuthToken authToken)
|
||||
{
|
||||
string openId = this.getOpenId(authToken);
|
||||
string response = doGetUserInfo(authToken);
|
||||
var userObj = response.parseObject();
|
||||
if (userObj.getInt32("ret") != 0)
|
||||
{
|
||||
throw new Exception(userObj.getString("msg"));
|
||||
}
|
||||
string avatar = userObj.getString("figureurl_qq_2");
|
||||
if (avatar.IsNullOrWhiteSpace())
|
||||
{
|
||||
avatar = userObj.getString("figureurl_qq_1");
|
||||
}
|
||||
|
||||
string location = $"{userObj.getString("province")}-{userObj.getString("city")}";
|
||||
|
||||
var authUser = new AuthUser();
|
||||
authUser.uuid = openId;
|
||||
authUser.username = userObj.getString("nickname");
|
||||
authUser.nickname = userObj.getString("nickname");
|
||||
authUser.avatar = avatar;
|
||||
authUser.location = location;
|
||||
authUser.email = userObj.getString("email");
|
||||
authUser.remark = userObj.getString("bio");
|
||||
authUser.gender = GlobalAuthUtil.getRealGender(userObj.getString("gender"));
|
||||
authUser.token = authToken;
|
||||
authUser.source = source.getName();
|
||||
|
||||
authUser.originalUser = userObj;
|
||||
authUser.originalUserStr = response;
|
||||
return authUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取QQ用户的OpenId,支持自定义是否启用查询unionid的功能,如果启用查询unionid的功能,
|
||||
* 那就需要开发者先通过邮件申请unionid功能,参考链接 {@see http://wiki.connect.qq.com/unionid%E4%BB%8B%E7%BB%8D}
|
||||
*
|
||||
* @param authToken 通过{@link AuthQqRequest#getAccessToken(AuthCallback)}获取到的{@code authToken}
|
||||
* @return openId
|
||||
*/
|
||||
private string getOpenId(AuthToken authToken)
|
||||
{
|
||||
var getOpenIdUrl = UrlBuilder.fromBaseUrl("https://graph.qq.com/oauth2.0/me")
|
||||
.queryParam("access_token", authToken.accessToken)
|
||||
.queryParam("unionid", config.unionId)
|
||||
.build();
|
||||
string response = HttpUtils.RequestGet(getOpenIdUrl);
|
||||
if (!response.IsNullOrWhiteSpace())
|
||||
{
|
||||
string body = response;
|
||||
string removePrefix = body.Replace("callback(", "");
|
||||
string removeSuffix = removePrefix.Replace(");", "");
|
||||
string openId = removeSuffix.Trim();
|
||||
var openIdObj = openId.parseObject();
|
||||
if (openIdObj.ContainsKey("error"))
|
||||
{
|
||||
throw new Exception(openIdObj.getString("error") + ":" + openIdObj.getString("error_description"));
|
||||
}
|
||||
authToken.openId = openIdObj.getString("openid");
|
||||
if (openIdObj.ContainsKey("unionid"))
|
||||
{
|
||||
authToken.unionId = openIdObj.getString("unionid");
|
||||
}
|
||||
|
||||
return authToken.unionId.IsNullOrWhiteSpace() ? authToken.openId : authToken.unionId;
|
||||
}
|
||||
|
||||
throw new Exception("request error");
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回获取userInfo的url
|
||||
*
|
||||
* @param authToken 用户授权token
|
||||
* @return 返回获取userInfo的url
|
||||
*/
|
||||
protected override string userInfoUrl(AuthToken authToken)
|
||||
{
|
||||
return UrlBuilder.fromBaseUrl(source.userInfo())
|
||||
.queryParam("access_token", authToken.accessToken)
|
||||
.queryParam("oauth_consumer_key", config.clientId)
|
||||
.queryParam("openid", authToken.openId)
|
||||
.build();
|
||||
}
|
||||
|
||||
private AuthToken getAuthToken(string response)
|
||||
{
|
||||
var accessTokenObject = response.parseStringObject();
|
||||
if (!accessTokenObject.ContainsKey("access_token") || accessTokenObject.ContainsKey("code"))
|
||||
{
|
||||
throw new Exception(accessTokenObject.getString("msg"));
|
||||
}
|
||||
var authToken = new AuthToken();
|
||||
authToken.accessToken = accessTokenObject.getString("access_token");
|
||||
authToken.expireIn = accessTokenObject.getInt32("expires_in");
|
||||
authToken.refreshToken = accessTokenObject.getString("refresh_token");
|
||||
|
||||
return authToken;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
using JNPF.Extras.CollectiveOAuth.Cache;
|
||||
using JNPF.Extras.CollectiveOAuth.Config;
|
||||
using JNPF.Extras.CollectiveOAuth.Models;
|
||||
using JNPF.Extras.CollectiveOAuth.Utils;
|
||||
using JNPF.Extras.CollectiveOAuth.Enums;
|
||||
|
||||
namespace JNPF.Extras.CollectiveOAuth.Request;
|
||||
|
||||
public class RenrenAuthRequest : DefaultAuthRequest
|
||||
{
|
||||
public RenrenAuthRequest(ClientConfig config) : base(config, new RenrenAuthSource())
|
||||
{
|
||||
}
|
||||
|
||||
public RenrenAuthRequest(ClientConfig config, IAuthStateCache authStateCache)
|
||||
: base(config, new RenrenAuthSource(), authStateCache)
|
||||
{
|
||||
}
|
||||
|
||||
protected override AuthToken getAccessToken(AuthCallback authCallback)
|
||||
{
|
||||
return this.getToken(accessTokenUrl(authCallback.code));
|
||||
}
|
||||
|
||||
protected override AuthUser getUserInfo(AuthToken authToken)
|
||||
{
|
||||
var response = doGetUserInfo(authToken);
|
||||
var userObj = response.parseObject().getJSONObject("response");
|
||||
|
||||
var authUser = new AuthUser();
|
||||
authUser.uuid = userObj.getString("id");
|
||||
authUser.username = userObj.getString("name");
|
||||
authUser.nickname = userObj.getString("name");
|
||||
authUser.avatar = getAvatarUrl(userObj);
|
||||
authUser.company = getCompany(userObj);
|
||||
authUser.gender = getGender(userObj);
|
||||
|
||||
authUser.token = authToken;
|
||||
authUser.source = source.getName();
|
||||
authUser.originalUser = userObj;
|
||||
authUser.originalUserStr = response;
|
||||
return authUser;
|
||||
}
|
||||
|
||||
public override AuthResponse refresh(AuthToken authToken)
|
||||
{
|
||||
var token = getToken(this.refreshTokenUrl(authToken.refreshToken));
|
||||
return new AuthResponse(AuthResponseStatus.SUCCESS.GetCode(), AuthResponseStatus.SUCCESS.GetDesc(), token);
|
||||
}
|
||||
|
||||
private AuthToken getToken(string url)
|
||||
{
|
||||
var response = HttpUtils.RequestPost(url);
|
||||
var jsonObject = response.parseObject();
|
||||
if (jsonObject.ContainsKey("error"))
|
||||
{
|
||||
throw new Exception("Failed to get token from Renren: " + jsonObject);
|
||||
}
|
||||
|
||||
var authToken = new AuthToken();
|
||||
authToken.accessToken = jsonObject.getString("access_token");
|
||||
authToken.tokenType = jsonObject.getString("token_type");
|
||||
authToken.expireIn = jsonObject.getInt32("expires_in");
|
||||
authToken.refreshToken = jsonObject.getString("refresh_token");
|
||||
authToken.openId = jsonObject.getJSONObject("user").getString("id");
|
||||
|
||||
return authToken;
|
||||
}
|
||||
|
||||
private string getAvatarUrl(Dictionary<string, object> userObj)
|
||||
{
|
||||
var jsonArray = userObj.getJSONArray("avatar");
|
||||
if (jsonArray.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return jsonArray[0].getString("url");
|
||||
}
|
||||
|
||||
private AuthUserGender getGender(Dictionary<string, object> userObj)
|
||||
{
|
||||
var basicInformation = userObj.getJSONObject("basicInformation");
|
||||
if (basicInformation.Count == 0)
|
||||
{
|
||||
return AuthUserGender.UNKNOWN;
|
||||
}
|
||||
return GlobalAuthUtil.getRealGender(basicInformation.getString("sex"));
|
||||
}
|
||||
|
||||
private string getCompany(Dictionary<string, object> userObj)
|
||||
{
|
||||
var jsonArray = userObj.getJSONArray("work");
|
||||
if (jsonArray.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return jsonArray[0].getString("name");
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回获取userInfo的url
|
||||
*
|
||||
* @param authToken 用户授权后的token
|
||||
* @return 返回获取userInfo的url
|
||||
*/
|
||||
protected override string userInfoUrl(AuthToken authToken)
|
||||
{
|
||||
return UrlBuilder.fromBaseUrl(source.userInfo())
|
||||
.queryParam("access_token", authToken.accessToken)
|
||||
.queryParam("userId", authToken.openId)
|
||||
.build();
|
||||
}
|
||||
|
||||
public override string authorize(string state)
|
||||
{
|
||||
return UrlBuilder.fromBaseUrl(source.authorize())
|
||||
.queryParam("response_type", "code")
|
||||
.queryParam("client_id", config.clientId)
|
||||
.queryParam("redirect_uri", config.redirectUri)
|
||||
.queryParam("state", getRealState(state))
|
||||
.queryParam("display", "page")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
using JNPF.Extras.CollectiveOAuth.Cache;
|
||||
using JNPF.Extras.CollectiveOAuth.Config;
|
||||
using JNPF.Extras.CollectiveOAuth.Models;
|
||||
using JNPF.Extras.CollectiveOAuth.Utils;
|
||||
using JNPF.Extras.CollectiveOAuth.Enums;
|
||||
|
||||
namespace JNPF.Extras.CollectiveOAuth.Request;
|
||||
|
||||
public class StackOverflowAuthRequest : DefaultAuthRequest
|
||||
{
|
||||
public StackOverflowAuthRequest(ClientConfig config) : base(config, new StackOverflowAuthSource())
|
||||
{
|
||||
}
|
||||
|
||||
public StackOverflowAuthRequest(ClientConfig config, IAuthStateCache authStateCache)
|
||||
: base(config, new StackOverflowAuthSource(), authStateCache)
|
||||
{
|
||||
}
|
||||
|
||||
protected override AuthToken getAccessToken(AuthCallback authCallback)
|
||||
{
|
||||
string accessTokenUrl = this.accessTokenUrl(authCallback.code);
|
||||
|
||||
var reqHeaders = new Dictionary<string, object>
|
||||
{
|
||||
{ "Content-Type", "application/x-www-form-urlencoded" },
|
||||
};
|
||||
var reqParams = accessTokenUrl.parseUrlObject();
|
||||
|
||||
var response = HttpUtils.RequestPost(source.accessToken(), reqParams.spellParams(), reqHeaders);
|
||||
|
||||
var accessTokenObject = response.parseObject();
|
||||
this.checkResponse(accessTokenObject);
|
||||
|
||||
var authToken = new AuthToken();
|
||||
authToken.accessToken = accessTokenObject.getString("access_token");
|
||||
authToken.expireIn = accessTokenObject.getInt32("expires");
|
||||
return authToken;
|
||||
}
|
||||
|
||||
protected override AuthUser getUserInfo(AuthToken authToken)
|
||||
{
|
||||
string userInfoUrl = UrlBuilder.fromBaseUrl(this.source.userInfo())
|
||||
.queryParam("access_token", authToken.accessToken)
|
||||
.queryParam("site", "stackoverflow")
|
||||
.queryParam("key", this.config.stackOverflowKey)
|
||||
.build();
|
||||
|
||||
var response = HttpUtils.RequestGet(userInfoUrl);
|
||||
var responseObj = response.parseObject();
|
||||
this.checkResponse(responseObj);
|
||||
var userObj = responseObj.getString("items").parseListObject()[0];
|
||||
|
||||
var authUser = new AuthUser();
|
||||
authUser.uuid = userObj.getString("user_id");
|
||||
authUser.username = userObj.getString("username");
|
||||
authUser.nickname = userObj.getString("display_name");
|
||||
authUser.avatar = userObj.getString("profile_image");
|
||||
authUser.location = userObj.getString("location");
|
||||
|
||||
authUser.gender = AuthUserGender.UNKNOWN;
|
||||
authUser.token = authToken;
|
||||
authUser.source = source.getName();
|
||||
authUser.originalUser = responseObj;
|
||||
authUser.originalUserStr = response;
|
||||
return authUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state}
|
||||
*
|
||||
* @param state state 验证授权流程的参数,可以防止csrf
|
||||
* @return 返回授权地址
|
||||
* @since 1.9.3
|
||||
*/
|
||||
|
||||
public override string authorize(string state)
|
||||
{
|
||||
return UrlBuilder.fromBaseUrl(source.authorize())
|
||||
.queryParam("response_type", "code")
|
||||
.queryParam("client_id", config.clientId)
|
||||
.queryParam("redirect_uri", config.redirectUri)
|
||||
.queryParam("scope", config.scope.IsNullOrWhiteSpace() ? "read_inbox" : config.scope)
|
||||
.queryParam("state", getRealState(state))
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 检查响应内容是否正确
|
||||
*
|
||||
* @param object 请求响应内容
|
||||
*/
|
||||
private void checkResponse(Dictionary<string, object> dic)
|
||||
{
|
||||
if (dic.ContainsKey("error"))
|
||||
{
|
||||
throw new Exception($"{dic.getString("error_description")}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
using JNPF.Extras.CollectiveOAuth.Cache;
|
||||
using JNPF.Extras.CollectiveOAuth.Config;
|
||||
using JNPF.Extras.CollectiveOAuth.Models;
|
||||
using JNPF.Extras.CollectiveOAuth.Utils;
|
||||
using JNPF.Extras.CollectiveOAuth.Enums;
|
||||
|
||||
namespace JNPF.Extras.CollectiveOAuth.Request;
|
||||
|
||||
public class TeambitionAuthRequest : DefaultAuthRequest
|
||||
{
|
||||
public TeambitionAuthRequest(ClientConfig config) : base(config, new GithubAuthSource())
|
||||
{
|
||||
}
|
||||
|
||||
public TeambitionAuthRequest(ClientConfig config, IAuthStateCache authStateCache)
|
||||
: base(config, new GithubAuthSource(), authStateCache)
|
||||
{
|
||||
}
|
||||
|
||||
protected override AuthToken getAccessToken(AuthCallback authCallback)
|
||||
{
|
||||
var reqHeaders = new Dictionary<string, object>
|
||||
{
|
||||
{ "Content-Type", "application/x-www-form-urlencoded" },
|
||||
};
|
||||
var reqParams = new Dictionary<string, object>
|
||||
{
|
||||
{ "client_id", config.clientId },
|
||||
{ "client_secret", config.clientSecret },
|
||||
{ "code", authCallback.code },
|
||||
{ "grant_type", "code" },
|
||||
};
|
||||
|
||||
var response = HttpUtils.RequestPost(source.accessToken(), reqParams.spellParams(), reqHeaders);
|
||||
|
||||
var accessTokenObject = response.parseObject();
|
||||
|
||||
this.checkResponse(accessTokenObject);
|
||||
|
||||
var authToken = new AuthToken();
|
||||
authToken.accessToken = accessTokenObject.getString("access_token");
|
||||
authToken.refreshToken = accessTokenObject.getString("refresh_token");
|
||||
|
||||
return authToken;
|
||||
}
|
||||
|
||||
protected override AuthUser getUserInfo(AuthToken authToken)
|
||||
{
|
||||
var accessToken = authToken.accessToken;
|
||||
var reqHeaders = new Dictionary<string, object>
|
||||
{
|
||||
{ "Authorization", "OAuth2 " + accessToken },
|
||||
};
|
||||
|
||||
var response = HttpUtils.RequestGet(source.userInfo(), reqHeaders);
|
||||
var userObj = response.parseObject();
|
||||
|
||||
this.checkResponse(userObj);
|
||||
authToken.uid = userObj.getString("_id");
|
||||
|
||||
var authUser = new AuthUser();
|
||||
authUser.uuid = userObj.getString("_id");
|
||||
authUser.username = userObj.getString("name");
|
||||
authUser.nickname = userObj.getString("name");
|
||||
authUser.avatar = userObj.getString("avatarUrl");
|
||||
authUser.blog = userObj.getString("website");
|
||||
authUser.location = userObj.getString("location");
|
||||
authUser.email = userObj.getString("email");
|
||||
authUser.gender = AuthUserGender.UNKNOWN;
|
||||
authUser.token = authToken;
|
||||
authUser.source = source.getName();
|
||||
authUser.originalUser = userObj;
|
||||
authUser.originalUserStr = response;
|
||||
return authUser;
|
||||
}
|
||||
|
||||
|
||||
public override AuthResponse refresh(AuthToken oldToken)
|
||||
{
|
||||
string uid = oldToken.uid;
|
||||
string refreshToken = oldToken.refreshToken;
|
||||
var reqHeaders = new Dictionary<string, object>
|
||||
{
|
||||
{ "Content-Type", "application/x-www-form-urlencoded" },
|
||||
};
|
||||
var reqParams = new Dictionary<string, object>
|
||||
{
|
||||
{ "_userId", uid },
|
||||
{ "refresh_token", refreshToken },
|
||||
};
|
||||
|
||||
var response = HttpUtils.RequestPost(source.refresh(), reqParams.spellParams(), reqHeaders);
|
||||
|
||||
var refreshTokenObject = response.parseObject();
|
||||
|
||||
this.checkResponse(refreshTokenObject);
|
||||
|
||||
var authToken = new AuthToken();
|
||||
authToken.accessToken = refreshTokenObject.getString("access_token");
|
||||
authToken.refreshToken = refreshTokenObject.getString("refresh_token");
|
||||
|
||||
return new AuthResponse(AuthResponseStatus.SUCCESS.GetCode(), AuthResponseStatus.SUCCESS.GetDesc(), authToken);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 校验请求结果
|
||||
*
|
||||
* @param response 请求结果
|
||||
* @return 如果请求结果正常,则返回Exception
|
||||
*/
|
||||
private void checkResponse(Dictionary<string, object> dic)
|
||||
{
|
||||
if (dic.ContainsKey("message") && dic.ContainsKey("name"))
|
||||
{
|
||||
throw new Exception($"{dic.getString("getString")}, {dic.getString("name")}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
using JNPF.Extras.CollectiveOAuth.Cache;
|
||||
using JNPF.Extras.CollectiveOAuth.Config;
|
||||
using JNPF.Extras.CollectiveOAuth.Models;
|
||||
using JNPF.Extras.CollectiveOAuth.Utils;
|
||||
using JNPF.Extras.CollectiveOAuth.Enums;
|
||||
|
||||
namespace JNPF.Extras.CollectiveOAuth.Request;
|
||||
|
||||
public class ToutiaoAuthRequest : DefaultAuthRequest
|
||||
{
|
||||
public ToutiaoAuthRequest(ClientConfig config) : base(config, new ToutiaoAuthSource())
|
||||
{
|
||||
}
|
||||
|
||||
public ToutiaoAuthRequest(ClientConfig config, IAuthStateCache authStateCache)
|
||||
: base(config, new ToutiaoAuthSource(), authStateCache)
|
||||
{
|
||||
}
|
||||
|
||||
protected override AuthToken getAccessToken(AuthCallback authCallback)
|
||||
{
|
||||
var response = doGetAuthorizationCode(authCallback.code);
|
||||
var accessTokenObject = response.parseObject();
|
||||
|
||||
this.checkResponse(accessTokenObject);
|
||||
|
||||
var authToken = new AuthToken();
|
||||
authToken.accessToken = accessTokenObject.getString("access_token");
|
||||
authToken.expireIn = accessTokenObject.getInt32("expires_in");
|
||||
authToken.openId = accessTokenObject.getString("open_id");
|
||||
authToken.code = authCallback.code;
|
||||
return authToken;
|
||||
}
|
||||
|
||||
protected override AuthUser getUserInfo(AuthToken authToken)
|
||||
{
|
||||
string userResponse = doGetUserInfo(authToken);
|
||||
var userProfile = userResponse.parseObject();
|
||||
this.checkResponse(userProfile);
|
||||
|
||||
var userObj = userProfile.getString("data").parseObject();
|
||||
|
||||
bool isAnonymousUser = userObj.getInt32("uid_type") == 14;
|
||||
string anonymousUserName = "匿名用户";
|
||||
|
||||
var authUser = new AuthUser();
|
||||
authUser.uuid = userObj.getString("uid");
|
||||
authUser.username = isAnonymousUser ? anonymousUserName : userObj.getString("screen_name");
|
||||
authUser.nickname = isAnonymousUser ? anonymousUserName : userObj.getString("screen_name");
|
||||
authUser.avatar = userObj.getString("avatar_url");
|
||||
authUser.remark = userObj.getString("description");
|
||||
authUser.gender = GlobalAuthUtil.getRealGender(userObj.getString("gender"));
|
||||
authUser.token = authToken;
|
||||
authUser.source = source.getName();
|
||||
authUser.originalUser = userProfile;
|
||||
authUser.originalUserStr = userResponse;
|
||||
return authUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state}
|
||||
*
|
||||
* @param state state 验证授权流程的参数,可以防止csrf
|
||||
* @return 返回授权地址
|
||||
* @since 1.9.3
|
||||
*/
|
||||
public override string authorize(string state)
|
||||
{
|
||||
return UrlBuilder.fromBaseUrl(source.authorize())
|
||||
.queryParam("response_type", "code")
|
||||
.queryParam("client_key", config.clientId)
|
||||
.queryParam("redirect_uri", config.redirectUri)
|
||||
.queryParam("auth_only", 1)
|
||||
.queryParam("display", 0)
|
||||
.queryParam("state", getRealState(state))
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回获取accessToken的url
|
||||
*
|
||||
* @param code 授权码
|
||||
* @return 返回获取accessToken的url
|
||||
*/
|
||||
protected override string accessTokenUrl(string code)
|
||||
{
|
||||
return UrlBuilder.fromBaseUrl(source.accessToken())
|
||||
.queryParam("code", code)
|
||||
.queryParam("client_key", config.clientId)
|
||||
.queryParam("client_secret", config.clientSecret)
|
||||
.queryParam("grant_type", "authorization_code")
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回获取userInfo的url
|
||||
*
|
||||
* @param authToken 用户授权后的token
|
||||
* @return 返回获取userInfo的url
|
||||
*/
|
||||
protected override string userInfoUrl(AuthToken authToken)
|
||||
{
|
||||
return UrlBuilder.fromBaseUrl(source.userInfo())
|
||||
.queryParam("client_key", config.clientId)
|
||||
.queryParam("access_token", authToken.accessToken)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验请求结果
|
||||
*
|
||||
* @param response 请求结果
|
||||
* @return 如果请求结果正常,则返回Exception
|
||||
*/
|
||||
private void checkResponse(Dictionary<string, object> dic)
|
||||
{
|
||||
if (dic.ContainsKey("error_code"))
|
||||
{
|
||||
throw new Exception(getToutiaoErrorCode(dic.getInt32("error_code")).GetDesc());
|
||||
}
|
||||
}
|
||||
|
||||
private AuthToutiaoErrorCode getToutiaoErrorCode(int errorCode)
|
||||
{
|
||||
var enumObjects = typeof(AuthToutiaoErrorCode).ToList();
|
||||
var codeEnum = enumObjects.Where(a => a.ID == errorCode).ToList();
|
||||
if (codeEnum.Count > 0)
|
||||
{
|
||||
return GlobalAuthUtil.enumFromString<AuthToutiaoErrorCode>(codeEnum[0].Name);
|
||||
}
|
||||
else
|
||||
{
|
||||
return AuthToutiaoErrorCode.EC999;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
//using JNPF.Extras.CollectiveOAuth.Cache;
|
||||
//using JNPF.Extras.CollectiveOAuth.Config;
|
||||
//using JNPF.Extras.CollectiveOAuth.Models;
|
||||
//using JNPF.Extras.CollectiveOAuth.Utils;
|
||||
//using System;
|
||||
//using System.Collections.Generic;
|
||||
//using JNPF.Extras.CollectiveOAuth.Enums;
|
||||
//
|
||||
//namespace JNPF.Extras.CollectiveOAuth.Request
|
||||
//{
|
||||
// public class TwitteAuthRequest : DefaultAuthRequest
|
||||
// {
|
||||
// public TwitteAuthRequest(ClientConfig config) : base(config, new TwitterAuthSource())
|
||||
// {
|
||||
// }
|
||||
//
|
||||
// public TwitteAuthRequest(ClientConfig config, IAuthStateCache authStateCache)
|
||||
// : base(config, new TwitterAuthSource(), authStateCache)
|
||||
// {
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Obtaining a request token
|
||||
// * https://developer.twitter.com/en/docs/twitter-for-websites/log-in-with-twitter/guides/implementing-sign-in-with-twitter
|
||||
// *
|
||||
// * @return request token
|
||||
// */
|
||||
// public AuthToken getRequestToken()
|
||||
// {
|
||||
// String baseUrl = "https://api.twitter.com/oauth/request_token";
|
||||
//
|
||||
// Map<String, Object> oauthParams = buildOauthParams();
|
||||
// oauthParams.put("oauth_callback", config.getRedirectUri());
|
||||
// oauthParams.put("oauth_signature", generateTwitterSignature(oauthParams, "POST", baseUrl, config.getClientSecret(), null));
|
||||
// String header = buildHeader(oauthParams);
|
||||
// HttpResponse requestToken = HttpRequest.post(baseUrl).header("Authorization", header).execute();
|
||||
// checkResponse(requestToken);
|
||||
//
|
||||
// Map<String, Object> res = GlobalAuthUtil.parseQueryToMap(requestToken.body());
|
||||
//
|
||||
// return AuthToken.builder()
|
||||
// .oauthToken(res.get("oauth_token").toString())
|
||||
// .oauthTokenSecret(res.get("oauth_token_secret").toString())
|
||||
// .oauthCallbackConfirmed(Boolean.valueOf(res.get("oauth_callback_confirmed").toString()))
|
||||
// .build();
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Convert request token to access token
|
||||
// * https://developer.twitter.com/en/docs/twitter-for-websites/log-in-with-twitter/guides/implementing-sign-in-with-twitter
|
||||
// *
|
||||
// * @return access token
|
||||
// */
|
||||
// protected override AuthToken getAccessToken(AuthCallback authCallback)
|
||||
// {
|
||||
// //Sign the request
|
||||
// /*using (HMACSHA1 hashAlgorithm = new HMACSHA1(new ASCIIEncoding().GetBytes(signingKey)))
|
||||
// {
|
||||
//
|
||||
// return Convert.ToBase64String(
|
||||
// hashAlgorithm.ComputeHash(
|
||||
// new ASCIIEncoding().GetBytes(baseSignatureString)
|
||||
// )
|
||||
// );
|
||||
// }*/
|
||||
//
|
||||
// Map<String, Object> oauthParams = buildOauthParams();
|
||||
// oauthParams.put("oauth_token", authCallback.getOauthToken());
|
||||
// oauthParams.put("oauth_verifier", authCallback.getOauthVerifier());
|
||||
// oauthParams.put("oauth_signature", generateTwitterSignature(oauthParams, "POST", source.accessToken(), config.getClientSecret(), authCallback.getOauthToken()));
|
||||
// String header = buildHeader(oauthParams);
|
||||
// HttpResponse response = HttpRequest.post(source.accessToken())
|
||||
// .header("Authorization", header)
|
||||
// .header("Content-Type", "application/x-www-form-urlencoded")
|
||||
// .form("oauth_verifier", authCallback.getOauthVerifier())
|
||||
// .execute();
|
||||
// checkResponse(response);
|
||||
//
|
||||
// Map<String, Object> requestToken = GlobalAuthUtil.parseQueryToMap(response.body());
|
||||
//
|
||||
// return AuthToken.builder()
|
||||
// .oauthToken(requestToken.get("oauth_token").toString())
|
||||
// .oauthTokenSecret(requestToken.get("oauth_token_secret").toString())
|
||||
// .userId(requestToken.get("user_id").toString())
|
||||
// .screenName(requestToken.get("screen_name").toString())
|
||||
// .build();
|
||||
// }
|
||||
//
|
||||
// protected override AuthUser getUserInfo(AuthToken authToken)
|
||||
// {
|
||||
// Map<String, Object> queryParams = new HashMap<>();
|
||||
// queryParams.put("user_id", authToken.getUserId());
|
||||
// queryParams.put("screen_name", authToken.getScreenName());
|
||||
// queryParams.put("include_entities", true);
|
||||
//
|
||||
// Map<String, Object> oauthParams = buildOauthParams();
|
||||
// oauthParams.put("oauth_token", authToken.getOauthToken());
|
||||
//
|
||||
// Map < String, Object > params = new HashMap<>(oauthParams);
|
||||
// params.putAll(queryParams);
|
||||
// oauthParams.put("oauth_signature", generateTwitterSignature(params, "GET", source.userInfo(), config.getClientSecret(), authToken.getOauthTokenSecret()));
|
||||
// String header = buildHeader(oauthParams);
|
||||
// HttpResponse response = HttpRequest.get(userInfoUrl(authToken)).header("Authorization", header).execute();
|
||||
// checkResponse(response);
|
||||
// JSONObject userInfo = JSONObject.parseObject(response.body());
|
||||
//
|
||||
// return AuthUser.builder()
|
||||
// .uuid(userInfo.getString("id_str"))
|
||||
// .username(userInfo.getString("screen_name"))
|
||||
// .nickname(userInfo.getString("name"))
|
||||
// .remark(userInfo.getString("description"))
|
||||
// .avatar(userInfo.getString("profile_image_url_https"))
|
||||
// .blog(userInfo.getString("url"))
|
||||
// .location(userInfo.getString("location"))
|
||||
// .source(source.toString())
|
||||
// .token(authToken)
|
||||
// .build();
|
||||
// }
|
||||
//
|
||||
// protected override string userInfoUrl(AuthToken authToken)
|
||||
// {
|
||||
// return UrlBuilder.fromBaseUrl(source.userInfo())
|
||||
// .queryParam("user_id", authToken.userId)
|
||||
// .queryParam("screen_name", authToken.screenName)
|
||||
// .queryParam("include_entities", true)
|
||||
// .build();
|
||||
// }
|
||||
//
|
||||
// private Map<String, Object> buildOauthParams()
|
||||
// {
|
||||
// Map < String, Object > params = new HashMap<>();
|
||||
// params.put("oauth_consumer_key", config.getClientId());
|
||||
// params.put("oauth_nonce", GlobalAuthUtil.generateNonce(32));
|
||||
// params.put("oauth_signature_method", "HMAC-SHA1");
|
||||
// params.put("oauth_timestamp", GlobalAuthUtil.getTimestamp());
|
||||
// params.put("oauth_version", "1.0");
|
||||
// return params;
|
||||
// }
|
||||
//
|
||||
// private string buildHeader(Map<String, Object> oauthParams)
|
||||
// {
|
||||
// final StringBuilder sb = new StringBuilder(PREAMBLE);
|
||||
//
|
||||
// for (Map.Entry<String, Object> param : oauthParams.entrySet())
|
||||
// {
|
||||
// if (sb.length() > PREAMBLE.length())
|
||||
// {
|
||||
// sb.append(", ");
|
||||
// }
|
||||
// sb.append(param.getKey())
|
||||
// .append("=\"")
|
||||
// .append(urlEncode(param.getValue().toString()))
|
||||
// .append('"');
|
||||
// }
|
||||
//
|
||||
// return sb.toString();
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 校验请求结果
|
||||
// *
|
||||
// * @param response 请求结果
|
||||
// * @return 如果请求结果正常,则返回Exception
|
||||
// */
|
||||
// private void checkResponse(Dictionary<string, object> dic)
|
||||
// {
|
||||
// if (dic.Count == 0)
|
||||
// {
|
||||
// throw new Exception("请求返回数据为空");
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
@@ -0,0 +1,148 @@
|
||||
using JNPF.Extras.CollectiveOAuth.Cache;
|
||||
using JNPF.Extras.CollectiveOAuth.Config;
|
||||
using JNPF.Extras.CollectiveOAuth.Enums;
|
||||
using JNPF.Extras.CollectiveOAuth.Models;
|
||||
using JNPF.Extras.CollectiveOAuth.Utils;
|
||||
|
||||
namespace JNPF.Extras.CollectiveOAuth.Request;
|
||||
|
||||
public class WeChatEnterpriseAuthRequest : DefaultAuthRequest
|
||||
{
|
||||
public WeChatEnterpriseAuthRequest(ClientConfig config) : base(config, new WechatEnterpriseAuthSource())
|
||||
{
|
||||
}
|
||||
|
||||
public WeChatEnterpriseAuthRequest(ClientConfig config, IAuthStateCache authStateCache)
|
||||
: base(config, new WechatEnterpriseAuthSource(), authStateCache)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* 微信的特殊性,此时返回的信息同时包含 openid 和 access_token
|
||||
*
|
||||
* @param authCallback 回调返回的参数
|
||||
* @return 所有信息
|
||||
*/
|
||||
protected override AuthToken getAccessToken(AuthCallback authCallback)
|
||||
{
|
||||
string response = doGetAuthorizationCode(accessTokenUrl(authCallback.code));
|
||||
var jsonObj = response.parseObject();
|
||||
|
||||
this.checkResponse(jsonObj);
|
||||
|
||||
var authToken = new AuthToken();
|
||||
authToken.accessToken = jsonObj.getString("access_token");
|
||||
authToken.expireIn = jsonObj.getInt32("expires_in");
|
||||
authToken.code = authCallback.code;
|
||||
|
||||
return authToken;
|
||||
}
|
||||
|
||||
protected override AuthUser getUserInfo(AuthToken authToken)
|
||||
{
|
||||
string response = doGetUserInfo(authToken);
|
||||
var jsonObj = response.parseObject();
|
||||
this.checkResponse(jsonObj);
|
||||
|
||||
// 返回 OpenId 或其他,均代表非当前企业用户,不支持
|
||||
if (!jsonObj.ContainsKey("UserId"))
|
||||
{
|
||||
throw new Exception(AuthResponseStatus.UNIDENTIFIED_PLATFORM.GetDesc());
|
||||
}
|
||||
string userId = jsonObj.getString("UserId");
|
||||
string userDetailResponse = getUserDetail(authToken.accessToken, userId);
|
||||
var userDetailObj = userDetailResponse.parseObject();
|
||||
this.checkResponse(userDetailObj);
|
||||
|
||||
var authUser = new AuthUser();
|
||||
authUser.username = userDetailObj.getString("name");
|
||||
authUser.nickname = userDetailObj.getString("alias");
|
||||
authUser.avatar = userDetailObj.getString("avatar");
|
||||
authUser.location = userDetailObj.getString("address");
|
||||
authUser.email = userDetailObj.getString("email");
|
||||
authUser.uuid = userDetailObj.getString("userId");
|
||||
authUser.token = authToken;
|
||||
authUser.source = source.getName();
|
||||
authUser.gender = GlobalAuthUtil.getWechatRealGender(userDetailObj.getString("gender"));
|
||||
|
||||
authUser.originalUser = userDetailObj;
|
||||
authUser.originalUserStr = response;
|
||||
return authUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验请求结果
|
||||
*
|
||||
* @param response 请求结果
|
||||
* @return 如果请求结果正常,则返回JSONObject
|
||||
*/
|
||||
private void checkResponse(Dictionary<string, object> dic)
|
||||
{
|
||||
if (dic.ContainsKey("errcode") && dic.getInt32("errcode") != 0)
|
||||
{
|
||||
throw new Exception($"errcode: {dic.getString("errcode")}, errmsg: {dic.getString("errmsg")}");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state}
|
||||
*
|
||||
* @param state state 验证授权流程的参数,可以防止csrf
|
||||
* @return 返回授权地址
|
||||
* @since 1.9.3
|
||||
*/
|
||||
public override string authorize(string state)
|
||||
{
|
||||
return UrlBuilder.fromBaseUrl(source.authorize())
|
||||
.queryParam("appid", config.clientId)
|
||||
.queryParam("response_type", "code")
|
||||
.queryParam("redirect_uri", config.redirectUri)
|
||||
.queryParam("scope", config.scope.IsNullOrWhiteSpace() ? "snsapi_userinfo" : config.scope)
|
||||
.queryParam("state", getRealState(state) + "#wechat_redirect")
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回获取accessToken的url
|
||||
*
|
||||
* @param code 授权码
|
||||
* @return 返回获取accessToken的url
|
||||
*/
|
||||
protected override string accessTokenUrl(String code)
|
||||
{
|
||||
return UrlBuilder.fromBaseUrl(source.accessToken())
|
||||
.queryParam("corpid", config.clientId)
|
||||
.queryParam("corpsecret", config.clientSecret)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回获取userInfo的url
|
||||
*
|
||||
* @param authToken 用户授权后的token
|
||||
* @return 返回获取userInfo的url
|
||||
*/
|
||||
protected override string userInfoUrl(AuthToken authToken)
|
||||
{
|
||||
return UrlBuilder.fromBaseUrl(source.userInfo())
|
||||
.queryParam("access_token", authToken.accessToken)
|
||||
.queryParam("code", authToken.code)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户详情
|
||||
*
|
||||
* @param accessToken accessToken
|
||||
* @param userId 企业内用户id
|
||||
* @return 用户详情
|
||||
*/
|
||||
private string getUserDetail(string accessToken, string userId)
|
||||
{
|
||||
string userDetailUrl = UrlBuilder.fromBaseUrl("https://qyapi.weixin.qq.com/cgi-bin/user/get")
|
||||
.queryParam("access_token", accessToken)
|
||||
.queryParam("userid", userId)
|
||||
.build();
|
||||
return HttpUtils.RequestGet(userDetailUrl);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
using JNPF.Extras.CollectiveOAuth.Cache;
|
||||
using JNPF.Extras.CollectiveOAuth.Config;
|
||||
using JNPF.Extras.CollectiveOAuth.Enums;
|
||||
using JNPF.Extras.CollectiveOAuth.Models;
|
||||
using JNPF.Extras.CollectiveOAuth.Utils;
|
||||
|
||||
namespace JNPF.Extras.CollectiveOAuth.Request;
|
||||
|
||||
public class WeChatEnterpriseScanAuthRequest : DefaultAuthRequest
|
||||
{
|
||||
public WeChatEnterpriseScanAuthRequest(ClientConfig config) : base(config, new WechatEnterpriseScanAuthSource())
|
||||
{
|
||||
}
|
||||
|
||||
public WeChatEnterpriseScanAuthRequest(ClientConfig config, IAuthStateCache authStateCache)
|
||||
: base(config, new WechatEnterpriseScanAuthSource(), authStateCache)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* 微信的特殊性,此时返回的信息同时包含 openid 和 access_token
|
||||
*
|
||||
* @param authCallback 回调返回的参数
|
||||
* @return 所有信息
|
||||
*/
|
||||
protected override AuthToken getAccessToken(AuthCallback authCallback)
|
||||
{
|
||||
string response = doGetAuthorizationCode(accessTokenUrl(authCallback.code));
|
||||
var jsonObj = response.parseObject();
|
||||
|
||||
this.checkResponse(jsonObj);
|
||||
|
||||
var authToken = new AuthToken();
|
||||
authToken.accessToken = jsonObj.getString("access_token");
|
||||
authToken.expireIn = jsonObj.getInt32("expires_in");
|
||||
authToken.code = authCallback.code;
|
||||
|
||||
return authToken;
|
||||
}
|
||||
|
||||
protected override AuthUser getUserInfo(AuthToken authToken)
|
||||
{
|
||||
string response = doGetUserInfo(authToken);
|
||||
var jsonObj = response.parseObject();
|
||||
this.checkResponse(jsonObj);
|
||||
|
||||
// 返回 OpenId 或其他,均代表非当前企业用户,不支持
|
||||
if (!jsonObj.ContainsKey("UserId"))
|
||||
{
|
||||
throw new Exception(AuthResponseStatus.UNIDENTIFIED_PLATFORM.GetDesc());
|
||||
}
|
||||
string userId = jsonObj.getString("UserId");
|
||||
string userDetailResponse = getUserDetail(authToken.accessToken, userId);
|
||||
var userDetailObj = userDetailResponse.parseObject();
|
||||
this.checkResponse(userDetailObj);
|
||||
|
||||
var authUser = new AuthUser();
|
||||
authUser.username = userDetailObj.getString("name");
|
||||
authUser.nickname = userDetailObj.getString("alias");
|
||||
authUser.avatar = userDetailObj.getString("avatar");
|
||||
authUser.location = userDetailObj.getString("address");
|
||||
authUser.email = userDetailObj.getString("email");
|
||||
authUser.uuid = userDetailObj.getString("userId");
|
||||
if(authUser.uuid.IsNullOrWhiteSpace()) authUser.uuid = userDetailObj.getString("userid");
|
||||
authUser.token = authToken;
|
||||
authUser.source = source.getName();
|
||||
authUser.gender = GlobalAuthUtil.getWechatRealGender(userDetailObj.getString("gender"));
|
||||
|
||||
authUser.originalUser = userDetailObj;
|
||||
authUser.originalUserStr = response;
|
||||
return authUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验请求结果
|
||||
*
|
||||
* @param response 请求结果
|
||||
* @return 如果请求结果正常,则返回JSONObject
|
||||
*/
|
||||
private void checkResponse(Dictionary<string, object> dic)
|
||||
{
|
||||
if (dic.ContainsKey("errcode") && dic.getInt32("errcode") != 0)
|
||||
{
|
||||
throw new Exception($"errcode: {dic.getString("errcode")}, errmsg: {dic.getString("errmsg")}");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state}
|
||||
*
|
||||
* @param state state 验证授权流程的参数,可以防止csrf
|
||||
* @return 返回授权地址
|
||||
* @since 1.9.3
|
||||
*/
|
||||
public override string authorize(string state)
|
||||
{
|
||||
return UrlBuilder.fromBaseUrl(source.authorize())
|
||||
.queryParam("appid", config.clientId)
|
||||
.queryParam("agentid", config.agentId)
|
||||
.queryParam("redirect_uri", config.redirectUri)
|
||||
.queryParam("state", getRealState(state))
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回获取accessToken的url
|
||||
*
|
||||
* @param code 授权码
|
||||
* @return 返回获取accessToken的url
|
||||
*/
|
||||
protected override string accessTokenUrl(String code)
|
||||
{
|
||||
return UrlBuilder.fromBaseUrl(source.accessToken())
|
||||
.queryParam("corpid", config.clientId)
|
||||
.queryParam("corpsecret", config.clientSecret)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回获取userInfo的url
|
||||
*
|
||||
* @param authToken 用户授权后的token
|
||||
* @return 返回获取userInfo的url
|
||||
*/
|
||||
protected override string userInfoUrl(AuthToken authToken)
|
||||
{
|
||||
return UrlBuilder.fromBaseUrl(source.userInfo())
|
||||
.queryParam("access_token", authToken.accessToken)
|
||||
.queryParam("code", authToken.code)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户详情
|
||||
*
|
||||
* @param accessToken accessToken
|
||||
* @param userId 企业内用户id
|
||||
* @return 用户详情
|
||||
*/
|
||||
private string getUserDetail(string accessToken, string userId)
|
||||
{
|
||||
string userDetailUrl = UrlBuilder.fromBaseUrl("https://qyapi.weixin.qq.com/cgi-bin/user/get")
|
||||
.queryParam("access_token", accessToken)
|
||||
.queryParam("userid", userId)
|
||||
.build();
|
||||
return HttpUtils.RequestGet(userDetailUrl);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
using JNPF.Extras.CollectiveOAuth.Config;
|
||||
using JNPF.Extras.CollectiveOAuth.Models;
|
||||
using JNPF.Extras.CollectiveOAuth.Utils;
|
||||
using JNPF.Extras.CollectiveOAuth.Enums;
|
||||
using JNPF.Extras.CollectiveOAuth.Cache;
|
||||
|
||||
namespace JNPF.Extras.CollectiveOAuth.Request;
|
||||
|
||||
public partial class WeChatMpAuthRequest : DefaultAuthRequest
|
||||
{
|
||||
public WeChatMpAuthRequest(ClientConfig config) : base(config, new WechatMPAuthSource())
|
||||
{
|
||||
}
|
||||
|
||||
public WeChatMpAuthRequest(ClientConfig config, IAuthStateCache authStateCache) : base(config, new WechatMPAuthSource(), authStateCache)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* 微信的特殊性,此时返回的信息同时包含 openid 和 access_token
|
||||
*
|
||||
* @param authCallback 回调返回的参数
|
||||
* @return 所有信息
|
||||
*/
|
||||
protected override AuthToken getAccessToken(AuthCallback authCallback)
|
||||
{
|
||||
return this.getToken(accessTokenUrl(authCallback.code));
|
||||
}
|
||||
|
||||
protected override AuthUser getUserInfo(AuthToken authToken)
|
||||
{
|
||||
string openId = authToken.openId;
|
||||
|
||||
string response = doGetUserInfo(authToken);
|
||||
var jsonObj = response.parseObject();
|
||||
|
||||
this.checkResponse(jsonObj);
|
||||
|
||||
//string location = String.format("%s-%s-%s", object.getString("country"), object.getString("province"), object.getString("city"));
|
||||
string location = $"{jsonObj.getString("country")}-{jsonObj.getString("province")}-{jsonObj.getString("city")}";
|
||||
if (jsonObj.ContainsKey("unionid"))
|
||||
{
|
||||
authToken.unionId = jsonObj.getString("unionid");
|
||||
}
|
||||
|
||||
var authUser = new AuthUser();
|
||||
|
||||
authUser.username = jsonObj.getString("nickname");
|
||||
authUser.nickname = jsonObj.getString("nickname");
|
||||
authUser.avatar = jsonObj.getString("headimgurl");
|
||||
authUser.location = location;
|
||||
authUser.uuid = openId;
|
||||
authUser.gender = GlobalAuthUtil.getWechatRealGender(jsonObj.getString("sex"));
|
||||
authUser.token = authToken;
|
||||
authUser.source = source.getName();
|
||||
|
||||
authUser.originalUser = jsonObj;
|
||||
authUser.originalUserStr = response;
|
||||
|
||||
return authUser;
|
||||
}
|
||||
|
||||
public override AuthResponse refresh(AuthToken oldToken)
|
||||
{
|
||||
return new AuthResponse(Convert.ToInt32(AuthResponseStatus.SUCCESS), null, this.getToken(refreshTokenUrl(oldToken.refreshToken)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查响应内容是否正确
|
||||
*
|
||||
* @param object 请求响应内容
|
||||
*/
|
||||
private void checkResponse(Dictionary<string, object> dic)
|
||||
{
|
||||
if (dic.ContainsKey("errcode"))
|
||||
{
|
||||
throw new Exception($"errcode: {dic.getString("errcode")}, errmsg: {dic.getString("errmsg")}");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取token,适用于获取access_token和刷新token
|
||||
*
|
||||
* @param accessTokenUrl 实际请求token的地址
|
||||
* @return token对象
|
||||
*/
|
||||
private AuthToken getToken(string accessTokenUrl)
|
||||
{
|
||||
string response = HttpUtils.RequestGet(accessTokenUrl);
|
||||
var accessTokenObject = response.parseObject();
|
||||
|
||||
this.checkResponse(accessTokenObject);
|
||||
|
||||
var authToken = new AuthToken();
|
||||
|
||||
authToken.accessToken = accessTokenObject.getString("access_token");
|
||||
authToken.refreshToken = accessTokenObject.getString("refresh_token");
|
||||
authToken.expireIn = accessTokenObject.getInt32("expires_in");
|
||||
authToken.openId = accessTokenObject.getString("openid");
|
||||
authToken.scope = accessTokenObject.getString("scope");
|
||||
|
||||
return authToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state}
|
||||
*
|
||||
* @param state state 验证授权流程的参数,可以防止csrf
|
||||
* @return 返回授权地址
|
||||
* @since 1.9.3
|
||||
*/
|
||||
public override string authorize(string state)
|
||||
{
|
||||
return UrlBuilder.fromBaseUrl(source.authorize())
|
||||
.queryParam("appid", config.clientId)
|
||||
.queryParam("redirect_uri", GlobalAuthUtil.urlEncode(config.redirectUri))
|
||||
.queryParam("response_type", "code")
|
||||
.queryParam("scope", config.scope.IsNullOrWhiteSpace() ? "snsapi_userinfo" : config.scope)
|
||||
.queryParam("state", getRealState(state) + "#wechat_redirect")
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回获取accessToken的url
|
||||
*
|
||||
* @param code 授权码
|
||||
* @return 返回获取accessToken的url
|
||||
*/
|
||||
protected override string accessTokenUrl(string code)
|
||||
{
|
||||
return UrlBuilder.fromBaseUrl(source.accessToken())
|
||||
.queryParam("appid", config.clientId)
|
||||
.queryParam("secret", config.clientSecret)
|
||||
.queryParam("code", code)
|
||||
.queryParam("grant_type", "authorization_code")
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回获取userInfo的url
|
||||
*
|
||||
* @param authToken 用户授权后的token
|
||||
* @return 返回获取userInfo的url
|
||||
*/
|
||||
protected override string userInfoUrl(AuthToken authToken)
|
||||
{
|
||||
return UrlBuilder.fromBaseUrl(source.userInfo())
|
||||
.queryParam("access_token", authToken.accessToken)
|
||||
.queryParam("openid", authToken.openId)
|
||||
.queryParam("lang", "zh_CN")
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回获取userInfo的url
|
||||
*
|
||||
* @param refreshToken getAccessToken方法返回的refreshToken
|
||||
* @return 返回获取userInfo的url
|
||||
*/
|
||||
protected override string refreshTokenUrl(String refreshToken)
|
||||
{
|
||||
return UrlBuilder.fromBaseUrl(source.refresh())
|
||||
.queryParam("appid", config.clientId)
|
||||
.queryParam("grant_type", "refresh_token")
|
||||
.queryParam("refresh_token", refreshToken)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
using JNPF.Extras.CollectiveOAuth.Config;
|
||||
using JNPF.Extras.CollectiveOAuth.Models;
|
||||
using JNPF.Extras.CollectiveOAuth.Utils;
|
||||
using JNPF.Extras.CollectiveOAuth.Enums;
|
||||
using JNPF.Extras.CollectiveOAuth.Cache;
|
||||
|
||||
namespace JNPF.Extras.CollectiveOAuth.Request;
|
||||
|
||||
public partial class WeChatOpenAuthRequest : DefaultAuthRequest
|
||||
{
|
||||
public WeChatOpenAuthRequest(ClientConfig config) : base(config, new WechatOpenAuthSource())
|
||||
{
|
||||
}
|
||||
|
||||
public WeChatOpenAuthRequest(ClientConfig config, IAuthStateCache authStateCache)
|
||||
: base(config, new WechatOpenAuthSource(), authStateCache)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* 微信的特殊性,此时返回的信息同时包含 openid 和 access_token
|
||||
*
|
||||
* @param authCallback 回调返回的参数
|
||||
* @return 所有信息
|
||||
*/
|
||||
protected override AuthToken getAccessToken(AuthCallback authCallback)
|
||||
{
|
||||
return this.getToken(accessTokenUrl(authCallback.code));
|
||||
}
|
||||
|
||||
protected override AuthUser getUserInfo(AuthToken authToken)
|
||||
{
|
||||
string openId = authToken.openId;
|
||||
|
||||
string response = doGetUserInfo(authToken);
|
||||
var jsonObj = response.parseObject();
|
||||
|
||||
this.checkResponse(jsonObj);
|
||||
|
||||
//string location = String.format("%s-%s-%s", object.getString("country"), object.getString("province"), object.getString("city"));
|
||||
string location = $"{jsonObj.getString("country")}-{jsonObj.getString("province")}-{jsonObj.getString("city")}";
|
||||
if (jsonObj.ContainsKey("unionid"))
|
||||
{
|
||||
authToken.unionId = jsonObj.getString("unionid");
|
||||
}
|
||||
|
||||
var authUser = new AuthUser();
|
||||
|
||||
authUser.username = jsonObj.getString("nickname");
|
||||
authUser.nickname = jsonObj.getString("nickname");
|
||||
authUser.avatar = jsonObj.getString("headimgurl");
|
||||
authUser.location = location;
|
||||
authUser.uuid = openId;
|
||||
authUser.gender = GlobalAuthUtil.getWechatRealGender(jsonObj.getString("sex"));
|
||||
authUser.token = authToken;
|
||||
authUser.source = source.getName();
|
||||
|
||||
authUser.originalUser = jsonObj;
|
||||
authUser.originalUserStr = response;
|
||||
|
||||
return authUser;
|
||||
}
|
||||
|
||||
public override AuthResponse refresh(AuthToken oldToken)
|
||||
{
|
||||
return new AuthResponse(Convert.ToInt32(AuthResponseStatus.SUCCESS), null, this.getToken(refreshTokenUrl(oldToken.refreshToken)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查响应内容是否正确
|
||||
*
|
||||
* @param object 请求响应内容
|
||||
*/
|
||||
private void checkResponse(Dictionary<string, object> dic)
|
||||
{
|
||||
if (dic.ContainsKey("errcode"))
|
||||
{
|
||||
throw new Exception($"errcode: {dic.getString("errcode")}, errmsg: {dic.getString("errmsg")}");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取token,适用于获取access_token和刷新token
|
||||
*
|
||||
* @param accessTokenUrl 实际请求token的地址
|
||||
* @return token对象
|
||||
*/
|
||||
private AuthToken getToken(string accessTokenUrl)
|
||||
{
|
||||
string response = HttpUtils.RequestGet(accessTokenUrl);
|
||||
var accessTokenObject = response.parseObject();
|
||||
|
||||
this.checkResponse(accessTokenObject);
|
||||
|
||||
var authToken = new AuthToken();
|
||||
|
||||
authToken.accessToken = accessTokenObject.getString("access_token");
|
||||
authToken.refreshToken = accessTokenObject.getString("refresh_token");
|
||||
authToken.expireIn = accessTokenObject.getInt32("expires_in");
|
||||
authToken.openId = accessTokenObject.getString("openid");
|
||||
|
||||
return authToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state}
|
||||
*
|
||||
* @param state state 验证授权流程的参数,可以防止csrf
|
||||
* @return 返回授权地址
|
||||
* @since 1.9.3
|
||||
*/
|
||||
public override string authorize(string state)
|
||||
{
|
||||
return UrlBuilder.fromBaseUrl(source.authorize())
|
||||
.queryParam("appid", config.clientId)
|
||||
.queryParam("redirect_uri", GlobalAuthUtil.urlEncode(config.redirectUri))
|
||||
.queryParam("response_type", "code")
|
||||
.queryParam("scope", config.scope.IsNullOrWhiteSpace() ? "snsapi_login" : config.scope)
|
||||
.queryParam("state", getRealState(state))
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回获取accessToken的url
|
||||
*
|
||||
* @param code 授权码
|
||||
* @return 返回获取accessToken的url
|
||||
*/
|
||||
protected override string accessTokenUrl(string code)
|
||||
{
|
||||
return UrlBuilder.fromBaseUrl(source.accessToken())
|
||||
.queryParam("appid", config.clientId)
|
||||
.queryParam("secret", config.clientSecret)
|
||||
.queryParam("code", code)
|
||||
.queryParam("grant_type", "authorization_code")
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回获取userInfo的url
|
||||
*
|
||||
* @param authToken 用户授权后的token
|
||||
* @return 返回获取userInfo的url
|
||||
*/
|
||||
protected override string userInfoUrl(AuthToken authToken)
|
||||
{
|
||||
return UrlBuilder.fromBaseUrl(source.userInfo())
|
||||
.queryParam("access_token", authToken.accessToken)
|
||||
.queryParam("openid", authToken.openId)
|
||||
.queryParam("lang", "zh_CN")
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回获取userInfo的url
|
||||
*
|
||||
* @param refreshToken getAccessToken方法返回的refreshToken
|
||||
* @return 返回获取userInfo的url
|
||||
*/
|
||||
protected override string refreshTokenUrl(string refreshToken)
|
||||
{
|
||||
return UrlBuilder.fromBaseUrl(source.refresh())
|
||||
.queryParam("appid", config.clientId)
|
||||
.queryParam("grant_type", "refresh_token")
|
||||
.queryParam("refresh_token", refreshToken)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
using JNPF.Extras.CollectiveOAuth.Cache;
|
||||
using JNPF.Extras.CollectiveOAuth.Config;
|
||||
using JNPF.Extras.CollectiveOAuth.Models;
|
||||
using JNPF.Extras.CollectiveOAuth.Utils;
|
||||
using JNPF.Extras.CollectiveOAuth.Enums;
|
||||
|
||||
namespace JNPF.Extras.CollectiveOAuth.Request;
|
||||
|
||||
public class WeiboAuthRequest : DefaultAuthRequest
|
||||
{
|
||||
public WeiboAuthRequest(ClientConfig config) : base(config, new WeiboAuthSource())
|
||||
{
|
||||
}
|
||||
|
||||
public WeiboAuthRequest(ClientConfig config, IAuthStateCache authStateCache)
|
||||
: base(config, new WeiboAuthSource(), authStateCache)
|
||||
{
|
||||
}
|
||||
|
||||
protected override AuthToken getAccessToken(AuthCallback authCallback)
|
||||
{
|
||||
var response = doPostAuthorizationCode(authCallback.code);
|
||||
var accessTokenObject = response.parseObject();
|
||||
if (accessTokenObject.ContainsKey("error"))
|
||||
{
|
||||
throw new Exception(accessTokenObject.getString("error_description"));
|
||||
}
|
||||
|
||||
var authToken = new AuthToken();
|
||||
authToken.accessToken = accessTokenObject.getString("access_token");
|
||||
authToken.uid = accessTokenObject.getString("uid");
|
||||
authToken.openId = accessTokenObject.getString("uid");
|
||||
authToken.expireIn = accessTokenObject.getInt32("expires_in");
|
||||
authToken.code = authCallback.code;
|
||||
|
||||
return authToken;
|
||||
}
|
||||
|
||||
protected override AuthUser getUserInfo(AuthToken authToken)
|
||||
{
|
||||
var accessToken = authToken.accessToken;
|
||||
var uid = authToken.uid;
|
||||
var oauthParam = $"uid={uid}&access_token={accessToken}";
|
||||
var reqParams = new Dictionary<string, object>();
|
||||
reqParams.Add("Authorization", "OAuth2 " + oauthParam);
|
||||
reqParams.Add("API-RemoteIP", "application/x-www-form-urlencoded");
|
||||
|
||||
string response = HttpUtils.RequestGet(userInfoUrl(authToken), reqParams);
|
||||
|
||||
var userObj = response.parseObject();
|
||||
if (userObj.ContainsKey("error"))
|
||||
{
|
||||
throw new Exception(userObj.getString("error"));
|
||||
}
|
||||
|
||||
var authUser = new AuthUser();
|
||||
authUser.uuid = userObj.getString("id");
|
||||
authUser.username = userObj.getString("name");
|
||||
authUser.nickname = userObj.getString("screen_name");
|
||||
authUser.avatar = userObj.getString("profile_image_url");
|
||||
authUser.blog = userObj.getString("url").IsNullOrWhiteSpace() ? $"{"https://weibo.com/"}{userObj.getString("profile_url")}" : userObj.getString("url");
|
||||
authUser.location = userObj.getString("location");
|
||||
authUser.remark = userObj.getString("description");
|
||||
authUser.gender = GlobalAuthUtil.getRealGender(userObj.getString("gender"));
|
||||
|
||||
authUser.token = authToken;
|
||||
authUser.source = source.getName();
|
||||
authUser.originalUser = userObj;
|
||||
authUser.originalUserStr = response;
|
||||
return authUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回获取userInfo的url
|
||||
*
|
||||
* @param authToken authToken
|
||||
* @return 返回获取userInfo的url
|
||||
*/
|
||||
protected override string userInfoUrl(AuthToken authToken)
|
||||
{
|
||||
return UrlBuilder.fromBaseUrl(source.userInfo())
|
||||
.queryParam("access_token", authToken.accessToken)
|
||||
.queryParam("uid", authToken.uid)
|
||||
.build();
|
||||
}
|
||||
|
||||
public override AuthResponse revoke(AuthToken authToken)
|
||||
{
|
||||
var response = doGetRevoke(authToken);
|
||||
var retObj = response.parseObject();
|
||||
if (retObj.ContainsKey("error"))
|
||||
{
|
||||
return new AuthResponse(AuthResponseStatus.FAILURE.GetCode(), retObj.getString("error"));
|
||||
}
|
||||
// 返回 result = true 表示取消授权成功,否则失败
|
||||
AuthResponseStatus status = retObj.getBool("result") ? AuthResponseStatus.SUCCESS : AuthResponseStatus.FAILURE;
|
||||
return new AuthResponse(status.GetCode(), status.GetDesc());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
using JNPF.Extras.CollectiveOAuth.Cache;
|
||||
using JNPF.Extras.CollectiveOAuth.Config;
|
||||
using JNPF.Extras.CollectiveOAuth.Enums;
|
||||
using JNPF.Extras.CollectiveOAuth.Models;
|
||||
using JNPF.Extras.CollectiveOAuth.Utils;
|
||||
|
||||
namespace JNPF.Extras.CollectiveOAuth.Request;
|
||||
|
||||
public class XiaoMiAuthRequest : DefaultAuthRequest
|
||||
{
|
||||
private static readonly string PREFIX = "&&&START&&&";
|
||||
public XiaoMiAuthRequest(ClientConfig config) : base(config, new XiaoMiAuthSource())
|
||||
{
|
||||
}
|
||||
|
||||
public XiaoMiAuthRequest(ClientConfig config, IAuthStateCache authStateCache)
|
||||
: base(config, new XiaoMiAuthSource(), authStateCache)
|
||||
{
|
||||
}
|
||||
|
||||
protected override AuthToken getAccessToken(AuthCallback authCallback)
|
||||
{
|
||||
return getToken(accessTokenUrl(authCallback.code));
|
||||
}
|
||||
|
||||
private AuthToken getToken(string accessTokenUrl)
|
||||
{
|
||||
string response = HttpUtils.RequestGet(accessTokenUrl);
|
||||
string jsonStr = response.Replace(PREFIX, "");
|
||||
var accessTokenObject = jsonStr.parseObject();
|
||||
|
||||
if (accessTokenObject.ContainsKey("error"))
|
||||
{
|
||||
throw new Exception(accessTokenObject.getString("error_description"));
|
||||
}
|
||||
|
||||
var authToken = new AuthToken();
|
||||
authToken.accessToken = accessTokenObject.getString("access_token");
|
||||
authToken.refreshToken = accessTokenObject.getString("refresh_token");
|
||||
authToken.tokenType = accessTokenObject.getString("token_type");
|
||||
authToken.expireIn = accessTokenObject.getInt32("expires_in");
|
||||
authToken.scope = accessTokenObject.getString("scope");
|
||||
|
||||
authToken.openId = accessTokenObject.getString("openId");
|
||||
authToken.macAlgorithm = accessTokenObject.getString("mac_algorithm");
|
||||
authToken.macKey = accessTokenObject.getString("mac_key");
|
||||
|
||||
return authToken;
|
||||
}
|
||||
|
||||
protected override AuthUser getUserInfo(AuthToken authToken)
|
||||
{
|
||||
// 获取用户信息
|
||||
string userResponse = doGetUserInfo(authToken);
|
||||
|
||||
var userProfile = userResponse.parseObject();
|
||||
if ("error".Equals(userProfile.getString("result"), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new Exception(userProfile.getString("description"));
|
||||
}
|
||||
|
||||
var userObj = userProfile.getString("data").parseObject();
|
||||
|
||||
var authUser = new AuthUser();
|
||||
authUser.uuid = userObj.getString("id");
|
||||
authUser.username = userObj.getString("miliaoNick");
|
||||
authUser.nickname = userObj.getString("miliaoNick");
|
||||
authUser.avatar = userObj.getString("miliaoIcon");
|
||||
authUser.email = userObj.getString("mail");
|
||||
authUser.gender = AuthUserGender.UNKNOWN;
|
||||
|
||||
authUser.token = authToken;
|
||||
authUser.source = source.getName();
|
||||
authUser.originalUser = userObj;
|
||||
authUser.originalUserStr = userResponse;
|
||||
//return authUser;
|
||||
|
||||
// 获取用户邮箱手机号等信息
|
||||
string emailPhoneUrl = $"{{https://open.account.xiaomi.com/user/phoneAndEmail}}?clientId={config.clientId}&token={authToken.accessToken}";
|
||||
|
||||
string emailResponse = HttpUtils.RequestGet(emailPhoneUrl);
|
||||
var userEmailPhone = emailResponse.parseObject();
|
||||
if (!"error".Equals(userEmailPhone.getString("result"), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var emailPhone = userEmailPhone.getString("data").parseObject();
|
||||
authUser.email = emailPhone.getString("email");
|
||||
}
|
||||
else
|
||||
{
|
||||
//Log.warn("小米开发平台暂时不对外开放用户手机及邮箱信息的获取");
|
||||
}
|
||||
|
||||
return authUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新access token (续期)
|
||||
*
|
||||
* @param authToken 登录成功后返回的Token信息
|
||||
* @return AuthResponse
|
||||
*/
|
||||
public override AuthResponse refresh(AuthToken authToken)
|
||||
{
|
||||
var token = getToken(refreshTokenUrl(authToken.refreshToken));
|
||||
return new AuthResponse(AuthResponseStatus.SUCCESS.GetCode(), AuthResponseStatus.SUCCESS.GetDesc(), token);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state}
|
||||
*
|
||||
* @param state state 验证授权流程的参数,可以防止csrf
|
||||
* @return 返回授权地址
|
||||
* @since 1.9.3
|
||||
*/
|
||||
public override String authorize(String state)
|
||||
{
|
||||
return UrlBuilder.fromBaseUrl(source.authorize())
|
||||
.queryParam("response_type", "code")
|
||||
.queryParam("client_id", config.clientId)
|
||||
.queryParam("redirect_uri", config.redirectUri)
|
||||
.queryParam("scope", config.scope.IsNullOrWhiteSpace() ? "user/profile%20user/openIdV2%20user/phoneAndEmail" : config.scope)
|
||||
.queryParam("skip_confirm", "false")
|
||||
.queryParam("state", getRealState(state))
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回获取userInfo的url
|
||||
*
|
||||
* @param authToken 用户授权后的token
|
||||
* @return 返回获取userInfo的url
|
||||
*/
|
||||
protected override string userInfoUrl(AuthToken authToken)
|
||||
{
|
||||
return UrlBuilder.fromBaseUrl(source.userInfo())
|
||||
.queryParam("clientId", config.clientId)
|
||||
.queryParam("token", authToken.accessToken)
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
265
common/Tnb.CollectiveOAuth/Request/DefaultAuthRequest.cs
Normal file
265
common/Tnb.CollectiveOAuth/Request/DefaultAuthRequest.cs
Normal file
@@ -0,0 +1,265 @@
|
||||
using JNPF.Extras.CollectiveOAuth.Cache;
|
||||
using JNPF.Extras.CollectiveOAuth.Config;
|
||||
using JNPF.Extras.CollectiveOAuth.Enums;
|
||||
using JNPF.Extras.CollectiveOAuth.Models;
|
||||
using JNPF.Extras.CollectiveOAuth.Utils;
|
||||
|
||||
namespace JNPF.Extras.CollectiveOAuth.Request;
|
||||
|
||||
public partial class DefaultAuthRequest : IAuthRequest
|
||||
{
|
||||
protected ClientConfig config;
|
||||
protected IAuthSource source;
|
||||
protected IAuthStateCache authStateCache { set; get; }
|
||||
|
||||
public DefaultAuthRequest(ClientConfig config, IAuthSource source)
|
||||
{
|
||||
this.config = config;
|
||||
this.source = source;
|
||||
this.authStateCache = new DefaultAuthStateCache();
|
||||
}
|
||||
|
||||
public DefaultAuthRequest(ClientConfig config, IAuthSource source, IAuthStateCache authStateCache)
|
||||
{
|
||||
this.config = config;
|
||||
this.source = source;
|
||||
this.authStateCache = authStateCache;
|
||||
}
|
||||
|
||||
public virtual AuthResponse refresh(AuthToken authToken)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public virtual AuthResponse revoke(AuthToken authToken)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取access token
|
||||
*
|
||||
* @param authCallback 授权成功后的回调参数
|
||||
* @return token
|
||||
* @see AuthDefaultRequest#authorize()
|
||||
* @see AuthDefaultRequest#authorize(String)
|
||||
*/
|
||||
protected virtual AuthToken getAccessToken(AuthCallback authCallback)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用token换取用户信息
|
||||
*
|
||||
* @param authToken token信息
|
||||
* @return 用户信息
|
||||
* @see AuthDefaultRequest#getAccessToken(AuthCallback)
|
||||
*/
|
||||
protected virtual AuthUser getUserInfo(AuthToken authToken)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回授权url,可自行跳转页面
|
||||
* <p>
|
||||
* 不建议使用该方式获取授权地址,不带{@code state}的授权地址,容易受到csrf攻击。
|
||||
* 建议使用{@link AuthDefaultRequest#authorize(String)}方法生成授权地址,在回调方法中对{@code state}进行校验
|
||||
*
|
||||
* @return 返回授权地址
|
||||
* @see AuthDefaultRequest#authorize(String).
|
||||
*/
|
||||
public virtual string authorize()
|
||||
{
|
||||
return this.authorize(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state}
|
||||
*
|
||||
* @param state state 验证授权流程的参数,可以防止csrf
|
||||
* @return 返回授权地址
|
||||
* @since 1.9.3
|
||||
*/
|
||||
public virtual string authorize(string state)
|
||||
{
|
||||
return UrlBuilder.fromBaseUrl(source.authorize())
|
||||
.queryParam("response_type", "code")
|
||||
.queryParam("client_id", config.clientId)
|
||||
.queryParam("redirect_uri", config.redirectUri)
|
||||
.queryParam("state", getRealState(state))
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回获取accessToken的url
|
||||
*
|
||||
* @param code 授权码
|
||||
* @return 返回获取accessToken的url
|
||||
*/
|
||||
protected virtual string accessTokenUrl(string code)
|
||||
{
|
||||
return UrlBuilder.fromBaseUrl(source.accessToken())
|
||||
.queryParam("code", code)
|
||||
.queryParam("client_id", config.clientId)
|
||||
.queryParam("client_secret", config.clientSecret)
|
||||
.queryParam("grant_type", "authorization_code")
|
||||
.queryParam("redirect_uri", config.redirectUri)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回获取accessToken的url
|
||||
*
|
||||
* @param refreshToken refreshToken
|
||||
* @return 返回获取accessToken的url
|
||||
*/
|
||||
protected virtual string refreshTokenUrl(string refreshToken)
|
||||
{
|
||||
return UrlBuilder.fromBaseUrl(source.refresh())
|
||||
.queryParam("client_id", config.clientId)
|
||||
.queryParam("client_secret", config.clientSecret)
|
||||
.queryParam("refresh_token", refreshToken)
|
||||
.queryParam("grant_type", "refresh_token")
|
||||
.queryParam("redirect_uri", config.redirectUri)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回获取userInfo的url
|
||||
*
|
||||
* @param authToken token
|
||||
* @return 返回获取userInfo的url
|
||||
*/
|
||||
protected virtual string userInfoUrl(AuthToken authToken)
|
||||
{
|
||||
return UrlBuilder.fromBaseUrl(source.userInfo()).queryParam("access_token", authToken.accessToken).build();
|
||||
}
|
||||
|
||||
public virtual AuthResponse login(AuthCallback authCallback)
|
||||
{
|
||||
try
|
||||
{
|
||||
AuthChecker.checkCode(source, authCallback);
|
||||
AuthChecker.checkState(authCallback.state, source, authStateCache);
|
||||
|
||||
AuthToken authToken = this.getAccessToken(authCallback);
|
||||
AuthUser user = this.getUserInfo(authToken);
|
||||
return new AuthResponse(Convert.ToInt32(AuthResponseStatus.SUCCESS), null, user);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return this.responseError(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回获取revoke authorization的url
|
||||
*
|
||||
* @param authToken token
|
||||
* @return 返回获取revoke authorization的url
|
||||
*/
|
||||
protected virtual string revokeUrl(AuthToken authToken)
|
||||
{
|
||||
return UrlBuilder.fromBaseUrl(source.revoke()).queryParam("access_token", authToken.accessToken).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取state,如果为空, 则默认取当前日期的时间戳
|
||||
*
|
||||
* @param state 原始的state
|
||||
* @return 返回不为null的state
|
||||
*/
|
||||
protected virtual string getRealState(string state)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(state))
|
||||
{
|
||||
state = Guid.NewGuid().ToString();
|
||||
}
|
||||
|
||||
// 缓存state
|
||||
authStateCache.cache(state, state);
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理{@link AuthDefaultRequest#login(AuthCallback)} 发生异常的情况,统一响应参数
|
||||
*
|
||||
* @param e 具体的异常
|
||||
* @return AuthResponse
|
||||
*/
|
||||
private AuthResponse responseError(Exception e)
|
||||
{
|
||||
int errorCode = Convert.ToInt32(AuthResponseStatus.FAILURE);
|
||||
string errorMsg = e.Message;
|
||||
return new AuthResponse(errorCode, errorMsg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用的 authorizationCode 协议
|
||||
*
|
||||
* @param code code码
|
||||
* @return HttpResponse
|
||||
*/
|
||||
protected virtual string doPostAuthorizationCode(string code)
|
||||
{
|
||||
return HttpUtils.RequestPost(accessTokenUrl(code));
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用的 authorizationCode 协议
|
||||
*
|
||||
* @param code code码
|
||||
* @return HttpResponse
|
||||
*/
|
||||
protected virtual string doGetAuthorizationCode(String code)
|
||||
{
|
||||
return HttpUtils.RequestGet(accessTokenUrl(code));
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用的 用户信息
|
||||
*
|
||||
* @param authToken token封装
|
||||
* @return HttpResponse
|
||||
*/
|
||||
protected virtual string doPostUserInfo(AuthToken authToken)
|
||||
{
|
||||
return HttpUtils.RequestPost(userInfoUrl(authToken));
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用的 用户信息
|
||||
*
|
||||
* @param authToken token封装
|
||||
* @return HttpResponse
|
||||
*/
|
||||
protected virtual string doGetUserInfo(AuthToken authToken)
|
||||
{
|
||||
return HttpUtils.RequestGet(userInfoUrl(authToken));
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用的post形式的取消授权方法
|
||||
*
|
||||
* @param authToken token封装
|
||||
* @return HttpResponse
|
||||
*/
|
||||
protected virtual string doPostRevoke(AuthToken authToken)
|
||||
{
|
||||
return HttpUtils.RequestPost(revokeUrl(authToken));
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用的post形式的取消授权方法
|
||||
*
|
||||
* @param authToken token封装
|
||||
* @return HttpResponse
|
||||
*/
|
||||
protected virtual string doGetRevoke(AuthToken authToken)
|
||||
{
|
||||
return HttpUtils.RequestGet(revokeUrl(authToken));
|
||||
}
|
||||
|
||||
}
|
||||
54
common/Tnb.CollectiveOAuth/Request/IAuthRequest.cs
Normal file
54
common/Tnb.CollectiveOAuth/Request/IAuthRequest.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
using JNPF.Extras.CollectiveOAuth.Models;
|
||||
|
||||
namespace JNPF.Extras.CollectiveOAuth.Request;
|
||||
|
||||
/**
|
||||
* JustAuth {@code Request}公共接口,所有平台的{@code Request}都需要实现该接口
|
||||
* <p>
|
||||
* {@link AuthRequest#authorize()}
|
||||
* {@link AuthRequest#authorize(string)}
|
||||
* {@link AuthRequest#login(AuthCallback)}
|
||||
* {@link AuthRequest#revoke(AuthToken)}
|
||||
* {@link AuthRequest#refresh(AuthToken)}
|
||||
*
|
||||
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
|
||||
* @since 1.8.
|
||||
*/
|
||||
public interface IAuthRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// 返回授权url,可自行跳转页面.
|
||||
/// <para>不建议使用该方式获取授权地址,不带{@code state}的授权地址,容易受到csrf攻击.</para>
|
||||
/// <para>建议使用{@link AuthDefaultRequest#authorize(string)}方法生成授权地址,在回调方法中对{@code state}进行校验.</para>
|
||||
/// </summary>
|
||||
/// <returns>返回授权地址.</returns>
|
||||
string authorize();
|
||||
|
||||
/// <summary>
|
||||
/// 返回带.<code>state</code>参数的授权url,授权回调时会带上这个.<code>state</code>.
|
||||
/// </summary>
|
||||
/// <param name="state">state 验证授权流程的参数,可以防止csrf.</param>
|
||||
/// <returns>返回授权地址.</returns>
|
||||
string authorize(string state);
|
||||
|
||||
/// <summary>
|
||||
/// 第三方登录.
|
||||
/// </summary>
|
||||
/// <param name="authCallback">用于接收回调参数的实体.</param>
|
||||
/// <returns>返回登录成功后的用户信息.</returns>
|
||||
AuthResponse login(AuthCallback authCallback);
|
||||
|
||||
/// <summary>
|
||||
/// 撤销授权.
|
||||
/// </summary>
|
||||
/// <param name="authToken">登录成功后返回的Token信息.</param>
|
||||
/// <returns>AuthResponse.</returns>
|
||||
AuthResponse revoke(AuthToken authToken);
|
||||
|
||||
/// <summary>
|
||||
/// 刷新access token (续期).
|
||||
/// </summary>
|
||||
/// <param name="authToken">登录成功后返回的Token信息.</param>
|
||||
/// <returns>AuthResponse.</returns>
|
||||
AuthResponse refresh(AuthToken authToken);
|
||||
}
|
||||
Reference in New Issue
Block a user