[英]ASP.NET Core 6 : permit Azure AD authentication and local authentication
I am working on a program which will allow a user to authenticate in two ways.我正在开发一个允许用户以两种方式进行身份验证的程序。 They can create and use a local (homebrew) account with any email;
他们可以通过任何 email 创建和使用本地(自制)帐户; or they can use Azure AD OAuth for our organization only.
或者他们可以仅将 Azure AD OAuth 用于我们的组织。 No matter what method of authentication is used the user should be treated the same and return true on
context.User.Identity.IsAuthenticated
.无论使用哪种身份验证方法,用户都应该被同等对待并在
context.User.Identity.IsAuthenticated
上返回 true。
I have been running into issues where only the Azure AD method worked, to fix this I used authentication policies inspired by this article .我遇到了只有 Azure AD 方法有效的问题,为了解决这个问题,我使用 了受本文启发的身份验证策略。 However, after following it both authentication methods seem to not be working:(
但是,在遵循它之后,两种身份验证方法似乎都不起作用:(
Here is the services code in startup.cs
:这是
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());
});
Here is the app code in startup.cs
:这是
startup.cs
中的应用程序代码:
app.UseAuthentication()
app.UseGraphQLPlayground(new PlaygroundOptions
{
GraphQLEndPoint = GraphQLApiEndpoint
});
app.UseWebSockets();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(x => x.MapGraphQL(path: GraphQLApiEndpoint));
Here is code in startup.cs
which I am using to test authentication:这是我用来测试身份验证的
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;
}
});
From what I can tell from debugging, the system never detects that I obtain authentication now.从调试中我可以看出,系统永远不会检测到我现在获得了身份验证。 I know the front end works because if I use only
services.AddMicrosoftIdentityWebApiAuthentication(Config, "AzureAd", "AdScheme");
我知道前端可以工作,因为如果我只使用
services.AddMicrosoftIdentityWebApiAuthentication(Config, "AzureAd", "AdScheme");
no matter if it is the default scheme or not, everything works.不管它是否是默认方案,一切正常。
Thank you for your help, stuck on this one谢谢你的帮助,坚持这个
Firstly, I followed this blog to add Cookie authentication in my code.首先,我按照这个博客在我的代码中添加了 Cookie 身份验证。 It provides a login page to let us sign in. And in the AccountController, it provides several mock user account so that we can use them to sign in for test.
它提供了一个登录页面让我们登录。并且在 AccountController 中,它提供了几个模拟用户帐户,以便我们可以使用它们登录进行测试。
Then I added code in the login page so that it provides the option to sign in with AAD.然后我在登录页面中添加了代码,以便它提供使用 AAD 登录的选项。
Changing the Program.cs
file to add multiple authentication scheme.更改
Program.cs
文件以添加多个身份验证方案。
Here's my code, 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();
My HomeController我的家庭控制器
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();
}
}
}
My AccountController:我的帐户控制器:
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("/");
}
}
}
My LoginModel and UserModel我的 LoginModel 和 UserModel
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; }
}
}
View -> Account -> Login.cshtml:查看 -> 帐户 -> Login.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>
View -> Home -> ConfidentialData.cshtml查看 -> 主页 -> ConfidentialData.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, pls don't forget add this partial view to layout.查看 -> 共享 -> _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: 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": "*"
}
This code worked well in my side and I can sign in with cookie auth and aad.这段代码在我这边运行良好,我可以使用 cookie auth 和 aad 登录。 I noticed that after signing with aad,
@User.Identity.Name
won't show user name.我注意到在使用 aad 签名后,
@User.Identity.Name
不会显示用户名。 But actually the sign in flow succeed.但实际上登录流程成功。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.