最近在写单位项目时,需要写一个权限管理,就是不同的用户对应的权限需要不同。第一次接触这方面的内容,在搜集资料和实践后,总结一下开发经验。按照先总后分的顺序来记录:
总的思路
正常的RABC包括三个角色:用户、角色以及权限。用户跟角色对应,角色与权限对应。但是,我的项目并没有这么复杂的需求,因此降低了难度,只做了用户以及对应的权限。相应的表格,就增加了三个:User,Permission以及UserPermission。表格部分可以参考这篇博文:
https://blog.csdn.net/qq_33285360/article/details/129837803?spm=1001.2014.3001.5502
然后是C#部分的代码,这里主要是利用过滤器的技术。过滤器的相关内容这里不赘述,可以自己翻阅一下资料,我这里用的是ActionFilterAttribute。C#部分的代码我认为主要有三个方面:第一个是过滤器的内容,第二个是过滤器的注册,第三个是登录部分的代码。下面对具体的代码进行记录,由于表格比较简单,参考那篇文章即可,下面主要对C#部分代码进行分析:
代码部分详解:
1.过滤器内容
我先贴代码
public class UserPermissionAttribute : ActionFilterAttribute
{
// 数据库连接字符串(实际项目中放在配置文件)
string MyConn = "server=.;database=didi;Trusted_Connection=true;Max Pool Size=500";
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
// 关键:检查当前请求是否有[AllowAnonymous]特性,有则直接跳过验证
var allowAnonymous = filterContext.ActionDescriptor.IsDefined(typeof(AllowAnonymousAttribute), true)
|| filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(AllowAnonymousAttribute), true);
if (allowAnonymous)
{
base.OnActionExecuting(filterContext);
return; // 跳过后续验证
}
// 获取当前用户身份
var user = filterContext.HttpContext.User.Identity;
Debug.WriteLine("用户是否登录:"+user.IsAuthenticated+"用户名:"+user.Name);
// 未登录用户拒绝访问
if (!user.IsAuthenticated)
{
filterContext.Result = new HttpUnauthorizedResult();
return;
}
// 构建当前操作所需权限标识(控制器名.动作名)
var requiredPermission = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName + "." +
filterContext.ActionDescriptor.ActionName;
Debug.WriteLine("需要的权限"+requiredPermission);
// 检查用户是否直接拥有该权限
if (!UserHasPermission(user.Name, requiredPermission))
{
filterContext.Result = new HttpStatusCodeResult(System.Net.HttpStatusCode.Forbidden);
return;
}
//这句话表示该过滤器是在动作方法前执行
base.OnActionExecuting(filterContext);
}
/// <summary>
/// 检查用户是否直接拥有指定权限
/// </summary>
private bool UserHasPermission(string userName, string permissionName)
{
using (var connection = new SqlConnection(MyConn))
{
// SQL查询:通过用户名关联用户表和用户权限表,检查是否存在匹配权限
string query = "SELECT COUNT(*) FROM UserPermissions up INNER JOIN Users u ON up.UserId = u.Id INNER JOIN Permissions p ON up.PermissionId = p.PermissionId WHERE u.UserName = @UserName AND p.PermissionName = @PermissionName";
using (var command = new SqlCommand(query, connection))
{
// 添加参数防止SQL注入
command.Parameters.AddWithValue("@UserName", userName);
command.Parameters.AddWithValue("@PermissionName", permissionName);
connection.Open();
// 执行查询并返回结果(大于0表示有权限)
var result=Convert.ToInt32(command.ExecuteScalar());
Debug.WriteLine("查询结果"+result);
return result > 0;
}
}
}
}
核心部分是函数UserHasPermission,当用户登录以后,会查询数据库。传入的参数中,user.Name是用户登录时输入的名字,而requiredPermission是controller.action的名字,通过对比数据库中UserPermission的内容,如果有,则符合权限,true,如果没有,则是false。由于将过滤器UserPermissionAttribute设置为全局,因此还有一小部分代码是用于登录时的[allowAnonymous]。
2.过滤器的注册
namespace qbxt
{
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new UserPermissionAttribute());
filters.Add(new HandleErrorAttribute());
}
}
}
这里不做赘述,设为了全局代码
3.登录界面的后端
namespace didi.Controllers
{
public class LoginController : Controller
{
// GET: /Login/
[AllowAnonymous] // 允许匿名访问登录页
public ActionResult Index()
{
// 如果已经登录,直接跳转到首页
if (User.Identity.IsAuthenticated)
{
return Redirect("/one_leida/Index");
}
return View();
}
// POST: /Login/Login (建议使用HttpPost特性限定提交方式)
//[HttpPost]
//[ValidateAntiForgeryToken] // 防止CSRF攻击
[AllowAnonymous]
public ActionResult Login(string username, string password)
{
// 基本参数验证
if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password))
{
ViewBag.error = "用户名和密码不能为空!";
return View();
}
string connectionString = "server=.;database=qbxt;Trusted_Connection=true;Max Pool Size=500";
using (SqlConnection connection = new SqlConnection(connectionString))
{
try
{
connection.Open();
string query = "SELECT * FROM [user] WHERE username = @Username AND password = @Password";
using (SqlCommand command = new SqlCommand(query, connection))
{
command.Parameters.AddWithValue("@Username", username);
command.Parameters.AddWithValue("@Password", password); // 注意:实际项目需加密存储密码
using (SqlDataReader reader = command.ExecuteReader())
{
if (reader.Read())
{
// 关键修复:创建用户认证票据,标记用户为已登录
// 参数1:用户名;参数2:是否记住登录状态
FormsAuthentication.SetAuthCookie(username, createPersistentCookie: false);
// 可选:记录用户ID到Session(如果后续需要)
Session["userId"] = reader["Id"];
// 登录成功跳转
return Redirect("/one_leida/Index");
}
else
{
ViewBag.error = "用户名或密码错误!";
}
}
}
}
catch (SqlException ex)
{
// 数据库异常处理(避免暴露敏感信息)
ViewBag.error = "数据库访问错误,请稍后重试";
// 实际项目中应记录详细日志
// Logger.Error("登录数据库错误", ex);
}
catch (Exception ex)
{
ViewBag.error = "系统错误,请稍后重试";
// Logger.Error("登录系统错误", ex);
}
}
return View();
}
// 退出登录功能
public ActionResult Logout()
{
// 清除认证状态
FormsAuthentication.SignOut();
// 清除Session
Session.Abandon();
// 重定向到登录页
return RedirectToAction("Index");
}
}
}
核心部分就是:public ActionResult Login(string username, string password)
从登录界面的前端获取用户名和密码,这里有个关键语句:
FormsAuthentication.SetAuthCookie(username, createPersistentCookie: false);
用于记录登录状态。并且记录了用户信息,为后面过滤器验证时提供了验证信息。值得注意的是,如果要使用FormsAuthentication,需要先注册。下面是web.config部分的注册内容:
<system.web>
<authentication mode="Forms">
<forms
loginUrl="~/Login/Index"
timeout="2880"
name=".ASPXAUTH"
path="/"
requireSSL="false"
slidingExpiration="true" />
</authentication>
</system.web>
以上是实现该功能的一个大致思路。

1684

被折叠的 条评论
为什么被折叠?



