Blazor Json Web Token 身份验证与授权

2023-05-16

Blazor 身份验证与授权

身份验证

Blazor Server应用和 Blazor WebAssembly 应用的安全方案有所不同。

  • Blazor WebAssembly

Blazor WebAssembly 应用在客户端上运行。 由于用户可绕过客户端检查,因为用户可修改所有客户端代码, 因此授权仅用于确定要显示的 UI 选项,所有客户端应用程序技术都是如此。

  • Blazor Server

Blazor Server应用通过使用 SignalR 创建的实时连接运行。 建立连接后,将处理基于 SignalR 的应用的身份验证。 可基于 cookie 或一些其他持有者令牌进行身份验证。

授权

AuthorizeView 组件根据用户是否获得授权来选择性地显示 UI 内容。 如果只需要为用户显示数据,而不需要在过程逻辑中使用用户的标识,那么此方法很有用。

<AuthorizeView>
    <Authorized>
<!--验证通过显示-->
    </Authorized>
    <NotAuthorized>
<!--验证不通过显示-->
    </NotAuthorized>
</AuthorizeView>

Blazor 中使用Token

在Blazor WebAssembly模式下, 因为应用都在客户端运行,所以使用Token作为身份认证的方式是一个比较好的选择。 基本的使用时序图如下

对于安全要求不高的应用采用这个方法简单、易维护,完全没有问题。

但是Token本身在安全性上存在以下两个风险:

  1. Token无法注销,所以可以在Token有效期内发送的非法请求,服务端无能为力。
  2. Token通过AES加密存储在客户端,理论上可以进行离线破解,破解后就能任意伪造Token。

因此遇到安全要求非常高的应用时,我们需要认证服务进行Token的有效性验证

改造ToDo

接着我们对之前的ToDo项目进行改造,让他支持登录功能。

ToDo.Shared

先把前后端交互所需的Dto创建了

public class LoginDto
{
    public string UserName { get; set; }
    public string Password { get; set; }
}
public class UserDto
{
    public string Name { get; set; }
    public string Token { get; set; }
}

ToDo.Server

先改造服务端,添加必要引用,编写身份认证代码等

添加引用

  • Microsoft.AspNetCore.Authentication.JwtBearer

Startup.cs

添加JwtBearer配置

public void ConfigureServices(IServiceCollection services)
{
    //......
    services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddJwtBearer(options =>
        {
            options.TokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuer = true,//是否验证Issuer
                ValidateAudience = true,//是否验证Audience
                ValidateLifetime = true,//是否验证失效时间
                ValidateIssuerSigningKey = true,//是否验证SecurityKey
                ValidAudience = "guetClient",//Audience
                ValidIssuer = "guetServer",//Issuer,这两项和签发jwt的设置一致
                IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("123456789012345678901234567890123456789"))//拿到SecurityKey
            };
        });
}

此处定义了Token的密钥,规则等,实际项目时可以将这些信息放到配置中。

AuthController.cs

行政验证控制器,用于验证用户身份,创建Token等。

[ApiController]
[Route("api/[controller]/[action]")]
public class AuthController : ControllerBase
{
    //登录
    [HttpPost]
    public UserDto Login(LoginDto dto)
    {
        //模拟获得Token
        var jwtToken = GetToken(dto.UserName);

        return new() { Name = dto.UserName, Token = jwtToken };
    }

    //获得用户,当页面客户端页面刷新时调用以获得用户信息
    [HttpGet]
    public UserDto GetUser()
    {
        if (User.Identity.IsAuthenticated)//如果Token有效
        {
            var name = User.Claims.First(x => x.Type == ClaimTypes.Name).Value;//从Token中拿出用户ID
            //模拟获得Token
            var jwtToken = GetToken(name);
            return new UserDto() { Name = name, Token = jwtToken };
        }
        else
        {
            return new UserDto() { Name = null, Token = null };
        }
    }

    public string GetToken(string name)
    {
        //此处加入账号密码验证代码

        var claims = new Claim[]
        {
            new Claim(ClaimTypes.Name,name),
            new Claim(ClaimTypes.Role,"Admin"),
        };

        var key = new SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes("123456789012345678901234567890123456789"));
        var expires = DateTime.Now.AddDays(30);
        var token = new JwtSecurityToken(
            issuer: "guetServer",
            audience: "guetClient",
            claims: claims,
            notBefore: DateTime.Now,
            expires: expires,
            signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256));

        return new JwtSecurityTokenHandler().WriteToken(token);
    }
}

ToDo.Client

改造客户端,让客户端支持身份认证

添加引用

  • Microsoft.AspNetCore.Components.Authorization

AuthenticationStateProvider

AuthenticationStateProvider 是 AuthorizeView 组件和 CascadingAuthenticationState 组件用于获取身份验证状态的基础服务。 通常不直接使用 AuthenticationStateProvider,直接使用主要缺点是,如果基础身份验证状态数据发生更改,不会自动通知组件。其次是项目中总会有一些自定义的认证逻辑。 所以我们通常写一个类继承他,并重写一些我们自己的逻辑。

//AuthProvider.cs
public class AuthProvider : AuthenticationStateProvider
{
    private readonly HttpClient HttpClient;
    public string UserName { get; set; }

    public AuthProvider(HttpClient httpClient)
    {
        HttpClient = httpClient;
    }

    public async override Task<AuthenticationState> GetAuthenticationStateAsync()
    {
        //这里获得用户登录状态
        var result = await HttpClient.GetFromJsonAsync<UserDto>($"api/Auth/GetUser");

        if (result?.Name == null)
        {
            MarkUserAsLoggedOut();
            return new AuthenticationState(new ClaimsPrincipal());
        }
        else
        {
            var claims = new List<Claim>();
            claims.Add(new Claim(ClaimTypes.Name, result.Name));
            var authenticatedUser = new ClaimsPrincipal(new ClaimsIdentity(claims, "apiauth"));
            return new AuthenticationState(authenticatedUser);
        }
    }

    /// <summary>
    /// 标记授权
    /// </summary>
    /// <param name="loginModel"></param>
    /// <returns></returns>
    public void MarkUserAsAuthenticated(UserDto userDto)
    {
        HttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", userDto.Token);
        UserName = userDto.Name;

        //此处应该根据服务器的返回的内容进行配置本地策略,作为演示,默认添加了“Admin”
        var claims = new List<Claim>();
        claims.Add(new Claim(ClaimTypes.Name, userDto.Name));
        claims.Add(new Claim("Admin", "Admin"));

        var authenticatedUser = new ClaimsPrincipal(new ClaimsIdentity(claims, "apiauth"));
        var authState = Task.FromResult(new AuthenticationState(authenticatedUser));
        NotifyAuthenticationStateChanged(authState);

        //慈湖可以可以将Token存储在本地存储中,实现页面刷新无需登录
    }

    /// <summary>
    /// 标记注销
    /// </summary>
    public void MarkUserAsLoggedOut()
    {
        HttpClient.DefaultRequestHeaders.Authorization = null;
        UserName = null;

        var anonymousUser = new ClaimsPrincipal(new ClaimsIdentity());
        var authState = Task.FromResult(new AuthenticationState(anonymousUser));
        NotifyAuthenticationStateChanged(authState);
    }
}

NotifyAuthenticationStateChanged方法会通知身份验证状态数据(例如 AuthorizeView)使用者使用新数据重新呈现。 HttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", userDto.Token);将HTTP请求头中加入Token,这样之后所有的请求都会带上Token。

Program中注入AuthProvider服务,以便于其他地方使用

//Program.cs
builder.Services.AddScoped<AuthenticationStateProvider, AuthProvider>();

Program中配置支持的策略

builder.Services.AddAuthorizationCore(option =>
{
    option.AddPolicy("Admin", policy => policy.RequireClaim("Admin"));
});

登录界面

添加Login.razor组件,代码如下

<div style="margin:100px">
    <Spin Spinning="isLoading">
        @if (model != null)
        {
            <Form OnFinish="OnSave" Model="@model" LabelCol="new ColLayoutParam() {Span = 6 }">
                <FormItem Label="用户名">
                    <Input @bind-Value="context.UserName" />
                </FormItem>
                <FormItem Label="密码">
                    <Input @bind-Value="context.Password" Type="password" />
                </FormItem>
                <FormItem  WrapperColOffset="6">
                    <Button Type="@ButtonType.Primary" HtmlType="submit">登录</Button>
                </FormItem>
            </Form>
        }
    </Spin>
</div>
public partial class Login
{
    [Inject] public HttpClient Http { get; set; }
    [Inject] public MessageService MsgSvr { get; set; }
    [Inject] public AuthenticationStateProvider AuthProvider { get; set; }

    LoginDto model = new LoginDto();
    bool isLoading;

    async void OnLogin()
    {
        isLoading = true;

        var httpResponse = await Http.PostAsJsonAsync<LoginDto>($"api/Auth/Login", model);
        UserDto result = await httpResponse.Content.ReadFromJsonAsync<UserDto>();

        if (string.IsNullOrWhiteSpace(result?.Token) == false )
        {
            MsgSvr.Success($"登录成功");
            ((AuthProvider)AuthProvider).MarkUserAsAuthenticated(result);
        }
        else
        {
            MsgSvr.Error($"用户名或密码错误");
        }
        isLoading = false;
       InvokeAsync( StateHasChanged);
    }
}

登录界面代码很简单,就是向api/Auth/Login请求,根据返回的结果判断是否登入成功。 ((AuthProvider)AuthProvider).MarkUserAsAuthenticated(result);标记身份认证状态已经修改。

修改布局

修改MainLayout.razor文件

<CascadingAuthenticationState>
    <AuthorizeView>
        <Authorized>
            <Layout>
                <Sider Style="overflow: auto;height: 100vh;position: fixed;left: 0;">
                    <div class="logo">
                        进击吧!Blazor!
                    </div>
                    <Menu Theme="MenuTheme.Dark" Mode=@MenuMode.Inline>
                        <MenuItem RouterLink="/">
                            主页
                        </MenuItem>
                        <MenuItem RouterLink="/today" RouterMatch="NavLinkMatch.Prefix">
                            我的一天
                        </MenuItem>
                        <MenuItem RouterLink="/star" RouterMatch="NavLinkMatch.Prefix">
                            重要任务
                        </MenuItem>
                        <MenuItem RouterLink="/search" RouterMatch="NavLinkMatch.Prefix">
                            全部
                        </MenuItem>
                    </Menu>
                </Sider>
                <Layout Class="site-layout">
                    @Body
                </Layout>
            </Layout>
        </Authorized>
        <NotAuthorized>
            <ToDo.Client.Pages.Login></ToDo.Client.Pages.Login>
        </NotAuthorized>
    </AuthorizeView>
</CascadingAuthenticationState>

当授权通过后显示<AuthorizeView><Authorized>的菜单及主页,反之显示<NotAuthorized>Login组件内容。 当需要根据权限显示不同内容,可以使用<AuthorizeView>Policy属性实现,具体是在AuthenticationStateProvider中通过配置策略,比如示例中claims.Add(new Claim("Admin", "Admin"));就添加了Admin策略,在页面上只需<AuthorizeView Policy="Admin">就可以控制只有Admin策略的账户显示其内容了。 CascadingAuthenticationState级联身份状态,它采用了Balzor组件中级联机制,这样我们可以在任意层级的组件中使用AuthorizeView来控制UI了 AuthorizeView 组件根据用户是否获得授权来选择性地显示 UI 内容。 Authorized组件中的内容只有在获得授权时显示。 NotAuthorized组件中的内容只有在未经授权时显示。

修改_Imports.razor文件,添加必要的引用

@using Microsoft.AspNetCore.Components.Authorization

运行查看效果

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Blazor Json Web Token 身份验证与授权 的相关文章

随机推荐

  • 基于Goland和dlv远程调试Kubernetes组件

    一个正常运行的 Kubernetes 集群包含以下的各种组件 通常开发者日常编码都基于Windows Mac上的IDE xff08 VsCode Goland xff09 xff0c 编码完成后再部署到远端Linux机器运行 因此 xff0
  • centos8 mips交叉编译环境搭建(保姆级教程)

    最近一直在折腾我的光猫 路由器以及学习嵌入式开发的相关知识 xff0c 于是有了下面这篇文章 xff0c 参考了网上很多文章 xff0c 终于实现了自己的目标 xff0c 简单的记录下来 xff0c 名为 保姆级 其实是因为自己技术很菜 x
  • Docker load 大镜像(17G) 报错no space left on device

    span class token comment 导入加载镜像 xff0c test tar大小有16G左右 span span class token function docker span load span class token
  • 收集yum install安装的软件的全量依赖 rpm 包

    离线安装主要有两种方式 xff1a 源码编译 rpm包安装 源码编译耗费时间长且缺乏编译环境 xff0c 所以一般都选择使用离线 rpm 包安装 有时候离线 rpm 包有比较难于收集齐全 xff0c 但测试环境我们又可以通过 yum ins
  • 镜像搬运工具 Skopeo 使用

    镜像搬运工具 Skopeo 使用 搬砖工具 作为公司内部 PaaS toB 产品的打包发布人员 xff0c 容器镜像对我们打工人而言就像是工地上的砖头 x1f9f1 xff0c 而我的一部分工作就是将这些砖头在各个仓库之间搬来搬去 xff0
  • Habor数据迁移方式有多少,skopeo效率最好

    迁移流程 两个不同的Harbor实例迁移数据 含镜像数据和数据库数据 迁移方式 Harbor 镜像skopeoimage syncer手工机制基于策略的内容机制 xff1a 支持多种过滤器 xff08 镜像库 tag和标签 xff09 与多
  • Harbor新建仓库目标提示 the registry is unhealthy

    两个harbor需要通过镜像复制策略来同步镜像 在仓库管理添加的harbor状态是unhealthy 问题排查 两个harbor需要通过镜像复制策略来同步镜像 在仓库管理添加的harbor状态是unhealthy 本文中 目标仓库的地址是i
  • Harbor断电重启postgres报错 could not locate a valid checkpoint record

    Harborv2 2 2内置的数据库是postgres9 6 21 服务器突然断电 重启harbor后报错 信息如下 Oct span class token number 21 span span class token number 1
  • Harbor镜像层膨胀,占用存储过大

    问题现象 使用Harbor镜像复制策略后 本地镜像存储目录飙升到1T 外链图片转存失败 源站可能有防盗链机制 建议将图片保存下来直接上传 img imf8FtHs 1666958403009 AppData Roaming Typora t
  • Harbor 镜像复制 每 30 分钟发生一次超时

    报错信息 span class token number 2022 span 10 27T22 27 13Z span class token punctuation span ERROR span class token punctuat
  • 配置对 Harbor 的 HTTPS 访问

    配置对 Harbor 的 HTTPS 访问 默认情况下 xff0c Harbor 不附带证书 可以在没有安全性的情况下部署 Harbor xff0c 以便你可以通过 HTTP 连接到它 但是 xff0c 在生产环境中 xff0c 建议使用
  • 申请阿里云免费证书

    阿里云免费SSL证书申请 阿里云免费SSL证书是赛门铁克 xff08 Symantec xff09 品牌的 xff0c 免费证书只能保护一个域名 xff08 带www和不带www可以通用 xff09 阿里云个人账号和企业账号均可申请 xff
  • centos 7 / 8 texinfo安装

    在安装一些软件时会出现缺少texinfo的提示 xff0c 在centos7中texinfo非常容易安装 xff0c 只要执行 yum span class token function install span y texinfo 就可以
  • Harbor使用公网证书

    Harbor使用公网证书 申请公网证书 申请阿里云免费证书 因为Harbor使用Nginx做代理 xff0c 因此在公网证书审核通过后 xff0c 在证书下载面板 xff0c 单击Nginx服务器操作列的下载 下载解压后您将会获得以下文件
  • mysql查看实时语句和慢sql

    mysql查看实时语句和慢sql 查看实时语句 Mysql除了手动执行的语句 xff0c 还有很多在后台由其他模块执行的语句 xff0c 按理来说 xff0c 那些由其他模块执行的语句是不能实时查看的 xff0c 因为这个资源消耗特别的大
  • iPerf3 命令通用选项

    iPerf3 命令通用选项 同时适用于客户端与服务器端 命令选项命令描述 p port nThe server port for the server to listen on and the client to connect to Th
  • 在Ubuntu 14.04中修改date的显示

    最近把开发环境切换到了Ubuntu 14 04 xff0c 发现一点很别扭 xff0c 就是date显示为中文 例如 lipeng 64 lipeng MS 7673 date 2015年 02月 14日 星期六 21 16 21 CST
  • 2021美团笔试题(第十套)个人解答

    1 淘汰分数 span class token comment 暴力解法 span span class token keyword import span span class token namespace java span clas
  • 不同方式输出九九乘法表(java)

    方式一 xff1a System out println 34 打印正方形 34 正方形 for int i 61 1 i lt 61 9 i 43 43 外层循环控制行 for int j 61 1 j lt 61 9 j 43 43 内
  • Blazor Json Web Token 身份验证与授权

    Blazor 身份验证与授权 身份验证 Blazor Server应用和 Blazor WebAssembly 应用的安全方案有所不同 Blazor WebAssembly Blazor WebAssembly 应用在客户端上运行 由于用户