Nginx集群之基于Redis的WebApi身份验证

来源:互联网 发布:mac os sierra升级 编辑:程序博客网 时间:2024/06/06 19:36

目录

1       大概思路... 1

2       Nginx集群之基于Redis的WebApi身份验证... 1

3       Redis数据库... 2

4       Visualbox虚拟机ubuntu下的redis部署... 3

5       编写.NET WebApi的OnAuthorization身份验证... 6

6       编写.NET WebApi的ActionFilterAttribute令牌验证... 8

7       编写.NET WebApi的服务端... 10

8       编写.NET WebApi的客户端... 11

9       部署WebApi到本机... 17

10     Nginx集群配置搭建... 18

11     运行结果... 19

12     总结... 20

1       大概思路

l  Nginx集群之基于Redis的WebApi身份验证

l  Redis数据库

l  Visualbox虚拟机ubuntu下的redis部署

l  编写.NET WebApi的OnAuthorization身份验证

l  编写.NET WebApi的ActionFilterAttribute令牌验证

l  编写.NET WebApi的服务端

l  编写.NET WebApi的客户端

l  部署WebApi到本机

l  Nginx集群配置搭建

l  运行结果

l  总结

2       Nginx集群之基于Redis的WebApi身份验证

Nginx在集群上使用Redis数据库进行身份验证,达到了支持集群、分布式。在此基础上能够实现单点登录、时效性的访问,结合WebApi最大限度地发挥了后台身份验证的管理。

基于Redis的原因有几个:第一点是Redis是基于内存的数据库访问起来比较快,能够设置数据库存储的时效性;第二点,Redis数据库的语法比较简单、轻巧,在Linux、Windows服务器均可以一键安装完成;第三点,Redis支持数据的备份,即master-slave模式的数据备份。

以下是本文讲述的主要结构图:

客户端输入用户名密码服务器,通过了用户名密码验证,其中一台WebApi服务器生成一个Token并返回https的响应。客户端收到Token保存在本地,带上token发出ajax请求、WebRequest请求,经过Action过滤器的检验,访问Action并返回数据。

Token令牌身份验证机制:

Nginx集群之基于Redis的WebApi身份验证,如下图所示:

3       Redis数据库

Redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。它通常被称为数据结构服务器,因为值(value)可以是 字符串(String), 哈希(Map), 列表(list), 集合(sets) 和 有序集合(sorted sets)等类型。

具体可以在以下网址进行学习:

http://www.runoob.com/redis/redis-tutorial.html

 

4       Visualbox虚拟机ubuntu下的redis部署

(已安装Oracle VM VirtualBox、ubuntu-17.10-desktop-amd64.iso)

Redis安装可参考:http://www.runoob.com/redis/redis-install.html

 

  • 虚拟机

因网络的限制,本文采取的是“网络地址转换(NAT)”方式,进行redis数据库连接访问。有条件的可以采用“桥接网上”的方式,便可以使多台PC机在同一redis数据库进行访问,达到集群的效果。

Linux的ubuntu打开6379端口:

sudo iptables -I INPUT -p tcp --dport 6379 -j ACCEPT

具体如下所示:

登录Redis服务器,设置密码:

redis-cliconfig set requirepass 123456

  • 主机

Windows安装Telnet客户端,进行测试连接成功与否

运行CMD,输入命令行如下:

Microsoft Windows [版本 6.1.7601]版权所有 (c) 2009 Microsoft Corporation。保留所有权利。C:\Users\zhyongfeng>telnet 10.93.85.66 6379

测试成功

5       编写.NET WebApi的OnAuthorization身份验证

CustomAuthorizeAttribute.cs

复制代码
using System.Web.Http;using System.Web.Http.Controllers;namespace SSLWebApi.Controllers{    public class CustomAuthorizeAttribute : AuthorizeAttribute    {        public override void OnAuthorization(HttpActionContext actionContext)        {            //判断用户是否登录            if (actionContext.Request.Headers.Authorization != null)            {                string userInfo = System.Text.Encoding.Default.GetString(System.Convert.FromBase64String(actionContext.Request.Headers.Authorization.Parameter));                //用户验证逻辑                  if (string.Equals(userInfo, string.Format("{0}:{1}", "zhyongfeng", "123456")))                {                    IsAuthorized(actionContext);                }                else                {                    HandleUnauthorizedRequest(actionContext);                }            }            else            {                HandleUnauthorizedRequest(actionContext);            }        }        protected override void HandleUnauthorizedRequest(System.Web.Http.Controllers.HttpActionContext actionContext)        {            var challengeMessage = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized);            challengeMessage.Headers.Add("WWW-Authenticate", "Basic");            throw new System.Web.Http.HttpResponseException(challengeMessage);        }    }}
复制代码

生成Token

BaseController.cs

复制代码
using ServiceStack.Redis;using SSLWebApi.Models;using System;using System.Web;using System.Web.Http;namespace SSLWebApi.Controllers{    /// <summary>    /// BaseController继承BaseController则需要身份验证    /// </summary>    [CustomAuthorize]    [RoutePrefix("api/Base")]    public class BaseController : ApiController    {        [HttpGet]        [Route("Login")]        public string Login(string userId)        {            if (HttpRuntime.Cache.Get(userId) == null)            {                return CreateToken(userId);            }            else            {                HttpRuntime.Cache.Remove(userId);                return CreateToken(userId);            }        }        /// <summary>        /// 生成token        /// </summary>        /// <param name="userId"></param>        /// <returns></returns>        private string CreateToken(string userId)        {            Token token = new Token();            token.UserId = userId;            token.SignToken = Guid.NewGuid();            token.Seconds = 12;            token.ExpireTime = DateTime.Now.AddSeconds(token.Seconds);            //redis使用sudo iptables -A INPUT -p tcp --dport 6379 -j ACCEPT打开6379的端口            //visualbox虚拟机使用“设置->网络->高级->端口转发”创建商品规则            //连接远程visualbox虚拟机ubtuntu的redis,www.cqchu.com 形成一个分布式的登录验证            string remoteIp = "10.93.85.66";            int remotePort = 6379;            string remoteDbPassword = "123456";            //RedisClient(地址,端口,密码,0)            using (var client = new RedisClient(remoteIp, remotePort, remoteDbPassword, 0))            {                string strToken = token.SignToken.ToString();                if (client.Exists(userId) > 0)                    client.Del(userId);                if (client.Add(userId, strToken))                {                    //设置key的过期时间为12s                    client.Expire(userId, 12);                    return token.SignToken.ToString();                }                else                    return string.Format("远程虚拟机{0}的redis创建token失败", remoteIp);            }        }    }}
复制代码

6       编写.NET WebApi的ActionFilterAttribute令牌验证

WebApiSecurityFilter.cs

复制代码
using ServiceStack.Redis;using SSLWebApi.Models;using System;using System.Linq;using System.Net.Http;using System.Web;using System.Web.Http.Controllers;using System.Web.Http.Filters;namespace SSLWebApi.Filter{    public class WebApiSecurityFilter : ActionFilterAttribute    {        public override void OnActionExecuting(HttpActionContext actionContext)        {            if (actionContext.ActionDescriptor.ActionName == "Login")            {                //登录成功则生成token                base.OnActionExecuting(actionContext);                return;            }            else            {                //判断token令牌                HttpRequestMessage request = actionContext.Request;                string staffid = request.Headers.Contains("userid") ? HttpUtility.UrlDecode(request.Headers.GetValues("userid").FirstOrDefault()) : string.Empty;                string timestamp = request.Headers.Contains("timestamp") ? HttpUtility.UrlDecode(request.Headers.GetValues("timestamp").FirstOrDefault()) : string.Empty;                string nonce = request.Headers.Contains("nonce") ? HttpUtility.UrlDecode(request.Headers.GetValues("nonce").FirstOrDefault()) : string.Empty;                string signature = request.Headers.Contains("signature") ? HttpUtility.UrlDecode(request.Headers.GetValues("signature").FirstOrDefault()) : string.Empty;                if (String.IsNullOrEmpty(staffid) || String.IsNullOrEmpty(timestamp) || String.IsNullOrEmpty(nonce) || String.IsNullOrEmpty(signature))                {                    //令牌检验不通过                    actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Forbidden);                    return;                }                else                {                    string remoteIp = "10.93.85.66";                    int remotePort = 6379;                    string remoteDbPassword = "123456";                    using (var client = new RedisClient(remoteIp, remotePort, remoteDbPassword, 0))                    {                        //令牌检验是否存在这个用户                        if (client.Exists(staffid) > 0)                        {                            string tempStr = System.Text.Encoding.UTF8.GetString(client.Get(staffid));                            if (tempStr.Trim('"').Equals(signature))                            {                                //时间转换成功、时间有效、token值相等                                //令牌通过                                base.OnActionExecuting(actionContext);                                return;                            }                            else                            {                                actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Forbidden);                                return;                            }                        }                        else                        {                            actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Forbidden);                            return;                        }                    }                }            }        }    }}
复制代码

 

7       编写.NET WebApi的服务端

UserController.cs

复制代码
using System;using System.Net;using System.Web.Http;namespace SSLWebApi.Controllers{    [RoutePrefix("api/User")]    public class UserController : ApiController    {        /// <summary>        /// 获取当前用户信息        /// </summary>        /// <param name="msg"></param>        /// <returns></returns>        [HttpPost]        [Route("PostMessage")]        public string PostMessage(dynamic obj)        {            return string.Format("当前输入的消息是:{0}", Convert.ToString(obj.msg));        }        [Route("GetMachine")]        public string GetMachine()        {            string AddressIP = string.Empty;            foreach (IPAddress _IPAddress in Dns.GetHostEntry(Dns.GetHostName()).AddressList)            {                if (_IPAddress.AddressFamily.ToString() == "InterNetwork")                {                    AddressIP = _IPAddress.ToString();                }            }            return string.Format("当前WebApi部署的IP是:{0}", AddressIP);        }    }}
复制代码

8       编写.NET WebApi的客户端

WebApiHelper.cs

复制代码
using Newtonsoft.Json;using System;using System.IO;using System.Net;using System.Text;namespace SSLWebApiClient.Common{    public class WebApiHelper    {        /// <summary>        /// Post请求        /// </summary>        /// <typeparam name="T"></typeparam>        /// <param name="url">url</param>        /// <param name="data">数据</param>        /// <param name="userid">帐户</param>        /// <param name="signature">数字签名</param>        /// <returns></returns>        public static string Post(string url, string data, string userid, string signature)        {            return PostData(url, data, userid, signature);        }        /// <summary>        /// Post请求        /// </summary>        /// <typeparam name="T"></typeparam>        /// <param name="url">url</param>        /// <param name="data">数据</param>        /// <param name="userid">帐户</param>        /// <param name="signature">数字签名</param>        /// <returns></returns>        public static T Post<T>(string url, string data, string userid, string signature)        {            return JsonConvert.DeserializeObject<T>(Post(url, data, userid, signature));        }        /// <summary>        ///         /// </summary>        /// <param name="webApi"></param>        /// <param name="queryStr"></param>        /// <param name="userid"></param>        /// <param name="signature"></param>        /// <returns></returns>        public static string Get(string webApi, string queryStr, string userid, string signature)        {            return GetData(webApi, queryStr, userid, signature);        }        /// <summary>        /// Get请求        /// </summary>        /// <typeparam name="T"></typeparam>        /// <param name="webApi"></param>        /// <param name="query"></param>        /// <param name="queryStr"></param>        /// <param name="userid"></param>        /// <param name="signature"></param>        /// <returns></returns>        public static T Get<T>(string webApi, string queryStr, string userid, string signature)        {            return JsonConvert.DeserializeObject<T>(GetData(webApi, queryStr, userid, signature));        }        /// <summary>          /// 获取时间戳          /// </summary>          /// <returns></returns>          private static string GetTimeStamp()        {            TimeSpan ts = DateTime.Now - new DateTime(1970, 1, 1, 0, 0, 0, 0);            return ts.TotalSeconds.ToString();        }        /// <summary>          /// 获取随机数        /// </summary>          /// <returns></returns>          private static string GetRandom()        {            Random rd = new Random(DateTime.Now.Millisecond);            int i = rd.Next(0, int.MaxValue);            return i.ToString();        }        /// <summary>        /// Post请求        /// </summary>        /// <param name="url"></param>        /// <param name="data"></param>        /// <param name="userid">用户名称</param>        /// <param name="signature">数字签名</param>        /// <returns></returns>        private static string PostData(string url, string data, string userid, string signature)        {            try            {                byte[] bytes = Encoding.UTF8.GetBytes(data);                HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);                string timeStamp = GetTimeStamp();                string nonce = GetRandom();                //加入头信息                //当前请求用户                request.Headers.Add("userid", userid);                //发起请求时的时间戳(单位:秒)                request.Headers.Add("timestamp", timeStamp);                //发起请求时的时间戳(单位:秒)                request.Headers.Add("nonce", nonce);                //当前请求内容的数字签名                request.Headers.Add("signature", signature);                //写数据                request.Method = "POST";                request.ContentLength = bytes.Length;                request.ContentType = "application/json";                request.GetRequestStream().Write(bytes, 0, bytes.Length);                //读数据                request.Timeout = 300000;                request.Headers.Set("Pragma", "no-cache");                HttpWebResponse response = (HttpWebResponse)request.GetResponse();                Stream streamReceive = response.GetResponseStream();                StreamReader streamReader = new StreamReader(streamReceive, Encoding.UTF8);                string strResult = streamReader.ReadToEnd();                                //关闭流                //reqstream.Close();                streamReader.Close();                streamReceive.Close();                request.Abort();                response.Close();                return strResult;            }            catch (Exception ex)            {                return ex.Message;            }        }        /// <summary>        /// Get请求        /// </summary>        /// <param name="webApi"></param>        /// <param name="queryStr"></param>        /// <param name="userid"></param>        /// <param name="signature"></param>        /// <returns></returns>        private static string GetData(string webApi, string queryStr, string userid, string signature)        {            try            {                HttpWebRequest request = (HttpWebRequest)WebRequest.Create(webApi + "?" + queryStr);                string timeStamp = GetTimeStamp();                string nonce = GetRandom();                //加入头信息                //当前请求用户                request.Headers.Add("userid", userid);                //发起请求时的时间戳(单位:秒)                request.Headers.Add("timestamp", timeStamp);                //发起请求时的时间戳(单位:秒)                request.Headers.Add("nonce", nonce);                //当前请求内容的数字签名                request.Headers.Add("signature", signature);                request.Method = "GET";                request.ContentType = "application/json";                request.Timeout = 90000;                request.Headers.Set("Pragma", "no-cache");                HttpWebResponse response = (HttpWebResponse)request.GetResponse();                Stream streamReceive = response.GetResponseStream();                StreamReader streamReader = new StreamReader(streamReceive, Encoding.UTF8);                string strResult = streamReader.ReadToEnd();                streamReader.Close();                streamReceive.Close();                request.Abort();                response.Close();                return strResult;            }            catch (Exception ex)            {                return ex.Message;            }        }    }}
复制代码

Program.cs

复制代码
using System;using System.IO;using System.Net;using System.Text;using Newtonsoft.Json;namespace SSLWebApiClient{    class Program    {        static void Main(string[] args)        {            string basicUrl = "http://zhyongfeng.com";            string html = string.Empty;            for (int i = 0; i < 1; i++)            {                //https协议基本认证 Authorization                string url = basicUrl + "/api/base/Login?userId=zhyongfeng";                ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };                HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);                NetworkCredential credential = new NetworkCredential("zhyongfeng", "123456");                req.Credentials = credential;                HttpWebResponse response = (HttpWebResponse)req.GetResponse();                Stream responseStream = response.GetResponseStream();                StreamReader streamReader = new StreamReader(responseStream, Encoding.UTF8);                html = streamReader.ReadToEnd().Replace("\"", "");                Console.WriteLine("Redis Token服务器保存时间为12s");                Console.WriteLine(String.Format("Redis服务器返回的Token值为:{0}", html));            }            //token设置了12s有效期            for (int j = 0; j < 5; j++)            {                System.Threading.Thread.Sleep(1000);                string url = basicUrl + "/api/user/PostMessage";                Console.WriteLine(Common.WebApiHelper.Post(url, JsonConvert.SerializeObject(new { msg = "hello" }), "zhyongfeng", html));            }            for (int j = 0; j < 10; j++)            {                System.Threading.Thread.Sleep(1000);                string url = basicUrl + "/api/user/GetMachine";                Console.WriteLine(Common.WebApiHelper.Get(url, null, "zhyongfeng", html));            }            Console.Read();        }    }}
复制代码

9       部署WebApi到本机

将WebApi部署到以下10.93.85.66(因网络限制,所以这里只做单个集群,虚拟机ubuntu中的数据redis主要是NAT网络连接方式,使用端口转发进行访问)

10             Nginx集群配置搭建

通过自主义域名zhyongfeng.com:80端口进行负载均衡集群访问,则访问C:\Windows\System32\drivers\etc\hosts,添加下列“本机IP 自定义的域名”:

10.93.85.66     zhyongfeng.com

Nginx的集群配置:

复制代码
worker_processes  1;events {    worker_connections  1024;}http {    include       mime.types;    default_type  application/octet-stream;    sendfile        on;    keepalive_timeout  65;     upstream zhyongfeng.com {        server       10.93.85.66:20107;    }    server {        listen       80;        server_name  localhost;        location / {            proxy_pass   http://zhyongfeng.com;        }        error_page   500 502 503 504  /50x.html;        location = /50x.html {            root   html;        }    }}
复制代码

运行CMD:

D:\DTLDownLoads\nginx-1.10.2>start nginxD:\DTLDownLoads\nginx-1.10.2>nginx -s reload

11             运行结果

访问集群:http://zhyongfeng.com

WebApi经过Action过滤器,生成了Token值,并存储到Redis,运行结果如下:

12             总结

Nginx集群使用Redis数据库,客户端利用 http basic身份验证,访问WebApi获得Token并将Token存储到Redis内在数据库,通过Token值获取相应的权限数据,这样子可以做到单点登录,集群分布式的身份验证效果。既方便了用户在整个业务领域的系统操作,同时可以为整个公司、集团等各个区域的系统进行统一有效的身份验证管理。

 

源代码下载:

http://download.csdn.net/download/ruby_matlab/10155491

原创粉丝点击