前言:
回顾上一节,我们利用webapi简单的登录并进行了同域访问与跨域访问来获得Token,您可以跳转到上一节下载代码来一起动手。
继续上一篇的文章,我们接下来演示利用拿到的Token来访问接口,管理接口,利用系统权限管理接口,对每个接口进行授权(管理接口为选读部分,因为你需要阅读最开始权限管理部分(18-27节),才能阅读这部分)
开发环境:
VS2015+无数据库(模拟数据)
样例代码下载 访问密码 8ca3
知识点:
- WebApi权限验证
- 应用到实际中来
- 调试
开始:
1.过滤器验证
我们之前也是通过过滤器来验证对于Action的操作权限,接口也不例外,在Action切入,在每次访问的时候都附带Token信息,也许你可以通过下列在Http请求头中来附加Token
添加过滤类:SupportFilter并继承AuthorizeAttribute权限筛选器OnAuthorization基类方法
using System.Linq; using System.Web; using System.Web.Http; using System.Web.Security; namespace Apps.WebApi.Core { public class SupportFilter : AuthorizeAttribute { //重写基类的验证方式,加入我们自定义的Ticket验证 public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext) { //url获取token var content = actionContext.Request.Properties["MS_HttpContext"] as HttpContextBase; var token = content.Request.QueryString["Token"]; if (!string.IsNullOrEmpty(token)) { //解密用户ticket,并校验用户名密码是否匹配 if (ValidateTicket(token)) { base.IsAuthorized(actionContext); } else { HandleUnauthorizedRequest(actionContext); } } //如果取不到身份验证信息,并且不允许匿名访问,则返回未验证401 else { var attributes = actionContext.ActionDescriptor.GetCustomAttributes<AllowAnonymousAttribute>().OfType<AllowAnonymousAttribute>(); bool isAnonymous = attributes.Any(a => a is AllowAnonymousAttribute); if (isAnonymous) base.OnAuthorization(actionContext); else HandleUnauthorizedRequest(actionContext); } } //校验用户名密码(对Session匹配,或数据库数据匹配) private bool ValidateTicket(string encryptToken) { //解密Ticket var strTicket = FormsAuthentication.Decrypt(encryptToken).UserData; //从Ticket里面获取用户名和密码 var index = strTicket.IndexOf("&"); string userName = strTicket.Substring(0, index); string password = strTicket.Substring(index + 1); //取得session,不通过说明用户退出,或者session已经过期 var token = HttpContext.Current.Session[userName]; if (token == null) { return false; } //对比session中的令牌 if (token.ToString() == encryptToken) { return true; } return false; } } }
HttpActionContext是无法取到URL的参数的,需要转换为HttpContextBase,这个类我都有注释,很容易看懂。
1.从URL取到Token,利用之前加密的方式来揭秘Token并得出Token中的用户名
2.利用用户名获取Session中的Token
3.ValidateTicket验证,判断Session中的Token是否与获取到的相同?
2.应用到实际当中来
最后对每个Action进行注解,好在调用接口的时候优先判断是否有权限访问
3.运行调试
调试之前,我们需要写点代码来访问
修改Home Index代码
<script src="~/Scripts/jquery-1.10.2.min.js"></script> <style>html,body{height:100%}.box{filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#6699FF', endColorstr='#6699FF');background-image:linear-gradient(bottom,#69F 0,#69F 100%);background-image:-o-linear-gradient(bottom,#69F 0,#69F 100%);background-image:-moz-linear-gradient(bottom,#69F 0,#69F 100%);background-image:-webkit-linear-gradient(bottom,#69F 0,#69F 100%);background-image:-ms-linear-gradient(bottom,#69F 0,#69F 100%);margin:0 auto;position:relative;100%;height:100%}.login-box{100%;max-500px;height:400px;position:absolute;top:50%;margin-top:-200px}@@media screen and (min-500px){.login-box{left:50%;margin-left:-250px}}.form{100%;max-500px;height:275px;margin:25px auto 0 auto;padding-top:25px}.login-content{height:300px;100%;max-500px; padding: 0px; color: rgb(128, 0, 128); line-height: 1.5 !important;">255,250,2550,.6);float:left}.input-group{margin:0 0 30px 0!important}.form-control,.input-group{height:40px}.form-group{margin-bottom:0!important}.login-title{padding:20px 10px; padding: 0px; color: rgb(128, 0, 128); line-height: 1.5 !important;">0,0,0,.6)}.login-title h1{margin-top:10px!important}.login-title small{color:#fff}.link p{line-height:20px;margin-top:30px}.btn-sm{padding:8px 24px!important;font-size:16px!important} </style> <div class="box" style="margin:100px;height:400px;500px;"> <div class="login-box"> <div class="login-title text-center"> <h1><small>登录</small></h1> </div> <div class="login-content "> <div class="form"> <form action="#" method="post"> <div class="form-group"> <div class="col-xs-12 "> <div class="input-group"> <span class="input-group-addon"><span class="glyphicon glyphicon-user"></span></span> <input type="text" id="username" name="username" class="form-control" placeholder="用户名"> </div> </div> </div> <div class="form-group"> <div class="col-xs-12 "> <div class="input-group"> <span class="input-group-addon"><span class="glyphicon glyphicon-lock"></span></span> <input type="text" id="password" name="password" class="form-control" placeholder="密码"> </div> </div> </div> <div class="form-group form-actions"> <div class="col-xs-4 col-xs-offset-4 "> <button type="button" id="Login" class="btn btn-sm btn-info"><span class="glyphicon glyphicon-off"></span> 登录</button> </div> </div> </form> </div> </div> </div> </div> Token:<div id="myToken"></div> <input type="button" id="getData" value="获取API Get/{id}的值" /> 利用Token后获得的值:<div id="myVal"></div> <script> $(function () { $("#Login").click(function () { $.ajax({ type: "get", url: "/api/Account/Login", data: { userName: $("#username").val(), password: $("#password").val() }, success: function (data, status) { if (data.type==0) { alert("登录失败"); return; } alert("登录成功:Token" + data.message); $("#myToken").html(data.message); }, error: function (e) { alert("登录失败!"); }, complete: function () { } }); }); $("#getData").click(function () { $.ajax({ type: "get", url: "/api/Values/Get/5?Token=" + $("#myToken").html(), success: function (data, status) { alert(data); }, error: function (e) { alert("失败!"); }, complete: function () { } }); }); }); </script>
添加一个按钮和DIV好显示结果:
我们获得了正确的数据。如果没有token,我们的结果将会返回一个401
大家可以下载代码把断点设置在
可以调试程序对于Token处理的顺序!
总结:
本节讲解了如何利用Token在来访问需要授权的接口!利用到了MVC的过滤器,在调用Action时候优先进行权限校验,这样就完成了对用户进行接口授权的样例。
以上部分一般都够用了,如果你(ˇˍˇ) 想~更加深入和细微的粒度授权,那么就要对每个接口进行单独授权
如果你有兴趣,那可以继续阅读下面的对API的管理授权
--------------------------------------------------------------------------------------------------------丑陋的分割线-----------------------------------------------------------------------------------------
下面将演示如何管理接口,这需要你之前有跟读本系列的18-27节权限才能阅读!因为大部门利用了之前的管理界面的功能和代码,是紧密联系的,不过没有关系,就算你没有学习之前的系列文章,也许也能从本节中获得知识!
知识点:
1.取所有类库中的Api接口
2.管理接口
3.对接口进行授权
4.在过滤器添加权限验证
开始:
回顾一下我们的模块管理:
管理的是每一个控制器中的Action(操作码)
我们的WebApi也是如此,每个控制器的操作码,在WebApi运行时候把数据填充到SysModule表和SysModuleOperation表中中来
1.取得类库中所有Api接口
这个真的好用,取得的接口,你平时可以用于管理或自动测试接口
//把控制器当成URL,把Aciton当成操作码插入到数据表做为权限设置,类似之前的权限系统 //获得API管理器 Collection<ApiDescription> apiColl = GlobalConfiguration.Configuration.Services.GetApiExplorer().ApiDescriptions; ILookup<HttpControllerDescriptor, ApiDescription> apiGroups = apiColl.ToLookup(api => api.ActionDescriptor.ControllerDescriptor); foreach (var group in apiGroups) { //-----------插入Action foreach (var m in group) { } }
第一个foreach取得控制器,第二个foreach取得控制器下面的Action。大家把这句话添加到Home的Index中来,跟踪一下,分别可以看到Group和m中模型的属性。
现在知道怎么加表了吧!
foreach (var group in apiGroups) { string controllerName = group.Key.ControllerName; //----------插入控制器 rootModel = m_BLL.GetById(controllerName); if (rootModel == null) { SysModuleModel model = new SysModuleModel() { Id = controllerName, Name = controllerName, EnglishName = "", ParentId = "ApiInterfaceAuth", Url = "api/"+ controllerName, Iconic = "fa fa-television", Enable = true, Remark = "Api接口授权", Sort = 1, CreatePerson = "Admin", CreateTime = DateTime.Now, IsLast = true }; m_BLL.Create(ref errors, model); } //-----------插入Action foreach (var m in group) { string actionName = m.ActionDescriptor.ActionName; SysModuleOperateModel model = operateBLL.GetById(m.ActionDescriptor.ActionName); if (model == null) { model = new SysModuleOperateModel(); model.Id = controllerName + actionName; model.Name = m.Documentation == null ? actionName : m.Documentation; model.KeyCode = actionName; model.ModuleId = controllerName; model.IsValid = true; model.Sort = 0; operateBLL.Create(ref errors, model); } } }
运行之后数据库将自动添加,几条数据
2.管理接口
表数据跟我们普通的是没有区别的,在界面想办法做个开关做为切换,我这里做了个下拉来切换类型
切换下拉时候,改变查询条件
<div style="float:left"> 菜单类型切换: <select class="easyui-combobox" name="swicthType" id="swicthType" style="80px;height:26px;margin-right:10px; "> <option value="menu">系统菜单</option> <option value="api">Api接口</option> </select> </div>
$('#swicthType').combobox({ editable: false, onSelect: function (record) { if (record.value == "api") { $('#List').treegrid({"url":'@Url.Action("GetList")?id=ApiInterfaceAuth'}); $('#OptList').datagrid('loadData', { total: 0, rows: [] }); } else { $('#List').treegrid({"url":'@Url.Action("GetList")?id=0'}); $('#OptList').datagrid('loadData', { total: 0, rows: [] }); } } });
3.对接口进行授权
授权后可以查询到:
为了更好的区分,我将只给超级管理员Get接口的权限
4.在过滤器中添加验证
这时候我们需要复制之前系统中过滤器的验证代码过来使用,并稍作修改为下面代码:
public class SupportFilter : AuthorizeAttribute { //重写基类的验证方式,加入我们自定义的Ticket验证 public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext) { //url获取token var content = actionContext.Request.Properties[ConfigPara.MS_HttpContext] as HttpContextBase; var token = content.Request.QueryString[ConfigPara.Token]; if (!string.IsNullOrEmpty(token)) { //解密用户ticket,并校验用户名密码是否匹配 //读取请求上下文中的Controller,Action,Id var routes = new RouteCollection(); RouteConfig.RegisterRoutes(routes); RouteData routeData = routes.GetRouteData(content); //取出区域的控制器Action,id string controller = actionContext.ActionDescriptor.ControllerDescriptor.ControllerName; string action = actionContext.ActionDescriptor.ActionName; //URL路径 string filePath = HttpContext.Current.Request.FilePath; if (LoginUserManage.ValidateTicket(token) && ValiddatePermission(token, controller, action, filePath)) { //已经登录,有权限,且没有单机登录限制 base.IsAuthorized(actionContext); } else { HandleUnauthorizedRequest(actionContext); } } //如果取不到身份验证信息,并且不允许匿名访问,则返回未验证401 else { var attributes = actionContext.ActionDescriptor.GetCustomAttributes<AllowAnonymousAttribute>().OfType<AllowAnonymousAttribute>(); bool isAnonymous = attributes.Any(a => a is AllowAnonymousAttribute); if (isAnonymous) base.OnAuthorization(actionContext); else HandleUnauthorizedRequest(actionContext); } } public bool ValiddatePermission(string token, string controller, string action, string filePath) { bool bResult = false; List<permModel> perm = null; perm = (List<permModel>)HttpContext.Current.Session[filePath]; if (perm == null) { SysUserBLL userBLL = new SysUserBLL() { m_Rep = new SysUserRepository(new DBContainer()), sysRightRep = new SysRightRepository(new DBContainer()) }; { var userName = LoginUserManage.DecryptToken(token.Trim()); perm = userBLL.GetPermission(userName, controller);//获取当前用户的权限列表 HttpContext.Current.Session[filePath] = perm;//获取的劝降放入会话由Controller调用 } //查询当前Action 是否有操作权限,大于0表示有,否则没有 int count = perm.Where(a => a.KeyCode.ToLower() == action.ToLower()).Count(); if (count > 0) { bResult = true; } else { bResult = false; LoginUserManage.RedirectUrl(); } } return bResult; } }
这样,访问时候将对当前访问的控制器和Action进行权限校验,没有权限同样返回401
接下来写两个方法测试一下,一个访问Values的Get方法,一个访问Values的Post
<script> $(function () { $("#Login").click(function () { $.ajax({ type: "get", url: "/api/Account/Login", data: { userName: $("#username").val(), password: $("#password").val() }, success: function (data, status) { if (data.type==0) { alert("登录失败"); return; } alert("登录成功:Token" + data.message); $("#myToken").html(data.message); }, error: function (e) { alert("登录失败!"); }, complete: function () { } }); }); $("#getData").click(function () { $.ajax({ type: "get", url: "/api/Values/Get/5?Token=" + $("#myToken").html(), success: function (data, status) { alert(data); }, error: function (e) { alert("失败!"); }, complete: function () { } }); }); $("#postData").click(function () { $.ajax({ type: "post", url: "/api/Values/Post?Token=" + $("#myToken").html(), data:{value:"123"}, success: function (data, status) { alert(data); }, error: function (e) { alert(e); }, complete: function () { } }); }); }); </script>
总结:
其实基于WebApi的权限他们也是如此设计的,您可以集成到你现有的权限系统来
感谢你抽时间看完本节,所谓 赞 得高尿得远,嘿嘿...