ASP.NET MVC服务器端Cookie过期引发空引用异常问题
问题诊断与解决方案
核心问题分析
上线后出现的空引用错误,确实是**roleId Cookie过期/丢失**导致的:
- 登录时创建的
roleIdCookie未设置过期时间,默认是会话级Cookie——当用户长时间无操作、浏览器后台被回收,或服务器会话超时后,该Cookie会被自动清除 NavigationPartial方法直接调用role.Value,未做空值判断,一旦Cookie丢失就触发空引用异常- 本地测试时会话不会轻易中断,因此问题不会暴露;服务器环境下用户会话持续周期更长,超时后问题显现
修复步骤
1. 给roleId Cookie设置持久化过期时间
在登录代码中,创建roleId Cookie时,同步设置与Forms认证Cookie一致的过期时间,避免Cookie提前失效:
HttpCookie role = new HttpCookie("roleId", roleId.ToString()); // 设置过期时间,与FormsAuthCookie保持一致(示例为7天,可根据业务调整) role.Expires = DateTime.Now.AddDays(7); // 开启HttpOnly和Secure提升安全性 role.HttpOnly = true; role.Secure = Request.IsSecureConnection; // 生产环境建议强制设为true(需配置HTTPS) Response.Cookies.Add(role);
2. 增加空值检查,避免空引用异常
修改NavigationPartial方法,先判断Cookie是否存在,再执行后续逻辑:
public PartialViewResult NavigationPartial() { List<KategoriModel> MenuKategoriModel = new List<KategoriModel>(); try { HttpCookie role = Request.Cookies.Get("roleId"); // 空值检查:Cookie不存在时引导用户重新登录 if (role == null || string.IsNullOrEmpty(role.Value)) { return RedirectToAction("Index", "Login"); } int roleId = Convert.ToInt32(role.Value); // 后续数据库查询代码保持不变... } catch (Exception ex) { Logger.LogRegister(ex.Message + " 导航分部视图部分:", ex.StackTrace, ex.Source); return PartialView(); } return PartialView(MenuKategoriModel); }
3. 优化方案:从Forms认证Cookie中解析角色ID
无需单独存储roleId Cookie,直接从Forms认证的UserData中解析角色信息,避免多Cookie不一致问题:
- 登录时精简UserData(务必移除明文密码):
// 只存储必要信息,移除明文密码 string userData = $"{roleId}&{email}"; FormsAuthentication.SetAuthCookie(userData, true);
- 在
NavigationPartial中解析角色ID:
public PartialViewResult NavigationPartial() { List<KategoriModel> MenuKategoriModel = new List<KategoriModel>(); try { if (User.Identity.IsAuthenticated) { FormsIdentity identity = (FormsIdentity)User.Identity; FormsAuthenticationTicket ticket = identity.Ticket; string userData = ticket.UserData; string[] userDataParts = userData.Split('&'); if (userDataParts.Length >=1 && int.TryParse(userDataParts[0], out int roleId)) { // 后续数据库查询代码保持不变... } else { // 数据格式异常,引导重新登录 return RedirectToAction("Index", "Login"); } } else { // 用户未认证,引导登录 return RedirectToAction("Index", "Login"); } } catch (Exception ex) { Logger.LogRegister(ex.Message + " 导航分部视图部分:", ex.StackTrace, ex.Source); return PartialView(); } return PartialView(MenuKategoriModel); }
安全建议
- 立即移除
UserData中的明文密码,修改密码时直接从数据库查询用户信息,避免泄露风险 - 生产环境强制开启Cookie的
Secure属性(需配置HTTPS),防止Cookie被明文传输窃取 - 导航部分的数据库查询存在SQL注入风险,改为参数化查询:
string query = @"SELECT MENU_ID,MENU_AD, PARENT_ID, CONTROLLER_AD, MENU_ICON, ACTIVE_FLAG, ACTION_AD FROM ( SELECT MENU_ID,PARENT_ID, CONTROLLER_AD,MENU_AD, MENU_ICON, ACTIVE_FLAG, ACTION_AD, ROW_NUMBER() OVER (PARTITION BY MENU_ID ORDER BY 1) AS rn FROM Z_TMP_PORTAL_MENU ztpm START WITH MENU_ID IN (SELECT MENU_ID FROM Z_TMP_PORTAL_MENUROLE_RS ztpmr WHERE ROLE_ID = :roleId AND ACTIVE_FLAG='Y' ) CONNECT BY PRIOR PARENT_ID = MENU_ID) WHERE rn = 1"; using (OracleCommand cmd = new OracleCommand(query, con)) { cmd.Parameters.Add(":roleId", OracleDbType.Int32).Value = roleId; // 后续执行代码... }
内容的提问来源于stack exchange,提问作者Osman TÜRK
相关产品推荐
相关产品推荐

