我有一个IdentityServer设置,带有一个API和一个用React编写的SPA。 SPA使用javascript oidc客户端库向IdentityServer进行身份验证,然后从API获取数据。 SPA和API在同一个项目中,SPA使用services.AddSpaStaticFiles和app.UseSpa提供服务,所以我认为我应该可以互换使用两种身份验证方案。
问题是我有图像存储在API端,我希望SPA客户端能够在标记中获取并放置图像,并可以选择单击并在新的图像中打开完整尺寸的图像窗口。图像必须要求用户经过身份验证才能访问它们。
我尝试将基于Cookie的身份验证添加到API的ConfigureServices中,希望让用户在SPA上进行身份验证,然后访问API上的图片URL即可。
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddIdentityServerAuthentication("apiAuth", options =>
{
options.Authority = "http://localhost:5000";
options.RequireHttpsMetadata = false;
options.ApiName = "api";
})
.AddCookie("cookieAuth")
.AddOpenIdConnect("oidc", options =>
{
options.Authority = "http://localhost:5000";
options.RequireHttpsMetadata = false;
options.ClientId = "afx_api";
options.SaveTokens = true;
});
然后在将返回图像的控制器上添加[Authorize(AuthenticationSchemes =“ cookieAuth”)),并在所有其他API控制器上添加[Authorize(AuthenticationSchemes =“ apiAuth”)]。
但是,当我尝试访问图像时,比如说http://localhost:6000/api/file/1,即使我已经通过身份验证并且正常的API调用也可以重定向到http://localhost:6001/Account/Login?ReturnUrl=%2Fapi%2Fdocuments%2Ffile%2F90404。
我将如何去做?谢谢
编辑:我设置中的更多代码
IdentityServer / Config.cs客户端配置
new Client
{
ClientId = "client",
ClientName = "React Client",
AllowedGrantTypes = GrantTypes.Implicit,
AllowAccessTokensViaBrowser = true,
RequireConsent = false,
AccessTokenLifetime = 3600,
RedirectUris = {
"http://localhost:6001/callback",
"http://localhost:6001/silent_renew.html",
},
PostLogoutRedirectUris =
{
"http://localhost:6001/",
},
AllowedCorsOrigins =
{
"http://localhost:6001",
},
AllowedScopes = new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"api"
},
AlwaysIncludeUserClaimsInIdToken = true
}
ClientApp / src / userManager.js
import { createUserManager } from 'redux-oidc';
const settings = {
client_id: 'client',
redirect_uri: `${window.location.protocol}//${window.location.hostname}${window.location.port ? `:${window.location.port}` : ''}/callback`,
response_type: 'id_token token',
scope:"openid profile api",
authority: 'http://localhost:5000',
silent_redirect_uri: `${window.location.protocol}//${window.location.hostname}${window.location.port ? `:${window.location.port}` : ''}/silent_renew.html`,
automaticSilentRenew: true,
loadUserInfo: true,
monitorSession: true
};
const userManager = createUserManager(settings);
export default userManager;
基于Elrashid的答案的新创业公司:
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddAuthentication(options =>
{
options.DefaultScheme = "cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("cookies",
options => options.ForwardDefaultSelector = ctx => ctx.Request.Path.StartsWithSegments("/api") ? "jwt" : "cookies")
.AddJwtBearer("jwt", options =>
{
options.Authority = "http://localhost:5000";
options.Audience = "api";
options.RequireHttpsMetadata = false;
})
.AddOpenIdConnect("oidc", options =>
{
options.SignInScheme = "cookies";
options.Authority = "http://localhost:5000";
options.RequireHttpsMetadata = false;
options.ClientId = "client";
options.SaveTokens = true;
options.ResponseType = "id_token token";
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("api");
options.Scope.Add("offline_access");
options.ForwardDefaultSelector = ctx => ctx.Request.Path.StartsWithSegments("/api") ? "jwt" : "oidc";
});
仍然得到相同的结果,当我尝试从控制器获取图像时,这是重定向到/ Account / Login?ReturnUrl =%2Fimages%2Ffile%2F90404。该API仍然有效。控制器方法返回一个PNG,并且无需授权即可使用。
[Authorize(AuthenticationSchemes = "cookies")]
[Route("[controller]")]
public class ImagesController : Controller
{
...
}
答案 0 :(得分:1)
所有路线都将使用cookie
但以 / api 开头的路由将使用jwt
ctx.Request.Path.StartsWithSegments("/api") ? "jwt" : "cookies")
ctx.Request.Path.StartsWithSegments("/api") ? "jwt" : "oidc")
- localhost/Secure/Index
- localhost/api/secure/Get
public class SecureController : Controller
{
[Authorize]
public IActionResult Index()
{
return View();
}
}
[Route("api/secure")]
[Authorize]
public class SecureApi : ControllerBase
{
[HttpGet]
public IActionResult Get()
{
return new JsonResult(from c in User.Claims select new { c.Type, c.Value });
}
}
JwtSecurityTokenHandler
.DefaultInboundClaimTypeMap.Clear();
services.AddAuthentication(options =>
{
// Notice the schema name is case sensitive [ cookies != Cookies ]
options.DefaultScheme = "cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("cookies", options =>
options.ForwardDefaultSelector = ctx =>
ctx.Request.Path.StartsWithSegments("/api") ? "jwt" : "cookies")
.AddJwtBearer("jwt", options =>
{
options.Authority = "http://localhost:5010";
options.Audience = "app2api";
options.RequireHttpsMetadata = false;
})
.AddOpenIdConnect("oidc", options =>
{
options.SignInScheme = "cookies";
options.Authority = "http://localhost:5010";
options.RequireHttpsMetadata = false;
options.ClientId = "mvc";
options.SaveTokens = true;
options.ClientSecret = "secret";
options.ResponseType = "code id_token";
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("app2api");
options.Scope.Add("offline_access");
//https://github.com/leastprivilege/AspNetCoreSecuritySamples/blob/aspnetcore21/OidcAndApi/src/AspNetCoreSecurity/Startup.cs
options.ForwardDefaultSelector = ctx => ctx.Request.Path.StartsWithSegments("/api") ? "jwt" : "oidc";
});
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
////////////////////////////////
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddAuthentication(options =>
{
// Notice the schema name is case sensitive [ cookies != Cookies ]
options.DefaultScheme = "cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("cookies", options => options.ForwardDefaultSelector = ctx => ctx.Request.Path.StartsWithSegments("/api") ? "jwt" : "cookies")
.AddJwtBearer("jwt", options =>
{
options.Authority = "http://localhost:5010";
options.Audience = "app2api";
options.RequireHttpsMetadata = false;
})
.AddOpenIdConnect("oidc", options =>
{
options.SignInScheme = "cookies";
options.Authority = "http://localhost:5010";
options.RequireHttpsMetadata = false;
options.ClientId = "mvc";
options.SaveTokens = true;
options.ClientSecret = "secret";
options.ResponseType = "code id_token";
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("app2api");
options.Scope.Add("offline_access");
options.ForwardDefaultSelector = ctx => ctx.Request.Path.StartsWithSegments("/api") ? "jwt" : "oidc";
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseAuthentication();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseMvcWithDefaultRoute();
}
}
new Client
{
ClientId = "mvc",
ClientName = "MVC Client",
AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,
ClientSecrets =
{
new Secret("secret".Sha256())
},
// where to redirect to after login
RedirectUris = { "http://localhost:5011/signin-oidc" },
// where to redirect to after logout
PostLogoutRedirectUris = { "http://localhost:5011/signout-callback-oidc" },
AllowedScopes = new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"app2api"
},
AllowOfflineAccess = true
}
克隆https://github.com/Elrashid/identityserver4-Sample-One-App-For-API-And-Web
git clone https://github.com/Elrashid/identityserver4-Sample-One-App-For-API-And-Web.git
第一次运行身份服务器
dotnet run -p identityserver/App1.csproj
然后运行mvc客户端
dotnet run -p mvc/App2.csproj
答案 1 :(得分:0)
@MichaelD, 我可以在您的帖子稍加修改后在我的项目上修复此问题。我通过这篇文章https://docs.microsoft.com/en-us/aspnet/core/security/authentication/scaffold-identity?view=aspnetcore-3.1&tabs=visual-studio#scaffold-identity-into-an-mvc-project-with-authorization架设了Identity。您需要选择Login.cshtml文件进行覆盖。完成此操作后,您应该在项目中获得一个Areas / Identity / Pages / Account / Manage / Login.cshtml.cs文件。打开它并转到方法OnPostAsync。
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
if (ModelState.IsValid)
{
// This doesn't count login failures towards account lockout
// To enable password failures to trigger account lockout, set lockoutOnFailure: true
var result = await _signInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: true);
if (result.Succeeded)
{
await AddAuthorizationCookie();
_logger.LogInformation("User logged in.");
return LocalRedirect(returnUrl);
}
if (result.RequiresTwoFactor)
{
return RedirectToPage("./LoginWith2fa", new { ReturnUrl = returnUrl, RememberMe = Input.RememberMe });
}
if (result.IsLockedOut)
{
_logger.LogWarning("User account locked out.");
return RedirectToPage("./Lockout");
}
else
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return Page();
}
}
// If we got this far, something failed, redisplay form
return Page();
}
private async Task AddAuthorizationCookie()
{
var user = await _userManager.FindByEmailAsync(Input.Email);
if (user == null)
{
return;
}
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, user.Email),
new Claim("FullName", user.FullName)
};
var claimsIdentity = new ClaimsIdentity(
claims, "cookies");
var authProperties = new AuthenticationProperties
{
//AllowRefresh = <bool>,
// Refreshing the authentication session should be allowed.
//ExpiresUtc = DateTimeOffset.UtcNow.AddMinutes(10),
// The time at which the authentication ticket expires. A
// value set here overrides the ExpireTimeSpan option of
// CookieAuthenticationOptions set with AddCookie.
//IsPersistent = true,
// Whether the authentication session is persisted across
// multiple requests. When used with cookies, controls
// whether the cookie's lifetime is absolute (matching the
// lifetime of the authentication ticket) or session-based.
//IssuedUtc = <DateTimeOffset>,
// The time at which the authentication ticket was issued.
//RedirectUri = <string>
// The full path or absolute URI to be used as an http
// redirect response value.
};
await HttpContext.SignInAsync(
"cookies",
new ClaimsPrincipal(claimsIdentity),
authProperties);
}
OnPostAsync方法来自Identity,但是我添加了“ await AddAuthorizationCookie()”行,该行允许我们向客户端添加所需的cookie。现在,当我访问图像控制器时,将显示正确的cookie。您还应该在注销时以及可能在更新令牌时删除cookie。我还没有更新令牌。