EF+SqlServer/Mysql(三)JWT+Authorize的使用

集成JWT权限认证

为什么要使用JWT,我们先来看看传统的身份校验方式
用户向服务器发送用户名和密码->服务器验证通过后,在当前session对话里保存用户登录信息,比如用户角色,用户名,登录时间等->服务器向用户返回一个session_id,写入用户的cookie->随后的用户每次发送请求都会将session_id传回服务器->服务器搜到session_id,找到前期保存的数据,由此得知用户的身份
这种模式的问题在于,扩展性不好,单机情况下没有问题,如果是服务器集群,要求session数据共享,每台服务器都能够读取session,如果session存储的节点挂了,那么整个服务都会瘫痪,体验相当不好,风险也很高,相比之下,JWT的实现方式是将用户信息存储在客户端,服务端不进行保存,每次请求都把令牌带上来校验用户的登录状态,这样服务器就变成了无状态的,服务器集群也很好扩展。

当用户需要访问带权限验证的API时,应该使用承载模式发送JWT,通常在Authorization标头,格式如下:
Authorization:Bearer <boken>

网站的功能简介
登录成功后将用户信息写入Claim对象生成token返回web端,写入cookie,当用户修改密码时,通过ajax调用Api接口在头部添加Authorization:Bearer <token>发送请求,authorize会验证token是否有效,如果失效则返回401,成功则进行密码修改的操作。

登录API

    /// <summary>
    /// 登录
    /// </summary>
    /// <param name="user"></param>
    /// <returns></returns>
    [HttpPost]
    [Route("Login")]
    public async Task<IActionResult> Login(UserDto user)
    {
        var result = new ResultInclusion();
        if (string.IsNullOrEmpty(user.UserName))
        {
            result.Code = -1;
            result.Msg = "请输入账号";
            return new JsonResult(result);
        }
        if (string.IsNullOrEmpty(user.Password))
        {
            result.Code = -1;
            result.Msg = "请输入密码";
            return new JsonResult(result);
        }
        //dto.LoginIP = ip.ToString();
        ////获取客户端浏览器信息
        //dto.Explorer = _httpContextAccessor.HttpContext.Request.Headers["User-Agent"].ToString();
        ////获取客户端浏览器信息
        //dto.ClientOS = HttpContextHelper.GetOsVersion(dto.Explorer);

        var usermodel = await _service.GetAsync(p=>p.UserName==user.UserName);
        if (usermodel != null)
        {
            if (usermodel.Password == GetMD5.Get_MD5(user.Password, "utf-8"))
            {
                var claims = new List<Claim>()
                {
                    new Claim(JwtRegisteredClaimNames.Nbf,$"{new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds()}"),
                    new Claim(JwtRegisteredClaimNames.Exp,$"{new DateTimeOffset(DateTime.Now.AddMinutes(30)).ToUnixTimeSeconds()}"),
                    new Claim(ClaimTypes.Name,user.UserName)
                };
                 
                var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:SecretKey"]));
                var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
                var token = new JwtSecurityToken(
                    issuer:Configuration["Jwt:Domain"],
                    audience:Configuration["Jwt:Domain"],
                    claims:claims,
                    expires:DateTime.Now.AddMinutes(30),
                    signingCredentials:creds
                    );
              var accesstoken=  new JwtSecurityTokenHandler().WriteToken(token);
                result.Code = 0;
                result.Msg = "登录成功";
                result.Data = new
                {
                    username = user.UserName,
                    token =accesstoken
                };
                return new JsonResult(result);
            }
            else {
                result.Code = -1;
                result.Msg = "密码错误";
                return new JsonResult(result);
            }
        }
        else {
            result.Code = -1;
            result.Msg = "用户名不存在";
            return new JsonResult(result);
        }
    }

appsettings.json配置

  "Jwt": {
"SecretKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDI2a2EJ7m872v0afyoSDJT2o1+SitIeJSWtLJU8/Wz2m7gStexajkeD+Lka6DSTy8gt9UwfgVQo6uKjVLG5Ex7PiGOODVqAEghBuS7JzIYU5RvI543nNDAPfnJsas96mSA7L/mD7RTE2drj6hf3oZjJpMPZUQI/B1Qjb5H3K3PNwIDAQAB",
"Domain": "http://localhost:5001"

}

startup.cs ConfigureServices方法中添加JWT相关配置

            //添加权限认证+jwt认证
        services.AddAuthentication(b =>
        {
            b.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            b.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
        })
            .AddJwtBearer(options => {
                options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
                {
                    ValidateIssuer = true,//是否验证Issuer
                    ValidateAudience = true,//是否验证Audience
                    ValidateLifetime = true,//是否验证失效时间
                    ClockSkew = TimeSpan.FromMilliseconds(30),
                    ValidateIssuerSigningKey = true,//是否验证SecurityKey
                    ValidAudience = Configuration["Jwt:Domain"],//Audience
                    ValidIssuer = Configuration["Jwt:Domain"],//Issuer,这两项和前面签发jwt的设置一致
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:SecretKey"]))//拿到SecurityKey

                };
            });

Configure方法中添加权限过滤器

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        app.UseAuthentication();

        app.UseMvc();
    }

Web端:用户登录成功后返回的token写进cookie

    <form class="form-signin form-group" id="AppForm">
    <h2 class="form-signin-heading">欢迎使用我的Blog</h2>
    <label for="inputEmail" >账号</label>
    <input type="text" id="username" name="username" class="form-control" placeholder="请输入账号" value="liulei" required autofocus>
    <label for="inputPassword" >密码</label>
    <input type="password" id="password" name="password" class="form-control" placeholder="请输入密码" value="123456" required>
    <button class="btn btn-lg btn-primary btn-block" id="loginForm" type="submit">登录</button>
    </form>      


                $.ajax({
                type: 'post',
                url: url + "/api/user/Login",
                data:JSON.stringify( { UserName: $("#username").val(), Password: $("#password").val() }),
                contentType:'application/json',
                success: function (data) {
                   //写入cookie
                    $.cookie("access_token", data.data.token, {
                        expires: new Date().getTime() + (30 * 60 * 1000)
                        , path: '/'
                    });
                   console.log(data);
                },
                error: function (xhr) {
                    $('button[type="submit"]').text('登录').removeAttr('disabled');
                },
                beforeSend: function (XHR) {
                    $('button[type="submit"]').text('登录中...').attr('disabled', 'disabled');
                },
                complete: function (XHR, TS) {
                    $('button[type="submit"]').text('登录').removeAttr('disabled');
                }
            });

修改密码

<form class="form-signin form-group" id="AppForm">
    <h2 class="form-signin-heading">修改密码</h2>
    <label for="inputEmail" >旧密码</label>
    <input type="password" id="oldpassword" name="oldpassword" class="form-control" placeholder="请输入旧密码" required>
    <label for="inputPassword" >新密码</label>
    <input type="password" id="newpassword" name="newpassword" class="form-control" placeholder="请输入新密码" required>

    <button class="btn btn-lg btn-primary btn-block" id="loginForm" type="submit">登录</button>
</form>      


      $.ajax({
                type: 'post',
                url: url + "/api/user/UpdatePWD",
                data: JSON.stringify({ OldPassWord: $("#oldpassword").val(), Password: $("#newpassword").val() }),
                contentType: 'application/json',
                beforeSend: function (XHR) {
                    XHR.setRequestHeader("Authorization", "bearer " + $.cookie("access_token"));
                },
               success: function (data) {
                   console.log(data);
                },
                error: function (xhr) {
                    $('button[type="submit"]').text('确定').removeAttr('disabled');
                },
                complete: function (XHR, TS) {
                    $('button[type="submit"]').text('确定').removeAttr('disabled');
                }
            });

修改密码API

    /// <summary>
    /// 修改密码
    /// </summary>
    /// <param name="user"></param>
    /// <returns></returns>
    [HttpPost]
    [Route("UpdatePWD")]
    [Authorize]
    public async Task<IActionResult> UpdatePWD(UserDto user) {
        var result = new ResultInclusion();
        //从claim中获取用户信息
        var schemeProvider = HttpContext.RequestServices.GetService(typeof(IAuthenticationSchemeProvider)) as IAuthenticationSchemeProvider;
        var defaultAuthenticate = await schemeProvider.GetDefaultAuthenticateSchemeAsync();
        if (defaultAuthenticate != null)
        {
            var JWTClaim = await HttpContext.AuthenticateAsync(defaultAuthenticate.Name);
            var ClaimUser = JWTClaim?.Principal;
            if (ClaimUser != null)
            {
                var userPassword = GetMD5.Get_MD5(user.OldPassWord, "utf-8");
                var userinfo = await _service.GetAsync(p => p.UserName == ClaimUser.Identity.Name);
                if (userinfo!=null&&userinfo.Password == userPassword)
                {
                    userinfo.Password = GetMD5.Get_MD5(user.Password, "utf-8");
                    result.Data = await _service.UpdateAsync(userinfo);
                    result.Code = 0;
                    result.Msg = "修改成功";
                }
                else
                {
                    result.Code = -1;
                    result.Msg = "旧密码不正确";
                }
            }
        }
        
        return new JsonResult(result);
    }

若未登录直接修改密码则返回401权限未通过:


image.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容