我正在使用SPA Web应用程序,并且我正在使用IdentityServer4代码流来处理授权。因此,我具有以下组件:
https://localhost:5001
https://localhost:5001
上运行的Asp.net 3 Web Api项目http://localhost:8100
上运行的使用JWT身份验证并使用API的本机移动应用程序 Rightnow正在尝试验证移动应用程序用户,但是用户交互登录屏幕不断重定向,并且正在获取login_required
。
回拨电话,这就是我得到的:
/connect/authorize
端点-检查 https://localhost:5001/auth/login?returnUrl=%2Fconnect%2Fauthorize%2Fcallback%3Fredirect_uri%3Dhttp%253A%252F%252Flocalhost%253A8100%252Fauth%252Fcallback%26client_id%3Dcharla-mobile%26response_type%3Dcode%26state%3Dpq1nokeuVj%26scope%3Dcharla-api%2520openid%2520profile%2520offline_access%26code_challenge%3DJxDVsm2YnMAbvOuemWWXjYLLt-Mi1TpHoO7zhDkCWSI%26code_challenge_method%3DS256
-检查 login
中呼叫AccountController.cs
端点-检查 https://localhost:5001/auth/login?returnUrl=%2Fconnect%2Fauthorize%2Fcallback%3Fredirect_uri%3Dhttp%253A%252F%252Flocalhost%253A8100%252Fauth%252Fcallback%26client_id%3Dcharla-mobile%26response_type%3Dcode%26state%3Dpq1nokeuVj%26scope%3Dcharla-api%2520openid%2520profile%2520offline_access%26code_challenge%3DJxDVsm2YnMAbvOuemWWXjYLLt-Mi1TpHoO7zhDkCWSI%26code_challenge_method%3DS256
-检查 https://localhost:5001/auth/login?returnUrl=%2Fconnect%2Fauthorize%2Fcallback%3Fredirect_uri%3Dhttp%253A%252F%252Flocalhost%253A8100%252Fauth%252Fcallback%26client_id%3Dcharla-mobile%26response_type%3Dcode%26state%3Dpbx8alT61z%26scope%3Dcharla-api%2520openid%2520profile%2520offline_access%26code_challenge%3DrGappKbnVpUNzlNHst4t5RlHephWFfJTVXuwtpQ8tZI%26code_challenge_method%3DS256
。-这里的问题 IdentityServer无法检测到用户现在已登录。调试终端中的login_required
错误。这是我的设置:
Startup.cs
namespace Charla
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddIdentityCore<ApplicationUser>(options => { });
new IdentityBuilder(typeof(ApplicationUser), typeof(IdentityRole), services)
.AddRoleManager<RoleManager<IdentityRole>>()
.AddSignInManager<SignInManager<ApplicationUser>>()
.AddEntityFrameworkStores<ConverseContext>();
/*services.AddIdentity<ApplicationUser, IdentityRole>()
.AddRoleManager<RoleManager<IdentityRole>>()
.AddSignInManager<SignInManager<ApplicationUser>>()
.AddEntityFrameworkStores<ConverseContext>()
.AddDefaultTokenProviders();*/
var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;
var builder = services
.AddIdentityServer(SetupIdentityServer)
.AddDeveloperSigningCredential()
.AddConfigurationStore(options =>
{
options.ConfigureDbContext = b => b.UseMySql(Configuration.GetConnectionString("DefaultConnection"),
sqloptions => {
sqloptions.ServerVersion(new Version(10, 1, 37), ServerType.MariaDb); // replace with your Server Version and Type
sqloptions.MigrationsAssembly(migrationsAssembly);
});
})
.AddOperationalStore(options =>
{
options.ConfigureDbContext = b => b.UseMySql(Configuration.GetConnectionString("DefaultConnection"),
sqloptions => {
sqloptions.ServerVersion(new Version(10, 1, 37), ServerType.MariaDb); // replace with your Server Version and Type
sqloptions.MigrationsAssembly(migrationsAssembly);
});
});
/*services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(options => {
options.Authority = Configuration.GetValue<string>("IdentityServer:Jwt:Authority");
options.RequireHttpsMetadata = false;
options.ApiName = "charla-api";
});*/
services.AddAuthentication(opt => {
opt.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
opt.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(jwt =>{
jwt.Authority = Configuration.GetValue<string>("IdentityServer:Jwt:Authority");
jwt.RequireHttpsMetadata = false;
jwt.TokenValidationParameters.ValidateAudience = false;
jwt.TokenValidationParameters.ValidTypes = new[] { "at+jwt" };
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IServiceProvider serviceProvider)
{
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseSpaStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseIdentityServer();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");
//endpoints.MapRazorPages();
endpoints.MapHub<ChatHub>("/hub");
});
}
private static void SetupIdentityServer(IdentityServerOptions options)
{
options.UserInteraction.LoginUrl = "/auth/login";
options.UserInteraction.LoginReturnUrlParameter = "returnUrl";
options.UserInteraction.LogoutUrl = "/logout";
options.UserInteraction.ErrorUrl= "/error/identity";
options.Events.RaiseErrorEvents = true;
options.Events.RaiseInformationEvents = true;
options.Events.RaiseFailureEvents = true;
options.Events.RaiseSuccessEvents = true;
// see https://identityserver4.readthedocs.io/en/latest/topics/resources.html
//options.EmitStaticAudienceClaim = true;
//identityServerOptions.Authentication.CookieLifetime = TimeSpan.FromDays(1);
}
}
}
}
AccountController.cs
[Route("api/[controller]")]
[ApiController]
[AllowAnonymous]
public class AccountController : ControllerBase
{
[HttpPost("login")]
public async Task<IActionResult> Login(UserResource model) {
var result = await signInManager.PasswordSignInAsync(model.email, model.password, isPersistent: true, lockoutOnFailure: false);
var context = await interaction.GetAuthorizationContextAsync(model.return_url);
if (result.Succeeded) {
var uo = db.Users.Include(q => q.UserOrganization).Single( q => q.Email == model.email ).UserOrganization.First();
uo.LastLogin = DateTime.UtcNow;
await db.SaveChangesAsync();
// let identity server know that we loggedin
await identityEvents.RaiseAsync(new UserLoginSuccessEvent(
model.email, uo.UserId, model.email, clientId: context?.Client.ClientId
));
//return Redirect(model.return_url);
return Ok( new{
email = model.email,
return_url = context.RedirectUri
} );
}
await identityEvents.RaiseAsync(new UserLoginFailureEvent(model.email, "invalid credentials", clientId:context?.Client.ClientId));
return NotFound(new {});
}
IdentityConfig.cs -我正在使用EF表,但是它是从以下种子中植入的:
using IdentityServer4;
using IdentityServer4.Models;
using System.Collections.Generic;
namespace Charla
{
public static class IdentityConfig
{
public static IEnumerable<IdentityResource> IdentityResources =>
new IdentityResource[]
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
};
public static IEnumerable<ApiResource> GetApiResources()
{
return new List<ApiResource>
{
new ApiResource("charla-api", "Charla API Resource")
};
}
public static IEnumerable<ApiScope> ApiScopes =>
new ApiScope[]
{
new ApiScope("charla-api", "Charla API Scope")
};
public static IEnumerable<Client> Clients =>
new Client[]
{
// charla web app
new Client
{
ClientId = "charla-spa",
ClientName = "Charla Web App",
RequireClientSecret = false,
AllowOfflineAccess = true,
AllowAccessTokensViaBrowser = true,
AllowedGrantTypes = GrantTypes.Code,
RedirectUris = {
"https://localhost:5001/authentication/login-callback",
"https://app-dev.getcharla.com/authentication/login-callback",
"https://app.getcharla.com/authentication/login-callback"
},
//FrontChannelLogoutUri = "https://localhost:5001/authentication/logout-callback",
PostLogoutRedirectUris = {
"https://localhost:5001/authentication/logout-callback",
"https://app-dev.getcharla.com/authentication/logout-callback",
"https://app.getcharla.com/authentication/logout-callback"
},
AllowedScopes = {
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.OfflineAccess,
"charla-api"
}
},
// mobile client
new Client
{
ClientId = "charla-mobile",
ClientName = "Charla Mobile Apps",
RequireClientSecret = false,
AllowedGrantTypes = GrantTypes.Code,
AllowAccessTokensViaBrowser = true,
AllowOfflineAccess = true,
RedirectUris = {
"https://getcharla.com/ios_redirect",
"http://localhost:8100/auth/callback"
},
PostLogoutRedirectUris = {
"https://getcharla.com/ios_redirect_endsession",
"http://localhost:8100/auth/endsession"
},
AllowedScopes = {
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.OfflineAccess,
"charla-api"
},
AllowedCorsOrigins = { "http://localhost:8100", "https://localhost:5001", "http://localhost:5000" }
},
};
}
}
login.component.ts -Web组件中的函数,该函数将触发对login
端点的调用并执行重定向:
ngOnInit(): void {
this.route.queryParams.subscribe(params => {
this.returnUrl = params['returnUrl'] || '/';
});
}
/**
* Form Submit
*/
submit() {
const controls = this.loginForm.controls;
const authData = {
email: controls['email'].value,
password: controls['password'].value
};
this.auth
.login(authData.email, authData.password, this.returnUrl)
.pipe(
finalize(() => {
this.loading = false;
this.cdr.markForCheck();
})
)
.subscribe(
result => {
window.location = this.returnUrl;
this.router.navigateByUrl(this.returnUrl); // Main page
},
err => {
console.log(err);
if (( 'status' in err) && ( err.status === 404)){
this.authNoticeService.setNotice(this.translate.instant('AUTH.VALIDATION.INVALID_LOGIN'), 'danger');
}
}
);
}
不知道为什么它不起作用。我读到我应该在HttpContent.SignInAsync
Web api端点中使用login
,但是我已经在使用var result = await signInManager.PasswordSignInAsync(model.email, model.password, isPersistent: true, lockoutOnFailure: false);
,所以我认为就足够了。
我不确定其他选择是否正确,例如使用AddIdentityCore
而不是AddIdentity
。我应该添加AddAspNetIdentity<ApplicationUser>()
吗?
答案 0 :(得分:0)
按照您提供的跟踪,由于有关浏览器Cookie的新规则,这种情况可能会发生。
在新的ASP.NET Core应用程序上会自动添加samesite=none
属性,但是浏览器也要求您也指定Secure
属性,否则Set-Cookie将被阻止。
要配置IdentityServer,请在Configure
类的Startup
方法内添加以下代码块:
app.UseCookiePolicy(new CookiePolicyOptions
{
HttpOnly = HttpOnlyPolicy.None,
MinimumSameSitePolicy = SameSiteMode.None,
Secure = CookieSecurePolicy.Always
});
使用安全属性时,必须使用HTTPS,否则它将失败
另一个与该主题有关的问题:Session cookie set `SameSite=None; Secure;` does not work
Microsoft提供的有关ASP.NET Core的一些信息:https://docs.microsoft.com/pt-br/aspnet/core/security/samesite?view=aspnetcore-5.0
答案 1 :(得分:0)
当我注销并重新登录时,我的 SPA 遇到了类似的问题。该问题似乎是由 Visual Studio 更新引起的。如果您使用命令提示符导航到项目文件夹并使用 dotnet run
' 或更好的 dotnet watch run
(以便您可以在运行时更改 .net 核心代码),您可以绕过该问题;购买不通过 IIS 在 Visual Studio 中运行代码。
如果这对您有用,请将 Properties Launch 更新为 Project(也使用 dotnet run),然后您也可以进行调试。