目录:

  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 disable namespace 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/10361
if (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 DEBUG
services.AddBlazorWebViewDeveloperTools();
#endif
}
}

运行

Blazor OIDC 单点登录授权实例7 - Blazor hybird app 端授权的更多相关文章

  1. 如何基于Security实现OIDC单点登录?

    一.说明 本文主要是给大家介绍 OIDC 的核心概念以及如何通过对 Spring Security 的授权码模式进行扩展来实现 OIDC 的单点登录. OIDC 是 OpenID Connect 的简 ...

  2. shiro 单点登录原理 实例

    原创 2017年02月08日 17:39:55 4006 Shiro 1.2开始提供了Jasig CAS单点登录的支持,单点登录主要用于多系统集成,即在多个系统中,用户只需要到一个中央服务器登录一次即 ...

  3. UCenter在JAVA项目中实现的单点登录应用实例

    Comsenz(康盛)的UCenter当前在国内的单点登录领域占据绝对份额,其完整的产品线令UCenter成为了账号集成方面事实上的标准. 基于UCenter,可以将Comsenz旗下的Discuz! ...

  4. CORS跨域、Cookie传递SessionID实现单点登录后的权限认证的移动端兼容性测试报告

    简述 本文仅记录如标题所述场景的测试所得,由于场景有些特殊,且并不需兼容所有浏览器,所以本文的内容对读者也许并无作用,仅为记录. 场景.与实现 需在移动端单点登录 需在移动端跨域访问我们的服务 基于历 ...

  5. CAS单点登录学习(一):服务端搭建

    下载先在网上下载cas-server-3.5.2,将里面的cas-server-webapp-3.5.2.war放到tomcat的webapps目录下. https设置cas单点登默认使用的是http ...

  6. (转)基于CAS实现单点登录(SSO):cas client端的退出问题

    出处:http://blog.csdn.net/tch918/article/details/22276627 自从CAS 3.4就很好的支持了单点注销功能,配置也很简单. 之前版本因为在CAS服务器 ...

  7. [原]基于CAS实现单点登录(SSO):cas client端的退出问题

    自从CAS 3.4就很好的支持了单点注销功能,配置也很简单. 之前版本因为在CAS服务器通过HttpClient发送消息时并未指定为POST方式,所以在CAS客户端的注销Filter中没有收到POST ...

  8. SSO 基于Cookie+fliter实现单点登录 实例解析(一)

    接上文,SSO的理论讲解,接下来实践实践! 1.使用Cookie解决单点登录 技术点: 1.设置Cookie的路径为setPath("/").即Tomcat的目录下都有效 2.设置 ...

  9. SSO 基于Cookie+fliter实现单点登录(SSO):工作原理

    SSO的概念: 单点登录SSO(Single Sign-On)是身份管理中的一部分. SSO的一种较为通俗的定义是:SSO是指訪问同一server不同应用中的受保护资源的同一用户,仅仅须要登录一次,即 ...

  10. 著名ERP厂商的SSO单点登录解决方案介绍一

          SSO英文全称Single Sign On,单点登录.SSO是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统.它包括可以将这次主要的登录映射到其他应用中用于同一个用户 ...

随机推荐

  1. Java纯手打web服务器(二)

    概要:这里对上一篇中的访问资源进行改进,将访问servlet和静态资源进行区分. 主要不同的地方是加入了两种分析器 servlet分析器 if (request.getUri().startsWith ...

  2. 【Azure Redis 缓存】Azure Redis 异常 - 因线程池Busy而产生的Timeout异常问题

    问题描述 StackExchange.Redis在使用线程池后,偶尔会出现Timeout awaiting response 或者 No connection is available to serv ...

  3. 有了 ETL 数据神器 dbt,表数据秒变 NebulaGraph 中的图数据

    本文搭配同主题分享视频阅读更佳,<多数据源的数据治理实践> 如果你装好某款数据库产品,比如:分布式图数据库 NebulaGrpah,跃跃欲试的第一步是不是就让它干活搞数据呢?好的,现在问题 ...

  4. PostgreSQL、KingBase 数据库 ORDER BY LIMIT 查询缓慢案例

    好久没写博客了,最近从人大金仓离职了,新公司入职了蚂蚁集团,正在全力学习 OcenaBase 数据库的体系结构中. 以后分享的案例知识基本上都是以 OcenaBase 分布式数据库为主了,呦西. 昨天 ...

  5. 轻松驾驭Python格式化:5个F-String实用技巧分享

    F-String(格式化字符串字面值)是在Python 3.6中引入的,它是一种非常强大且灵活的字符串格式化方法. 它允许你在字符串中嵌入表达式,这些表达式在运行时会被求值并转换为字符串,这种特性使得 ...

  6. 基于pythondetcp多个客户端连接服务器

    壹: TCP是面向运输层的协议.使用TCP协议之前,必须先建立TCP连接,在传输完成后,必须释放已经建立的TCP连接.每条TCP连接只能有两个端,每一条TCP连接只能是点对点的.TCP提供可靠的交付的 ...

  7. python中往json中添加文件的方法

    一 前言: python中常用的一种方式,这里给大家列出来一下. 二 实例 比如,最简单的一个json文件 test_json = { "a": 1, "b": ...

  8. 三种方式使用纯 CSS 实现星级评分

    本文介绍三种使用纯 CSS 实现星级评分的方式.每种都值得细品一番~ 五角星取自 Element Plus 的 svg 资源 <svg xmlns="http://www.w3.org ...

  9. day02-MySQL基础知识

    MySQL基本知识 1.数据库 1.1.创建数据库 语法: CREATE DATABASE [IF NOT EXISTS] db_name [create_specification[,create_ ...

  10. 小程序登录V2

    参考:https://developers.weixin.qq.com/community/develop/doc/000cacfa20ce88df04cb468bc52801(通知) https:/ ...