ASP.NET Core 6:允许Azure AD验证和本地验证

blmhpbnm  于 2023-04-22  发布在  .NET
关注(0)|答案(1)|浏览(319)

我正在开发一个程序,它允许用户以两种方式进行身份验证:他们可以创建和使用本地(自制)帐户和任何电子邮件;或者他们可以仅为我们的组织使用Azure AD OAuth。无论使用哪种身份验证方法,用户都应被同等对待,并在context.User.Identity.IsAuthenticated上返回true。
我遇到了只有Azure AD方法有效的问题,为了解决这个问题,我使用了身份验证策略inspired by this article。然而,在遵循它之后,两种身份验证方法似乎都不起作用:(
以下是startup.cs中的服务代码:

services.AddJwtAuthorization();

services.AddAuthentication(o =>
{
    o.DefaultScheme = "MultiAuthSchemes";
    o.DefaultChallengeScheme = "MultiAuthSchemes";
})
.AddCookie(o =>
{
    o.LoginPath = "/login";
})
.AddJwtBearer("HomebrewScheme", _ => { })
.AddPolicyScheme("MultiAuthSchemes", JwtBearerDefaults.AuthenticationScheme, options =>
{
    options.ForwardDefaultSelector = context =>
    {
        string authorization = context.Request.Headers[HeaderNames.Authorization];
        if (!string.IsNullOrEmpty(authorization) && authorization.Contains("Bearer "))
        {
            var token = authorization["Bearer ".Length..].Trim();
            var jwtHandler = new JwtSecurityTokenHandler();
            return jwtHandler.CanReadToken(token)
                ? "HomebrewScheme" : "AdScheme";
        }
        return CookieAuthenticationDefaults.AuthenticationScheme;
    };
})
.AddMicrosoftIdentityWebApi(Config, "AzureAd", "AdScheme");

services.AddAuthorization(o =>
{
    var defaultAuthorizationPolicyBuilder = new AuthorizationPolicyBuilder(
        JwtBearerDefaults.AuthenticationScheme,
        CookieAuthenticationDefaults.AuthenticationScheme,
        "HomebrewScheme", "AdScheme");
    defaultAuthorizationPolicyBuilder =
        defaultAuthorizationPolicyBuilder.RequireAuthenticatedUser();
    o.DefaultPolicy = defaultAuthorizationPolicyBuilder.Build();
    
    var onlySecondJwtSchemePolicyBuilder = new AuthorizationPolicyBuilder("HomebrewScheme");
    o.AddPolicy("OnlyHomebrewScheme", onlySecondJwtSchemePolicyBuilder
        .RequireAuthenticatedUser()
        .AddAuthenticationSchemes()
        .Build());
    var onlyCookieSchemePolicyBuilder = new AuthorizationPolicyBuilder("AdScheme");
    o.AddPolicy("OnlyAdScheme", onlyCookieSchemePolicyBuilder
        .RequireAuthenticatedUser()
        .Build());
});

以下是startup.cs中的应用代码:

app.UseAuthentication()
   
app.UseGraphQLPlayground(new PlaygroundOptions
{
    GraphQLEndPoint = GraphQLApiEndpoint
});
app.UseWebSockets();
app.UseRouting();

app.UseAuthorization();

app.UseEndpoints(x => x.MapGraphQL(path: GraphQLApiEndpoint));

下面是我用来测试身份验证的startup.cs代码:

app.Use((context, next) =>
{
    //Grab the first identity because the authentication type does not matter
    if (context.User.Identity?.IsAuthenticated == true)
    {
        PermissionLevel = Permissions.Authorized;
    }
});

从我调试的情况来看,系统从来没有检测到我现在获得了身份验证。我知道前端工作正常,因为如果我只使用services.AddMicrosoftIdentityWebApiAuthentication(Config, "AzureAd", "AdScheme");,不管它是否是默认方案,一切都正常。
谢谢你的帮助,我被困在这件事上了😊

pw9qyyiw

pw9qyyiw1#

首先,我跟随this blog在我的代码中添加了Cookie认证。它提供了一个登录页面让我们登录。在AccountController中,它提供了几个模拟用户帐户,以便我们可以使用它们登录进行测试。
然后,我在登录页面中添加了代码,以便它提供使用AAD登录的选项。
更改Program.cs文件以添加多重身份验证方案。
这是我的代码Program.cs

using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.Identity.Web;
using Microsoft.Identity.Web.UI;

var builder = WebApplication.CreateBuilder(args);

//set CookieAuthenticationDefaults.AuthenticationScheme as the default authentication scheme
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(x => x.LoginPath = "/account/login");
builder.Services.AddAuthentication()
    .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"), OpenIdConnectDefaults.AuthenticationScheme, "ADCookies");

// Add microsoft sign in page
builder.Services.AddControllersWithViews().AddMicrosoftIdentityUI();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Home/Error");
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();

我的家庭控制器

using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Diagnostics;
using WebAppMvcCookieAuthAad.Models;

namespace WebAppMvcCookieAuthAad.Controllers
{
    [AllowAnonymous]
    public class HomeController : Controller
    {
        public IActionResult Index()
        {
            return View();
        }

        [Authorize]
        public async Task<IActionResult> ConfidentialDataAsync()
        {
            return View();
        }
    }
}

我的帐户控制器:

using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Mvc;
using System.Security.Claims;
using WebAppMvcCookieAuthAad.Models;

namespace WebAppMvcCookieAuthAad.Controllers
{
    public class AccountController : Controller
    { 
        public List<UserModel> users = null;
        public AccountController()
        {
            users = new List<UserModel>();
            users.Add(new UserModel()
            {
                UserId = 1,
                Username = "Tiny",
                Password = "123",
                Role = "Admin"
            });
            users.Add(new UserModel()
            {
                UserId = 2,
                Username = "Other",
                Password = "123",
                Role = "User"
            });
        }
        public IActionResult Login(string ReturnUrl = "/")
        {
            LoginModel objLoginModel = new LoginModel();
            objLoginModel.ReturnUrl = ReturnUrl;
            return View(objLoginModel);
        }
        [HttpPost]
        public async Task<IActionResult> Login(LoginModel objLoginModel)
        {
            if (ModelState.IsValid)
            {
                var user = users.Where(x => x.Username == objLoginModel.UserName && x.Password == objLoginModel.Password).FirstOrDefault();
                if (user == null)
                { 
                    ViewBag.Message = "Invalid Credential";
                    return View(objLoginModel);
                }
                else
                {
                    var claims = new List<Claim>() {
                    new Claim(ClaimTypes.NameIdentifier, Convert.ToString(user.UserId)),
                        new Claim(ClaimTypes.Name, user.Username),
                        new Claim(ClaimTypes.Role, user.Role),
                        new Claim("FavoriteDrink", "Tea")
                    };
                    var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
                    var principal = new ClaimsPrincipal(identity);
                    await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal, new AuthenticationProperties()
                    {
                        IsPersistent = objLoginModel.RememberLogin
                    });
                    return LocalRedirect(objLoginModel.ReturnUrl);
                }
            }
            return View(objLoginModel);
        }
        public async Task<IActionResult> LogOut()
        {
            await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
            return LocalRedirect("/");
        }
    }
}

我的登录模型和用户模型

using System.ComponentModel.DataAnnotations;

namespace WebAppMvcCookieAuthAad.Models
{
    public class LoginModel
    {
        [Required]
        [Display(Name = "Username")]
        public string UserName{get;set;}
        [Required]
        [DataType(DataType.Password)]
        public string Password{get;set;}
        public bool RememberLogin{get;set;}
        public string ReturnUrl{get;set;}
    }
}

namespace WebAppMvcCookieAuthAad.Models
{
    public class UserModel
    {
        public int UserId { get; set; }
        public string Username { get; set; }
        public string Password { get; set; }
        public string Role { get; set; }
    }
}

查看-〉账户-〉登录。cshtml:

@model WebAppMvcCookieAuthAad.Models.LoginModel
@{
    ViewData["Title"] = "Login";
    Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Login</h2>
<hr />
<div class="row">
    <div class="col-md-4">
        <form asp-action="Login">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            @if (!string.IsNullOrEmpty(ViewBag.Message))
            {
                <span class="text-danger">
                    @ViewBag.Message
                </span>
            }
            @Html.HiddenFor(x => x.ReturnUrl)
            <div class="form-group">
                <label asp-for="UserName" class="control-label"></label>
                <input asp-for="UserName" class="form-control" />
                <span asp-validation-for="UserName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Password" class="control-label"></label>
                <input asp-for="Password" class="form-control" />
                <span asp-validation-for="Password" class="text-danger"></span>
            </div>
            <div class="form-group">
                <div class="checkbox">
                    <label>
                        <input asp-for="RememberLogin" /> @Html.DisplayNameFor(model => model.RememberLogin)
                    </label>
                </div>
            </div>
            <div class="form-group">
                <input type="submit" value="Login" />
            </div>
        </form>
    </div>
</div>

<div>
    <label>sign in with aad</label>
    <a asp-area="MicrosoftIdentity" asp-controller="Account" asp-action="SignIn">Sign in with aad</a>
</div>

查看-〉首页-〉保密数据. cshtml

@if(User.Identity.IsAuthenticated){
    <table>
        @foreach (var item in User.Claims)
        {
            <tr><td>@item.Type</td><td>@item.Value</td></tr>
        }
    </table>
}

View-〉Shared-〉_LoginPartial. cshtml,请不要忘记将此部分视图添加到布局中。

@using System.Security.Principal

<ul class="navbar-nav">
    @if (User.Identity.IsAuthenticated)
    {
        <li class="nav-item">
            <span class="navbar-text text-dark">Hello @User.Identity.Name!</span>
        </li>
        <li class="nav-item">
            <a class="nav-link text-dark" asp-controller="Account" asp-action="LogOut">log out</a>
        </li>
        @*asp - area = "MicrosoftIdentity"*@
    }
    else
    {
        <li class="nav-item">
            <a class="nav-link text-dark" asp-controller="Account" asp-action="Login">log in</a>
        </li>
    }
</ul>

appsetting.json:

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "Domain": "tenant_name",
    "TenantId": "tenant_id",
    "ClientId": "azure_ad_app_id",
    "ClientSecret": "azure_ad_client_secret",
    "CallbackPath": "/home", //don't forget to set redirect url in azure portal
    "SignedOutCallbackPath ": "/signout-callback-oidc"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*"
}

这段代码在我这边运行得很好,我可以用cookie auth和aad登录。我注意到用aad登录后,@User.Identity.Name不会显示用户名。但实际上登录流程成功了。

相关问题