.net - .Net Core 2.1 与 IdentityServer4 Cookie 和基于 API JWT 的同一应用程序的身份验证
问题描述
我有一个带有 API 的 IdentityServer 设置和一个用 React 编写的 SPA。SPA 使用 javascript oidc 客户端库向 IdentityServer 进行身份验证,然后从 API 获取数据。SPA 和 API 在同一个项目中,SPA 使用 services.AddSpaStaticFiles 和 app.UseSpa 提供服务,所以我认为我应该能够互换使用这两种身份验证方案。
问题是我在 API 端存储了图像,我希望 SPA 客户端能够获取并放置在 <img> 标记中,可以选择单击它并在新窗口中打开全尺寸图像。图像必须要求用户通过身份验证才能访问它们。
我尝试将基于 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我在这里被重定向http://localhost:6001/Account/Login?ReturnUrl=%2Fapi%2Fdocuments%2Ffile%2F90404即使我已经通过身份验证并且正常的 API 调用正常工作。
我该怎么做呢?谢谢
编辑:我的设置中的更多代码
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
{
...
}
解决方案
使用 ForwardDefaultSelector
所有路线都将使用 cookie
但是以/api开头的路由将使用 jwt
ctx.Request.Path.StartsWithSegments("/api") ? "jwt" : "cookies") ctx.Request.Path.StartsWithSegments("/api") ? "jwt" : "oidc")
如果你有这 2 条路线
- localhost/Secure/Index
- localhost/api/secure/Get
安全控制器
public class SecureController : Controller
{
[Authorize]
public IActionResult Index()
{
return View();
}
}
安全API
[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 });
}
}
清除 Jwt 安全令牌处理程序
JwtSecurityTokenHandler
.DefaultInboundClaimTypeMap.Clear();
使用 AddAuthentication 将 cookie 设置为 DefaultScheme
services.AddAuthentication(options =>
{
// Notice the schema name is case sensitive [ cookies != Cookies ]
options.DefaultScheme = "cookies";
options.DefaultChallengeScheme = "oidc";
})
添加 Cookie 选项
.AddCookie("cookies", options =>
options.ForwardDefaultSelector = ctx =>
ctx.Request.Path.StartsWithSegments("/api") ? "jwt" : "cookies")
添加 Jwt 承载
.AddJwtBearer("jwt", options =>
{
options.Authority = "http://localhost:5010";
options.Audience = "app2api";
options.RequireHttpsMetadata = false;
})
添加OpenIdConnect
.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";
});
完整的 Startup.cs
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();
}
}
在身份服务器中使用 HybridAndClientCredentials
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
}
摘要:我的 Github 上的示例应用程序
克隆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
推荐阅读
- python - 带有有效负载请求的scrapy
- php - 正则表达式匹配一个数字后跟一个特定的字符串
- android-studio - 在 webview 中加载具有唯一 Android 或 iOS 设备 ID 的 URL
- python-3.x - 使用正则表达式更改多索引列中的所有匹配值
- html - 在 chrome 浏览器中反应解析 html 代码未显示完整
- angular - 在 rxjs catchError() 之后,角度 POST 请求未将结果传回
- reactjs - 使用 Axios 发布到 Firebase 时如何停止随机 ID?
- javascript - 切换对象中的多个布尔值
- react-native - 在本机反应中删除背景
- mvvm - 未捕获的 ReferenceError:无法处理绑定“提交:函数(){return add_list_form_submit }”