首页 > 其他分享 >Blazor OIDC 单点登录授权实例7 - Blazor hybird app 端授权

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

时间:2024-04-10 09:55:36浏览次数:25  
标签:Task hybird using ctx new var 授权 Blazor public

目录:

  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();
    }
}
		<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
    }
}

运行

标签:Task,hybird,using,ctx,new,var,授权,Blazor,public
From: https://www.cnblogs.com/densen2014/p/18014332

相关文章

  • .net maui blazor路由和导航,传参,刷新
    .netmauiblazor路由和导航,传参,刷新参考:https://learn.microsoft.com/zh-cn/aspnet/core/blazor/fundamentals/routing?view=aspnetcore-8.0页面:TestPage1.razor:@page"/test1/{Text}"<p>必填参数:@Text</p>@code{[Parameter]publicstring?Text{get;......
  • Blazor学习记录_12._IIS部署_组件的引用_
    27.Blazor项目发布与IIS部署27.1如果是Auto模版的项目,选择两个项目中的Server项目进行发布27.2服务器必要的运行时安装与配置1.安装运行时可先通过命令行输入:dotnet--info来查看本地已经安装的运行时情况。运行时官方下载页面:https://dotnet.microsoft.com/zh-cn/dow......
  • Redis未授权漏洞复现
    目录Redis漏洞的产生条件及利用Redis环境搭建漏洞复现利用Redis写入Webshell利用Redis写入SSH公钥利用Redis写入计划任务Redis安全防护Redisredis(remotedictionaryserver)是一个key-value存储系统,是跨平台的非关系型数据库。redis默认情况下,会绑定在0.0.0.0:6379,如果没有采用......
  • 鉴权、授权:token和session
    1、session服务器认证、授权、鉴权:cookie+session来做认证cookie是放在游览器中的session是保存在服务器的数据库中的session鉴权处理:self.session=request.session() 直接拿self.session发送请求就好了,无需收到添加cookie  2、token安全令牌机制token是服务器生成......
  • 系统多种用户角色认证登录授权如何实现?
    前提:本项目是springboot写的后端,使用springsecurity+jwt实现去实现student、company、admin三种用户角色的认证与授权方法1:建立全局唯一的用户标识符uuid为每个用户生成一个全局的UUID。前情提要:本来设计了三张数据库表:student、company、admin。每张表存放相应角色相应用户......
  • SpringSecurity认证和授权流程详解
    什么是SpringSecuritySpringSecurity是一个Java框架,用于保护应用程序的安全性。它提供了一套全面的安全解决方案,包括身份验证、授权、防止攻击等功能。SpringSecurity基于过滤器链的概念,可以轻松地集成到任何基于Spring的应用程序中。它支持多种身份验证选项和授权策略,开发人员......
  • 2024最新同城上门家政按摩H5小程序源码 | 全开源无需授权 | 上门预约系统
    简介:2024最新同城上门家政按摩H5小程序源码 |上门预约系统 后端thinkphp框架开发。前端采用uni-app开发,适配多端(小程序+公众号H5+APP)2024最新同城上门家政按摩H5小程序源码|全开源无需授权|上门预约系统-百创网-源码交易平台_网站源码_商城源码_小程序源码此套源码......
  • EXE一机一码加密大师更新1.4.5,新增支持注销授权功能
    EXE一机一码打包加密大师是一款功能强大的工具,用于保护和加密EXE文件。它具有以下特性:一机一码授权:您可以为EXE文件添加一机一码认证,这意味着不同的电脑打开加密后的文件需要输入不同的激活码才能正常使用。这有助于保护您的文件安全,并方便向用户收费。静态密码:除了一机一码认证......
  • Office365和批量授权版本visio共存安装
    officeclicktorunversionandvolumeauthorizationversionvisiocoexistinstallation如果已经安装了office365版本的office,或者其他即点即用版本的office,再想安装批量授权版本的visio等软件时,安装时候会提示不能和即点即用的Office共存,会提示让卸载后再安装,但是既然已......
  • Blazor学习记录_9.C#和JS互操作__
    23.C#和JS互操作23.1C#调用JS,使用IJSRuntimejs代码:<buttononclick="javascript:alter("提示信息")">点我弹出提示</button>C#调用JS:JsInteractive.razor页面代码,InvokeVoidAsync()方法和InvokeAsync()方法@Page"/jsinteractive"<button@onclick=......