ASP.NET MVC5+EF6+EasyUI 后台管理系统(65)-MVC WebApi 用户验证 (1)

前言:

WebAPI主要开放数据给手机APP,其他需要得知数据的系统,或者软件应用,所以移动端与系统的数据源往往是相通的。

Web 用户的身份验证,及页面操作权限验证是B/S系统的基础功能,一个功能复杂的业务应用系统,通过角色授权来控制用户访问

本文通过Basic 方式进行基础认证Mvc的Controller基类及Action的权限验证来实现Web系统登录,Mvc前端权限校验以及WebApi服务端的访问校验功能,本文主要作为本人备忘使用,如能给予人帮助,深感荣幸,欢迎讨论和指正,下面梳理一下验证的流程

开发环境:

VS2015+无数据库(模拟数据)

知识点:

  1. WebApi简单使用
  2. 用户校验
  3. 同域访问
  4. 跨域访问

验证流程:

1.WebApi服务端接收访问请求,需要做安全验证处理,验证处理步骤具体如下:

1) 如果是合法的Http请求,在Http请求头中会有用户身份的票据信息(如果是跨域那么无法在请求头中添加票据),服务端会读取票据信息,并校验票据信息是否完整有效,如果满足校验要求,则进行业务数据的处理,并返回给请求发起方;

2) 如果没有票据信息,或者票据信息不是合法的,则返回“未授权的访问”异常消息给前端,由前端处理此异常。

2. 登录及权限验证流程

1) 用户打开浏览器,并在地址栏中输入页面请求地址,提交;

2) 浏览器解析Http请求,发送到Web服务器;Web服务器验证用户请求,首先判断是否有登录的票据信息;

3) 用户没有登录票据信息,则跳转到登录页面;

4) 用户输入用户名和密码信息;

5) 浏览器提交登录表单数据给Web服务器;

6) Web服务需要验证用户名和密码是否匹配,发送api请求给api服务器;

7) api用户账户服务根据用户名,读取存储在数据库中的用户资料,判断密码是否匹配;

7.1)如果用户名和密码不匹配,则提示密码错误等信息,然该用户重新填写登录资料;

7.2)如果验证通过,则保存用户票据信息;

8)

3.如果用户有登录票据信息,则跳转到用户请求的页面;

9) 验证用户对当前要操作的页面或页面元素是否有权限操作,首先需要发起api服务请求,获取用户的权限数据;

10). api用户权限服务根据用户名,查找该用户的角色信息,并计算用户权限列表,封装为Json数据并返回;

11). 当用户有权限操作页面或页面元素时,跳转到页面,并由页面Controller提交业务数据处理请求到api服务器; 如果用户没有权限访问该页面或页面元素时,则显示“未授权的访问操作”,跳转到系统异常处理页面。

12). api业务服务处理业务逻辑,并将结果以Json 数据返回;

13). 返回渲染后的页面给浏览器前端,并呈现业务数据到页面;

14). 用户填写业务数据,或者查找业务数据;

15). 当填写或查找完业务数据后,用户提交表单数据;

16). 浏览器脚本提交get,post等请求给web服务器,由web服务器再次解析请求操作,重复步骤2的后续流程;

17). 当api服务器验证用户身份是,没有可信用户票据,系统提示“未授权的访问操作”,跳转到系统异常处理页面。

开始:

1.添加一个空的WebApi,无身份验证WebApi

2.新建Account控制器 AccountController

using Apps.Common;
using Apps.WebApi.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web;
using System.Web.Http;
using System.Web.Security;
namespace Apps.WebApi.Controllers
{
    public class AccountController : ApiController
    {
        [HttpGet]
        public object Login(string userName, string password)
        {
            //实际场景应该到数据库进行校验
            if (userName != "123" && password!="123")
            {
                return Json(JsonHandler.CreateMessage(0, "用户名或密码错误"));
            }
            FormsAuthenticationTicket token = new FormsAuthenticationTicket(0, userName, DateTime.Now,
                            DateTime.Now.AddHours(1), true, string.Format("{0}&{1}", userName, password),
                            FormsAuthentication.FormsCookiePath);
            //返回登录结果、用户信息、用户验证票据信息
            var Token = FormsAuthentication.Encrypt(token);
            //将身份信息保存在session中,验证当前请求是否是有效请求
            HttpContext.Current.Session[userName] = Token;

            return Json(JsonHandler.CreateMessage(1, Token));
        }

    }
}

对用户名和密码进行校验,这里没有数据库演示,所以直接是进行固定匹配,帐号123,密码123(可参考19节用户登录,获得数据库的校验方式

登录失败:返回错误提示

登录成功:返回Token并保存Token到 Session

可见代码中包含Session的操作,但是Webapi默认是不支持Session的,所以我们需要在Global加载时候添加对Session的支持,不然运行调用会直接异常

    protected void Application_PostAuthorizeRequest()
    {
        HttpContext.Current.SetSessionStateBehavior(System.Web.SessionState.SessionStateBehavior.Required);
    }

3.运行Webapi

输入http://localhost:13743/help可以看到,我们的接口已经在webapi help列出,并可以查看调用方式(VS2012可能没有自动生成WebApi Help,需要从Nuget包获得)

4.同域调用

在Home的Index.cshtm添加登录代码

<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;width:100%;height:100%}.login-box{width:100%;max-width:500px;height:400px;position:absolute;top:50%;margin-top:-200px}@@media screen and (min-width:500px){.login-box{left:50%;margin-left:-250px}}.form{width:100%;max-width:500px;height:275px;margin:25px auto 0 auto;padding-top:25px}.login-content{height:300px;width:100%;max-width:500px;background-color:rgba(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;background-color:rgba(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;width: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>
<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);
                },
                error: function (e) {
                    alert("登录失败!");
                },
                complete: function () {

                }
            });
        });
    });
</script>

浏览器中运行/Home/Index

成功取得Token

5.跨域访问

同域名访问,一般系统任务这是安全的,可以信任的,所以不需要做过多的考虑,这是我们来看看跨域的情况

1.便于好记,把Apps.WebApi的端口设置为固定的8866

2.新建一个新的Web MVC普通无用户验证站点Apps.Web 设置端口为4455

把8866的Home/index登录界面代码复制到4455下的Home/index,修改访问URL

 url: "http://localhost:8866/api/Account/Login"

3.设置解决方案为多项目启动,同时启动4455,8866

这样才用让4455去访问6655的API,不然绝对报404

访问成功,但是没有返回值,jquery显示jquery的jsonp格式有callback返回

设置Ajax的dataType 为Jsonp

dataType:"jsonp",

再次运行,带回来的值正常

但是结果并没有弹出token并提示一个js错误

到这里真是一波三折

因为返回的值是:{"Id":"123"}

然而Jsonp需要你返回:jQuery*([{"Id":123"}])

4.让WebApi支持跨域返回的格式

注册一个全局属性

using System.Net.Http;
using System.Text;
using System.Web;
using System.Web.Http.Filters;

namespace Apps.WebApi.Core
{
    public class JsonCallbackAttribute : ActionFilterAttribute
    {
        private const string CallbackQueryParameter = "callback";

        public override void OnActionExecuted(HttpActionExecutedContext context)
        {
            var callback = string.Empty;

            if (IsJsonp(out callback))
            {
                var jsonBuilder = new StringBuilder(callback);

                jsonBuilder.AppendFormat("({0})", context.Response.Content.ReadAsStringAsync().Result);
                context.Response.Content = new StringContent(jsonBuilder.ToString());
            }

            base.OnActionExecuted(context);
        }

        private bool IsJsonp(out string callback)
        {
            callback = HttpContext.Current.Request.QueryString[CallbackQueryParameter];

            return !string.IsNullOrEmpty(callback);
        }
    }
}
GlobalConfiguration.Configuration.Filters.Add(new JsonCallbackAttribute());

再次运行:

本节结束,下节再学习怎么利用Token进行访问获得权限

参考资料:

http://stackoverflow.com/questions/9594229/accessing-session-using-asp-net-web-api

http://stackoverflow.com/questions/23698804/asp-net-mvc-with-forms-auth-and-webapi-with-basic-auth

https://weblog.west-wind.com/posts/2013/apr/18/a-webapi-basic-authentication-authorization-filter

http://stackoverflow.com/questions/17121964/asp-net-web-api-restful-web-service-basic-authentication

http://www.cnblogs.com/Kummy/p/3767269.html

实例代码下载   访问密码 13df

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏我的博客

TP入门第二天

1、配置说明 //’配置项’=>’配置值,配置在conf文件夹下config.php 2、Action文件定义规定 类名和文件名一样,首字母大写,后面必须要加A...

3105
来自专栏破晓之歌

pm2常用命令 原

1.pm2官方文档:http://pm2.keymetrics.io/docs/usage/cluster-mode/

1.3K5
来自专栏Java架构沉思录

如何优雅地用Redis实现分布式锁

在学习Java多线程编程的时候,锁是一个很重要也很基础的概念,锁可以看做是多线程情况下访问共享资源的一种线程同步机制。这是对于单进程应用而言的,即所有线程都在同...

41217
来自专栏Java架构沉思录

如何优雅地用Redis实现分布式锁

什么是分布式锁 在学习Java多线程编程的时候,锁是一个很重要也很基础的概念,锁可以看做是多线程情况下访问共享资源的一种线程同步机制。这是对于单进程应用而言的,...

2876
来自专栏扎心了老铁

python codis集群客户端(一) - 基于客户端daemon探活与服务列表维护

在使用codis时候,我们遇到的场景是,公司提供了HA的Proxy(例如N个),但是不暴露zookeeper(也就是说没有codis后端服务列表)。 如果暴露z...

67210
来自专栏决胜机器学习

Redis专题(九)——Redis管理工具

Redis专题(八) ——Redis管理工具 (原创内容,转载请注明来源,谢谢) 一、安全性 1、运行环境 Redis以简洁为美,其安全性...

4865
来自专栏Flutter&Dart

DartVM服务器开发(第二天)--处理请求

在上一节中,我们对所有请求都回复它一个Hello World!这个信息,我们现在改变一下,添加一个方法,传入request,把Hello World!这一条注释...

2582
来自专栏Java架构沉思录

如何优雅地用Redis实现分布式锁

在学习Java多线程编程的时候,锁是一个很重要也很基础的概念,锁可以看做是多线程情况下访问共享资源的一种线程同步机制。这是对于单进程应用而言的,即所有线程都在同...

721
来自专栏Linux驱动

45.INIT_WORK()工作队列使用

中断中通过调用schedule_work(work)来通知内核线程,然后中断结束后,再去继续执行work对应的func函数

1831
来自专栏跟着阿笨一起玩NET

C#(VB.NET)操作Windows自带的防火墙 之 启用(开启)/禁用(关闭)防火墙

在上一篇 C#(VB.NET)操作Windows自带的防火墙 之 综述篇 里,我们提到了下面这幅图

2752

扫码关注云+社区

领取腾讯云代金券