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"); + } + } +}