diff --git a/aspnet-core/LINGYUN.MicroService.All.sln b/aspnet-core/LINGYUN.MicroService.All.sln index a91f6cf7c..ea419eead 100644 --- a/aspnet-core/LINGYUN.MicroService.All.sln +++ b/aspnet-core/LINGYUN.MicroService.All.sln @@ -259,7 +259,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.AspNetCore.Sign EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "wechat", "wechat", "{DD9BE9E7-F6BF-4869-BCD2-82F5072BDA21}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.WeChat", "modules\wechat\LINGYUN.Abp.WeChat\LINGYUN.Abp.WeChat.csproj", "{BAE74ABC-1096-495F-A624-BEBFBC1896F2}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.WeChat", "modules\wechat\LINGYUN.Abp.WeChat\LINGYUN.Abp.WeChat.csproj", "{BAE74ABC-1096-495F-A624-BEBFBC1896F2}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChatValidator/LINGYUN.Abp.IdentityServer.WeChatValidator.csproj b/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChatValidator/LINGYUN.Abp.IdentityServer.WeChatValidator.csproj index e02f2e7f2..241c181ff 100644 --- a/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChatValidator/LINGYUN.Abp.IdentityServer.WeChatValidator.csproj +++ b/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChatValidator/LINGYUN.Abp.IdentityServer.WeChatValidator.csproj @@ -25,4 +25,8 @@ + + + + diff --git a/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChatValidator/LINGYUN/Abp/IdentityServer/AbpIdentityServerWeChatValidatorModule.cs b/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChatValidator/LINGYUN/Abp/IdentityServer/AbpIdentityServerWeChatValidatorModule.cs index 1fb0af4e3..000356b5c 100644 --- a/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChatValidator/LINGYUN/Abp/IdentityServer/AbpIdentityServerWeChatValidatorModule.cs +++ b/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChatValidator/LINGYUN/Abp/IdentityServer/AbpIdentityServerWeChatValidatorModule.cs @@ -1,6 +1,8 @@ using LINGYUN.Abp.IdentityServer.AspNetIdentity; using LINGYUN.Abp.IdentityServer.WeChatValidator; using LINGYUN.Abp.WeChat.Authorization; +using Microsoft.AspNetCore.Authentication; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Volo.Abp.IdentityServer; using Volo.Abp.IdentityServer.Localization; @@ -17,6 +19,8 @@ namespace LINGYUN.Abp.IdentityServer { public override void PreConfigureServices(ServiceConfigurationContext context) { + var configuration = context.Services.GetConfiguration(); + PreConfigure(builder => { builder.AddProfileService(); @@ -30,6 +34,13 @@ namespace LINGYUN.Abp.IdentityServer Configure(configuration.GetSection("WeChat:Signature")); + context.Services + .AddAuthentication() + .AddWeChat(options => // 加入微信认证登录 + { + configuration.GetSection("WeChat:Auth")?.Bind(options); + }); + Configure(options => { options.FileSets.AddEmbedded(); diff --git a/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChatValidator/LINGYUN/Abp/IdentityServer/AspNetIdentity/AbpWeChatProfileService.cs b/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChatValidator/LINGYUN/Abp/IdentityServer/AspNetIdentity/AbpWeChatProfileService.cs index 9cb7a6ca5..11246cab3 100644 --- a/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChatValidator/LINGYUN/Abp/IdentityServer/AspNetIdentity/AbpWeChatProfileService.cs +++ b/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChatValidator/LINGYUN/Abp/IdentityServer/AspNetIdentity/AbpWeChatProfileService.cs @@ -1,5 +1,6 @@ using IdentityServer4.AspNetIdentity; using IdentityServer4.Models; +using LINGYUN.Abp.WeChat.Authorization; using Microsoft.AspNetCore.Identity; using System.Linq; using System.Security.Principal; @@ -32,8 +33,8 @@ namespace LINGYUN.Abp.IdentityServer.AspNetIdentity await base.GetProfileDataAsync(context); // TODO: 可以从令牌获取openid, 安全性呢? - TryAddWeChatClaim(context, WeChatClaimTypes.OpenId); - TryAddWeChatClaim(context, WeChatClaimTypes.UnionId); + TryAddWeChatClaim(context, AbpWeChatClaimTypes.OpenId); + TryAddWeChatClaim(context, AbpWeChatClaimTypes.UnionId); } } diff --git a/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChatValidator/LINGYUN/Abp/IdentityServer/IWeChatResourceDataSeeder.cs b/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChatValidator/LINGYUN/Abp/IdentityServer/IWeChatResourceDataSeeder.cs new file mode 100644 index 000000000..38d591d26 --- /dev/null +++ b/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChatValidator/LINGYUN/Abp/IdentityServer/IWeChatResourceDataSeeder.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace LINGYUN.Abp.IdentityServer +{ + public interface IWeChatResourceDataSeeder + { + Task CreateStandardResourcesAsync(); + } +} diff --git a/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChatValidator/LINGYUN/Abp/IdentityServer/WeChatResourceDataSeeder.cs b/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChatValidator/LINGYUN/Abp/IdentityServer/WeChatResourceDataSeeder.cs new file mode 100644 index 000000000..eb2e69766 --- /dev/null +++ b/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChatValidator/LINGYUN/Abp/IdentityServer/WeChatResourceDataSeeder.cs @@ -0,0 +1,85 @@ +using LINGYUN.Abp.WeChat.Authorization; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Guids; +using Volo.Abp.Identity; +using Volo.Abp.IdentityServer.IdentityResources; + +namespace LINGYUN.Abp.IdentityServer +{ + public class WeChatResourceDataSeeder : IWeChatResourceDataSeeder, ITransientDependency + { + protected IIdentityClaimTypeRepository ClaimTypeRepository { get; } + protected IIdentityResourceRepository IdentityResourceRepository { get; } + protected IGuidGenerator GuidGenerator { get; } + + public WeChatResourceDataSeeder( + IIdentityResourceRepository identityResourceRepository, + IGuidGenerator guidGenerator, + IIdentityClaimTypeRepository claimTypeRepository) + { + IdentityResourceRepository = identityResourceRepository; + GuidGenerator = guidGenerator; + ClaimTypeRepository = claimTypeRepository; + } + + public virtual async Task CreateStandardResourcesAsync() + { + var wechatClaimTypes = new string[] + { + AbpWeChatClaimTypes.AvatarUrl, + AbpWeChatClaimTypes.City, + AbpWeChatClaimTypes.Country, + AbpWeChatClaimTypes.NickName, + AbpWeChatClaimTypes.OpenId, + AbpWeChatClaimTypes.Privilege, + AbpWeChatClaimTypes.Province, + AbpWeChatClaimTypes.Sex, + AbpWeChatClaimTypes.UnionId + }; + + var wechatResource = new IdentityServer4.Models.IdentityResource( + AbpWeChatAuthorizationConsts.ProfileKey, + AbpWeChatAuthorizationConsts.DisplayName, + wechatClaimTypes); + + foreach (var claimType in wechatClaimTypes) + { + await AddClaimTypeIfNotExistsAsync(claimType); + } + + await AddIdentityResourceIfNotExistsAsync(wechatResource); + } + + protected virtual async Task AddIdentityResourceIfNotExistsAsync(IdentityServer4.Models.IdentityResource resource) + { + if (await IdentityResourceRepository.CheckNameExistAsync(resource.Name)) + { + return; + } + + await IdentityResourceRepository.InsertAsync( + new IdentityResource( + GuidGenerator.Create(), + resource + ) + ); + } + + protected virtual async Task AddClaimTypeIfNotExistsAsync(string claimType) + { + if (await ClaimTypeRepository.AnyAsync(claimType)) + { + return; + } + + await ClaimTypeRepository.InsertAsync( + new IdentityClaimType( + GuidGenerator.Create(), + claimType, + isStatic: true + ) + ); + } + } +} diff --git a/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChatValidator/LINGYUN/Abp/IdentityServer/WeChatSignatureMiddleware.cs b/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChatValidator/LINGYUN/Abp/IdentityServer/WeChatSignatureMiddleware.cs index 48c3a040d..74b1a70b7 100644 --- a/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChatValidator/LINGYUN/Abp/IdentityServer/WeChatSignatureMiddleware.cs +++ b/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChatValidator/LINGYUN/Abp/IdentityServer/WeChatSignatureMiddleware.cs @@ -2,8 +2,6 @@ using Microsoft.Extensions.Options; using System; using System.Collections; -using System.Security.Cryptography; -using System.Text; using System.Threading.Tasks; using Volo.Abp; using Volo.Abp.DependencyInjection; @@ -63,9 +61,7 @@ namespace LINGYUN.Abp.IdentityServer signatureStr += al[i]; } // step3 SHA1加密 - using var sha1 = new SHA1CryptoServiceProvider(); - byte[] bytes_in = Encoding.ASCII.GetBytes(signatureStr); - byte[] bytes_out = sha1.ComputeHash(bytes_in); + byte[] bytes_out = signatureStr.Sha1(); string result = BitConverter.ToString(bytes_out).Replace("-", ""); // step4 比对 if (result.Equals(signature, StringComparison.CurrentCultureIgnoreCase)) diff --git a/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChatValidator/LINGYUN/Abp/IdentityServer/WeChatValidator/WeChatTokenGrantValidator.cs b/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChatValidator/LINGYUN/Abp/IdentityServer/WeChatValidator/WeChatTokenGrantValidator.cs index 444a9fef7..25827a066 100644 --- a/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChatValidator/LINGYUN/Abp/IdentityServer/WeChatValidator/WeChatTokenGrantValidator.cs +++ b/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChatValidator/LINGYUN/Abp/IdentityServer/WeChatValidator/WeChatTokenGrantValidator.cs @@ -23,7 +23,7 @@ namespace LINGYUN.Abp.IdentityServer.WeChatValidator public class WeChatTokenGrantValidator : IExtensionGrantValidator { protected ILogger Logger { get; } - protected AbpWeChatOptions Options { get; } + protected AbpWeChatAuthorizationOptions Options { get; } protected IHttpClientFactory HttpClientFactory{ get; } protected IEventService EventService { get; } protected IWeChatOpenIdFinder WeChatOpenIdFinder { get; } @@ -43,7 +43,7 @@ namespace LINGYUN.Abp.IdentityServer.WeChatValidator SignInManager signInManager, IStringLocalizer stringLocalizer, PhoneNumberTokenProvider phoneNumberTokenProvider, - IOptions options, + IOptions options, ILogger logger) { Logger = logger; @@ -82,7 +82,7 @@ namespace LINGYUN.Abp.IdentityServer.WeChatValidator return; } var wechatOpenId = await WeChatOpenIdFinder.FindAsync(wechatCode); - var currentUser = await UserManager.FindByLoginAsync(WeChatAuthorizationConsts.ProviderKey, wechatOpenId.OpenId); + var currentUser = await UserManager.FindByLoginAsync(AbpWeChatAuthorizationConsts.ProviderKey, wechatOpenId.OpenId); if(currentUser == null) { Logger.LogWarning("Invalid grant type: wechat openid: {0} not register", wechatOpenId.OpenId); @@ -92,20 +92,15 @@ namespace LINGYUN.Abp.IdentityServer.WeChatValidator } var sub = await UserManager.GetUserIdAsync(currentUser); - // 微信登录的用户写入token - currentUser.SetToken(WeChatAuthorizationConsts.ProviderKey, WeChatAuthorizationConsts.WeCahtCodeKey, wechatCode); - currentUser.SetToken(WeChatAuthorizationConsts.ProviderKey, WeChatAuthorizationConsts.WeCahtOpenIdKey, wechatOpenId.OpenId); - currentUser.SetToken(WeChatAuthorizationConsts.ProviderKey, WeChatAuthorizationConsts.WeCahtSessionKey, wechatOpenId.SessionKey); - var additionalClaims = new List(); if (currentUser.TenantId.HasValue) { additionalClaims.Add(new Claim(AbpClaimTypes.TenantId, currentUser.TenantId?.ToString())); } - additionalClaims.Add(new Claim(WeChatClaimTypes.OpenId, wechatOpenId.OpenId)); + additionalClaims.Add(new Claim(AbpWeChatClaimTypes.OpenId, wechatOpenId.OpenId)); if (!wechatOpenId.UnionId.IsNullOrWhiteSpace()) { - additionalClaims.Add(new Claim(WeChatClaimTypes.UnionId, wechatOpenId.UnionId)); + additionalClaims.Add(new Claim(AbpWeChatClaimTypes.UnionId, wechatOpenId.UnionId)); } await EventService.RaiseAsync(new UserLoginSuccessEvent(currentUser.UserName, wechatOpenId.OpenId, null)); diff --git a/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChatValidator/LINGYUN/Abp/WeChat/Authorization/UserWeChatCodeFinder.cs b/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChatValidator/LINGYUN/Abp/WeChat/Authorization/UserWeChatCodeFinder.cs index e0a966910..39a384e10 100644 --- a/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChatValidator/LINGYUN/Abp/WeChat/Authorization/UserWeChatCodeFinder.cs +++ b/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChatValidator/LINGYUN/Abp/WeChat/Authorization/UserWeChatCodeFinder.cs @@ -1,15 +1,15 @@ using Microsoft.Extensions.DependencyInjection; using System; +using System.Linq; using System.Threading.Tasks; using Volo.Abp.DependencyInjection; using Volo.Abp.Identity; namespace LINGYUN.Abp.WeChat.Authorization { - // TODO: 真正的项目需要扩展Abp框架实体来关联微信 [Dependency(ServiceLifetime.Transient, ReplaceServices = true)] - [ExposeServices(typeof(IUserWeChatCodeFinder))] - public class UserWeChatCodeFinder : IUserWeChatCodeFinder + [ExposeServices(typeof(IUserWeChatOpenIdFinder))] + public class UserWeChatCodeFinder : IUserWeChatOpenIdFinder { protected IdentityUserManager UserManager { get; } @@ -23,18 +23,24 @@ namespace LINGYUN.Abp.WeChat.Authorization { var user = await UserManager.FindByIdAsync(userId.ToString()); - var weChatCodeToken = user?.FindToken(WeChatAuthorizationConsts.ProviderKey, WeChatAuthorizationConsts.WeCahtCodeKey); - - return weChatCodeToken?.Value ?? userId.ToString(); + return GetUserOpenIdOrNull(user); } public virtual async Task FindByUserNameAsync(string userName) { var user = await UserManager.FindByNameAsync(userName); - var weChatCodeToken = user?.FindToken(WeChatAuthorizationConsts.ProviderKey, WeChatAuthorizationConsts.WeCahtCodeKey); + return GetUserOpenIdOrNull(user); + } + + protected string GetUserOpenIdOrNull(IdentityUser user) + { + // 微信扩展登录后openid存储在Login中 + var userLogin = user?.Logins + .Where(login => login.LoginProvider == AbpWeChatAuthorizationConsts.ProviderKey) + .FirstOrDefault(); - return weChatCodeToken?.Value ?? userName; + return userLogin?.ProviderKey; } } } diff --git a/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChatValidator/Microsoft/AspNetCore/Authentication/WeChat/WeChatAuthenticationHandler.cs b/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChatValidator/Microsoft/AspNetCore/Authentication/WeChat/WeChatAuthenticationHandler.cs new file mode 100644 index 000000000..446c9cb59 --- /dev/null +++ b/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChatValidator/Microsoft/AspNetCore/Authentication/WeChat/WeChatAuthenticationHandler.cs @@ -0,0 +1,316 @@ +using LINGYUN.Abp.WeChat.Authorization; +using Microsoft.AspNetCore.Authentication.OAuth; +using Microsoft.AspNetCore.WebUtilities; +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Microsoft.Extensions.Primitives; +using Microsoft.Net.Http.Headers; +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.Text.Json; +using System.Threading.Tasks; +using Volo.Abp.Caching; + +namespace Microsoft.AspNetCore.Authentication.WeChat +{ + public class WeChatAuthenticationHandler : OAuthHandler + { + protected IDistributedCache Cache { get; } + public WeChatAuthenticationHandler( + IDistributedCache cache, + IOptionsMonitor options, + ILoggerFactory logger, + UrlEncoder encoder, + ISystemClock clock) + : base(options, logger, encoder, clock) + { + Cache = cache; + } + + protected override async Task CreateTicketAsync(ClaimsIdentity identity, AuthenticationProperties properties, OAuthTokenResponse tokens) + { + var address = QueryHelpers.AddQueryString(Options.UserInformationEndpoint, new Dictionary + { + ["access_token"] = tokens.AccessToken, + ["openid"] = tokens.Response.GetRootString("openid") + }); + + 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 = JsonDocument.Parse(await response.Content.ReadAsStringAsync()); + if (!string.IsNullOrEmpty(payload.GetRootString("errcode"))) + { + 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 context = new OAuthCreatingTicketContext(new ClaimsPrincipal(identity), properties, Context, Scheme, Options, Backchannel, tokens, payload.RootElement); + context.RunClaimActions(); + + await Events.CreatingTicket(context); + + // TODO: 此处通过唯一的 CorrelationId, 将 properties生成的State缓存删除 + var state = Request.Query["state"]; + + var stateCacheKey = WeChatAuthenticationStateCacheItem.CalculateCacheKey(state, null); + await Cache.RemoveAsync(stateCacheKey, token: Context.RequestAborted); + + return new AuthenticationTicket(context.Principal, context.Properties, Scheme.Name); + } + + /// + /// code换取access_token + /// + protected override async Task ExchangeCodeAsync(OAuthCodeExchangeContext context) + { + var address = QueryHelpers.AddQueryString(Options.TokenEndpoint, new Dictionary() + { + ["appid"] = Options.ClientId, + ["secret"] = Options.ClientSecret, + ["code"] = context.Code, + ["grant_type"] = "authorization_code" + }); + + 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 = JsonDocument.Parse(await response.Content.ReadAsStringAsync()); + if (!string.IsNullOrEmpty(payload.GetRootString("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 async Task HandleChallengeAsync(AuthenticationProperties properties) + { + await base.HandleChallengeAsync(properties); + + // TODO: 此处已经生成唯一的 CorrelationId, 可以借此将 properties生成State之后再进行缓存 + var state = properties.Items[".xsrf"]; + + var stateToken = Options.StateDataFormat.Protect(properties); + var stateCacheKey = WeChatAuthenticationStateCacheItem.CalculateCacheKey(state, null); + + await Cache + .SetAsync( + stateCacheKey, + new WeChatAuthenticationStateCacheItem(stateToken), + new DistributedCacheEntryOptions + { + AbsoluteExpiration = Clock.UtcNow.AddMinutes(2) // TODO: 设定2分钟过期? + }, + token: Context.RequestAborted); + } + /// + /// 构建用户授权地址 + /// + protected override string BuildChallengeUrl(AuthenticationProperties properties, string redirectUri) + { + var state = properties.Items[".xsrf"]; + + var isWeChatBrewserRequest = IsWeChatBrowser(); + + var scope = isWeChatBrewserRequest + ? AbpWeChatAuthorizationConsts.UserInfoScope + : FormatScope(); + + var endPoint = isWeChatBrewserRequest + ? Options.AuthorizationEndpoint + : AbpWeChatAuthorizationConsts.QrConnectEndpoint; + + var challengeUrl = QueryHelpers.AddQueryString(endPoint, new Dictionary + { + ["appid"] = Options.ClientId, + ["redirect_uri"] = redirectUri, + ["response_type"] = "code" + }); + + challengeUrl += $"&scope={scope}&state={state}"; + + return challengeUrl; + } + + protected override async Task HandleRemoteAuthenticateAsync() + { + var query = Request.Query; + + // TODO: 此处借用唯一的 CorrelationId, 将 properties生成的State缓存取出,进行解密 + var state = query["state"]; + + var stateCacheKey = WeChatAuthenticationStateCacheItem.CalculateCacheKey(state, null); + var stateCacheItem = await Cache.GetAsync(stateCacheKey, token: Context.RequestAborted); + + var properties = Options.StateDataFormat.Unprotect(stateCacheItem.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.", properties); + } + + var error = query["error"]; + if (!StringValues.IsNullOrEmpty(error)) + { + // Note: access_denied errors are special protocol errors indicating the user didn't + // approve the authorization demand requested by the remote authorization server. + // Since it's a frequent scenario (that is not caused by incorrect configuration), + // denied errors are handled differently using HandleAccessDeniedErrorAsync(). + // Visit https://tools.ietf.org/html/rfc6749#section-4.1.2.1 for more information. + var errorDescription = query["error_description"]; + var errorUri = query["error_uri"]; + if (StringValues.Equals(error, "access_denied")) + { + var result = await HandleAccessDeniedErrorAsync(properties); + if (!result.None) + { + return result; + } + var deniedEx = new Exception("Access was denied by the resource owner or by the remote server."); + deniedEx.Data["error"] = error.ToString(); + deniedEx.Data["error_description"] = errorDescription.ToString(); + deniedEx.Data["error_uri"] = errorUri.ToString(); + + return HandleRequestResult.Fail(deniedEx, properties); + } + + var failureMessage = new StringBuilder(); + failureMessage.Append(error); + if (!StringValues.IsNullOrEmpty(errorDescription)) + { + failureMessage.Append(";Description=").Append(errorDescription); + } + if (!StringValues.IsNullOrEmpty(errorUri)) + { + failureMessage.Append(";Uri=").Append(errorUri); + } + + var ex = new Exception(failureMessage.ToString()); + ex.Data["error"] = error.ToString(); + ex.Data["error_description"] = errorDescription.ToString(); + ex.Data["error_uri"] = errorUri.ToString(); + + return HandleRequestResult.Fail(ex, properties); + } + + var code = query["code"]; + + if (StringValues.IsNullOrEmpty(code)) + { + return HandleRequestResult.Fail("Code was not found.", properties); + } + + var codeExchangeContext = new OAuthCodeExchangeContext(properties, code, BuildRedirectUri(Options.CallbackPath)); + using var tokens = await ExchangeCodeAsync(codeExchangeContext); + + if (tokens.Error != null) + { + return HandleRequestResult.Fail(tokens.Error, properties); + } + + if (string.IsNullOrEmpty(tokens.AccessToken)) + { + return HandleRequestResult.Fail("Failed to retrieve access token.", properties); + } + + var identity = new ClaimsIdentity(ClaimsIssuer); + + if (Options.SaveTokens) + { + var authTokens = new List(); + + authTokens.Add(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)) + { + int value; + if (int.TryParse(tokens.ExpiresIn, NumberStyles.Integer, CultureInfo.InvariantCulture, out 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); + if (ticket != null) + { + return HandleRequestResult.Success(ticket); + } + else + { + return HandleRequestResult.Fail("Failed to retrieve user information from remote server.", properties); + } + } + + protected override string FormatScope() + { + return string.Join(",", Options.Scope); + } + + protected virtual bool IsWeChatBrowser() + { + var userAgent = Request.Headers[HeaderNames.UserAgent].ToString(); + + return userAgent.Contains("micromessenger", StringComparison.InvariantCultureIgnoreCase); + } + } +} \ No newline at end of file diff --git a/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChatValidator/Microsoft/AspNetCore/Authentication/WeChat/WeChatAuthenticationOptions.cs b/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChatValidator/Microsoft/AspNetCore/Authentication/WeChat/WeChatAuthenticationOptions.cs new file mode 100644 index 000000000..9efc7600b --- /dev/null +++ b/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChatValidator/Microsoft/AspNetCore/Authentication/WeChat/WeChatAuthenticationOptions.cs @@ -0,0 +1,54 @@ +using LINGYUN.Abp.WeChat.Authorization; +using Microsoft.AspNetCore.Authentication.OAuth; +using Microsoft.AspNetCore.Http; +using System.Security.Claims; +using System.Text.Json; + +namespace Microsoft.AspNetCore.Authentication.WeChat +{ + public class WeChatAuthenticationOptions : OAuthOptions + { + public string AppId + { + get => ClientId; + set => ClientId = value; + } + + public string AppSecret + { + get => ClientSecret; + set => ClientSecret = value; + } + + public WeChatAuthenticationOptions() + { + ClaimsIssuer = AbpWeChatAuthorizationConsts.ProviderKey; + CallbackPath = new PathString(AbpWeChatAuthorizationConsts.CallbackPath); + + AuthorizationEndpoint = AbpWeChatAuthorizationConsts.AuthorizationEndpoint; + TokenEndpoint = AbpWeChatAuthorizationConsts.TokenEndpoint; + UserInformationEndpoint = AbpWeChatAuthorizationConsts.UserInformationEndpoint; + + Scope.Add(AbpWeChatAuthorizationConsts.LoginScope); + Scope.Add(AbpWeChatAuthorizationConsts.UserInfoScope); + + // 这个原始的属性一定要写进去,框架与UserLogin.ProviderKey进行关联判断是否绑定微信 + ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "openid"); + ClaimActions.MapJsonKey(ClaimTypes.Name, "nickname"); + + // 把自定义的身份标识写进令牌 + ClaimActions.MapJsonKey(AbpWeChatClaimTypes.OpenId, "openid"); + ClaimActions.MapJsonKey(AbpWeChatClaimTypes.UnionId, "unionid"); // TODO: 可用作tenant对比? + ClaimActions.MapJsonKey(AbpWeChatClaimTypes.NickName, "nickname"); + ClaimActions.MapJsonKey(AbpWeChatClaimTypes.Sex, "sex", ClaimValueTypes.Integer); + ClaimActions.MapJsonKey(AbpWeChatClaimTypes.Country, "country"); + ClaimActions.MapJsonKey(AbpWeChatClaimTypes.Province, "province"); + ClaimActions.MapJsonKey(AbpWeChatClaimTypes.City, "city"); + ClaimActions.MapJsonKey(AbpWeChatClaimTypes.AvatarUrl, "headimgurl"); + ClaimActions.MapCustomJson(AbpWeChatClaimTypes.Privilege, user => + { + return string.Join(",", user.GetStrings("privilege")); + }); + } + } +} diff --git a/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChatValidator/Microsoft/AspNetCore/Authentication/WeChat/WeChatAuthenticationStateCacheItem.cs b/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChatValidator/Microsoft/AspNetCore/Authentication/WeChat/WeChatAuthenticationStateCacheItem.cs new file mode 100644 index 000000000..efc3ab3a5 --- /dev/null +++ b/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChatValidator/Microsoft/AspNetCore/Authentication/WeChat/WeChatAuthenticationStateCacheItem.cs @@ -0,0 +1,18 @@ +namespace Microsoft.AspNetCore.Authentication.WeChat +{ + public class WeChatAuthenticationStateCacheItem + { + public string State { get; set; } + + public WeChatAuthenticationStateCacheItem() { } + public WeChatAuthenticationStateCacheItem(string state) + { + State = state; + } + + public static string CalculateCacheKey(string correlationId, string purpose) + { + return $"ci:{correlationId};p:{purpose ?? "null"}"; + } + } +} diff --git a/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChatValidator/Microsoft/AspNetCore/Authentication/WeChatAuthenticationExtensions.cs b/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChatValidator/Microsoft/AspNetCore/Authentication/WeChatAuthenticationExtensions.cs new file mode 100644 index 000000000..3fa471dc4 --- /dev/null +++ b/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChatValidator/Microsoft/AspNetCore/Authentication/WeChatAuthenticationExtensions.cs @@ -0,0 +1,64 @@ +using LINGYUN.Abp.WeChat.Authorization; +using Microsoft.AspNetCore.Authentication.WeChat; +using Microsoft.Extensions.DependencyInjection; +using System; + +namespace Microsoft.AspNetCore.Authentication +{ + public static class WeChatAuthenticationExtensions + { + /// + /// + public static AuthenticationBuilder AddWeChat( + this AuthenticationBuilder builder) + { + return builder + .AddWeChat( + AbpWeChatAuthorizationConsts.AuthenticationScheme, + AbpWeChatAuthorizationConsts.DisplayName, + options => { }); + } + + /// + /// + public static AuthenticationBuilder AddWeChat( + this AuthenticationBuilder builder, + Action configureOptions) + { + return builder + .AddWeChat( + AbpWeChatAuthorizationConsts.AuthenticationScheme, + AbpWeChatAuthorizationConsts.DisplayName, + configureOptions); + } + + /// + /// + public static AuthenticationBuilder AddWeChat( + this AuthenticationBuilder builder, + string authenticationScheme, + Action configureOptions) + { + return builder + .AddWeChat( + authenticationScheme, + AbpWeChatAuthorizationConsts.DisplayName, + configureOptions); + } + + /// + /// + public static AuthenticationBuilder AddWeChat( + this AuthenticationBuilder builder, + string authenticationScheme, + string displayName, + Action configureOptions) + { + return builder + .AddOAuth( + authenticationScheme, + displayName, + configureOptions); + } + } +} diff --git a/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChatValidator/System/BytesExtensions.cs b/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChatValidator/System/BytesExtensions.cs new file mode 100644 index 000000000..d1bad7915 --- /dev/null +++ b/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChatValidator/System/BytesExtensions.cs @@ -0,0 +1,16 @@ +using System.Security.Cryptography; + +namespace System +{ + internal static class BytesExtensions + { + public static byte[] Sha1(this byte[] data) + { + using (var sha = SHA1.Create()) + { + var hashBytes = sha.ComputeHash(data); + return hashBytes; + } + } + } +} diff --git a/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChatValidator/System/StringExtensions.cs b/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChatValidator/System/StringExtensions.cs new file mode 100644 index 000000000..a8eb40c27 --- /dev/null +++ b/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChatValidator/System/StringExtensions.cs @@ -0,0 +1,17 @@ +using System.Security.Cryptography; +using System.Text; + +namespace System +{ + internal static class StringExtensions + { + public static byte[] Sha1(this string str) + { + using (var sha = SHA1.Create()) + { + var hashBytes = sha.ComputeHash(Encoding.ASCII.GetBytes(str)); + return hashBytes; + } + } + } +} diff --git a/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChatValidator/System/Text/Json/JsonElementExtensions.cs b/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChatValidator/System/Text/Json/JsonElementExtensions.cs new file mode 100644 index 000000000..653a868ac --- /dev/null +++ b/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChatValidator/System/Text/Json/JsonElementExtensions.cs @@ -0,0 +1,63 @@ +using System.Collections.Generic; + +namespace System.Text.Json +{ + internal static class JsonElementExtensions + { + public static IEnumerable GetRootStrings(this JsonDocument json, string key) + { + return json.RootElement.GetStrings(key); + } + + public static IEnumerable GetStrings(this JsonElement json, string key) + { + var result = new List(); + + if (json.TryGetProperty(key, out JsonElement property) && property.ValueKind == JsonValueKind.Array) + { + foreach (var jsonProp in property.EnumerateArray()) + { + result.Add(jsonProp.GetString()); + } + } + + return result; + } + + public static string GetRootString(this JsonDocument json, string key, string defaultValue = "") + { + if (json.RootElement.TryGetProperty(key, out JsonElement property)) + { + return property.GetString(); + } + return defaultValue; + } + + public static string GetString(this JsonElement json, string key, string defaultValue = "") + { + if (json.TryGetProperty(key, out JsonElement property)) + { + return property.GetString(); + } + return defaultValue; + } + + public static int GetRootInt32(this JsonDocument json, string key, int defaultValue = 0) + { + if (json.RootElement.TryGetProperty(key, out JsonElement property) && property.TryGetInt32(out int value)) + { + return value; + } + return defaultValue; + } + + public static int GetInt32(this JsonElement json, string key, int defaultValue = 0) + { + if (json.TryGetProperty(key, out JsonElement property) && property.TryGetInt32(out int value)) + { + return value; + } + return defaultValue; + } + } +} diff --git a/aspnet-core/modules/wechat/LINGYUN.Abp.Notifications.WeChat/LINGYUN/Abp/Notifications/WeChat/WeApp/WeChatWeAppNotificationPublishProvider.cs b/aspnet-core/modules/wechat/LINGYUN.Abp.Notifications.WeChat/LINGYUN/Abp/Notifications/WeChat/WeApp/WeChatWeAppNotificationPublishProvider.cs index 42f7e3b15..91136b562 100644 --- a/aspnet-core/modules/wechat/LINGYUN.Abp.Notifications.WeChat/LINGYUN/Abp/Notifications/WeChat/WeApp/WeChatWeAppNotificationPublishProvider.cs +++ b/aspnet-core/modules/wechat/LINGYUN.Abp.Notifications.WeChat/LINGYUN/Abp/Notifications/WeChat/WeApp/WeChatWeAppNotificationPublishProvider.cs @@ -20,8 +20,8 @@ namespace LINGYUN.Abp.Notifications.WeChat.WeApp private IFeatureChecker _featureChecker; protected IFeatureChecker FeatureChecker => LazyGetRequiredService(ref _featureChecker); - private IWeChatOpenIdFinder _weChatOpenIdFinder; - protected IWeChatOpenIdFinder WeChatOpenIdFinder => LazyGetRequiredService(ref _weChatOpenIdFinder); + private IUserWeChatOpenIdFinder _userWeChatOpenIdFinder; + protected IUserWeChatOpenIdFinder UserWeChatOpenIdFinder => LazyGetRequiredService(ref _userWeChatOpenIdFinder); protected IWeChatWeAppNotificationSender NotificationSender { get; } protected AbpWeChatWeAppNotificationOptions Options { get; } @@ -79,15 +79,13 @@ namespace LINGYUN.Abp.Notifications.WeChat.WeApp var weAppLang = GetOrDefault(notification.Data, "WeAppLanguage", Options.DefaultWeAppLanguage); Logger.LogDebug($"Get wechat weapp language: {weAppLang ?? null}"); - // TODO: 如果微信端发布通知,请组装好 wx-code 字段在通知数据内容里面 - string weChatCode = GetOrDefault(notification.Data, "wx-code", ""); + // TODO: 如果微信端发布通知,请组装好 openid 字段在通知数据内容里面 + string weChatCode = GetOrDefault(notification.Data, AbpWeChatClaimTypes.OpenId, ""); - WeChatOpenId openId = weChatCode.IsNullOrWhiteSpace() - ? await WeChatOpenIdFinder.FindByUserNameAsync(identifier.UserName) // 按照实际情况,需要自行实现 IUserWeChatCodeFinder 接口以获取微信Code,然后通过Code来获取OpenId - : await WeChatOpenIdFinder.FindAsync(weChatCode); - + var openId = !weChatCode.IsNullOrWhiteSpace() ? weChatCode + : await UserWeChatOpenIdFinder.FindByUserIdAsync(identifier.UserId); - var weChatWeAppNotificationData = new WeChatWeAppSendNotificationData(openId.OpenId, + var weChatWeAppNotificationData = new WeChatWeAppSendNotificationData(openId, templateId, redirect, weAppState, weAppLang); // 写入模板数据 diff --git a/aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/AbpWeChatAuthorizationConsts.cs b/aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/AbpWeChatAuthorizationConsts.cs new file mode 100644 index 000000000..a97731c44 --- /dev/null +++ b/aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/AbpWeChatAuthorizationConsts.cs @@ -0,0 +1,72 @@ +namespace LINGYUN.Abp.WeChat.Authorization +{ + /// + /// 与微信认证相关的静态(可变)常量 + /// + public static class AbpWeChatAuthorizationConsts + { + /// + /// 微信授权名称 + /// + public const string AuthenticationScheme = "WeChat"; + /// + /// 微信授权显示名称 + /// + public static string DisplayName = "WeChat"; + /// + /// 微信个人信息标识 + /// + public static string ProfileKey { get; set; } = "wechat.profile"; + /// + /// 微信提供者标识 + /// + public static string ProviderKey { get; set; } = AuthenticationScheme; + /// + /// 回调地址 + /// + public static string CallbackPath { get; set; } = "/signin-wechat"; + + /// + /// 微信客户端外的网页登录 + /// + public const string QrConnectEndpoint = "https://open.weixin.qq.com/connect/qrconnect"; + + /// + /// 微信客户端内的网页登录 + /// + public const string AuthorizationEndpoint = "https://open.weixin.qq.com/connect/oauth2/authorize"; + + /// + /// 用户允许授权后通过返回的code换取access_token地址 + /// + public const string TokenEndpoint = "https://api.weixin.qq.com/sns/oauth2/access_token"; + + /// + /// 使用access_token获取用户个人信息地址 + /// + public const string UserInformationEndpoint = "https://api.weixin.qq.com/sns/userinfo"; + /// + /// 弹出授权页面,可通过openid拿到昵称、性别、所在地。 + /// 并且, 即使在未关注的情况下,只要用户授权,也能获取其信息 + ///
+ ///
+ /// 详询: + ///
+ /// + /// 以snsapi_userinfo为scope发起的网页授权,是用来获取用户的基本信息的。 + /// 但这种授权需要用户手动同意,并且由于用户同意过,所以无须关注,就可在授权后获取该用户的基本信息 + /// + public const string UserInfoScope = "snsapi_userinfo"; + /// + /// 不弹出授权页面,直接跳转,只能获取用户openid + ///
+ ///
+ /// 详询: + ///
+ /// + /// 以snsapi_base为scope发起的网页授权,是用来获取进入页面的用户的openid的,并且是静默授权并自动跳转到回调页的。 + /// 用户感知的就是直接进入了回调页(往往是业务页面) + /// + public const string LoginScope = "snsapi_login"; + } +} diff --git a/aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/AbpWeChatAuthorizationModule.cs b/aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/AbpWeChatAuthorizationModule.cs index e69091dd7..5618c6c7d 100644 --- a/aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/AbpWeChatAuthorizationModule.cs +++ b/aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/AbpWeChatAuthorizationModule.cs @@ -16,7 +16,7 @@ namespace LINGYUN.Abp.WeChat.Authorization public override void ConfigureServices(ServiceConfigurationContext context) { var configuration = context.Services.GetConfiguration(); - Configure(configuration.GetSection("WeChat:Auth")); + Configure(configuration.GetSection("WeChat:Auth")); context.Services.AddHttpClient("WeChatRequestClient", options => { diff --git a/aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/AbpWeChatOptions.cs b/aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/AbpWeChatAuthorizationOptions.cs similarity index 76% rename from aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/AbpWeChatOptions.cs rename to aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/AbpWeChatAuthorizationOptions.cs index 3c4174d9f..5c059e05b 100644 --- a/aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/AbpWeChatOptions.cs +++ b/aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/AbpWeChatAuthorizationOptions.cs @@ -1,6 +1,6 @@ namespace LINGYUN.Abp.WeChat.Authorization { - public class AbpWeChatOptions + public class AbpWeChatAuthorizationOptions { public string AppId { get; set; } public string AppSecret { get; set; } diff --git a/aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/AbpWeChatClaimTypes.cs b/aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/AbpWeChatClaimTypes.cs new file mode 100644 index 000000000..58fec0452 --- /dev/null +++ b/aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/AbpWeChatClaimTypes.cs @@ -0,0 +1,48 @@ +namespace LINGYUN.Abp.WeChat.Authorization +{ + /// + /// 微信认证身份类型,可以像 自行配置 + ///
+ /// See: + ///
+ public class AbpWeChatClaimTypes + { + /// + /// 用户的唯一标识 + /// + public static string OpenId { get; set; } = "wx-openid"; // 可变更 + /// + /// 只有在用户将公众号绑定到微信开放平台帐号后,才会出现该字段。 + /// + public static string UnionId { get; set; } = "wx-unionid"; //可变更 + /// + /// 用户昵称 + /// + public static string NickName { get; set; } = "nickname"; + /// + /// 用户的性别,值为1时是男性,值为2时是女性,值为0时是未知 + /// + public static string Sex { get; set; } = "sex"; + /// + /// 国家,如中国为CN + /// + public static string Country { get; set; } = "country"; + /// + /// 用户个人资料填写的省份 + /// + public static string Province { get; set; } = "province"; + /// + /// 普通用户个人资料填写的城市 + /// + public static string City { get; set; } = "city"; + /// + /// 用户头像,最后一个数值代表正方形头像大小(有0、46、64、96、132数值可选,0代表640*640正方形头像),用户没有头像时该项为空。 + /// 若用户更换头像,原有头像URL将失效。 + /// + public static string AvatarUrl { get; set; } = "avatar"; + /// + /// 用户特权信息,json 数组,如微信沃卡用户为(chinaunicom) + /// + public static string Privilege { get; set; } = "privilege"; + } +} diff --git a/aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/OpenId/IUserWeChatCodeFinder.cs b/aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/OpenId/IUserWeChatOpenIdFinder.cs similarity index 83% rename from aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/OpenId/IUserWeChatCodeFinder.cs rename to aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/OpenId/IUserWeChatOpenIdFinder.cs index 1edba6807..bc56fc43c 100644 --- a/aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/OpenId/IUserWeChatCodeFinder.cs +++ b/aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/OpenId/IUserWeChatOpenIdFinder.cs @@ -3,7 +3,7 @@ using System.Threading.Tasks; namespace LINGYUN.Abp.WeChat.Authorization { - public interface IUserWeChatCodeFinder + public interface IUserWeChatOpenIdFinder { Task FindByUserIdAsync(Guid userId); diff --git a/aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/OpenId/IWeChatOpenIdFinder.cs b/aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/OpenId/IWeChatOpenIdFinder.cs index 6fb15bcd9..1b548958f 100644 --- a/aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/OpenId/IWeChatOpenIdFinder.cs +++ b/aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/OpenId/IWeChatOpenIdFinder.cs @@ -6,9 +6,5 @@ namespace LINGYUN.Abp.WeChat.Authorization public interface IWeChatOpenIdFinder { Task FindAsync(string code); - - Task FindByUserIdAsync(Guid userId); - - Task FindByUserNameAsync(string userName); } } diff --git a/aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/OpenId/NullUserWeChatCodeFinder.cs b/aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/OpenId/NullUserWeChatOpenIdFinder.cs similarity index 61% rename from aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/OpenId/NullUserWeChatCodeFinder.cs rename to aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/OpenId/NullUserWeChatOpenIdFinder.cs index 364da9ef0..d05fcfbc7 100644 --- a/aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/OpenId/NullUserWeChatCodeFinder.cs +++ b/aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/OpenId/NullUserWeChatOpenIdFinder.cs @@ -4,16 +4,16 @@ using Volo.Abp.DependencyInjection; namespace LINGYUN.Abp.WeChat.Authorization { - public class NullUserWeChatCodeFinder : IUserWeChatCodeFinder, ISingletonDependency + public class NullUserWeChatOpenIdFinder : IUserWeChatOpenIdFinder, ISingletonDependency { public Task FindByUserIdAsync(Guid userId) { - return Task.FromResult(userId.ToString()); + return Task.FromResult(""); } public Task FindByUserNameAsync(string userName) { - return Task.FromResult(userName); + return Task.FromResult(""); } } } diff --git a/aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/OpenId/WeChatOpenIdFinder.cs b/aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/OpenId/WeChatOpenIdFinder.cs index fcf22aecc..a6e4b7dbc 100644 --- a/aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/OpenId/WeChatOpenIdFinder.cs +++ b/aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/OpenId/WeChatOpenIdFinder.cs @@ -18,23 +18,20 @@ namespace LINGYUN.Abp.WeChat.Authorization public class WeChatOpenIdFinder : IWeChatOpenIdFinder { public ILogger Logger { get; set; } - protected AbpWeChatOptions Options { get; } + protected AbpWeChatAuthorizationOptions Options { get; } protected ICurrentTenant CurrentTenant { get; } protected IHttpClientFactory HttpClientFactory { get; } protected IJsonSerializer JsonSerializer { get; } - protected IUserWeChatCodeFinder UserWeChatCodeFinder { get; } protected IDistributedCache Cache { get; } public WeChatOpenIdFinder( ICurrentTenant currentTenant, IJsonSerializer jsonSerializer, - IUserWeChatCodeFinder userWeChatCodeFinder, IHttpClientFactory httpClientFactory, - IOptions options, + IOptions options, IDistributedCache cache) { CurrentTenant = currentTenant; JsonSerializer = jsonSerializer; - UserWeChatCodeFinder = userWeChatCodeFinder; HttpClientFactory = httpClientFactory; Cache = cache; @@ -49,24 +46,6 @@ namespace LINGYUN.Abp.WeChat.Authorization return (await GetCacheItemAsync(code)).WeChatOpenId; } - public virtual async Task FindByUserIdAsync(Guid userId) - { - var code = await UserWeChatCodeFinder.FindByUserIdAsync(userId); - // TODO: 如果需要获取SessionKey的话呢,需要再以openid作为标识来缓存一下吗 - // 或者前端保存code,通过传递code来获取 - return (await GetCacheItemAsync(code)).WeChatOpenId; - } - - public virtual async Task FindByUserNameAsync(string userName) - { - var code = await UserWeChatCodeFinder.FindByUserNameAsync(userName); - // TODO: 如果需要获取SessionKey的话呢,需要再以openid作为标识来缓存一下吗 - // 或者前端保存code,通过传递code来获取 - return (await GetCacheItemAsync(code)).WeChatOpenId; - } - - - protected virtual async Task GetCacheItemAsync(string code) { var cacheKey = WeChatOpenIdCacheItem.CalculateCacheKey(code); diff --git a/aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/Token/WeChatTokenProvider.cs b/aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/Token/WeChatTokenProvider.cs index 4e1cb40e5..6df509537 100644 --- a/aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/Token/WeChatTokenProvider.cs +++ b/aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/Token/WeChatTokenProvider.cs @@ -18,11 +18,11 @@ namespace LINGYUN.Abp.WeChat.Authorization protected IHttpClientFactory HttpClientFactory { get; } protected IJsonSerializer JsonSerializer { get; } protected IDistributedCache Cache { get; } - protected AbpWeChatOptions Options { get; } + protected AbpWeChatAuthorizationOptions Options { get; } public WeChatTokenProvider( IJsonSerializer jsonSerializer, IHttpClientFactory httpClientFactory, - IOptions options, + IOptions options, IDistributedCache cache) { JsonSerializer = jsonSerializer; diff --git a/aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/WeChatAuthorizationConsts.cs b/aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/WeChatAuthorizationConsts.cs deleted file mode 100644 index d1ffc1a13..000000000 --- a/aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Authorization/LINGYUN/Abp/WeChat/Authorization/WeChatAuthorizationConsts.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace LINGYUN.Abp.WeChat.Authorization -{ - public class WeChatAuthorizationConsts - { - /// - /// 微信提供者标识 - /// - public static string ProviderKey { get; set; } = "WeChat"; - /// - /// 微信Code参数名称 - /// - public static string WeCahtCodeKey { get; set; } = "wx-code"; - /// - /// 微信OpenId参数名称 - /// - public static string WeCahtOpenIdKey { get; set; } = "wx-open-id"; - /// - /// 微信SessionKey参数名称 - /// - public static string WeCahtSessionKey { get; set; } = "wx-session-key"; - } -} diff --git a/aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Authorization/Volo/Abp/Security/Claims/WeChatClaimTypes.cs b/aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Authorization/Volo/Abp/Security/Claims/WeChatClaimTypes.cs deleted file mode 100644 index 792813c49..000000000 --- a/aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Authorization/Volo/Abp/Security/Claims/WeChatClaimTypes.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Volo.Abp.Security.Claims -{ - public class WeChatClaimTypes - { - public static string OpenId { get; set; } = "wx-openid"; - public static string UnionId { get; set; } = "wx-unionid"; - } -} diff --git a/aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Authorization/Volo/Abp/Users/CurrentUserExtensions.cs b/aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Authorization/Volo/Abp/Users/CurrentUserExtensions.cs index b3a68ea18..bf9c16cad 100644 --- a/aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Authorization/Volo/Abp/Users/CurrentUserExtensions.cs +++ b/aspnet-core/modules/wechat/LINGYUN.Abp.WeChat.Authorization/Volo/Abp/Users/CurrentUserExtensions.cs @@ -1,4 +1,4 @@ -using Volo.Abp.Security.Claims; +using LINGYUN.Abp.WeChat.Authorization; namespace Volo.Abp.Users { @@ -11,7 +11,7 @@ namespace Volo.Abp.Users /// public static string FindWeChatOpenId(this ICurrentUser currentUser) { - var weChatClaim = currentUser.FindClaim(WeChatClaimTypes.OpenId); + var weChatClaim = currentUser.FindClaim(AbpWeChatClaimTypes.OpenId); if (weChatClaim == null) { return null; @@ -27,7 +27,7 @@ namespace Volo.Abp.Users /// public static string FindWeChatUnionId(this ICurrentUser currentUser) { - var weChatClaim = currentUser.FindClaim(WeChatClaimTypes.UnionId); + var weChatClaim = currentUser.FindClaim(AbpWeChatClaimTypes.UnionId); if (weChatClaim == null) { return null; diff --git a/aspnet-core/services/account/AuthServer.Host/AuthIdentityServerModule.cs b/aspnet-core/services/account/AuthServer.Host/AuthIdentityServerModule.cs index 54d45ee68..687c2c696 100644 --- a/aspnet-core/services/account/AuthServer.Host/AuthIdentityServerModule.cs +++ b/aspnet-core/services/account/AuthServer.Host/AuthIdentityServerModule.cs @@ -3,6 +3,7 @@ using LINGYUN.Abp.EventBus.CAP; using LINGYUN.Abp.IdentityServer; using LINGYUN.Abp.MultiTenancy.DbFinder; using LINGYUN.Abp.PermissionManagement.Identity; +using Microsoft.AspNetCore.Authentication.WeChat; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.DataProtection; @@ -114,6 +115,11 @@ namespace AuthServer.Host options.InitVectorBytes = encryptionConfiguration.GetSection("InitVector").Exists() ? Encoding.ASCII.GetBytes(encryptionConfiguration["InitVector"]) : options.InitVectorBytes; + + var keySizeConfig = encryptionConfiguration.GetSection("Keysize"); + options.Keysize = keySizeConfig.Exists() + ? keySizeConfig.Get() + : options.Keysize; } }); @@ -216,10 +222,11 @@ namespace AuthServer.Host app.UseVirtualFiles(); app.UseRouting(); app.UseCors(DefaultCorsPolicyName); + app.UseWeChatSignature(); + app.UseMultiTenancy(); app.UseAuthentication(); app.UseJwtTokenMiddleware(); app.UseAbpClaimsMap(); - app.UseMultiTenancy(); app.UseAbpRequestLocalization(); app.UseIdentityServer(); app.UseAuthorization(); diff --git a/aspnet-core/services/account/AuthServer.Host/DataSeeder/IdentityServerDataSeedContributor.cs b/aspnet-core/services/account/AuthServer.Host/DataSeeder/IdentityServerDataSeedContributor.cs index b28a8e9a8..18feae7bd 100644 --- a/aspnet-core/services/account/AuthServer.Host/DataSeeder/IdentityServerDataSeedContributor.cs +++ b/aspnet-core/services/account/AuthServer.Host/DataSeeder/IdentityServerDataSeedContributor.cs @@ -1,4 +1,5 @@ -using Microsoft.Extensions.Configuration; +using LINGYUN.Abp.IdentityServer; +using Microsoft.Extensions.Configuration; using System; using System.Collections.Generic; using System.IO; @@ -13,7 +14,6 @@ using Volo.Abp.IdentityServer.ApiResources; using Volo.Abp.IdentityServer.Clients; using Volo.Abp.IdentityServer.IdentityResources; using Volo.Abp.PermissionManagement; -using Volo.Abp.Security.Claims; using Volo.Abp.Uow; namespace AuthServer.DataSeeder @@ -25,6 +25,7 @@ namespace AuthServer.DataSeeder private readonly IIdentityResourceDataSeeder _identityResourceDataSeeder; private readonly IIdentityClaimTypeRepository _identityClaimTypeRepository; private readonly IPermissionDataSeeder _permissionDataSeeder; + private readonly IWeChatResourceDataSeeder _weChatResourceDataSeeder; private readonly IGuidGenerator _guidGenerator; private readonly IConfiguration _configuration; @@ -32,6 +33,7 @@ namespace AuthServer.DataSeeder IClientRepository clientRepository, IPermissionDataSeeder permissionDataSeeder, IApiResourceRepository apiResourceRepository, + IWeChatResourceDataSeeder weChatResourceDataSeeder, IIdentityResourceDataSeeder identityResourceDataSeeder, IIdentityClaimTypeRepository identityClaimTypeRepository, IGuidGenerator guidGenerator) @@ -40,6 +42,7 @@ namespace AuthServer.DataSeeder _permissionDataSeeder = permissionDataSeeder; _apiResourceRepository = apiResourceRepository; _identityClaimTypeRepository = identityClaimTypeRepository; + _weChatResourceDataSeeder = weChatResourceDataSeeder; _identityResourceDataSeeder = identityResourceDataSeeder; _guidGenerator = guidGenerator; var env = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"; @@ -62,13 +65,7 @@ namespace AuthServer.DataSeeder private async Task CreateWeChatClaimTypeAsync() { - if (!await _identityClaimTypeRepository.AnyAsync(WeChatClaimTypes.OpenId)) - { - var wechatClaimType = new IdentityClaimType(_guidGenerator.Create(), WeChatClaimTypes.OpenId, - isStatic: true, description: "适用于微信认证的用户标识"); - - await _identityClaimTypeRepository.InsertAsync(wechatClaimType); - } + await _weChatResourceDataSeeder.CreateStandardResourcesAsync(); } private async Task CreateApiResourcesAsync() diff --git a/aspnet-core/services/account/AuthServer.Host/Microsoft/Extensions/DependencyInjection/SameSiteCookiesServiceCollectionExtensions.cs b/aspnet-core/services/account/AuthServer.Host/Microsoft/Extensions/DependencyInjection/SameSiteCookiesServiceCollectionExtensions.cs index b7a2b72bb..9847bd905 100644 --- a/aspnet-core/services/account/AuthServer.Host/Microsoft/Extensions/DependencyInjection/SameSiteCookiesServiceCollectionExtensions.cs +++ b/aspnet-core/services/account/AuthServer.Host/Microsoft/Extensions/DependencyInjection/SameSiteCookiesServiceCollectionExtensions.cs @@ -146,7 +146,13 @@ namespace Microsoft.Extensions.DependencyInjection { try { - return Convert.ToInt32(userAgent.Split("Chrome/")[1].Split('.')[0]); + string version = "0"; + var chromeAgents = userAgent.Split("Chrome/"); + if (chromeAgents.Length > 1 && chromeAgents[1].Split('.').Length > 0) + { + version = chromeAgents[1].Split('.')[0]; + } + return Convert.ToInt32(version); } catch (Exception) { @@ -154,4 +160,4 @@ namespace Microsoft.Extensions.DependencyInjection } } } -} +} \ No newline at end of file