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生成,保存,发送,接收,验证 ,验证结果返回这个流程就结束了。
如有描述不清楚的地方,也可以直接看代码。