diff --git a/AspNet.Security.OAuth.Providers.sln b/AspNet.Security.OAuth.Providers.sln
index 362af8418..ee64dca09 100644
--- a/AspNet.Security.OAuth.Providers.sln
+++ b/AspNet.Security.OAuth.Providers.sln
@@ -110,6 +110,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspNet.Security.OAuth.Patre
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspNet.Security.OAuth.QQ", "src\AspNet.Security.OAuth.QQ\AspNet.Security.OAuth.QQ.csproj", "{7C2E82CE-F6EC-41A8-AA22-3466505F95D8}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspNet.Security.OAuth.Baidu", "src\AspNet.Security.OAuth.Baidu\AspNet.Security.OAuth.Baidu.csproj", "{E1F11A29-5705-4D1C-A60B-1D05C0115076}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspNet.Security.OAuth.Csdn", "src\AspNet.Security.OAuth.Csdn\AspNet.Security.OAuth.Csdn.csproj", "{8A941D58-85FB-49D1-BD8D-4ADA978CB8C4}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThirdPartyConnectDemo", "..\..\GitHub\AspNet.Security.OAuth.Providers\samples\ThirdPartyConnectDemo\ThirdPartyConnectDemo.csproj", "{72DFA56D-712B-4AC9-A56D-E4C545F92418}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspNet.Security.OAuth.Alipay", "src\AspNet.Security.OAuth.Alipay\AspNet.Security.OAuth.Alipay.csproj", "{AB60D63A-293B-4913-8DF6-5EE5AB593B1F}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -304,6 +312,22 @@ Global
{7C2E82CE-F6EC-41A8-AA22-3466505F95D8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7C2E82CE-F6EC-41A8-AA22-3466505F95D8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7C2E82CE-F6EC-41A8-AA22-3466505F95D8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E1F11A29-5705-4D1C-A60B-1D05C0115076}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E1F11A29-5705-4D1C-A60B-1D05C0115076}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E1F11A29-5705-4D1C-A60B-1D05C0115076}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E1F11A29-5705-4D1C-A60B-1D05C0115076}.Release|Any CPU.Build.0 = Release|Any CPU
+ {8A941D58-85FB-49D1-BD8D-4ADA978CB8C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8A941D58-85FB-49D1-BD8D-4ADA978CB8C4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8A941D58-85FB-49D1-BD8D-4ADA978CB8C4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8A941D58-85FB-49D1-BD8D-4ADA978CB8C4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {72DFA56D-712B-4AC9-A56D-E4C545F92418}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {72DFA56D-712B-4AC9-A56D-E4C545F92418}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {72DFA56D-712B-4AC9-A56D-E4C545F92418}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {72DFA56D-712B-4AC9-A56D-E4C545F92418}.Release|Any CPU.Build.0 = Release|Any CPU
+ {AB60D63A-293B-4913-8DF6-5EE5AB593B1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {AB60D63A-293B-4913-8DF6-5EE5AB593B1F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {AB60D63A-293B-4913-8DF6-5EE5AB593B1F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {AB60D63A-293B-4913-8DF6-5EE5AB593B1F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -356,6 +380,10 @@ Global
{86614CB9-0768-40BF-8C27-699E3990B733} = {C1352FD3-AE8B-43EE-B45B-F6E0B3FBAC6D}
{ED8A220C-45FE-45C6-9B8F-BB009ACE972E} = {C1352FD3-AE8B-43EE-B45B-F6E0B3FBAC6D}
{7C2E82CE-F6EC-41A8-AA22-3466505F95D8} = {C1352FD3-AE8B-43EE-B45B-F6E0B3FBAC6D}
+ {E1F11A29-5705-4D1C-A60B-1D05C0115076} = {C1352FD3-AE8B-43EE-B45B-F6E0B3FBAC6D}
+ {8A941D58-85FB-49D1-BD8D-4ADA978CB8C4} = {C1352FD3-AE8B-43EE-B45B-F6E0B3FBAC6D}
+ {72DFA56D-712B-4AC9-A56D-E4C545F92418} = {BAC7067D-88FE-4385-8AC9-1A325FFBDE69}
+ {AB60D63A-293B-4913-8DF6-5EE5AB593B1F} = {C1352FD3-AE8B-43EE-B45B-F6E0B3FBAC6D}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C7B54DE2-6407-4802-AD9C-CE54BF414C8C}
diff --git a/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationConstants.cs b/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationConstants.cs
new file mode 100644
index 000000000..ab5d98f30
--- /dev/null
+++ b/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationConstants.cs
@@ -0,0 +1,65 @@
+namespace AspNet.Security.OAuth.Alipay
+{
+ ///
+ /// Contains constants specific to the .
+ ///
+ public static class AlipayAuthenticationConstants
+ {
+ public static class Claims
+ {
+ ///
+ /// 当前登录用户的数字ID
+ ///
+ public const string UserId = "urn:alipay:user_id";
+
+ ///
+ /// 用户昵称
+ ///
+ public const string NickName = "urn:alipay:nick_name";
+
+ ///
+ /// 用户头像地址
+ ///
+ public const string Avatar = "urn:alipay:avatar";
+
+ ///
+ /// 省份名称
+ ///
+ public const string Province = "urn:alipay:province";
+
+ ///
+ /// 市名称
+ ///
+ public const string City = "urn:alipay:city";
+
+ ///
+ /// 是否是学生(选填)
+ ///
+ public const string IsStudentCertified = "urn:alipay:is_student_certified";
+
+ ///
+ /// 用户类型(1/2) 1代表公司账户2代表个人账户
+ ///
+ public const string UserType = "urn:alipay:user_type";
+
+ ///
+ /// 用户状态(Q/T/B/W)。
+ /// Q代表快速注册用户
+ /// T代表已认证用户
+ /// B代表被冻结账户
+ /// W代表已注册,未激活的账户
+ ///
+ public const string UserStatus = "urn:alipay:user_status";
+
+ ///
+ /// 是否通过实名认证。T是通过 F是没有实名认证。
+ ///
+ public const string IsCertified = "urn:alipay:is_certified";
+
+ /////
+ ///// 【注意】只有is_certified为T的时候才有意义,否则不保证准确性. 性别(F:女性;M:男性)。
+ /////
+ //public const string Gender = "urn:alipay:gender";
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationDefaults.cs b/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationDefaults.cs
new file mode 100644
index 000000000..880594c42
--- /dev/null
+++ b/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationDefaults.cs
@@ -0,0 +1,105 @@
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Authentication.OAuth;
+#pragma warning disable 1584,1711,1572,1581,1580
+
+namespace AspNet.Security.OAuth.Alipay
+{
+ ///
+ /// Default values for Alipay authentication.
+ ///
+ public static class AlipayAuthenticationDefaults
+ {
+ ///
+ /// Default value for .
+ ///
+ public const string AuthenticationScheme = "Alipay";
+
+ ///
+ /// Default value for .
+ ///
+ public const string DisplayName = "Alipay";
+
+ ///
+ /// Default value for .
+ ///
+ public const string CallbackPath = "/signin-alipay";
+
+ ///
+ /// Default value for .
+ ///
+ public const string Issuer = "Alipay";
+
+ ///
+ /// Default value for .
+ ///
+ //public const string AuthorizationEndpoint = "https://openauth.alipaydev.com/oauth2/publicAppAuthorize.htm";//沙箱
+ public const string AuthorizationEndpoint = "https://openauth.alipay.com/oauth2/publicAppAuthorize.htm";
+
+ ///
+ /// Default value for .
+ ///
+ public const string TokenEndpoint = "alipay.system.oauth.token";
+
+ ///
+ /// Default value for .
+ ///
+ public const string UserInformationEndpoint = "alipay.user.userinfo.share";
+
+ //alipay.user.info.auth(用户登陆授权)
+ //alipay.system.oauth.token(换取授权访问令牌)
+ //alipay.user.userinfo.share(支付宝钱包用户信息共享)
+
+ ///
+ /// 支付宝默认网关
+ ///
+ //public const string Gateway = "https://openapi.alipaydev.com/gateway.do";//沙箱
+ public const string GatewayUrl = "https://openapi.alipay.com/gateway.do";
+
+ ///
+ /// 支付宝公钥,查看地址:https://openhome.alipay.com/platform/keyManage.htm 对应APPID下的支付宝公钥。
+ ///
+ public const string AlipayPublicKey = "";
+
+ /////
+ ///// 商户应用私钥,您的原始格式RSA2私钥
+ /////
+
+ //public const string MerchantPrivateKey = "";
+
+ /////
+ ///// 商户应用公钥,您的原始格式RSA公钥
+ /////
+
+ //public const string MerchantPublicKey = "";
+
+ ///
+ /// 签名方式
+ ///
+
+ public const string SignType = "RSA2";
+
+ ///
+ /// 编码格式
+ ///
+
+ public const string CharSet = "UTF-8";
+
+ ///
+ /// 版本
+ ///
+
+ public const string Version = "1.0";
+
+ ///
+ /// 数据格式
+ ///
+
+ public const string Format = "JSON";
+
+ ///
+ /// 是否从文件读取公私钥 如果为true ,那么公私钥应该配置为密钥文件路径
+ ///
+
+ public const bool IsKeyFromFile = false;
+ }
+}
\ No newline at end of file
diff --git a/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationExtensions.cs b/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationExtensions.cs
new file mode 100644
index 000000000..f12ccb8f6
--- /dev/null
+++ b/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationExtensions.cs
@@ -0,0 +1,77 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
+ * See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers
+ * for more information concerning the license and the contributors participating to this project.
+ */
+
+using AspNet.Security.OAuth.Alipay;
+using JetBrains.Annotations;
+using Microsoft.AspNetCore.Authentication;
+using System;
+
+// ReSharper disable once CheckNamespace
+namespace Microsoft.Extensions.DependencyInjection
+{
+ ///
+ /// Extension methods to add Alipay authentication capabilities to an HTTP application pipeline.
+ ///
+ public static class AlipayAuthenticationExtensions
+ {
+ ///
+ /// Adds to the specified
+ /// , which enables Alipay authentication capabilities.
+ ///
+ /// The authentication builder.
+ /// A reference to this instance after the operation has completed.
+ public static AuthenticationBuilder AddAlipay([NotNull] this AuthenticationBuilder builder)
+ {
+ return builder.AddAlipay(AlipayAuthenticationDefaults.AuthenticationScheme, options => { });
+ }
+
+ ///
+ /// Adds to the specified
+ /// , which enables Alipay authentication capabilities.
+ ///
+ /// The authentication builder.
+ /// The delegate used to configure the OpenID 2.0 options.
+ /// A reference to this instance after the operation has completed.
+ public static AuthenticationBuilder AddAlipay(
+ [NotNull] this AuthenticationBuilder builder,
+ [NotNull] Action configuration)
+ {
+ return builder.AddAlipay(AlipayAuthenticationDefaults.AuthenticationScheme, configuration);
+ }
+
+ ///
+ /// Adds to the specified
+ /// , which enables Alipay authentication capabilities.
+ ///
+ /// The authentication builder.
+ /// The authentication scheme associated with this instance.
+ /// The delegate used to configure the Alipay options.
+ /// The .
+ public static AuthenticationBuilder AddAlipay(
+ [NotNull] this AuthenticationBuilder builder, [NotNull] string scheme,
+ [NotNull] Action configuration)
+ {
+ return builder.AddAlipay(scheme, AlipayAuthenticationDefaults.DisplayName, configuration);
+ }
+
+ ///
+ /// Adds to the specified
+ /// , which enables Alipay authentication capabilities.
+ ///
+ /// The authentication builder.
+ /// The authentication scheme associated with this instance.
+ /// The optional display name associated with this instance.
+ /// The delegate used to configure the Alipay options.
+ /// The .
+ public static AuthenticationBuilder AddAlipay(
+ [NotNull] this AuthenticationBuilder builder,
+ [NotNull] string scheme, [CanBeNull] string caption,
+ [NotNull] Action configuration)
+ {
+ return builder.AddOAuth(scheme, caption, configuration);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationHandler.cs b/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationHandler.cs
new file mode 100644
index 000000000..98a8f1b54
--- /dev/null
+++ b/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationHandler.cs
@@ -0,0 +1,246 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
+ * See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers
+ * for more information concerning the license and the contributors participating to this project.
+ */
+
+using Alipay.AopSdk.Core;
+using Alipay.AopSdk.Core.Request;
+using JetBrains.Annotations;
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Authentication.OAuth;
+using Microsoft.AspNetCore.WebUtilities;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using Microsoft.Extensions.Primitives;
+using Newtonsoft.Json.Linq;
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Net.Http;
+using System.Security.Claims;
+using System.Text;
+using System.Text.Encodings.Web;
+using System.Threading.Tasks;
+
+namespace AspNet.Security.OAuth.Alipay
+{
+ public class AlipayAuthenticationHandler : OAuthHandler
+ {
+ private DefaultAopClient _alipayClient;
+
+ public AlipayAuthenticationHandler(
+ [NotNull] IOptionsMonitor options,
+ [NotNull] ILoggerFactory logger,
+ [NotNull] UrlEncoder encoder,
+ [NotNull] ISystemClock clock)
+ : base(options, logger, encoder, clock)
+ {
+ }
+
+ ///
+ /// 获取用户信息
+ ///
+ ///
+ ///
+ ///
+ ///
+ protected override async Task CreateTicketAsync([NotNull] ClaimsIdentity identity, [NotNull] AuthenticationProperties properties, [NotNull] OAuthTokenResponse tokens)
+ {
+ var requestUser = new AlipayUserInfoShareRequest();
+ var userinfoShareResponse = _alipayClient.Execute(requestUser, tokens.AccessToken);
+ if (userinfoShareResponse.IsError)
+ {
+ Logger.LogError("An error occurred while retrieving the user profile: the remote server " +
+ "returned a {Status} response with the following payload: {Headers} {Body}.",
+ /* Status: */ userinfoShareResponse.Code,
+ /* Headers: */ userinfoShareResponse.Msg,
+ /* Body: */ userinfoShareResponse.Body);
+
+ throw new HttpRequestException("An error occurred while retrieving user information.");
+ }
+ else
+ {
+ var payload = JObject.FromObject(userinfoShareResponse);
+ var principal = new ClaimsPrincipal(identity);
+ var context = new OAuthCreatingTicketContext(principal, properties, Context, Scheme, Options, Backchannel, tokens, payload);
+ context.RunClaimActions(payload);
+ await Options.Events.CreatingTicket(context);
+ return new AuthenticationTicket(context.Principal, context.Properties, Scheme.Name);
+ }
+ }
+
+ ///
+ /// 使用auth_code换取access_token及用户userId
+ ///
+ ///
+ ///
+ ///
+ protected override async Task ExchangeCodeAsync([NotNull] string code, [NotNull] string redirectUri)
+ {
+ try
+ {
+ var alipayRequest = new AlipaySystemOauthTokenRequest
+ {
+ Code = code,
+ GrantType = "authorization_code"
+ //GetApiName()
+ };
+
+ var alipayResponse = await _alipayClient.ExecuteAsync(alipayRequest);
+ if (alipayResponse.IsError)
+ {
+ Logger.LogError("An error occurred while retrieving an access token: the remote server " +
+ "returned a {Status} response with the following payload: {Headers} {Body}.",
+ /* Status: */ alipayResponse.Code,
+ /* Headers: */ alipayResponse.Msg,
+ /* Body: */ alipayResponse.Body);
+ return OAuthTokenResponse.Failed(new Exception("An error occurred while retrieving an access token."));
+ }
+ else
+ {
+ var payload = JObject.FromObject(alipayResponse);
+ return OAuthTokenResponse.Success(payload);
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError(ex.ToString());
+ return OAuthTokenResponse.Failed(new Exception("An error occurred while retrieving an access token."));
+ }
+ }
+
+ ///
+ /// 拼接授权页面url
+ ///
+ ///
+ ///
+ ///
+ protected override string BuildChallengeUrl(AuthenticationProperties properties, string redirectUri)
+ {
+ return QueryHelpers.AddQueryString(Options.AuthorizationEndpoint, new Dictionary
+ {
+ ["app_id"] = Options.ClientId,
+ ["scope"] = FormatScope(),
+ ["redirect_uri"] = redirectUri,
+ ["state"] = Options.StateDataFormat.Protect(properties)
+ });
+ }
+
+ protected override async Task HandleRemoteAuthenticateAsync()
+ {
+ var query = Request.Query;
+
+ var error = query["error"];
+ if (!StringValues.IsNullOrEmpty(error))
+ {
+ var failureMessage = new StringBuilder();
+ failureMessage.Append(error);
+ var errorDescription = query["error_description"];
+ if (!StringValues.IsNullOrEmpty(errorDescription))
+ {
+ failureMessage.Append(";Description=").Append(errorDescription);
+ }
+ var errorUri = query["error_uri"];
+ if (!StringValues.IsNullOrEmpty(errorUri))
+ {
+ failureMessage.Append(";Uri=").Append(errorUri);
+ }
+
+ return HandleRequestResult.Fail(failureMessage.ToString());
+ }
+
+ var code = query["auth_code"];
+ var state = query["state"];
+
+ AuthenticationProperties properties = Options.StateDataFormat.Unprotect(state);
+ if (properties == null)
+ {
+ return HandleRequestResult.Fail("The oauth state was missing or invalid.");
+ }
+
+ // OAuth2 10.12 CSRF
+ if (!ValidateCorrelationId(properties))
+ {
+ return HandleRequestResult.Fail("Correlation failed.");
+ }
+
+ if (StringValues.IsNullOrEmpty(code))
+ {
+ return HandleRequestResult.Fail("Code was not found.");
+ }
+
+ var tokens = await ExchangeCodeAsync(code, BuildRedirectUri(Options.CallbackPath));
+
+ if (tokens.Error != null)
+ {
+ return HandleRequestResult.Fail(tokens.Error);
+ }
+
+ if (string.IsNullOrEmpty(tokens.AccessToken))
+ {
+ return HandleRequestResult.Fail("Failed to retrieve access token.");
+ }
+
+ var identity = new ClaimsIdentity(ClaimsIssuer);
+
+ if (Options.SaveTokens)
+ {
+ var authTokens = new List
+ {
+ new AuthenticationToken { Name = "access_token", Value = tokens.AccessToken }
+ };
+ if (!string.IsNullOrEmpty(tokens.RefreshToken))
+ {
+ authTokens.Add(new AuthenticationToken { Name = "refresh_token", Value = tokens.RefreshToken });
+ }
+
+ if (!string.IsNullOrEmpty(tokens.TokenType))
+ {
+ authTokens.Add(new AuthenticationToken { Name = "token_type", Value = tokens.TokenType });
+ }
+
+ if (!string.IsNullOrEmpty(tokens.ExpiresIn))
+ {
+ if (int.TryParse(tokens.ExpiresIn, NumberStyles.Integer, CultureInfo.InvariantCulture, out int value))
+ {
+ // https://www.w3.org/TR/xmlschema-2/#dateTime
+ // https://msdn.microsoft.com/en-us/library/az4se3k1(v=vs.110).aspx
+ var expiresAt = Clock.UtcNow + TimeSpan.FromSeconds(value);
+ authTokens.Add(new AuthenticationToken
+ {
+ Name = "expires_at",
+ Value = expiresAt.ToString("o", CultureInfo.InvariantCulture)
+ });
+ }
+ }
+
+ properties.StoreTokens(authTokens);
+ }
+
+ var ticket = await CreateTicketAsync(identity, properties, tokens);
+ return ticket != null
+ ? HandleRequestResult.Success(ticket)
+ : HandleRequestResult.Fail("Failed to retrieve user information from remote server.");
+ }
+
+ protected override Task InitializeHandlerAsync()
+ {
+ if (_alipayClient == null)
+ {
+ _alipayClient = new DefaultAopClient(Options.GatewayUrl,
+ Options.ClientId,
+ Options.ClientSecret,
+ Options.Format,
+ Options.Version,
+ Options.SignType,
+ Options.AlipayPublicKey,
+ Options.CharSet,
+ Options.IsKeyFromFile);
+ }
+ return Task.CompletedTask;
+ }
+
+ protected override string FormatScope() => string.Join(",", Options.Scope);
+ }
+}
\ No newline at end of file
diff --git a/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationOptions.cs b/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationOptions.cs
new file mode 100644
index 000000000..2a2c5ad64
--- /dev/null
+++ b/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationOptions.cs
@@ -0,0 +1,92 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
+ * See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers
+ * for more information concerning the license and the contributors participating to this project.
+ */
+
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Authentication.OAuth;
+using Microsoft.AspNetCore.Http;
+using System.Security.Claims;
+using static AspNet.Security.OAuth.Alipay.AlipayAuthenticationConstants;
+
+// ReSharper disable UnusedAutoPropertyAccessor.Local
+
+namespace AspNet.Security.OAuth.Alipay
+{
+ ///
+ /// Defines a set of options used by .
+ ///
+ public class AlipayAuthenticationOptions : OAuthOptions
+ {
+ public AlipayAuthenticationOptions()
+ {
+ ClaimsIssuer = AlipayAuthenticationDefaults.Issuer;
+ CallbackPath = new PathString(AlipayAuthenticationDefaults.CallbackPath);
+
+ AuthorizationEndpoint = AlipayAuthenticationDefaults.AuthorizationEndpoint;
+ TokenEndpoint = AlipayAuthenticationDefaults.TokenEndpoint;
+ UserInformationEndpoint = AlipayAuthenticationDefaults.UserInformationEndpoint;
+
+ GatewayUrl = AlipayAuthenticationDefaults.GatewayUrl;
+ AlipayPublicKey = AlipayAuthenticationDefaults.AlipayPublicKey;
+ SignType = AlipayAuthenticationDefaults.SignType;
+ CharSet = AlipayAuthenticationDefaults.CharSet;
+ Version = AlipayAuthenticationDefaults.Version;
+ Format = AlipayAuthenticationDefaults.Format;
+ IsKeyFromFile = AlipayAuthenticationDefaults.IsKeyFromFile;
+
+ Scope.Add("auth_user");
+
+ ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "user_id");
+ ClaimActions.MapJsonKey(ClaimTypes.Name, "nick_name");
+ //【注意】只有is_certified为T的时候才有意义,否则不保证准确性. 性别(F:女性;M:男性)。
+ ClaimActions.MapJsonKey(ClaimTypes.Gender, "gender", ClaimValueTypes.Integer);
+
+ ClaimActions.MapJsonKey(Claims.UserId, "user_id");
+ ClaimActions.MapJsonKey(Claims.NickName, "nick_name");
+ ClaimActions.MapJsonKey(Claims.Avatar, "avatar");
+ ClaimActions.MapJsonKey(Claims.Province, "province");
+ ClaimActions.MapJsonKey(Claims.City, "city");
+ ClaimActions.MapJsonKey(Claims.IsStudentCertified, "is_student_certified");
+ ClaimActions.MapJsonKey(Claims.UserType, "user_type");
+ ClaimActions.MapJsonKey(Claims.UserStatus, "user_status");
+ ClaimActions.MapJsonKey(Claims.IsCertified, "is_certified");
+ }
+
+ ///
+ /// 支付宝网关
+ ///
+ public string GatewayUrl { get; set; } //= "https://openapi.alipay.com/gateway.do";
+
+ ///
+ /// 支付宝公钥
+ ///
+ public string AlipayPublicKey { get; set; }
+
+ ///
+ /// 签名方式
+ ///
+ public string SignType { get; set; } //= "RSA2";
+
+ ///
+ /// 编码格式
+ ///
+ public string CharSet { get; set; } //= "UTF-8";
+
+ ///
+ /// 版本
+ ///
+ public string Version { get; private set; } //= "1.0";
+
+ ///
+ /// 数据格式
+ ///
+ public string Format { get; private set; } //= "JSON";
+
+ ///
+ /// 是否从文件读取公私钥
+ ///
+ public bool IsKeyFromFile { get; set; } //= false;
+ }
+}
\ No newline at end of file
diff --git a/src/AspNet.Security.OAuth.Alipay/AspNet.Security.OAuth.Alipay.csproj b/src/AspNet.Security.OAuth.Alipay/AspNet.Security.OAuth.Alipay.csproj
new file mode 100644
index 000000000..f947a463a
--- /dev/null
+++ b/src/AspNet.Security.OAuth.Alipay/AspNet.Security.OAuth.Alipay.csproj
@@ -0,0 +1,26 @@
+
+
+
+
+
+ netstandard2.0
+
+
+
+ ASP.NET Core security middleware enabling Alipay authentication.
+ ArcherTrister
+ aspnetcore;authentication;oauth;security;alipay
+ https://oauth.net/images/oauth-2-sm.png
+
+ Copyright 2017-2018 LeXun
+
+ Add Alipay Provider
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/AspNet.Security.OAuth.Baidu/AspNet.Security.OAuth.Baidu.csproj b/src/AspNet.Security.OAuth.Baidu/AspNet.Security.OAuth.Baidu.csproj
new file mode 100644
index 000000000..ff38f9314
--- /dev/null
+++ b/src/AspNet.Security.OAuth.Baidu/AspNet.Security.OAuth.Baidu.csproj
@@ -0,0 +1,22 @@
+
+
+
+
+
+ netstandard2.0
+
+
+
+ ASP.NET Core security middleware enabling Baidu authentication.
+ zhengchun;Chino Chang
+ aspnetcore;authentication;oauth;security;baidu
+ true
+ true
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/AspNet.Security.OAuth.Baidu/BaiduAuthenticationConstants.cs b/src/AspNet.Security.OAuth.Baidu/BaiduAuthenticationConstants.cs
new file mode 100644
index 000000000..df906e2bb
--- /dev/null
+++ b/src/AspNet.Security.OAuth.Baidu/BaiduAuthenticationConstants.cs
@@ -0,0 +1,70 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
+ * See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers
+ * for more information concerning the license and the contributors participating to this project.
+ */
+
+namespace AspNet.Security.OAuth.Baidu
+{
+ ///
+ /// Contains constants specific to the .
+ ///
+ public static class BaiduAuthenticationConstants
+ {
+ public static class Claims
+ {
+ ///
+ /// 当前登录用户的数字ID
+ ///
+ public const string UserId = "urn:baidu:userid";
+ ///
+ /// 当前登录用户的用户名,值可能为空。
+ ///
+ public const string UserName = "urn:baidu:username";
+ ///
+ /// 用户真实姓名,可能为空。
+ ///
+ public const string RealName = "urn:baidu:realname";
+ ///
+ /// 当前登录用户的头像
+ ///
+ public const string Portrait = "urn:baidu:portrait";
+ ///
+ /// 自我简介,可能为空。
+ ///
+ public const string UserDetail = "urn:baidu:userdetail";
+ ///
+ /// 生日,以yyyy-mm-dd格式显示。
+ ///
+ public const string Birthday = "urn:baidu:birthday";
+ ///
+ /// 婚姻状况
+ ///
+ public const string Marriage = "urn:baidu:marriage";
+ ///
+ /// 血型
+ ///
+ public const string Blood = "urn:baidu:blood";
+ ///
+ /// 体型
+ ///
+ public const string Figure = "urn:baidu:figure";
+ ///
+ /// 星座
+ ///
+ public const string Constellation = "urn:baidu:constellation";
+ ///
+ /// 学历
+ ///
+ public const string Education = "urn:baidu:education";
+ ///
+ /// 当前职业
+ ///
+ public const string Trade = "urn:baidu:trade";
+ ///
+ /// 职位
+ ///
+ public const string Job = "urn:baidu:job";
+ }
+ }
+}
diff --git a/src/AspNet.Security.OAuth.Baidu/BaiduAuthenticationDefaults.cs b/src/AspNet.Security.OAuth.Baidu/BaiduAuthenticationDefaults.cs
new file mode 100644
index 000000000..715dff772
--- /dev/null
+++ b/src/AspNet.Security.OAuth.Baidu/BaiduAuthenticationDefaults.cs
@@ -0,0 +1,52 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
+ * See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers
+ * for more information concerning the license and the contributors participating to this project.
+ */
+
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Authentication.OAuth;
+
+namespace AspNet.Security.OAuth.Baidu
+{
+ ///
+ /// Default values for Baidu authentication.
+ ///
+ public static class BaiduAuthenticationDefaults
+ {
+ ///
+ /// Default value for .
+ ///
+ public const string AuthenticationScheme = "Baidu";
+
+ ///
+ /// Default value for .
+ ///
+ public const string DisplayName = "Baidu";
+
+ ///
+ /// Default value for .
+ ///
+ public const string CallbackPath = "/signin-baidu";
+
+ ///
+ /// Default value for .
+ ///
+ public const string Issuer = "Baidu";
+
+ ///
+ /// Default value for .
+ ///
+ public const string AuthorizationEndpoint = "http://openapi.baidu.com/oauth/2.0/authorize";
+
+ ///
+ /// Default value for .
+ ///
+ public const string TokenEndpoint = "https://openapi.baidu.com/oauth/2.0/token";
+
+ ///
+ /// Default value for .
+ ///
+ public const string UserInformationEndpoint = "https://openapi.baidu.com/rest/2.0/passport/users/getInfo";
+ }
+}
diff --git a/src/AspNet.Security.OAuth.Baidu/BaiduAuthenticationExtensions.cs b/src/AspNet.Security.OAuth.Baidu/BaiduAuthenticationExtensions.cs
new file mode 100644
index 000000000..0fedc58de
--- /dev/null
+++ b/src/AspNet.Security.OAuth.Baidu/BaiduAuthenticationExtensions.cs
@@ -0,0 +1,76 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
+ * See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers
+ * for more information concerning the license and the contributors participating to this project.
+ */
+
+using System;
+using AspNet.Security.OAuth.Baidu;
+using JetBrains.Annotations;
+using Microsoft.AspNetCore.Authentication;
+
+namespace Microsoft.Extensions.DependencyInjection
+{
+ ///
+ /// Extension methods to add Baidu authentication capabilities to an HTTP application pipeline.
+ ///
+ public static class BaiduAuthenticationExtensions
+ {
+ ///
+ /// Adds to the specified
+ /// , which enables Baidu authentication capabilities.
+ ///
+ /// The authentication builder.
+ /// A reference to this instance after the operation has completed.
+ public static AuthenticationBuilder AddBaidu([NotNull] this AuthenticationBuilder builder)
+ {
+ return builder.AddBaidu(BaiduAuthenticationDefaults.AuthenticationScheme, options => { });
+ }
+
+ ///
+ /// Adds to the specified
+ /// , which enables Baidu authentication capabilities.
+ ///
+ /// The authentication builder.
+ /// The delegate used to configure the OpenID 2.0 options.
+ /// A reference to this instance after the operation has completed.
+ public static AuthenticationBuilder AddBaidu(
+ [NotNull] this AuthenticationBuilder builder,
+ [NotNull] Action configuration)
+ {
+ return builder.AddBaidu(BaiduAuthenticationDefaults.AuthenticationScheme, configuration);
+ }
+
+ ///
+ /// Adds to the specified
+ /// , which enables Baidu authentication capabilities.
+ ///
+ /// The authentication builder.
+ /// The authentication scheme associated with this instance.
+ /// The delegate used to configure the Baidu options.
+ /// The .
+ public static AuthenticationBuilder AddBaidu(
+ [NotNull] this AuthenticationBuilder builder, [NotNull] string scheme,
+ [NotNull] Action configuration)
+ {
+ return builder.AddBaidu(scheme, BaiduAuthenticationDefaults.DisplayName, configuration);
+ }
+
+ ///
+ /// Adds to the specified
+ /// , which enables Baidu authentication capabilities.
+ ///
+ /// The authentication builder.
+ /// The authentication scheme associated with this instance.
+ /// The optional display name associated with this instance.
+ /// The delegate used to configure the Baidu options.
+ /// The .
+ public static AuthenticationBuilder AddBaidu(
+ [NotNull] this AuthenticationBuilder builder,
+ [NotNull] string scheme, [CanBeNull] string caption,
+ [NotNull] Action configuration)
+ {
+ return builder.AddOAuth(scheme, caption, configuration);
+ }
+ }
+}
diff --git a/src/AspNet.Security.OAuth.Baidu/BaiduAuthenticationHandler.cs b/src/AspNet.Security.OAuth.Baidu/BaiduAuthenticationHandler.cs
new file mode 100644
index 000000000..fb35c76e7
--- /dev/null
+++ b/src/AspNet.Security.OAuth.Baidu/BaiduAuthenticationHandler.cs
@@ -0,0 +1,126 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
+ * See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers
+ * for more information concerning the license and the contributors participating to this project.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.Net.Http;
+using System.Security.Claims;
+using System.Text.Encodings.Web;
+using System.Threading.Tasks;
+using JetBrains.Annotations;
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Authentication.OAuth;
+using Microsoft.AspNetCore.WebUtilities;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using Newtonsoft.Json.Linq;
+
+namespace AspNet.Security.OAuth.Baidu
+{
+ public class BaiduAuthenticationHandler : OAuthHandler
+ {
+ public BaiduAuthenticationHandler(
+ [NotNull] IOptionsMonitor options,
+ [NotNull] ILoggerFactory logger,
+ [NotNull] UrlEncoder encoder,
+ [NotNull] ISystemClock clock)
+ : base(options, logger, encoder, clock)
+ {
+ }
+
+ protected override async Task CreateTicketAsync([NotNull] ClaimsIdentity identity, [NotNull] AuthenticationProperties properties, [NotNull] OAuthTokenResponse tokens)
+ {
+ var address = QueryHelpers.AddQueryString(Options.UserInformationEndpoint, new Dictionary
+ {
+ ["access_token"] = tokens.AccessToken
+ });
+
+ var response = await Backchannel.GetAsync(address);
+ if (!response.IsSuccessStatusCode)
+ {
+ Logger.LogError("An error occurred while retrieving the user profile: the remote server " +
+ "returned a {Status} response with the following payload: {Headers} {Body}.",
+ /* Status: */ response.StatusCode,
+ /* Headers: */ response.Headers.ToString(),
+ /* Body: */ await response.Content.ReadAsStringAsync());
+
+ throw new HttpRequestException("An error occurred while retrieving user information.");
+ }
+
+ var payload = JObject.Parse(await response.Content.ReadAsStringAsync());
+
+ var status = payload.Value("userid");
+ if (status <= 0)
+ {
+ Logger.LogError("An error occurred while retrieving the user profile: the remote server " +
+ "returned a {Status} response with the following payload: {Headers} {Body}.",
+ /* Status: */ response.StatusCode,
+ /* Headers: */ response.Headers.ToString(),
+ /* Body: */ await response.Content.ReadAsStringAsync());
+
+ throw new HttpRequestException("An error occurred while retrieving user information.");
+ }
+
+ var principal = new ClaimsPrincipal(identity);
+ var context = new OAuthCreatingTicketContext(principal, properties, Context, Scheme, Options, Backchannel, tokens, payload);
+ context.RunClaimActions(payload);
+
+ await Options.Events.CreatingTicket(context);
+ return new AuthenticationTicket(context.Principal, context.Properties, Scheme.Name);
+ }
+
+ protected override async Task ExchangeCodeAsync([NotNull] string code, [NotNull] string redirectUri)
+ {
+ var address = QueryHelpers.AddQueryString(Options.TokenEndpoint, new Dictionary()
+ {
+ ["client_id"] = Options.ClientId,
+ ["client_secret"] = Options.ClientSecret,
+ ["code"] = code,
+ ["grant_type"] = "authorization_code",
+ ["redirect_uri"] = redirectUri
+ });
+
+ var response = await Backchannel.GetAsync(address);
+ if (!response.IsSuccessStatusCode)
+ {
+ Logger.LogError("An error occurred while retrieving an access token: the remote server " +
+ "returned a {Status} response with the following payload: {Headers} {Body}.",
+ /* Status: */ response.StatusCode,
+ /* Headers: */ response.Headers.ToString(),
+ /* Body: */ await response.Content.ReadAsStringAsync());
+
+ return OAuthTokenResponse.Failed(new Exception("An error occurred while retrieving an access token."));
+ }
+
+ var payload = JObject.Parse(await response.Content.ReadAsStringAsync());
+ if (!string.IsNullOrEmpty(payload.Value("errcode")))
+ {
+ Logger.LogError("An error occurred while retrieving an access token: the remote server " +
+ "returned a {Status} response with the following payload: {Headers} {Body}.",
+ /* Status: */ response.StatusCode,
+ /* Headers: */ response.Headers.ToString(),
+ /* Body: */ await response.Content.ReadAsStringAsync());
+
+ return OAuthTokenResponse.Failed(new Exception("An error occurred while retrieving an access token."));
+ }
+ return OAuthTokenResponse.Success(payload);
+ }
+
+ protected override string BuildChallengeUrl(AuthenticationProperties properties, string redirectUri)
+ {
+ return QueryHelpers.AddQueryString(Options.AuthorizationEndpoint, new Dictionary
+ {
+ ["client_id"] = Options.ClientId,
+ ["scope"] = FormatScope(),
+ ["response_type"] = "code",
+ ["redirect_uri"] = redirectUri,
+ ["state"] = Options.StateDataFormat.Protect(properties)
+ });
+ }
+
+ protected override string FormatScope() => string.Join(",", Options.Scope);
+ }
+}
diff --git a/src/AspNet.Security.OAuth.Baidu/BaiduAuthenticationOptions.cs b/src/AspNet.Security.OAuth.Baidu/BaiduAuthenticationOptions.cs
new file mode 100644
index 000000000..fad4eaa02
--- /dev/null
+++ b/src/AspNet.Security.OAuth.Baidu/BaiduAuthenticationOptions.cs
@@ -0,0 +1,51 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
+ * See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers
+ * for more information concerning the license and the contributors participating to this project.
+ */
+
+using System.Security.Claims;
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Authentication.OAuth;
+using Microsoft.AspNetCore.Http;
+using static AspNet.Security.OAuth.Baidu.BaiduAuthenticationConstants;
+
+namespace AspNet.Security.OAuth.Baidu
+{
+ ///
+ /// Defines a set of options used by .
+ ///
+ public class BaiduAuthenticationOptions : OAuthOptions
+ {
+ public BaiduAuthenticationOptions()
+ {
+ ClaimsIssuer = BaiduAuthenticationDefaults.Issuer;
+ CallbackPath = new PathString(BaiduAuthenticationDefaults.CallbackPath);
+
+ AuthorizationEndpoint = BaiduAuthenticationDefaults.AuthorizationEndpoint;
+ TokenEndpoint = BaiduAuthenticationDefaults.TokenEndpoint;
+ UserInformationEndpoint = BaiduAuthenticationDefaults.UserInformationEndpoint;
+
+ Scope.Add("basic");
+
+ ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "userid");
+ ClaimActions.MapJsonKey(ClaimTypes.Name, "username");
+ //"1"表示男,"0"表示女。
+ ClaimActions.MapJsonKey(ClaimTypes.Gender, "sex", ClaimValueTypes.Integer);
+
+ ClaimActions.MapJsonKey(Claims.UserId, "userid");
+ ClaimActions.MapJsonKey(Claims.UserName, "username");
+ ClaimActions.MapJsonKey(Claims.RealName, "realname");
+ ClaimActions.MapJsonKey(Claims.Portrait, "portrait");
+ ClaimActions.MapJsonKey(Claims.UserDetail, "userdetail");
+ ClaimActions.MapJsonKey(Claims.Birthday, "birthday");
+ ClaimActions.MapJsonKey(Claims.Marriage, "marriage");
+ ClaimActions.MapJsonKey(Claims.Blood, "blood");
+ ClaimActions.MapJsonKey(Claims.Figure, "figure");
+ ClaimActions.MapJsonKey(Claims.Constellation, "constellation");
+ ClaimActions.MapJsonKey(Claims.Education, "education");
+ ClaimActions.MapJsonKey(Claims.Trade, "trade");
+ ClaimActions.MapJsonKey(Claims.Job, "job");
+ }
+ }
+}
diff --git a/src/AspNet.Security.OAuth.Baidu/Properties/PublishProfiles/FolderProfile.pubxml b/src/AspNet.Security.OAuth.Baidu/Properties/PublishProfiles/FolderProfile.pubxml
new file mode 100644
index 000000000..620f2e494
--- /dev/null
+++ b/src/AspNet.Security.OAuth.Baidu/Properties/PublishProfiles/FolderProfile.pubxml
@@ -0,0 +1,13 @@
+
+
+
+
+ FileSystem
+ Release
+ Any CPU
+ netstandard2.0
+ G:\Source\AspNet.Security.OAuth.Providers\src\AspNet.Security.OAuth.Baidu\bin\Release\netstandard2.0\publish
+
+
\ No newline at end of file
diff --git a/src/AspNet.Security.OAuth.Csdn/AspNet.Security.OAuth.Csdn.csproj b/src/AspNet.Security.OAuth.Csdn/AspNet.Security.OAuth.Csdn.csproj
new file mode 100644
index 000000000..85b2d2bf6
--- /dev/null
+++ b/src/AspNet.Security.OAuth.Csdn/AspNet.Security.OAuth.Csdn.csproj
@@ -0,0 +1,20 @@
+
+
+
+
+
+ netstandard2.0
+
+
+
+ ASP.NET Core security middleware enabling Csdn authentication.
+ ArcherTrister
+ aspnetcore;authentication;oauth;security;csdn
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/AspNet.Security.OAuth.Csdn/CsdnAuthenticationConstants.cs b/src/AspNet.Security.OAuth.Csdn/CsdnAuthenticationConstants.cs
new file mode 100644
index 000000000..fabf5d1c0
--- /dev/null
+++ b/src/AspNet.Security.OAuth.Csdn/CsdnAuthenticationConstants.cs
@@ -0,0 +1,37 @@
+namespace AspNet.Security.OAuth.Csdn
+{
+ ///
+ /// Contains constants specific to the .
+ ///
+ public static class CsdnAuthenticationConstants
+ {
+ public static class Claims
+ {
+ /////
+ ///// 当前登录用户的数字ID
+ /////
+ //public const string UserId = "urn:csdn:userid";
+ ///
+ /// 当前登录用户的用户名,值可能为空。
+ ///
+ public const string UserName = "urn:csdn:username";
+ ///
+ /// 工作
+ ///
+ public const string Job = "urn:csdn:job";
+ ///
+ /// 工作年限
+ ///
+ public const string WorkYear = "urn:csdn:workyear";
+ ///
+ /// 网站
+ ///
+ public const string Website = "urn:csdn:website";
+ ///
+ /// 个人简介
+ ///
+ public const string Description = "urn:csdn:description";
+
+ }
+ }
+}
diff --git a/src/AspNet.Security.OAuth.Csdn/CsdnAuthenticationDefaults.cs b/src/AspNet.Security.OAuth.Csdn/CsdnAuthenticationDefaults.cs
new file mode 100644
index 000000000..0fbe84233
--- /dev/null
+++ b/src/AspNet.Security.OAuth.Csdn/CsdnAuthenticationDefaults.cs
@@ -0,0 +1,46 @@
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Authentication.OAuth;
+
+namespace AspNet.Security.OAuth.Csdn
+{
+ ///
+ /// Default values for Csdn authentication.
+ ///
+ public static class CsdnAuthenticationDefaults
+ {
+ ///
+ /// Default value for .
+ ///
+ public const string AuthenticationScheme = "Csdn";
+
+ ///
+ /// Default value for .
+ ///
+ public const string DisplayName = "Csdn";
+
+ ///
+ /// Default value for .
+ ///
+ public const string CallbackPath = "/signin-csdn";
+
+ ///
+ /// Default value for .
+ ///
+ public const string Issuer = "Csdn";
+
+ ///
+ /// Default value for .
+ ///
+ public const string AuthorizationEndpoint = "http://api.csdn.net/oauth2/authorize";
+
+ ///
+ /// Default value for .
+ ///
+ public const string TokenEndpoint = "http://api.csdn.net/oauth2/access_token";
+
+ ///
+ /// Default value for .
+ ///
+ public const string UserInformationEndpoint = "http://api.csdn.net/user/getinfo";
+ }
+}
diff --git a/src/AspNet.Security.OAuth.Csdn/CsdnAuthenticationExtensions.cs b/src/AspNet.Security.OAuth.Csdn/CsdnAuthenticationExtensions.cs
new file mode 100644
index 000000000..e9263002e
--- /dev/null
+++ b/src/AspNet.Security.OAuth.Csdn/CsdnAuthenticationExtensions.cs
@@ -0,0 +1,76 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
+ * See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers
+ * for more information concerning the license and the contributors participating to this project.
+ */
+
+using System;
+using AspNet.Security.OAuth.Csdn;
+using JetBrains.Annotations;
+using Microsoft.AspNetCore.Authentication;
+
+namespace Microsoft.Extensions.DependencyInjection
+{
+ ///
+ /// Extension methods to add Csdn authentication capabilities to an HTTP application pipeline.
+ ///
+ public static class CsdnAuthenticationExtensions
+ {
+ ///
+ /// Adds to the specified
+ /// , which enables Csdn authentication capabilities.
+ ///
+ /// The authentication builder.
+ /// A reference to this instance after the operation has completed.
+ public static AuthenticationBuilder AddCsdn([NotNull] this AuthenticationBuilder builder)
+ {
+ return builder.AddCsdn(CsdnAuthenticationDefaults.AuthenticationScheme, options => { });
+ }
+
+ ///
+ /// Adds to the specified
+ /// , which enables Csdn authentication capabilities.
+ ///
+ /// The authentication builder.
+ /// The delegate used to configure the OpenID 2.0 options.
+ /// A reference to this instance after the operation has completed.
+ public static AuthenticationBuilder AddCsdn(
+ [NotNull] this AuthenticationBuilder builder,
+ [NotNull] Action configuration)
+ {
+ return builder.AddCsdn(CsdnAuthenticationDefaults.AuthenticationScheme, configuration);
+ }
+
+ ///
+ /// Adds to the specified
+ /// , which enables Csdn authentication capabilities.
+ ///
+ /// The authentication builder.
+ /// The authentication scheme associated with this instance.
+ /// The delegate used to configure the Csdn options.
+ /// The .
+ public static AuthenticationBuilder AddCsdn(
+ [NotNull] this AuthenticationBuilder builder, [NotNull] string scheme,
+ [NotNull] Action configuration)
+ {
+ return builder.AddCsdn(scheme, CsdnAuthenticationDefaults.DisplayName, configuration);
+ }
+
+ ///
+ /// Adds to the specified
+ /// , which enables Csdn authentication capabilities.
+ ///
+ /// The authentication builder.
+ /// The authentication scheme associated with this instance.
+ /// The optional display name associated with this instance.
+ /// The delegate used to configure the Csdn options.
+ /// The .
+ public static AuthenticationBuilder AddCsdn(
+ [NotNull] this AuthenticationBuilder builder,
+ [NotNull] string scheme, [CanBeNull] string caption,
+ [NotNull] Action configuration)
+ {
+ return builder.AddOAuth(scheme, caption, configuration);
+ }
+ }
+}
diff --git a/src/AspNet.Security.OAuth.Csdn/CsdnAuthenticationHandler.cs b/src/AspNet.Security.OAuth.Csdn/CsdnAuthenticationHandler.cs
new file mode 100644
index 000000000..f5b0420df
--- /dev/null
+++ b/src/AspNet.Security.OAuth.Csdn/CsdnAuthenticationHandler.cs
@@ -0,0 +1,127 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
+ * See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers
+ * for more information concerning the license and the contributors participating to this project.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.Net.Http;
+using System.Security.Claims;
+using System.Text.Encodings.Web;
+using System.Threading.Tasks;
+using JetBrains.Annotations;
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Authentication.OAuth;
+using Microsoft.AspNetCore.WebUtilities;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using Newtonsoft.Json.Linq;
+
+namespace AspNet.Security.OAuth.Csdn
+{
+ public class CsdnAuthenticationHandler : OAuthHandler
+ {
+ public CsdnAuthenticationHandler(
+ [NotNull] IOptionsMonitor options,
+ [NotNull] ILoggerFactory logger,
+ [NotNull] UrlEncoder encoder,
+ [NotNull] ISystemClock clock)
+ : base(options, logger, encoder, clock)
+ {
+ }
+
+ protected override async Task CreateTicketAsync([NotNull] ClaimsIdentity identity, [NotNull] AuthenticationProperties properties, [NotNull] OAuthTokenResponse tokens)
+ {
+ var address = QueryHelpers.AddQueryString(Options.UserInformationEndpoint, new Dictionary
+ {
+ ["access_token"] = tokens.AccessToken,
+ ["client_id"] = Options.ClientId
+ });
+
+ var response = await Backchannel.GetAsync(address);
+ if (!response.IsSuccessStatusCode)
+ {
+ Logger.LogError("An error occurred while retrieving the user profile: the remote server " +
+ "returned a {Status} response with the following payload: {Headers} {Body}.",
+ /* Status: */ response.StatusCode,
+ /* Headers: */ response.Headers.ToString(),
+ /* Body: */ await response.Content.ReadAsStringAsync());
+
+ throw new HttpRequestException("An error occurred while retrieving user information.");
+ }
+
+ var payload = JObject.Parse(await response.Content.ReadAsStringAsync());
+
+ var status = payload.Value("userid");
+ if (status <= 0)
+ {
+ Logger.LogError("An error occurred while retrieving the user profile: the remote server " +
+ "returned a {Status} response with the following payload: {Headers} {Body}.",
+ /* Status: */ response.StatusCode,
+ /* Headers: */ response.Headers.ToString(),
+ /* Body: */ await response.Content.ReadAsStringAsync());
+
+ throw new HttpRequestException("An error occurred while retrieving user information.");
+ }
+
+ var principal = new ClaimsPrincipal(identity);
+ var context = new OAuthCreatingTicketContext(principal, properties, Context, Scheme, Options, Backchannel, tokens, payload);
+ context.RunClaimActions(payload);
+
+ await Options.Events.CreatingTicket(context);
+ return new AuthenticationTicket(context.Principal, context.Properties, Scheme.Name);
+ }
+
+ protected override async Task ExchangeCodeAsync(string code, string redirectUri)
+ {
+ var address = QueryHelpers.AddQueryString(Options.TokenEndpoint, new Dictionary()
+ {
+ ["client_id"] = Options.ClientId,
+ ["client_secret"] = Options.ClientSecret,
+ ["code"] = code,
+ ["grant_type"] = "authorization_code",
+ ["redirect_uri"] = redirectUri
+ });
+
+ var response = await Backchannel.GetAsync(address);
+ if (!response.IsSuccessStatusCode)
+ {
+ Logger.LogError("An error occurred while retrieving an access token: the remote server " +
+ "returned a {Status} response with the following payload: {Headers} {Body}.",
+ /* Status: */ response.StatusCode,
+ /* Headers: */ response.Headers.ToString(),
+ /* Body: */ await response.Content.ReadAsStringAsync());
+
+ return OAuthTokenResponse.Failed(new Exception("An error occurred while retrieving an access token."));
+ }
+
+ var payload = JObject.Parse(await response.Content.ReadAsStringAsync());
+ if (!string.IsNullOrEmpty(payload.Value("errcode")))
+ {
+ Logger.LogError("An error occurred while retrieving an access token: the remote server " +
+ "returned a {Status} response with the following payload: {Headers} {Body}.",
+ /* Status: */ response.StatusCode,
+ /* Headers: */ response.Headers.ToString(),
+ /* Body: */ await response.Content.ReadAsStringAsync());
+
+ return OAuthTokenResponse.Failed(new Exception("An error occurred while retrieving an access token."));
+ }
+ return OAuthTokenResponse.Success(payload);
+ }
+
+ protected override string BuildChallengeUrl(AuthenticationProperties properties, string redirectUri)
+ {
+ return QueryHelpers.AddQueryString(Options.AuthorizationEndpoint, new Dictionary
+ {
+ ["client_id"] = Options.ClientId,
+ //["scope"] = FormatScope(),
+ ["response_type"] = "code",
+ ["redirect_uri"] = redirectUri,
+ //["state"] = Options.StateDataFormat.Protect(properties)
+ });
+ }
+
+ protected override string FormatScope() => string.Join(",", Options.Scope);
+ }
+}
diff --git a/src/AspNet.Security.OAuth.Csdn/CsdnAuthenticationOptions.cs b/src/AspNet.Security.OAuth.Csdn/CsdnAuthenticationOptions.cs
new file mode 100644
index 000000000..f78072816
--- /dev/null
+++ b/src/AspNet.Security.OAuth.Csdn/CsdnAuthenticationOptions.cs
@@ -0,0 +1,56 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
+ * See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers
+ * for more information concerning the license and the contributors participating to this project.
+ */
+
+using System.Linq;
+using System.Security.Claims;
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Authentication.OAuth;
+using Microsoft.AspNetCore.Http;
+using Newtonsoft.Json.Linq;
+using static AspNet.Security.OAuth.Csdn.CsdnAuthenticationConstants;
+
+namespace AspNet.Security.OAuth.Csdn
+{
+ ///
+ /// Defines a set of options used by .
+ ///
+ public class CsdnAuthenticationOptions : OAuthOptions
+ {
+ public CsdnAuthenticationOptions()
+ {
+ ClaimsIssuer = CsdnAuthenticationDefaults.Issuer;
+ CallbackPath = new PathString(CsdnAuthenticationDefaults.CallbackPath);
+
+ AuthorizationEndpoint = CsdnAuthenticationDefaults.AuthorizationEndpoint;
+ TokenEndpoint = CsdnAuthenticationDefaults.TokenEndpoint;
+ UserInformationEndpoint = CsdnAuthenticationDefaults.UserInformationEndpoint;
+
+ //Scope.Add("snsapi_login");
+ //Scope.Add("snsapi_userinfo");
+
+ //Scope.Add("basic");
+
+ //ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "userid");
+ ClaimActions.MapJsonKey(ClaimTypes.Name, "username");
+ //"1"表示男,"0"表示女。
+ ClaimActions.MapJsonKey(ClaimTypes.Gender, "sex", ClaimValueTypes.Integer);
+
+ //ClaimActions.MapJsonKey(Claims.UserId, "userid");
+ ClaimActions.MapJsonKey(Claims.UserName, "username");
+ ClaimActions.MapJsonKey(Claims.Job, "job");
+ ClaimActions.MapJsonKey(Claims.WorkYear, "portrait");
+ ClaimActions.MapJsonKey(Claims.Website, "userdetail");
+ ClaimActions.MapJsonKey(Claims.Description, "birthday");
+ //ClaimActions.MapJsonKey(Claims.Marriage, "marriage");
+ //ClaimActions.MapJsonKey(Claims.Blood, "blood");
+ //ClaimActions.MapJsonKey(Claims.Figure, "figure");
+ //ClaimActions.MapJsonKey(Claims.Constellation, "constellation");
+ //ClaimActions.MapJsonKey(Claims.Education, "education");
+ //ClaimActions.MapJsonKey(Claims.Trade, "trade");
+ //ClaimActions.MapJsonKey(Claims.Job, "job");
+ }
+ }
+}