protected override async Task <AuthenticationTicket> CreateTicketAsync(ClaimsIdentity identity, AuthenticationProperties properties, OAuthTokenResponse tokens) { var address = QueryHelpers.AddQueryString(Options.UserInformationEndpoint, new Dictionary <string, string> { ["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 = WeChatOfficialStateCacheItem.CalculateCacheKey(state.ToString().ToMd5(), null); await Cache.RemoveAsync(stateCacheKey, token : Context.RequestAborted); return(new AuthenticationTicket(context.Principal, context.Properties, Scheme.Name)); }
protected override async Task HandleChallengeAsync(AuthenticationProperties properties) { await base.HandleChallengeAsync(properties); // TODO: 此处已经生成唯一的 CorrelationId, 可以借此将 properties生成State之后再进行缓存 // 注: 默认的State对于微信来说太长(微信只支持128位长度的State),因此巧妙的利用CorrelationId的MD5值来替代State // MD5转换防止直接通过CorrelationId干些别的事情... var state = properties.Items[".xsrf"]; var stateToken = Options.StateDataFormat.Protect(properties); var stateCacheKey = WeChatOfficialStateCacheItem.CalculateCacheKey(state.ToMd5(), null); await Cache .SetAsync( stateCacheKey, new WeChatOfficialStateCacheItem(stateToken), new DistributedCacheEntryOptions { AbsoluteExpiration = Clock.UtcNow.AddMinutes(2) // TODO: 设定2分钟过期? }, token : Context.RequestAborted); }
protected override async Task <HandleRequestResult> HandleRemoteAuthenticateAsync() { var query = Request.Query; // TODO: 此处借用唯一的 CorrelationId, 将 properties生成的State缓存取出,进行解密 var state = query["state"]; var stateCacheKey = WeChatOfficialStateCacheItem.CalculateCacheKey(state.ToString().ToMd5(), 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 <AuthenticationToken>(); 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)); } }