使用JWT Token实现WebApi访问验证及用户登录限制

JSON Web Token(JWT)跨域身份验证解决方案,详细说明网上很多,这里不就重点介绍了。

问题:WebApi项目开发完成后部署到服务器后WebApi方法任何人都可以调用,有些项目肯定是不允许的,也过不了安全审计。那如何解决呢,这里介绍一种使用JWT 产生Token,为每个方法加一个Token的身份验证,这样,用户在正常登录后,获取到系统为该用户产生的Token,以后,每次调用服务端的WebApi方法时,都需要把这个Token送到服务端做验证,只有正确的Token,才能访问,否则,服务端返回异常。同时,还可以通过这个Token来限制登录,同一时间,相同的用户,只允许存在一个有效用户。关于产生的Token如何存储,或者不存储也可以,这要看具体业务场景了,具体事情具体分析,本文主要是通过示例介绍一种思路,文中示例客户端使用Cookie存储,服务端使用了Redis。下面还是上代码吧。

开发工具:VS2015 

软件环境:redis

必需工具:Newtonsoft.Json.dll, RedisClient, JWT.dll

在需要加Token验证的webapi项目中加jwt.dll的引用

同时也把redisclient项目引用进来,可以上git hub下载。

然后新增一个用产生和验证Token的类JwtHelper

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Newtonsoft.Json.Linq;
using JWT;
using JWT.Algorithms;
using JWT.Serializers;
using JWT.Exceptions;
using CSRedis;

namespace JwtToken
{
    public class JwtHelper
    {
        private const string secret = "scott";
        public static string GenerateToken(string userCode)
        {
            try
            {
                IDateTimeProvider provider = new UtcDateTimeProvider();
                var now = provider.GetNow();

                var unixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); // or use JwtValidator.UnixEpoch

                var secondsSinceEpoch = Math.Round((now - unixEpoch).TotalSeconds);

                //3分钟后失效
                var payload = new Dictionary<string, object>
                {
                    { "user",userCode},
                    { "exp",secondsSinceEpoch+ 60*3 },
                    { "jti",Guid.NewGuid() }
                };

                

                IJwtAlgorithm algorithm = new HMACSHA256Algorithm();
                IJsonSerializer serializer = new JsonNetSerializer();
                IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
                IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, urlEncoder);

                var token = encoder.Encode(payload, secret);
                return token;
            }
            catch
            {
                return "";
            }
        }

        public static string VerifyToken(string Token)
        {
            string result = "";
            try
            {
                IDateTimeProvider provider = new UtcDateTimeProvider();
                IJwtAlgorithm algorithm = new HMACSHA256Algorithm();
                IJsonSerializer serializer = new JsonNetSerializer();
                IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
                IJwtValidator validator = new JwtValidator(serializer, provider);
                IJwtDecoder decoder = new JwtDecoder(serializer, validator, urlEncoder, algorithm);
                var json = decoder.Decode(Token, secret, verify: true);//token为之前生成的字符串
                JObject jobj = (JObject)Newtonsoft.Json.JsonConvert.DeserializeObject(json);
                string userCode = jobj["user"].ToString();
                RedisClient redis = new RedisClient("192.168.197.132", 6379);
                string originalToken = redis.Get(userCode);
                if(!Token.Equals(originalToken))
                {
                    result = "该帐号在其它地方登录";
                }
            }
            catch (TokenExpiredException)
            {
                result = "Token 过期";
            }
            catch (SignatureVerificationException)
            {
                result = "Token 不正确";
            }
            catch (Exception ex)
            {
                result = "Token 验证出错";
            }
            return result;
        }
    }
}

 然后在登录方法中,加入

string _token = JwtHelper.GenerateToken(登录ID);

将用户编码加到Token中,Token生成成功后,将该用户编码的Token存入redis并返回客户端,这里推荐将Token放入header中返加,而不是放在json中返回,放json中,容易被中途截获。

httpResponseMessage.Headers.Add("Authorization", _token);

header中的token数据

客户端在收到登录验证通过的信息后,从header中取出token保存到cookie中

$.ajax({
            url: "/api/Login/CheckLogin",
            type: "Post",
            contentType: "application/json",
            dataType: "json",
            data: JSON.stringify(objPara),
            success: function (data, textStatus, jqXHR) {
                $.cookie("Authorization", jqXHR.getResponseHeader("Authorization"), { expires: 1, path: '/' });
                
            },
            error: function (XMLHttpRequest, textStatus, errorThrown) {
                
            }
        });

$.cookie("Authorization", jqXHR.getResponseHeader("Authorization"), { expires: 1, path: '/' })

这句代码就是通过jqXHR.getResponseHeader("Authorization")获取header上的token后保存到cookie("Authorization")中,有效时间1天,所有页面均可访问。现在用户已经获得有效Token,接下来,每访问一次webapi,都要将该token发送给服务端,然后验证。接下来有两步工作要做,发送和验证,先说发送,服务端通过header把token送过来,那客户端也应该是通过header将token送过去,先把刚存在cookie中的token取出来

var token = $.cookie("Authorization");

然后在ajax调用中加入headers的参数,如下图。

这样,token就包含在header上,被送到了服务端。

接下来,服务端如何进行验证,这部分是个重点。在这之前,先借用一下网上的一个解释说明一下,有助于理解验证方法。

将生成的Token发送给客户端后,随后,客户端的每次请求只需带上这个Token即可

一般都是将Token存放在Http请求的Headers中,也就是:context.Request.Headers,那么如何接收请求头中的Token呢?接收到Token后如何验证呢?

验证TOKEN时就需要构建 MVC Action 过滤器(AuthorizeAttribute)了,不过在构建 AuthorizeAttribute 之前,有必要对 AuthorizeAttribute 说明下,如下:

首先,AuthorizeAttribute 类位于System.Web.Http 命名空间下及System.Web.Mvc命名空间下,

一般情况下,如果你需要对C# MVC 控制器的访问作认证与授权,你需要用System.Web.Mvc命名空间下的 AuthorizeAttribute ,如果你需要对C# API 控制器的访问作认证与授权,你需要用System.Web.Http 命名空间下的 AuthorizeAttribute !

看完这个解释,接下来,需要新建一个验证属性类ApiTokenCheck,这里只针对webapi方法验证,所以类继承自System.Web.Http.AuthorizeAttribute

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Web;
using System.Web.Http;
using System.Web.Http.Controllers;

namespace JwtToken
{
    public class ApiTokenCheck : AuthorizeAttribute
    {
        public override void OnAuthorization(HttpActionContext context)
        {
            var authHeader = context.Request.Headers.FirstOrDefault(a => a.Key == "Authorization");//获取接收的Token
            if (context.Request.Headers == null)
            {
                context.Response = context.Request.CreateErrorResponse(System.Net.HttpStatusCode.Unauthorized, new HttpError("Token 不正确"));
            }
            if(!context.Request.Headers.Any())
            {
                context.Response = context.Request.CreateErrorResponse(System.Net.HttpStatusCode.Unauthorized, new HttpError("Token 不正确"));
            }
            if(authHeader.Key == null)
            {
                context.Response = context.Request.CreateErrorResponse(System.Net.HttpStatusCode.Unauthorized, new HttpError("Token 不正确"));
            }
            var token = authHeader.Value.FirstOrDefault();
            string Verify = JwtHelper.VerifyToken(token);
            if(Verify != "")
            {
                context.Response = context.Request.CreateErrorResponse(System.Net.HttpStatusCode.Unauthorized, new HttpError(Verify));
            }
            
        }
    }
}

建好后,在需要进行验证的webapi方法加上验证属性即可

加了[ApiTokenCheck]的方法被请求调用时,都会先触发OnAuthorization方法进行验证,验证不通过通过下列代码返回异常信息给浏览器

context.Response = context.Request.CreateErrorResponse(System.Net.HttpStatusCode.Unauthorized, new HttpError(Verify));

OK,到这里,token生成,保存,发送,接收,验证 ,验证结果返回这个流程就结束了。

如有描述不清楚的地方,也可以直接看代码。

本文代码位置:https://github.com/ScottFan/CsharpJwtToken

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值