我有一个移动和服务器端(带有 API 的 Web)应用程序,它使用 Identity 来对用户进行身份验证。移动应用程序在服务器上打开一个 WebView,一旦用户成功登录,就会使用从服务器发回的访问令牌重新打开移动应用程序。然后,它可以利用服务器端需要 Bearer 令牌身份验证的 API。
此模式的解释请参见此处。
使用移动应用程序时,如果用户通过用户名/密码登录,则持有者令牌将起作用。如果用户通过 google 登录,API 将返回 401(访问被拒绝)错误。这是我的身份验证设置代码:
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = IdentityConstants.ApplicationScheme;
options.DefaultChallengeScheme = IdentityConstants.ApplicationScheme;
options.DefaultSignInScheme = IdentityConstants.ApplicationScheme;
})
// I tried .AddGoogle here previously, but this is the only way I've been able to return an id_token. The access_token is for google API's, not internal API's
.AddOpenIdConnect(
GoogleDefaults.AuthenticationScheme,
GoogleDefaults.DisplayName, o =>
{
var googleAuthNSection = configuration.GetSection("Authentication:Google");
o.SignInScheme = IdentityConstants.ExternalScheme;
o.Authority = "https://accounts.google.com";
o.ClientId = googleAuthNSection["ClientId"]!;
o.ClientSecret = googleAuthNSection["ClientSecret"]!;
o.ResponseType = OpenIdConnectResponseType.IdToken;
o.CallbackPath = "/signin-google";
o.SaveTokens = true;
o.Scope.Add("email");
})
.AddCookie(IdentityConstants.ApplicationScheme, o =>
{
o.LoginPath = new PathString("/Account/Login");
o.Events = new CookieAuthenticationEvents
{
OnValidatePrincipal = SecurityStampValidator.ValidatePrincipalAsync
};
})
.AddBearerToken(IdentityConstants.BearerScheme, o =>
{
o.BearerTokenExpiration = TimeSpan.FromHours(5);
})
.AddExternalCookie();
这里是如果用户使用用户名/密码登录则获取不记名令牌的代码。请注意,我发布到 /login 端点以获取带有当前用户电子邮件/密码的不记名令牌,因为我可以在此处访问它。使用谷歌登录时,我没有电子邮件/密码,因此无法重新验证身份端点以获取不记名令牌。
var body = new { email, password };
var content = JsonSerializer.Serialize(body);
var buffer = Encoding.UTF8.GetBytes(content);
var byteContent = new ByteArrayContent(buffer);
byteContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
var client = httpClientFactory.CreateClient(MealPlanApi.ApiClient);
var loginResponse = await client.PostAsync("/login", byteContent);
var contents = await loginResponse.Content.ReadAsStringAsync();
var doc = JsonSerializer.Deserialize<JsonElement>(contents);
var accessToken = doc.GetProperty("accessToken").GetString()!;
await RedirectToMobileAuthenticatedAsync(context, user, accessToken, returnUrl);
以下是登录第 3 方身份验证并将令牌发送回移动应用程序的代码。用户显然已通过身份验证,但此处获取的访问令牌无法用于访问 API。
[Route("mobileauth")]
[ApiController]
public class AuthController : ControllerBase
{
const string callbackScheme = "MyCallbackScheme";
[HttpGet("{scheme}")] // eg: Microsoft, Facebook, Apple, etc
public async Task Get([FromRoute] string scheme)
{
var auth = await Request.HttpContext.AuthenticateAsync(scheme);
if (!auth.Succeeded
|| auth?.Principal == null
|| !auth.Principal.Identities.Any(id => id.IsAuthenticated)
|| string.IsNullOrEmpty(auth.Properties.GetTokenValue("access_token")))
{
// Not authenticated, challenge
await Request.HttpContext.ChallengeAsync(scheme);
}
else
{
var claims = auth.Principal.Identities.FirstOrDefault()?.Claims;
var email = string.Empty;
email = claims?.FirstOrDefault(c => c.Type == ClaimTypes.Email)?.Value;
// Get parameters to send back to the callback
var qs = new Dictionary<string, string>
{
{ "access_token", auth.Properties.GetTokenValue("access_token") },
{ "refresh_token", auth.Properties.GetTokenValue("refresh_token") ?? string.Empty },
{ "expires", (auth.Properties.ExpiresUtc?.ToUnixTimeSeconds() ?? -1).ToString() },
{ "email", email }
};
// Build the result url
var url = callbackScheme + "://#" + string.Join(
"&",
qs.Where(kvp => !string.IsNullOrEmpty(kvp.Value) && kvp.Value != "-1")
.Select(kvp => $"{WebUtility.UrlEncode(kvp.Key)}={WebUtility.UrlEncode(kvp.Value)}"));
// Redirect to final url
Request.HttpContext.Response.Redirect(url);
}
}
}
这可能是我保护和使用端点的方式存在问题吗?
在主程序中
app.MapControllers();
控制器
[Route("api/[controller]")]
[Authorize(AuthenticationSchemes = "Identity.Bearer,Identity.External,Google")]
[ApiController]
public class MyController() : ControllerBase
这是我来自 HttpContext.AuthenticateAsync("Google") 的回复
注意我已通过身份验证。当前用户可以访问网络上的资源。但是,如果我将此访问令牌或 ID 令牌传递给移动应用程序,则它不可用于 API。
.AddOpenIdConnect
(进行身份验证)
API 需要.AddJwtBearer
(以提供对 API 的访问权限)
如果这是两个独立的应用程序,它们将进入两个独立的初创公司。就我而言,Web 应用程序和 API 在同一个服务中运行,因此它们是在一起的。
.AddOpenIdConnect(
GoogleDefaults.AuthenticationScheme,
GoogleDefaults.DisplayName, o =>
{
var googleAuthNSection = configuration.GetSection("Authentication:Google");
o.SignInScheme = IdentityConstants.ExternalScheme;
o.Authority = "https://accounts.google.com";
o.ClientId = googleAuthNSection["ClientId"]!;
o.ClientSecret = googleAuthNSection["ClientSecret"]!;
o.ResponseType = OpenIdConnectResponseType.CodeToken;
o.CallbackPath = "/signin-google";
o.SaveTokens = true;
o.Scope.Add("email");
})
.AddJwtBearer(o =>
{
var googleAuthNSection = configuration.GetSection("Authentication:Google");
var googleIssuer = "https://accounts.google.com";
var googleClientId = googleAuthNSection["ClientId"]!;
o.Authority = googleIssuer;
o.Audience = googleClientId;
o.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = false,
ValidateIssuerSigningKey = false,
ValidIssuer = googleIssuer,
ValidAudience = googleClientId,
};
})
然后使用“承载”方案来保护 API
[Route("api/[controller]")]
[Authorize(AuthenticationSchemes = "Bearer")]
[ApiController]
public class MyController : ControllerBase