如何在OIDC提供的用户在数据库中不可用时不进行身份验证

huangapple go评论89阅读模式
英文:

.NET How to NOT authenticate if provided user from OIDC is not available in database

问题

以下是您要翻译的内容:

"所以,我的.NET Core Web应用程序通过Azure AD登录用户,我有一个包含用户和他们角色的数据库。

我已经创建了OIDC中间件,以添加来自我的数据库的声明,以供试图登录的用户使用。

所以流程是这样的:

  • 我使用我的AD帐户登录
  • 我获取电子邮件地址,并在数据库中检查它的角色
  • 现在,如果用户被阻止或未分配,登录应该失败

所以我的问题是:是否有一种方式可以拒绝用户的身份验证,当他在数据库中不可用或被阻止时?

我现在所做的是设置了一个声明,如果该声明不可用,将被重定向到“访问被拒绝”页面(使用AuthorizationPolicy),但我希望用户将被重定向到Microsoft/AD的登录页面(最好附带一条消息)。

这是否有可能,如果是,如何实现?

这是我的代码:

services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
.AddAzureAD(options => { Configuration.Bind("AzureAd", options); }
);

services.AddControllersWithViews(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.RequireAssertion(context =>
{
Claim claim = context.User.Claims.First(claim => claim.Type == ClaimTypes.Expired);
return context.User.HasClaim(x => x.Type == ClaimTypes.Expired) &&
context.User.Claims.First(claim => claim.Type == ClaimTypes.Expired).Value
.Equals("false", StringComparison.OrdinalIgnoreCase);
})
.RequireClaim(ClaimTypes.Name)
.RequireClaim(ClaimTypes.Role)
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
});

// OIDC Middleware,用于在通过AzureAD登录时访问用户的声明
services.Configure(AzureADDefaults.OpenIdScheme, options =>
{
options.Events = new OpenIdConnectEvents
{
OnRemoteFailure = ctx =>
{
ctx.Response.Redirect("/");
ctx.HandleResponse();
return Task.CompletedTask;
},
OnSignedOutCallbackRedirect = ctx =>
{
ctx.Response.Redirect("/");
ctx.HandleResponse();
return Task.CompletedTask;
},
OnTokenValidated = ctx =>
{
// 获取用户的电子邮件
var email = ctx.Principal.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Name)?.Value;

// 查询数据库以获取角色
using (var db = ctx.HttpContext.RequestServices.GetRequiredService())
{
// 从数据库中获取具有已登录电子邮件地址(来自Azure)的用户
User user = db.Users.FirstOrDefault(u => u.UPN.Equals(email));

if (user != null)
{
user.LastLogin = DateTime.Now;
db.SaveChanges();

// 添加声明
var claims = new List
{
new Claim(ClaimTypes.Role, user.Role.ToString()),
new Claim(ClaimTypes.Expired, (!user.IsActivated || user.IsBlocked).ToString())
};

// 保存声明
var appIdentity = new ClaimsIdentity(claims);
ctx.Principal.AddIdentity(appIdentity);
}
else
{
// 返回登录页面(带有错误消息,也许?)
}
}
return Task.CompletedTask;
},
};
});
"

英文:

So my .NET Core WebApplication is logging in an user via Azure AD and I have a database with users and their roles.

I created already OIDC MiddleWare to add Claims from my database for the user that tries to log in.

So the flow is:

  • I login with my AD account
  • I get the email address and check in the database for it's role
  • Now if the user is blocked or not assigned, the login should fail

So my question: Is there a way how I could deny the authentication for the user, when he is not available in the DB or blocked?

What I did now is that I set a Claim and if that claim is not available it will be redirected to Access Denied page (by using the AuthorizationPolicy), but I want that the user will be redirected to the login page from Microsoft/AD (in best case with a message).

Is that possible somehow and if so, how ?

This is my code now:

 services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
     .AddAzureAD(options => { Configuration.Bind("AzureAd", options); }
 );

 services.AddControllersWithViews(options =>
 {
   var policy = new AuthorizationPolicyBuilder()
                .RequireAuthenticatedUser()
                .RequireAssertion(context =>
                {
                    Claim claim = context.User.Claims.First(claim => claim.Type == ClaimTypes.Expired);
                    return context.User.HasClaim(x => x.Type == ClaimTypes.Expired) &&
                           context.User.Claims.First(claim => claim.Type == ClaimTypes.Expired).Value
                               .Equals("false", StringComparison.OrdinalIgnoreCase);
                })
                .RequireClaim(ClaimTypes.Name)
                .RequireClaim(ClaimTypes.Role)
                .Build();
            options.Filters.Add(new AuthorizeFilter(policy));
        });

 // OIDC Middleware, to access the User's Claims while logging in through AzureAD
        services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options =>
        {
            options.Events = new OpenIdConnectEvents
            {
                OnRemoteFailure = ctx =>
                {
                    ctx.Response.Redirect("/");
                    ctx.HandleResponse();
                    return Task.CompletedTask;
                },
                OnSignedOutCallbackRedirect = ctx =>
                {
                    ctx.Response.Redirect("/");
                    ctx.HandleResponse();
                    return Task.CompletedTask;
                },
                OnTokenValidated = ctx =>
                {
                    // Get the user's email 
                    var email = ctx.Principal.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Name)?.Value;

                    // Query the database to get the role
                    using (var db = ctx.HttpContext.RequestServices.GetRequiredService<TracingContext>())
                    {
                        // Get the Users from the database, with the logged in email address (from Azure)
                        User user = db.Users.FirstOrDefault(u => u.UPN.Equals(email));

                        if (user != null)
                        {
                            user.LastLogin = DateTime.Now;
                            db.SaveChanges();
                            
                            // Add claims
                            var claims = new List<Claim>
                            {
                                new Claim(ClaimTypes.Role, user.Role.ToString()),
                                new Claim(ClaimTypes.Expired, (!user.IsActivated || user.IsBlocked).ToString())
                            };

                            // Save the claim
                            var appIdentity = new ClaimsIdentity(claims);
                            ctx.Principal.AddIdentity(appIdentity);
                        }
                        else
                        {
                           **// Send back to Login Page (with error message, maybe?)**
                        }
                    }
                    return Task.CompletedTask;
                },
            };
        });

答案1

得分: 2

你可以覆盖默认处理并自行处理响应:

// 发送回登录页面(带有错误消息,也许?)
ctx.HandleResponse();
ctx.Response.Redirect("/path/to/login");

HandleResponse 的调用表示我们希望自行处理响应,随后的调用设置了重定向。有几种方法可以发送错误消息。一种方法是向登录URL提供查询字符串参数。

英文:

You can override the default processing and handle the response yourself:

// Send back to Login Page (with error message, maybe?)
ctx.HandleResponse();
ctx.Response.Redirect("/path/to/login");

The call to HandleResponse signals that we want to handle the response ourselves and the following call sets up the redirect. There are a few approaches to sending over an error message. One approach is to provide a query-string parameter to the login URL.

huangapple
  • 本文由 发表于 2020年1月6日 20:24:06
  • 转载请务必保留本文链接:https://go.coder-hub.com/59612081.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定