Nginx集羣之SSL證書的WebApi令牌驗證

目錄html

1       大概思路... 1nginx

2       Nginx集羣之SSL證書的WebApi令牌驗證... 1web

3       Openssl生成SSL證書... 2ajax

4       編寫.NET WebApi的OnAuthorization身份驗證... 2redis

5       編寫.NET WebApi的ActionFilterAttribute令牌驗證... 4json

6       編寫.NET WebApi的服務端... 6後端

7       編寫.NET WebApi的客戶端... 7api

8       部署WebApi到局域網內3臺PC機... 13安全

9       Nginx集羣配置搭建... 13ruby

10     運行結果... 15

11     總結... 16

1       大概思路

l  Nginx集羣之SSL證書的WebApi令牌驗證

l  Openssl生成SSL證書

l  編寫.NET WebApi的OnAuthorization身份驗證

l  編寫.NET WebApi的ActionFilterAttribute令牌驗證

l  編寫.NET WebApi的服務端

l  編寫.NET WebApi的客戶端

l  部署WebApi到局域網內3臺PC機

l  Nginx集羣配置搭建

l  運行結果

l  總結

2       Nginx集羣之SSL證書的WebApi令牌驗證

Nginx在WebApi集羣,除了OAUTH身份驗證外,針對移動端的手機、平板電腦等,還常常使用Token令牌驗證,經過服務器受權發出有效期的Token,客戶端經過此Token在當前有效期內,進行訪問獲取信息數據。

Token驗證在不少方面都普遍應用,舉一個實際應用場景:A客戶想經過接收郵件或者短信網址打開一個URL的PDF報表,可是又不想安裝APP、或者訪問咱們的系統,連登陸都不想登陸。這時候,即可以使用一個有效期的Token,而後結合URL發送給用戶,過了有效期,當前URL就失效。即可以解決用戶臨時訪問的問題。

如下是本文講述的主要結構圖:

客戶端輸入用戶名密碼服務器,經過了用戶名密碼驗證,其中一臺WebApi服務器生成一個Token並返回https的響應。客戶端收到Token保存在本地,帶上token發出ajax請求、WebRequest請求,通過Action過濾器的檢驗,訪問Action並返回數據。

Token令牌身份驗證機制:

Nginx集羣之SSL證書的WebApi令牌驗證,以下圖所示:

3       Openssl生成SSL證書

請參照《Nginx集羣之SSL證書的WebApi微服務》

http://www.cnblogs.com/yongfeng/p/7921905.html

4       編寫.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 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);
            HttpRuntime.Cache.Insert(token.UserId, token, null, token.ExpireTime, TimeSpan.Zero);
            return token.SignToken.ToString();
        }
    }
}

5       編寫.NET WebApi的ActionFilterAttribute令牌驗證

WebApiSecurityFilter.cs

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
                {
                    //令牌檢驗token失效
                    if (HttpRuntime.Cache.Get(staffid) == null)
                    {
                        actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Forbidden);
                        return;
                    }
                    else
                    {
                        //停牌檢驗2:判斷timespan是否失效
                        Token token = (Token)HttpRuntime.Cache.Get(staffid);
                        double ts1 = 0;
                        bool timespanvalidate = double.TryParse(timestamp, out ts1);
                        double ts2 = (token.ExpireTime - new DateTime(1970, 1, 1, 0, 0, 0, 0)).TotalSeconds;
                        bool flag = (ts2 - ts1) > token.Seconds ? true : false;
                        bool tokenFlag = (token.SignToken.ToString() == signature) ? true : false;
                        if (timespanvalidate && (!flag) && tokenFlag)
                        {
                            //時間轉換成功、時間有效、token值相等
                            //令牌經過
                            base.OnActionExecuting(actionContext);
                            return;
                        }
                        else
                        {
                            actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Forbidden);
                            return;
                        }
                    }
                }
            }
        }

    }
}

6       編寫.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);
        }
    }
}

7       編寫.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://localhost:20107";
            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("Token服務器保存時間爲12s");
                Console.WriteLine(String.Format("服務器返回的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();
        }

    }
}

8       部署WebApi到局域網內3臺PC機

將WebApi部署到如下10.92.202.56的3臺PC機

9       Nginx集羣配置搭建

經過自主義域名zhyongfeng.com:80端口進行負載均衡集羣訪問,則訪問C:\Windows\System32\drivers\etc\hosts,添加下列「本機IP 自定義的域名」:

10.93.85.66     zhyongfeng.com

Nginx的集羣配置:

#user  nobody;
worker_processes  1;
events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    keepalive_timeout  65;
    #server {
    #    listen       80;
    #    server_name  localhost;
    #    location / {
    #        root   html;
    #        index  index.html index.htm;
    #    }
    #    error_page   500 502 503 504  /50x.html;
    #    location = /50x.html {
    #        root   html;
    #    }
    #}

    upstream zhyongfeng.com {
        server    10.92.202.56:560;
        server    10.92.202.57:570; 
        server    10.92.202.58:580;
    }
    server {
        listen       80;
        server_name  zhyongfeng.com;
        rewrite ^(.*)$  https://$host$1 permanent;
    }
    # HTTPS server
    #
    server {
        listen       443 ssl;
        server_name  zhyongfeng.com;
        ssl_certificate      server.crt;
        ssl_certificate_key  server_nopass.key;
    #    ssl_session_cache    shared:SSL:1m;
    #    ssl_session_timeout  5m;
    #    ssl_ciphers  HIGH:!aNULL:!MD5;
    #    ssl_prefer_server_ciphers  on;
        location / {
            proxy_pass   http://zhyongfeng.com;
        }
    }
}

運行CMD:

D:\DTLDownLoads\nginx-1.10.2>start nginx

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

10             運行結果

因只有其中一臺計算生成了相應的Token值(這裏只作其中一臺,若是要三臺都可以響應,能夠用redis作相應的Token值存儲)

其中一臺10.92.202.58生成了Token值,運行結果以下:

  • 訪問指定的http://10.92.202.56:560

因Token值設定了12s後失效,則返回「遠程服務器返回錯誤:<403>已禁止」,運行效果以下:

 

11             總結

Nginx基於SSL協議下,客戶端利用 http basic身份驗證,訪問WebApi得到Token,經過Token值獲取相應的權限數據,使系統的安全性有了保障,同時靈活運用Token身份令牌,能夠實現時效性的數據訪問。基於Token的身份驗證,針對先後端分離有着很大的做用。例如手機移動端、平板電腦。

 

源代碼下載:

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

 

PDF下載:

Nginx集羣之SSL證書的WebApi令牌驗證.pdf

相關文章
相關標籤/搜索