通过一些代码过滤验证,与前端使用交互token实现限制访问端,避免使用非正常工具访问,应可满足一些等保需求。
控制器代码:
using Dcampus.Controllers;
using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web.Http.Results;
using System.Web.Mvc;
using Common;
namespace Dcampus.Areas.Admin_Areas.Controllers
{
/// <summary>
/// 新学生端,所有方法只支持post方式
/// </summary>
public class StunController : BaseController
{
#region 学生端返回数据封装
/// <summary>
/// 交互token字典,key=用户token,value=当前交互token
/// </summary>
private static Dictionary<string, Vobj> StuTokenDic = new Dictionary<string, Vobj>();
/// <summary>
/// 用户访问对象
/// </summary>
protected class Vobj
{
/// <summary>
/// 身份token
/// </summary>
public string token { get; set; }
private Queue<string> cts = new Queue<string>();
/// <summary>
/// 交互token
/// </summary>
public Queue<string> changeToken { get { return cts; } set { cts = value; } }
/// <summary>
/// 访问次数
/// </summary>
public int vnums { get; set; }
/// <summary>
/// 最后访问时间
/// </summary>
public DateTime lastTime { get; set; }
/// <summary>
/// 禁止访问
/// </summary>
public bool disv { get; set; }
/// <summary>
/// 最后访问的路径
/// </summary>
public string lastUrl { get; set; }
public Vobj(string token, string lastToken, DateTime lastTime, string lastUrl)
{
this.token = token;
this.changeToken.Enqueue(lastToken);
this.lastTime = lastTime;
this.lastUrl = lastUrl;
}
}
protected JsonResult Msg(AjaxStatu status, object data = null, bool sendtoken = true)
{
Hashtable hs = new Hashtable();
hs.Add("status", status);
hs.Add("data", data);
try
{
if (sendtoken)
{
hs.Add("ctoken", GetChangeToken());
}
}
catch (Exception e)
{
hs["status"] = AjaxStatu.err;
hs["data"] = string.Format("{0},请联系管理员,并提供当前使用场景截图。", e.Message);
}
return Json(hs);
}
protected JsonResult ActFunc(Action func, bool chktoken = true)
{
if (chktoken)
{
try
{
ChkChangeToken();//先检查交互数据token
}
catch (Exception e)
{
return Msg(AjaxStatu.err, e.Message, false);
}
}
try
{
func();
return Msg(AjaxStatu.ok);
}
catch (Exception e)
{
return Msg(AjaxStatu.err, e.Message);
}
}
protected JsonResult ActFunc(Func<object> func, bool chktoken = true)
{
if (chktoken)
{
try
{
ChkChangeToken();//先检查交互数据token
}
catch (Exception e)
{
return Msg(AjaxStatu.err, e.Message, false);
}
}
try
{
object data = func();
return Msg(AjaxStatu.ok, data);
}
catch (Exception e)
{
return Msg(AjaxStatu.err, e.Message);
}
}
/// <summary>
/// 获取交互token
/// </summary>
/// <returns></returns>
private string GetChangeToken()
{
//未登录
if (Session["stutoken"] == null)
{
throw new Exception("未登录");
}
string token = Session["stutoken"].ToString();
//未找到用户token
if (!StuTokenDic.ContainsKey(token))
{
throw new Exception("请使用合法的访问方式,原因为一");
}
//交互token为空
if (StuTokenDic[token] == null)
{
throw new Exception("交互数据出现异常");
}
//一定时间段内操作超过一定次数
var last = StuTokenDic[token];
if (last.disv)
{
throw new Exception("已禁止访问,请退出登录后,再重新登录");
}
TimeSpan ts = DateTime.Now - last.lastTime;
int secs = 5;//访问间隔 5 秒
int nums = 5;//访问次数 5
if (Request.Url.AbsolutePath == last.lastUrl)
{
if (ts.Seconds <= secs)
{
if (last.vnums >= nums)
{
last.disv = true;
StuTokenDic[token] = last;//禁止访问
throw new Exception("请使用合法的访问方式,原因为二");
}
else
{
last.vnums++;
}
}
else
{
last.lastTime = DateTime.Now;
last.vnums = 0;
}
}
else
{
last.lastTime = DateTime.Now;
last.vnums = 0;
}
last.lastUrl = Request.Url.AbsolutePath;
string lasttoken = DataAll.Dal.Common.CreateToken();
last.changeToken.Enqueue(lasttoken);
while (last.changeToken.Count > 20)
{
last.changeToken.Dequeue();
}
StuTokenDic[token] = last;
return lasttoken;
}
/// <summary>
/// 检查当前用户的交互token是否一致
/// </summary>
/// <returns></returns>
private void ChkChangeToken()
{
//此时应已登录
var last = StuTokenDic[Session["stutoken"].ToString()];
string changeToken = Request.Form["ctoken"];
if (string.IsNullOrEmpty(changeToken)) { throw new Exception("参数异常"); }
if (!last.changeToken.Contains(changeToken))
{
throw new Exception("交互数据出现异常,请使用合法的访问方式");
}
}
#endregion
/// <summary>
/// 获取当前登录用户的token
/// </summary>
/// <remarks>此方法不限制访问次数</remarks>
/// <returns></returns>
//[AllowAnonymous]
public JsonResult getCurrent(string sid)
{
return ActFunc(() =>
{
string url = Request.Url.AbsolutePath;
if (Session["stutoken"] != null)
{
string token = Session["stutoken"].ToString();
if (!StuTokenDic.ContainsKey(token))
{
StuTokenDic.Add(token, new Vobj(token, DataAll.Dal.Common.CreateToken(), DateTime.Now, url));
}
return new { token = token };
}
else
{
if (!string.IsNullOrEmpty(sid))
{
if (oc.CurrentUser != null)
{
if (oc.CurrentUser.barcode == sid)
{
string token = DataAll.Dal.Common.CreateToken();
Session["stutoken"] = token;
StuTokenDic.Add(token, new Vobj(token, DataAll.Dal.Common.CreateToken(), DateTime.Now, url));
return new { token = token, ctoken = StuTokenDic[token].changeToken };
}
else
{
throw new Exception("未登录");
}
}
else
{
throw new Exception("未登录");
}
}
else
{
throw new Exception("未登录");
}
}
}, false);
}
/// <summary>
/// 获取用户权限菜单树
/// </summary>
/// <returns></returns>
public JsonResult getPerTree()
{
return ActFunc(() =>
{
return oc.UserPermission;
});
}
/// <summary>
/// 退出登录
/// </summary>
/// <returns></returns>
public void logout()
{
string token = Session["stutoken"].ToString();
StuTokenDic.Remove(token);
Session.Abandon();
}
}
}
mvc中的过滤器,加入对stun的免过滤判断
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
// filters.Add(new HandleErrorAttribute());
//注册过滤器
//filters.Add(new LoginValidateAttribute());
//filters.Add(new DataAll.Filter.LoginValidateAttribute());
filters.Add(new LoginAttribute());
filters.Add(new CustomErrorProcessAttribute());
}
}
public class LoginAttribute : AuthorizeAttribute
{
#region 在过程请求授权时调用
//
// 摘要:
// 在过程请求授权时调用。
//
// 参数:
// filterContext:
// 筛选器上下文,它封装有关使用 System.Web.Mvc.AuthorizeAttribute 的信息。
//
// 异常:
// System.ArgumentNullException:
// filterContext 参数为 null。
public override void OnAuthorization(AuthorizationContext filterContext)
{
string strAreaName = "";
if (filterContext.RouteData.DataTokens["area"] != null)
strAreaName = filterContext.RouteData.DataTokens["area"].ToString().ToLower();
string strControllerName = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName.ToLower();
string strActionName = filterContext.ActionDescriptor.ActionName.ToLower();
string strHttpMethod = filterContext.HttpContext.Request.HttpMethod;
//新学生端跳过验证
if (strControllerName == "stun")
{
return;
}
var actionAnonymous = filterContext.ActionDescriptor.GetCustomAttributes(typeof(AllowAnonymousAttribute), true) as IEnumerable<AllowAnonymousAttribute>;
if (actionAnonymous != null && actionAnonymous.Any())
{
return;
}
/**
* 如果请求的区域包含area并且area的名称等于Admin_Areas
* 那么就进行权限验证
* */
if (filterContext.RouteData.DataTokens.Keys.Contains("area"))
{
/**
* 验证用户是否登录
* */
if (filterContext.HttpContext.Request.Cookies.Get(OperContext.LOGINID) == null || string.IsNullOrEmpty(filterContext.HttpContext.Request.Cookies.Get(OperContext.LOGINID).Value))
{
#region 开发期自动登录
string isdevelop = System.Configuration.ConfigurationManager.AppSettings["isdevelop"];
if (!string.IsNullOrEmpty(isdevelop))
{
DataAll.Dal.Users.UserImpl.Login("wyl", "1234", "wyl");
return;
}
else
{
filterContext.Result = new BaseController().Redirect("/AdminLogin/Login?msg=noLogin1", filterContext.ActionDescriptor, AjaxStatu.nologin);
}
#endregion
return;
}
if (!Model_sys_users.IsLogin())
{
string isdevelop = System.Configuration.ConfigurationManager.AppSettings["isdevelop"];
if (!string.IsNullOrEmpty(isdevelop))
{
DataAll.Dal.Users.UserImpl.Login("wyl", "1234", "wyl");
return;
}
else
{
//如果没有登录那么就跳转到登录页面
filterContext.Result = new BaseController().Redirect("/AdminLogin/Login?msg=noLogin2", filterContext.ActionDescriptor, AjaxStatu.nologin);
}
}
else
{
List<string> roles = (List<string>)HttpContext.Current.Session["userRole"];
string limstr = System.Configuration.ConfigurationManager.AppSettings["stulimit"];
if (!string.IsNullOrEmpty(limstr))
{
string[] lims = limstr.Split('|');
if (roles != null)
{
if (roles.Count == 1)
{
if (roles[0].ToLower() == "student")
{
bool allow = false;
foreach (string c in lims)
{
if (c.ToLower() == strControllerName.ToLower())
{
allow = true;
break;
}
}
if (!allow)
{
filterContext.Result = new BaseController().Redirect("/AdminLogin/Login?msg=noPermission", filterContext.ActionDescriptor, AjaxStatu.noperm);
return;
}
}
}
}
}
//设置滑动时间
Model_sys_users.SetNewTime();
//if (Model_USERS.LoginName == "admin")
// return;
/**
* Action方法本身及它所属控制器都没有定义Skip特性
* 那么就可以进行权限验证
* */
if (!filterContext.ActionDescriptor.AttributeExists<Common.Attributes.SkipAttribute>(false)
&& !filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(Common.Attributes.SkipAttribute), false))
{
//验证该登录用户是否有访问该页面的权限
HttpMethod httpMethod = strHttpMethod.ToLower().Equals("get") ? HttpMethod.Get
: strHttpMethod.ToLower().Equals("post") ? HttpMethod.Post : HttpMethod.HEAD;
if (Model_sys_module.IsCheckPermission(strAreaName, strControllerName, strActionName))//需验证
if (!Model_sys_module.HasPermission(strAreaName, strControllerName, strActionName))
{
filterContext.Result = new BaseController().Redirect("/AdminLogin/Login?msg=noPermission", filterContext.ActionDescriptor, AjaxStatu.noperm);
}
}
}
}
}
#endregion
}
vue中的路由验证,放在app.vue中的mounted中
mounted() {
//刷新浏览器时清除缓存
tools.delCookie(tools.utoken);
tools.delCookie(tools.ctoken);
//重新获取token
this.axios.post("/admin_areas/stun/getCurrent").then(response => {
tools.chkResult(
response.data,
function() {
tools.setCookie(tools.utoken, response.data.data.token);
tools.setCookie(tools.ctoken, response.data.ctoken);
tools.post("/admin_areas/stun/test1", null, function(res) {
console.log(res);
});
tools.post("/admin_areas/stun/getpertree", null, function(res) {
console.log(res);
});
},
function() {
window.location.href = "/adminlogin/login";
}
);
});
// this.axios.post("/admin_areas/res/gettitle").then(response => {
// this.title = response.data;
// });
},
tools.vue中封装cookie和post方法,post方法中给参数加入ctoken值,并用chkResult来接收返回值取出ctoken写入cookie
<script>
import axios from 'axios'
import qs from 'qs'
const utoken = "edustu";
const ctoken = "dbtoken";
function getCookie(name) {
var arr,
reg = new RegExp("(^| )" + name + "=([^;]*)(;|$)");
if ((arr = document.cookie.match(reg))) return unescape(arr[2]);
else return null;
}
function setCookie(name, value) {
document.cookie = name + "=" + escape(value) + ";";
}
function delCookie(name) {
var date = new Date();
date.setTime(date.getTime() - 1);
var delValue = getCookie(name);
if (!!delValue) {
document.cookie = name + "=" + delValue + ";expires=" + date.toGMTString();
}
}
function chkResult(data, funok, funerr) {
if (data.data.status == 1) {
setCookie(ctoken, data.data.ctoken);//写入cookie
if (funok) {
funok();
}
} else {
if (funerr) {
funerr();
} else {
if (data.data.indexOf("未登录") >= 0 || data.data.indexOf("原因为一")>=0 ) {
window.location.href="/adminlogin/login"
} else {
alert(data.data);
}
}
}
}
function post(url,p,func){
if(!p){
p={};
}
p.ctoken=getCookie(ctoken);
axios.post(url,qs.stringify(p),{
headers: {
'Content-Type':'application/x-www-form-urlencoded; charset=UTF-8'
}
}).then(response => {
func(response);
});
}
export default {
utoken,
ctoken,
getCookie,
setCookie,
delCookie,
chkResult,
post
};
</script>
这个方案还有弊端,并不完善,慎用