Blazor OIDC 单点登录授权实例7 - Blazor hybird app 端授权

目录:

  1. OpenID 与 OAuth2 基础知识
  2. Blazor wasm Google 登录
  3. Blazor wasm Gitee 码云登录
  4. Blazor OIDC 单点登录授权实例1-建立和配置IDS身份验证服务
  5. Blazor OIDC 单点登录授权实例2-登录信息组件wasm
  6. Blazor OIDC 单点登录授权实例3-服务端管理组件
  7. Blazor OIDC 单点登录授权实例4 - 部署服务端/独立WASM端授权
  8. Blazor OIDC 单点登录授权实例5 - 独立SSR App (net8 webapp)端授权
  9. Blazor OIDC 单点登录授权实例6 - Winform 端授权
  10. Blazor OIDC 单点登录授权实例7 - Blazor hybird app 端授权

(目录暂时不更新,跟随合集标题往下走)

源码

BlazorOIDC.WinForms

建立 BlazorOIDC.WinForms 工程

自行安装 Vijay Anand E G 模板,快速建立 Blazor WinForms 工程, 命名为 BlazorOIDC.WinForms

引用以下库

    <ItemGroup><PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="8.0.4" /><PackageReference Include="Microsoft.AspNetCore.Components.WebView.WindowsForms" Version="8.*" /><PackageReference Include="Microsoft.Extensions.Hosting" Version="8.*" /><FrameworkReference Include="Microsoft.AspNetCore.App"></FrameworkReference><PackageReference Include="IdentityModel.OidcClient" Version="5.2.1" /></ItemGroup>

_Imports.razor 加入引用

@using Microsoft.AspNetCore.Components.Authorization

Main.razor 加入授权

完整代码

<CascadingAuthenticationState><Router AppAssembly="@GetType().Assembly"><Found Context="routeData"><RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" /></Found><NotFound><LayoutView Layout="@typeof(MainLayout)"><p>Sorry, there's nothing at this address.</p></LayoutView></NotFound></Router>
</CascadingAuthenticationState>

添加Oidc授权配置

新建文件 ExternalAuthStateProvider.cs

完整代码

using IdentityModel.OidcClient;
using Microsoft.AspNetCore.Components.Authorization;
using System.Security.Claims;namespace BlazorOIDC.WinForms;public class ExternalAuthStateProvider : AuthenticationStateProvider
{private readonly Task<AuthenticationState> authenticationState;public ExternalAuthStateProvider(AuthenticatedUser user) =>authenticationState = Task.FromResult(new AuthenticationState(user.Principal));private ClaimsPrincipal currentUser = new ClaimsPrincipal(new ClaimsIdentity());public override Task<AuthenticationState> GetAuthenticationStateAsync() =>Task.FromResult(new AuthenticationState(currentUser));public Task<AuthenticationState> LogInAsync(){var loginTask = LogInAsyncCore();NotifyAuthenticationStateChanged(loginTask);return loginTask;async Task<AuthenticationState> LogInAsyncCore(){var user = await LoginWithExternalProviderAsync();currentUser = user;return new AuthenticationState(currentUser);}}private async Task<ClaimsPrincipal> LoginWithExternalProviderAsync(){/*提供 Open ID/MSAL 代码以对用户进行身份验证。查看您的身份提供商的文档以获取详细信息。根据新的声明身份返回新的声明主体。*/string authority = "https://localhost:5001/";//string authority = "https://ids2.app1.es/"; //真实环境string api = $"{authority}WeatherForecast";string clientId = "Blazor5002";OidcClient? _oidcClient;HttpClient _apiClient = new HttpClient { BaseAddress = new Uri(api) };var browser = new SystemBrowser(5002);var redirectUri = string.Format($"http://localhost:{browser.Port}/authentication/login-callback");var redirectLogoutUri = string.Format($"http://localhost:{browser.Port}/authentication/logout-callback");var options = new OidcClientOptions{Authority = authority,ClientId = clientId,RedirectUri = redirectUri,PostLogoutRedirectUri = redirectLogoutUri,Scope = "BlazorWasmIdentity.ServerAPI openid profile",//Scope = "Blazor7.ServerAPI openid profile",Browser = browser,Policy = new Policy { RequireIdentityTokenSignature = false }};_oidcClient = new OidcClient(options);var result = await _oidcClient.LoginAsync(new LoginRequest());ShowResult(result);var authenticatedUser = result.User;return authenticatedUser;}private static void ShowResult(LoginResult result, bool showToken = false){if (result.IsError){Console.WriteLine("\n\nError:\n{0}", result.Error);return;}Console.WriteLine("\n\nClaims:");foreach (var claim in result.User.Claims){Console.WriteLine("{0}: {1}", claim.Type, claim.Value);}if (showToken){Console.WriteLine($"\nidentity token: {result.IdentityToken}");Console.WriteLine($"access token:   {result.AccessToken}");Console.WriteLine($"refresh token:  {result?.RefreshToken ?? "none"}");}}public Task Logout(){currentUser = new ClaimsPrincipal(new ClaimsIdentity());NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(currentUser)));return Task.CompletedTask;}
}public class AuthenticatedUser
{public ClaimsPrincipal Principal { get; set; } = new();
}

添加Oidc浏览器授权方法

新建文件 SystemBrowser.cs

完整代码

using IdentityModel.OidcClient.Browser;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Text;
#nullable disablenamespace BlazorOIDC.WinForms;public class SystemBrowser : IBrowser
{public int Port { get; }private readonly string _path;public SystemBrowser(int? port = null, string path = null){_path = path;if (!port.HasValue){Port = GetRandomUnusedPort();}else{Port = port.Value;}}private int GetRandomUnusedPort(){var listener = new TcpListener(IPAddress.Loopback, 0);listener.Start();var port = ((IPEndPoint)listener.LocalEndpoint).Port;listener.Stop();return port;}public async Task<BrowserResult> InvokeAsync(BrowserOptions options, CancellationToken cancellationToken = default){using (var listener = new LoopbackHttpListener(Port, _path)){OpenBrowser(options.StartUrl);try{var result = await listener.WaitForCallbackAsync();if (string.IsNullOrWhiteSpace(result)){return new BrowserResult { ResultType = BrowserResultType.UnknownError, Error = "Empty response." };}return new BrowserResult { Response = result, ResultType = BrowserResultType.Success };}catch (TaskCanceledException ex){return new BrowserResult { ResultType = BrowserResultType.Timeout, Error = ex.Message };}catch (Exception ex){return new BrowserResult { ResultType = BrowserResultType.UnknownError, Error = ex.Message };}}}public static void OpenBrowser(string url){try{Process.Start(url);}catch{// hack because of this: https://github.com/dotnet/corefx/issues/10361if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)){url = url.Replace("&", "^&");Process.Start(new ProcessStartInfo("cmd", $"/c start {url}") { CreateNoWindow = true });}else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)){Process.Start("xdg-open", url);}else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)){Process.Start("open", url);}else{throw;}}}
}public class LoopbackHttpListener : IDisposable
{const int DefaultTimeout = 60 * 5; // 5 mins (in seconds)IWebHost _host;TaskCompletionSource<string> _source = new TaskCompletionSource<string>();public string Url { get; }public LoopbackHttpListener(int port, string path = null){path = path ?? string.Empty;if (path.StartsWith("/")) path = path.Substring(1);Url = $"http://localhost:{port}/{path}";_host = new WebHostBuilder().UseKestrel().UseUrls(Url).Configure(Configure).Build();_host.Start();}public void Dispose(){Task.Run(async () =>{await Task.Delay(500);_host.Dispose();});}void Configure(IApplicationBuilder app){app.Run(async ctx =>{if (ctx.Request.Method == "GET"){await SetResultAsync(ctx.Request.QueryString.Value, ctx);}else if (ctx.Request.Method == "POST"){if (!ctx.Request.ContentType.Equals("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase)){ctx.Response.StatusCode = 415;}else{using (var sr = new StreamReader(ctx.Request.Body, Encoding.UTF8)){var body = await sr.ReadToEndAsync();await SetResultAsync(body, ctx);}}}else{ctx.Response.StatusCode = 405;}});}private async Task SetResultAsync(string value, HttpContext ctx){try{ctx.Response.StatusCode = 200;ctx.Response.ContentType = "text/html; charset=utf-8";await ctx.Response.WriteAsync("<h1>您现在可以返回应用程序.</h1>");await ctx.Response.Body.FlushAsync();_source.TrySetResult(value);}catch(Exception ex){Console.WriteLine(ex.ToString());ctx.Response.StatusCode = 400;ctx.Response.ContentType = "text/html; charset=utf-8";await ctx.Response.WriteAsync("<h1>无效的请求.</h1>");await ctx.Response.Body.FlushAsync();}}public Task<string> WaitForCallbackAsync(int timeoutInSeconds = DefaultTimeout){Task.Run(async () =>{await Task.Delay(timeoutInSeconds * 1000);_source.TrySetCanceled();});return _source.Task;}
}

Shared 文件夹新建登录/注销页面组件

LoginComponent.razor

完整代码

@inject AuthenticationStateProvider AuthenticationStateProvider
@page "/Login"
@using System.Security.Claims<button @onclick="Login">Log in</button><p>@Msg</p><AuthorizeView><Authorized>你好, @context.User.Identity?.Name<br /><br /><br /><h5>以下是用户的声明</h5><br />@foreach (var claim in context.User.Claims){<p>@claim.Type: @claim.Value</p>} </Authorized> </AuthorizeView><p>以下是基于角色或基于策略的授权,未登录不显示 </p><AuthorizeView Roles="Admin, Superuser"><p>只有管理员或超级用户才能看到.</p>
</AuthorizeView>@code
{[Inject]private AuthenticatedUser? authenticatedUser { get; set; }/// <summary>/// 级联参数获取身份验证状态数据/// </summary>[CascadingParameter]private Task<AuthenticationState>? authenticationStateTask { get; set; }private string? Msg { get; set; }private ClaimsPrincipal? User { get; set; }public async Task Login(){var authenticationState = await ((ExternalAuthStateProvider)AuthenticationStateProvider).LogInAsync();User = authenticationState?.User;if (User != null){if (User.Identity != null && User.Identity.IsAuthenticated){Msg += "已登录." + Environment.NewLine;}}}
}

LogoutComponent.razor

完整代码

@inject AuthenticationStateProvider AuthenticationStateProvider
@page "/Logout"<button @onclick="Logout">Log out</button>@code
{public async Task Logout(){await ((ExternalAuthStateProvider)AuthenticationStateProvider).Logout();}
}

NavMenu.razor 加入菜单

		<div class="nav-item px-3"><NavLink class="nav-link" href="Login"><span class="oi oi-plus" aria-hidden="true"></span> Login</NavLink></div><div class="nav-item px-3"><NavLink class="nav-link" href="Logout"><span class="oi oi-plus" aria-hidden="true"></span> Logout</NavLink></div>

Form1.cs 修改首页

var blazor = new BlazorWebView(){Dock = DockStyle.Fill,HostPage = "wwwroot/index.html",Services = Startup.Services!,StartPath = "/Login"};blazor.RootComponents.Add<Main>("#app");Controls.Add(blazor);

Startup.cs 注册服务

完整代码

using BlazorOIDC.WinForms.Data;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;namespace BlazorOIDC.WinForms;
public static class Startup
{public static IServiceProvider? Services { get; private set; }public static void Init(){var host = Host.CreateDefaultBuilder().ConfigureServices(WireupServices).Build();Services = host.Services;}private static void WireupServices(IServiceCollection services){services.AddWindowsFormsBlazorWebView();services.AddSingleton<WeatherForecastService>();services.AddAuthorizationCore();services.TryAddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();services.AddSingleton<AuthenticatedUser>();#if DEBUGservices.AddBlazorWebViewDeveloperTools();
#endif}
}

运行

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/610546.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Kyligence 发布企业级 AI 解决方案,Data + AI 落地迈向新阶段

4月11日&#xff0c;Kyligence 2024 数智论坛暨春季发布会成功召开。Kyligence 正式发布全新的企业级 AI 解决方案&#xff0c;基于服务金融、零售、制造、医药等行业领先客户的落地实践&#xff0c;Kyligence 为企业提供准确、可靠、智能的 AI 指标平台一站式解决方案&#x…

VQ-BeT: Behavior Generation with Latent Actions 代码复现(Mujoco 安装)

代码地址&#xff1a;https://github.com/jayLEE0301/vq_bet_official.git 创建环境 conda create -n vq-bet python3.9 conda activate vq-bet拉取库 git clone https://github.com/jayLEE0301/vq_bet_official.git export PROJ_ROOT$(pwd)安装pytorch conda install pyto…

win11 连接海康摄像头 ONVif协议

目录 Win 11 通过脚本打开自带的IE浏览器访问海康摄像头 海康摄像头设置支持onvif协议 安装onvif协议 onvif协议示例代码 Win 11 通过脚本打开自带的IE浏览器访问海康摄像头 第一步、桌面右键新建一个 txt 的文档 第二步、打开文档并且复制粘贴下面代码 CreateObject(&…

数仓指标体系

数仓指标体系 明确统计指标 明确统计指标具体的工作是&#xff0c;深入分析需求&#xff0c;构建指标体系。构建指标体系的主要意义就是指标定义标准化。所有指标的定义&#xff0c;都必须遵循同一套标准&#xff0c;这样能有效的避免指标定义存在歧义&#xff0c;指标定义重复…

SSH远程登陆系统(RedHat9)

ssh的基本用法 ssh hostname/IP # 如果没有指定用什么用户进行连接&#xff0c;默认使用当前用户登录 ssh –l username hostname/IP ssh usernamehostname ssh usernameIP在第一次连接到服务器时&#xff0c;会自动记录服务器的公钥指纹信息 如果出现密钥变更导致错误可以…

LeetCode 最长重复子串的更换问题

LeetCode 最长重复子串的更换问题 在处理字符串问题时&#xff0c;我们经常会遇到一些具有挑战性的问题&#xff0c;比如今天的这个问题&#xff1a;给定一个字符串 s 和一个整数 k&#xff0c;我们的任务是找到可以通过最多更换 k 个字符来形成的最长重复子串的长度。 我的解…

基于SpringBoot+Vue的公园管理系统(源码+文档+部署+讲解)

一.系统概述 近年来&#xff0c;科技飞速发展&#xff0c;在经济全球化的背景之下&#xff0c;互联网技术将进一步提高社会综合发展的效率和速度&#xff0c;互联网技术也会涉及到各个领域&#xff0c;而公园管理系统在网络背景下有着无法忽视的作用。信息管理系统的开发是一个…

HashMap的put方法--源码解读

我们放入值&#xff0c;会发生什么 看下官方给的说明&#xff1a; 实现 Map.put 和相关方法 参数&#xff1a; hash – 键的哈希值 key-键 value-值 onlyIfAbsent – 如果为 true&#xff0c;则不更改现有值 evict – 如果为 false&#xff0c;则表处于创建模式。 返回&#xf…

【送书活动】用“价值”的视角来看安全:《构建新型网络形态下的网络空间安全体系》

文章目录 每日一句正能量前言本书概况赠书活动目录 每日一句正能量 成功与失败&#xff0c;幸福与不幸&#xff0c;在各自心里的定义都不会相同。 前言 过去&#xff0c;安全从未如此复杂&#xff1b; 现在&#xff0c;安全从未如此重要&#xff1b; 未来&#xff0c;安全更需如…

定时任务、 cron 表达式语法

定时任务的实现方式有很多&#xff0c;比如XXL-Job等。但是其实核心功能和概念都是类似的&#xff0c;很多情况下只是调用的API不同而已。 这里就先用SpringBoot为我们提供的定时任务的API来实现一个简单的定时任务&#xff0c;让大家先对定时任务里面的一些核心概念有个大致的…

读书笔记之《如何精心设计提示词来精通ChatGPT》

《如何精心设计提示词来精通ChatGPT》这本书英文标题为&#xff1a;《The Art of Prompt Engineering with chatGPT》&#xff0c;于2023年出版。作者是Nathan Hunter 。 Nathan Hunter简介&#xff1a;ChatGPT培训的创始人。作为一名资深培训师和教学设计师&#xff0c;我在过…

分布式向量数据库-安装部署

下载 GitHub - pgvector/pgvector: Open-source vector similarity search for Postgres 源码编译 ##文件解压缩 unzip pgvector-0.6.2.zip ##编译 make && make install 功能验证 #安装扩展CREATE EXTENSION vector;#创建测试表CREATE TABLE items (id bigseri…