栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > .Net

《进击吧!Blazor!》系列入门教程 第一章 6.安全

.Net 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

《进击吧!Blazor!》系列入门教程 第一章 6.安全

《进击吧!Blazor!》是本人与张善友老师合作的Blazor零基础入门教程视频,此教程能让一个从未接触过Blazor的程序员掌握开发Blazor应用的能力。
视频地址:https://space.bilibili.com/483888821/channel/detail?cid=151273
Blazor WebAssembly 是单页应用 (SPA) 框架,用于使用 .NET 生成交互式客户端 Web 应用,采用 C# 代替 JavaScript 来编写前端代码
本系列文章基于《进击吧!Blazor!》编写,升级.Net5,但因篇幅有限,省略了部分代码,完整示例代码:https://github.com/TimChen44/Blazor-ToDo

作者:陈超超
Ant Design Blazor 项目贡献者,拥有十多年从业经验,长期基于.Net技术栈进行架构与开发产品的工作,现就职于正泰集团。
邮箱:timchen@live.com
欢迎各位读者有任何问题联系我,我们共同进步。

我的的ToDo应用基本功能已经完成,但是自己的待办当然只有自己知道,所以我们这次给我们的应用增加一些安全方面的功能。

Blazor 身份验证与授权 身份验证

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

  • Blazor WebAssembly

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

  • Blazor Server

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

授权

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


    

    
    

    

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 GetAuthenticationStateAsync()
    {
        //这里获得用户登录状态
        var result = await HttpClient.GetFromJsonAsync($"api/Auth/GetUser");

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

    /// 
    /// 标记授权
    /// 
    /// 
    /// 
    public void MarkUserAsAuthenticated(UserDto userDto)
    {
        HttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", userDto.Token);
        UserName = userDto.Name;

        //此处应该根据服务器的返回的内容进行配置本地策略,作为演示,默认添加了“Admin”
        var claims = new List();
        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存储在本地存储中,实现页面刷新无需登录
    }

    /// 
    /// 标记注销
    /// 
    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();

在Program中配置支持的策略

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

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

@if (model != null) {
}
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($"api/Auth/Login", model);
        UserDto result = await httpResponse.Content.ReadFromJsonAsync();

        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文件


    
        
            
                
                    
                    
                        
                            主页
                        
                        
                            我的一天
                        
                        
                            重要任务
                        
                        
                            全部
                        
                    
                
                
                    @Body
                
            
        
        
            
        
    

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

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

@using Microsoft.AspNetCore.Components.Authorization

运行查看效果

关于更多安全

安全是一个很大的话题,这个章节只是介绍了其最简单的实现方式,还有更多内容推荐阅读官方文档:https://docs.microsoft.com/zh-cn/aspnet/core/blazor/security/?view=aspnetcore-5.0

如果想了解更多,可以直接浏览微软文档:
ASP.NET Core Blazor 身份验证和授权

次回预告

我们通过几张图表,将我们ToDo应用中任务情况做个完美统计。

返回目录

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/984865.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号