网站首页 > 基础教程 正文
前言
在 Blazor 中,认证登录向来是个棘手的难题。在网络之上,能够找到的完整示例基本上没有。历经周折方才拼凑出一个可用的方案。本示例乃是一个 Session 认证登录的范例,肯定可用。倘若您依此例仍无法使程序得以运行,可私信于我索求此示例的源工程代码。
为了避免版本差异导致的不可用,请仔细阅读注意事项:
1、此项目通过BootStrapBlazor项目模板直接创建
2、此项目使用的是纯Server模式,.net 8.0框架
3、使用了SQL Server 2016版本,数据库连接采用FreeSql,开发使用VS2022,系统为win10 22H2版本
项目文件说明
使用bootstrapblazor模版创建基于Server的工程后,删除了其中可能影响你编写代码的示例,增加了Session认证的部分,最终保留的文件如下
API:实现Session登录认证和退出的API控制器
Login:实现登录的页面和模板文件
OA.cs:实现密码加密的功能,无关的代码已清理掉了
UserList.cs:数据实体类,用户的信息存放于此,相应的数据库中也得有相同的表
上面这些都是新增的,其余的为删除后保留的,你也可以不删除,不影响运行。
数据实体类和OA类
数据实体类直接复制代码便可用,登录只使用到了用户_账户表,其他的可以删除,在数据库中,表名和字段都是中文名称,如果你的不是,请修改这里的实体类就行。
public class UserList
{
}
[Table("用户_账户")]
public class UserAccout
{
[Key]
public int UID { get; set; }
[Required(ErrorMessage = "用户名不能为空")]
public string? 用户名 { get; set; }
[Required(ErrorMessage = "密码不能为空")]
public string? 密码 { get; set; }
public string? 姓名 { get; set; }
public DateTime? 登录日期 { get; set; }
public int? 角色ID { get; set; }
}
[Table("用户_角色")]
public class UserRole
{
[Key]
public int UID { get; set; }
public string? 角色名称 { get; set; }
public int? 角色ID { get; set; }
}
[Table("用户_权限")]
public class UserQx
{
[Key]
public int id { get; set; }
public string? 用户名 { get; set; }
public string? 权限id { get; set; }
public string? 状态 { get; set; }
}
OA类,对用户输入的密码进行加密,并返回加密后的字符串
/// <summary>
/// SHA256加密,返回加密后的字符串
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
public static string SHA256EncryptString(string data)
{
byte[] bytes = Encoding.UTF8.GetBytes(data);
byte[] hash = SHA256.Create().ComputeHash(bytes);
StringBuilder builder = new StringBuilder();
for (int i = 0; i < hash.Length; i++)
{
builder.Append(hash[i].ToString("x2"));
}
return builder.ToString();
}
登录验证API控制器
新建API文件夹,新建控制器文件AccountController,可直接复制使用,FreeSql使用构造注入
[Microsoft.AspNetCore.Mvc.Route("api/[controller]/[action]")]
[ApiController]
public class AccountController : ControllerBase
{
private readonly IFreeSql fsql;
public AccountController(IFreeSql _fsql)
{
fsql = _fsql;
}
public object Login([FromBody]UserAccout user)
{
var t = fsql.Select<UserAccout>().Where(p=>p.用户名==user.用户名 && p.密码==user.密码).ToOne();
if (t != null)
{
var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme);
identity.AddClaim(new Claim(ClaimTypes.Name, user.用户名));
HttpContext.SignInAsync(new ClaimsPrincipal(identity), new AuthenticationProperties()
{
IsPersistent = true,
ExpiresUtc = DateTimeOffset.Now.AddHours(8)
});
return new { code = 2000, Message = "登录成功" };
}
else
{
return new { code = 5000, Message = "登录失败" };
}
}
[HttpGet]
[Authorize]
public async Task<IActionResult> Logout()
{
await HttpContext.SignOutAsync();
return Redirect("/login");
}
}
登录页面
新建login文件夹,新建登录页面和模板,用于实现登录功能,登录页面改造于bootstrapblazor的示例,简化掉了一些不必要的元素。
Login.razor界面,@attribute [AllowAnonymous]表示所有人都可以访问,不需要权限
@page "/Login"
@layout LoginLayout
@inject NavigationManager NavigationManager
@attribute [AllowAnonymous]
<div class="login-item">
<div class="text-center" style="font-size:18px; padding-bottom:30px;">
</div>
<ValidateForm Model="@user" OnValidSubmit="SubmitHandler">
<BootstrapInput @bind-Value="@user.用户名" ShowLabel="false" PlaceHolder="请输入账号" />
<BootstrapPassword @bind-Value="@pwd" ShowLabel="false" PlaceHolder="请输入密码" />
<hr />
<Button Text="登录" Color="Color.Success" ButtonType="ButtonType.Submit"></Button>
<br /><br />
</ValidateForm>
</div>
Login.razor.cs代码文件,如果没有登录的话转到登录页,如果已经登录转到主页。
using Test.Data;
using Test.Models;
using BootstrapBlazor.Components;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.AspNetCore.Identity;
using Microsoft.IdentityModel.Tokens;
using Microsoft.VisualBasic;
using System.Diagnostics.CodeAnalysis;
using System.Reflection.Metadata;
namespace Test.Components.Login
{
public partial class Login
{
private UserAccout user { get; set; }=new UserAccout();
private string pwd { get; set; } = "";
//弹窗提示
[Inject]
[NotNull]
private SwalService? SA { get; set; }
[Inject] AjaxService? Ajax { get; set; }
[Inject] NavigationManager NM { get; set; }
[Inject]AuthenticationStateProvider AUTH { get; set; }
[Inject]IFreeSql fsql{ get; set; }
private async Task SubmitHandler(EditContext arg)
{
var t=fsql.Select<UserAccout>().ToList();
if (string.IsNullOrEmpty(user.用户名) || string.IsNullOrEmpty(pwd))
{
await SA.Show(new SwalOption()
{
Category = SwalCategory.Information,
Content = #34;用户名或密码不能为空!",
});
}
else
{
user.密码 = OA.SHA256EncryptString(pwd);
var doc=await Ajax.InvokeAsync(new AjaxOption()
{
Url="/api/Account/Login",
Data=user,
});
if (doc?.RootElement.GetProperty("code").GetInt32() == 2000)
{
await Ajax.Goto("/");
await SA.Show(new SwalOption()
{
Category = SwalCategory.Information,
Content = #34;登录成功!",
});
}
else
{
await SA.Show(new SwalOption()
{
Category = SwalCategory.Information,
Content = #34;登录出错!",
});
}
}
}
protected override async void OnInitialized()
{
base.OnInitialized();
var user=(await AUTH.GetAuthenticationStateAsync()).User;
if (user?.Identity?.IsAuthenticated==true)
{
NM.NavigateTo("/");
}
}
}
}
登录页使用的LoginLayout模板文件
@inherits LayoutComponentBase
<html >
<body >
<BootstrapBlazorRoot>
<div class="container">
<div class="row" style="padding-top:260px;">
<div class="col-12">
<Card class="shadow-lg border-0 my-5">
<HeaderTemplate>
<div style="text-align:center;width:100%;font-size:18px;">
欢迎使用内部系统
</div>
</HeaderTemplate>
<BodyTemplate>
@Body
</BodyTemplate>
</Card>
</div>
</div>
</div>
</BootstrapBlazorRoot>
</body>
</html>
模板的样式文件
.layout-drawer-body {
padding: 1rem;
background-color:red
}
.layout-drawer-body ::deep .groupbox {
margin-top: 1rem;
}
.layout-drawer-body ::deep .btn-info {
margin-bottom: 1rem;
}
.layout-item {
--bb-layout-sidebar-bg: #f8f9fa;
--bb-layout-footer-bg: #e9ecef;
cursor: pointer;
border: 2px solid #e9ecef;
padding: 4px;
border-radius: 4px;
height: 80px;
width: 120px;
transition: border .3s linear;
}
.layout-item:hover,
.layout-item.active {
border: 2px solid #28a745;
}
.layout-item .layout-left {
width: 30%;
border-right: 1px solid var(--bs-border-color);
}
.layout-item .layout-left .layout-left-header {
height: 16px;
background-color: var(--bb-header-bg);
}
.layout-item .layout-left .layout-left-body,
.layout-item .layout-body .layout-left {
background-color: var(--bb-layout-sidebar-bg);
}
.layout-item .layout-right .layout-right-header,
.layout-item .layout-top {
background-color: var(--bb-header-bg);
height: 16px;
}
.layout-item .layout-right .layout-right-footer,
.layout-item .layout-right-footer {
background-color: var(--bb-layout-footer-bg);
height: 12px;
}
.layout-item .layout-top,
.layout-item .layout-body,
.layout-item .layout-right-footer {
width: 100%;
}
#blazor-error-ui {
background: lightyellow;
bottom: 0;
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
display: none;
left: 0;
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
position: fixed;
width: 100%;
z-index: 1000;
}
#blazor-error-ui .dismiss {
cursor: pointer;
position: absolute;
right: 0.75rem;
top: 0.5rem;
}
.back-main {
background-color: aqua;
background-image: url(../images/bg.jpg);
background-position: center;
background-repeat: no-repeat;
background-attachment: fixed;
background-size: cover;
overflow: hidden;
margin: 0;
padding: 0;
}
.container {
--login-max-width: 540px;
--login-padding-x: 1.5rem;
--login-padding-y: .75rem;
--bs-border-radius: 10px;
max-width: var(--login-max-width);
margin: 0 auto;
}
::deep h4 {
margin-bottom: 1.5rem;
}
::deep .form-control {
--bb-form-control-padding: var(--login-padding-y) var(--login-padding-x);
font-size: .875rem;
margin-bottom: 1rem;
}
::deep .btn {
--bs-btn-padding-x: var(--login-padding-x);
--bs-btn-padding-y: var(--login-padding-y);
width: 100%;
margin-bottom: 1rem;
}
::deep .btn:first-of-type {
margin-bottom: 0;
}
::deep .form-check {
padding: var(--login-padding-y) var(--login-padding-x);
margin-bottom: 1rem;
}
html {
height: 100%;
background-image: url(../images/bg2.jpeg);
background-position: center;
background-repeat: no-repeat;
background-attachment: fixed;
background-size: cover;
overflow: hidden;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
body {
background-image: url(../images/bg2.jpeg);
background-position: center;
background-repeat: no-repeat;
background-attachment: fixed;
background-size: cover;
}
跳转组件,应该是没用,当时留下来做测试用的
<h3>RedirectToLogin</h3>
@inject NavigationManager Nav
@code {
protected override void OnInitialized()
{
base.OnInitialized();
Nav.NavigateTo("/Login", true);
}
}
其他原有文件的修改
_imports.razor文件,在最下面增加一句@attribute [Authorize],表示需要通过认证才能访问
routes.razor文件,在.net8.0中需要写成下面这样子,和以前不一样了,默认使用的模板是MainLayout
<Router AppAssembly="@typeof(Program).Assembly">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
</Found>
</Router>
program.cs文件,app.MapDefaultControllerRoute();这一句一定要有,不然在登录时不会执行API控制器中的内容,FreeSql服务也在这里注入。
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.SignalR;
using System.Text;
using Test.Components;
using Test.Data;
using FreeSql;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
builder.Services.AddRazorComponents().AddInteractiveServerComponents();
IFreeSql _fsql = new FreeSql.FreeSqlBuilder()
.UseConnectionString(FreeSql.DataType.SqlServer, "Data Source=192.168.3.111;User Id=sa;Password=数据库密码;Initial Catalog=数据库名称;Encrypt=True;TrustServerCertificate=True;Pooling=true;Min Pool Size=1")
.Build();
builder.Services.AddSingleton<IFreeSql>(_fsql);
builder.Services.AddBootstrapBlazor();
//添加压缩中间件,不然部署到IIS上时会报错
builder.Services.AddResponseCompression();
builder.Services.AddControllers();
builder.Services.AddAuthenticationCore();
builder.Services.AddCascadingAuthenticationState();
builder.Services.AddHttpContextAccessor();
//添加Cookies认证中间件
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(options =>
{
options.Cookie.HttpOnly = true;
options.LoginPath = "/login";
});
// 增加 SignalR 服务数据传输大小限制配置
builder.Services.Configure<HubOptions>(option => option.MaximumReceiveMessageSize = null);
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseResponseCompression();
}
//出错直接使用自带的error模版,而不是路由中的
app.UseStatusCodePagesWithRedirects("/Error");
//添加Cookie认证中间件
app.UseAuthentication();
app.UseAuthorization();
app.MapDefaultControllerRoute();
//结束
app.UseStaticFiles();
app.UseAntiforgery();
app.MapRazorComponents<App>().AddInteractiveServerRenderMode();
app.Run();
用户登录以后,如何在其他页面中获取登录的用户账号信息了,以index.razor为例,
@page "/"
@attribute [TabItemOption(Text = "Index", Closable = false)]
<PageTitle>Index</PageTitle>
<h1>各位好!</h1>
欢迎使用Blazor程式,当前登录用户为:@UserName
@code
{
private string? UserName { get; set; }
private string? Name { get; set; }
[Inject] AuthenticationStateProvider Auth { get; set; }
protected override async void OnInitialized()
{
base.OnInitialized();
var user = (await Auth.GetAuthenticationStateAsync()).User;
UserName = user.Identity.Name;
}
}
获取到用户账号信息后,就可以通过用户账号去查询用户是什么角色,具有哪些权限了,到此就已经很清晰的说完了。
在这里无法上传附件信息,有需要的请私信交流。
猜你喜欢
- 2025-01-13 alibaba cola 框架代码生成器
- 2025-01-13 Web前端【超出隐藏与显示】多种解决方案集锦
- 2025-01-13 商城微服务项目组件搭建(五)——Kafka、Tomcat等安装部署
- 2025-01-13 写给设计师的网页设计简史
- 2025-01-13 深入理解Java虚拟机之自己编译JDK
- 2025-01-13 谷歌正式发布Android 12,UI更好看,打造属于自己的定制化属性
- 2025-01-13 零基础开始学 Web 前端开发,有什么建议?(附视频教程)
- 2025-01-13 iOS 13全系统越狱详细教程&疑难解答
- 2025-01-13 「技术分享」你知道微前端吗?
- 2025-01-13 微前端方案 qiankun(实践及总结)
- 05-162025前端最新面试题之HTML和CSS篇
- 05-16大数据开发基础之HTML基础知识
- 05-16微软专家告诉你Win10 Edge浏览器和EdgeHTML的区别
- 05-16快速免费将网站部署到公网方法(仅支持HTML,CSS,JS)
- 05-16《从零开始学前端:HTML+CSS+JavaScript的黄金三角》
- 05-16一个简单的标准 HTML 设计参考
- 05-16css入门
- 05-16前端-干货分享:更牛逼的CSS管理方法-层(CSS Layers)
- 最近发表
- 标签列表
-
- jsp (69)
- pythonlist (60)
- gitpush (78)
- gitreset (66)
- python字典 (67)
- dockercp (63)
- gitclone命令 (63)
- dockersave (62)
- linux命令大全 (65)
- pythonif (86)
- location.href (69)
- dockerexec (65)
- deletesql (62)
- c++模板 (62)
- linuxgzip (68)
- 字符串连接 (73)
- nginx配置文件详解 (61)
- html标签 (69)
- c++初始化列表 (64)
- mysqlinnodbmyisam区别 (63)
- arraylistadd (66)
- console.table (62)
- mysqldatesub函数 (63)
- window10java环境变量设置 (66)
- c++虚函数和纯虚函数的区别 (66)