Blazor WebAssembly 与 AzureAD
Blazor WebAssembly with AzureAD
我需要让我正在开发的 WebAssembly 应用程序的用户通过 AzureAD 登录。
我正在使用回购 Microsoft.Identity.Web(和 UI)。
这是我目前所做的:
appsetting.json
"AzureAd": {
"Instance": "https://login.microsoftonline.com",
"Domain": "xxx",
"TenantId": "xxx",
"ClientId": "xxx",
"CallbackPath": "/signin-oidc",
"SignedOutCallbackPath ": "/signout-callback-oidc",
"ClientSecret": "xxx"
},
服务器端startup.cs
public class Startup
{
public IConfiguration Configuration { get; }
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddResponseCompression(opts =>
{
opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
new[] { "application/octet-stream" });
});
// Sign-in users with the Microsoft identity platform
//services.AddSignIn(Configuration);
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddSignIn("AzureAD", Configuration, options => Configuration.Bind("AzureAD", options));
// Looks like I need this to have the login UI
services.AddControllersWithViews(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
}).AddMicrosoftIdentityUI();
services.AddRazorPages();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseResponseCompression();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBlazorDebugging();
}
app.UseCors();
app.UseStaticFiles();
app.UseClientSideBlazorFiles<Client.Program>();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
endpoints.MapFallbackToClientSideBlazor<Client.Program>("index.html");
});
}
}
UserController.cs
[ApiController]
[Route("api/[controller]")]
public class UserController : ControllerBase
{
[HttpGet]
public AppUser Get()
{
AppUser toReturn = new AppUser();
if (this.User.Identity.IsAuthenticated)
{
toReturn.UserName = this.User.Identity.Name;
}
else
{
toReturn.UserName = ""; // Not logged in
}
return toReturn;
}
}
客户program.cs
public class Program
{
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
// Use our CustomAuthenticationProvider as the
// AuthenticationStateProvider
builder.Services.AddScoped<AuthenticationStateProvider, CustomAuthenticationProvider>();
// Add Authentication support
builder.Services.AddOptions();
builder.Services.AddAuthorizationCore();
builder.RootComponents.Add<App>("app");
await builder.Build().RunAsync();
}
}
public class CustomAuthenticationProvider : AuthenticationStateProvider
{
private readonly HttpClient _httpClient;
public CustomAuthenticationProvider(HttpClient httpClient)
{
_httpClient = httpClient;
}
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
ClaimsPrincipal user;
// Call the GetUser method to get the status
// This only sets things like the AuthorizeView
// and the AuthenticationState CascadingParameter
var result = await _httpClient.GetJsonAsync<AppUser>("api/user");
// Was a UserName returned?
if (result.UserName != "")
{
// Create a ClaimsPrincipal for the user
var identity = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Name, result.UserName),
}, "AzureAdAuth");
user = new ClaimsPrincipal(identity);
}
else
{
user = new ClaimsPrincipal(); // Not logged in
}
return await Task.FromResult(new AuthenticationState(user));
}
}
App.razor
<Router AppAssembly="@typeof(Program).Assembly">
<Found Context="routeData">
<AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
</Found>
<NotFound>
<CascadingAuthenticationState>
<LayoutView Layout="@typeof(MainLayout)">
<p>Sorry, there's nothing at this address.</p>
</LayoutView>
</CascadingAuthenticationState>
</NotFound>
</Router>
Login.razor
<AuthorizeView>
<Authorized>
<h6>Hello, @context.User.Identity.Name! </h6>
<a href="MicrosoftIdentity/Account/SignOut">[Log out]</a>
</Authorized>
<NotAuthorized>
<a href="MicrosoftIdentity/Account/SignIn">[Log in]</a>
</NotAuthorized>
</AuthorizeView>
我手动输入路由就可以登录https://localhost:xxxxx/MicrosoftIdentity/Account/SignIn。当用户登录时,一切都按预期进行。
当用户未登录时出现以下异常,blazor 尝试加载布局的 <AuthorizeView />
部分:
Access to fetch at 'https://login.microsoftonline.com/xxx/oauth2/v2.0/authorize?client_id=xxx&redirect_uri=https%3A%2F%2Flocalhost%3A44316%2Fsignin-oidc&response_type=code%20id_token&scope=openid%20profile%20offline_access%xxx%2Fuser_impersonation&response_mode=form_post&nonce=xxx&x-client-SKU=ID_NETSTANDARD2_0&x-client-ver=5.5.0.0' (redirected from 'https://localhost:44316/api/user') from origin 'https://localhost:44316' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
我正在尝试按照 here 的说明在服务器端启用 CORS,但我还没有成功。
我错过了什么?
https://devblogs.microsoft.com/aspnet/blazor-webassembly-3-2-0-preview-2-release-now-available/
Blazor WebAssembly 预览版 2 附带了一个开箱即用的工作模板。
我需要让我正在开发的 WebAssembly 应用程序的用户通过 AzureAD 登录。 我正在使用回购 Microsoft.Identity.Web(和 UI)。
这是我目前所做的:
appsetting.json
"AzureAd": {
"Instance": "https://login.microsoftonline.com",
"Domain": "xxx",
"TenantId": "xxx",
"ClientId": "xxx",
"CallbackPath": "/signin-oidc",
"SignedOutCallbackPath ": "/signout-callback-oidc",
"ClientSecret": "xxx"
},
服务器端startup.cs
public class Startup
{
public IConfiguration Configuration { get; }
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddResponseCompression(opts =>
{
opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
new[] { "application/octet-stream" });
});
// Sign-in users with the Microsoft identity platform
//services.AddSignIn(Configuration);
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddSignIn("AzureAD", Configuration, options => Configuration.Bind("AzureAD", options));
// Looks like I need this to have the login UI
services.AddControllersWithViews(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
}).AddMicrosoftIdentityUI();
services.AddRazorPages();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseResponseCompression();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBlazorDebugging();
}
app.UseCors();
app.UseStaticFiles();
app.UseClientSideBlazorFiles<Client.Program>();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
endpoints.MapFallbackToClientSideBlazor<Client.Program>("index.html");
});
}
}
UserController.cs
[ApiController]
[Route("api/[controller]")]
public class UserController : ControllerBase
{
[HttpGet]
public AppUser Get()
{
AppUser toReturn = new AppUser();
if (this.User.Identity.IsAuthenticated)
{
toReturn.UserName = this.User.Identity.Name;
}
else
{
toReturn.UserName = ""; // Not logged in
}
return toReturn;
}
}
客户program.cs
public class Program
{
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
// Use our CustomAuthenticationProvider as the
// AuthenticationStateProvider
builder.Services.AddScoped<AuthenticationStateProvider, CustomAuthenticationProvider>();
// Add Authentication support
builder.Services.AddOptions();
builder.Services.AddAuthorizationCore();
builder.RootComponents.Add<App>("app");
await builder.Build().RunAsync();
}
}
public class CustomAuthenticationProvider : AuthenticationStateProvider
{
private readonly HttpClient _httpClient;
public CustomAuthenticationProvider(HttpClient httpClient)
{
_httpClient = httpClient;
}
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
ClaimsPrincipal user;
// Call the GetUser method to get the status
// This only sets things like the AuthorizeView
// and the AuthenticationState CascadingParameter
var result = await _httpClient.GetJsonAsync<AppUser>("api/user");
// Was a UserName returned?
if (result.UserName != "")
{
// Create a ClaimsPrincipal for the user
var identity = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Name, result.UserName),
}, "AzureAdAuth");
user = new ClaimsPrincipal(identity);
}
else
{
user = new ClaimsPrincipal(); // Not logged in
}
return await Task.FromResult(new AuthenticationState(user));
}
}
App.razor
<Router AppAssembly="@typeof(Program).Assembly">
<Found Context="routeData">
<AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
</Found>
<NotFound>
<CascadingAuthenticationState>
<LayoutView Layout="@typeof(MainLayout)">
<p>Sorry, there's nothing at this address.</p>
</LayoutView>
</CascadingAuthenticationState>
</NotFound>
</Router>
Login.razor
<AuthorizeView>
<Authorized>
<h6>Hello, @context.User.Identity.Name! </h6>
<a href="MicrosoftIdentity/Account/SignOut">[Log out]</a>
</Authorized>
<NotAuthorized>
<a href="MicrosoftIdentity/Account/SignIn">[Log in]</a>
</NotAuthorized>
</AuthorizeView>
我手动输入路由就可以登录https://localhost:xxxxx/MicrosoftIdentity/Account/SignIn。当用户登录时,一切都按预期进行。
当用户未登录时出现以下异常,blazor 尝试加载布局的 <AuthorizeView />
部分:
Access to fetch at 'https://login.microsoftonline.com/xxx/oauth2/v2.0/authorize?client_id=xxx&redirect_uri=https%3A%2F%2Flocalhost%3A44316%2Fsignin-oidc&response_type=code%20id_token&scope=openid%20profile%20offline_access%xxx%2Fuser_impersonation&response_mode=form_post&nonce=xxx&x-client-SKU=ID_NETSTANDARD2_0&x-client-ver=5.5.0.0' (redirected from 'https://localhost:44316/api/user') from origin 'https://localhost:44316' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
我正在尝试按照 here 的说明在服务器端启用 CORS,但我还没有成功。 我错过了什么?
https://devblogs.microsoft.com/aspnet/blazor-webassembly-3-2-0-preview-2-release-now-available/
Blazor WebAssembly 预览版 2 附带了一个开箱即用的工作模板。