Knowledge should be shared free.

我们都知道WebApi最重要的作用就是为外部服务提供相应的数据接口和服务,所以一般WebApi都会连接后台数据库,那么最重要的一件事就是校验,要不然后台数据和服务就等于对所有人开放,那还了得(局域网项目除外,因为系统不挂接外系统,可能安全性要求不那么高,所以就不用那么严格,但是最好也有),总不能直来直去,谁都能用吧,这又不是08年的奥运会,我们一块唱北京欢迎你,我们WebApi不欢迎“陌生人”。这就相当于给WebApi安排一个门卫大爷,那么我们想想一般门卫的流程是什么样的呢?门口站个大爷,来了一个人,大爷问你出入证呢?你给他看,然后看完了,大爷验证证件正确真实,开门让进。我们得给WebApi也安排这么一个大爷,这个大爷就是Token。BlaBla一堆背景,就是想告诉大家WebApi一般都要验证的,用以保证安全,而目前用的比较多的方式都是Token。

Token的好处……很多,百度吧

那么我们该怎么做。

首先Token结构:

Token头,主要数据是Token类型和加密方法;Token载荷,就是有效数据,校验成功后,经Token回传的数据,一般为一个json对象;Token签名,Token的头和尾拼一起,用加密算法和密钥加密后的数据。最后组合一起用点分隔再Base64转义一下,就是Token值了。

Token通信机制:

第一步想进门得申请出入证,所以先向服务提交Token请求。第二步以后进门都得带着出入证,否则门卫大爷会拦住不让进,所以Token获取成功之后的WebApi请求一般要在头部携带Token信息(并不是所有WebApi都需要Token,根据项目需求定)。

代码实现(blabla一堆废话,干货……)

环境:与前一篇一致,不清楚的请查看《VS Code WebApi系列——1、配置》

要引入的Nuget包:

1)Microsoft.AspNetCore.Authentication.JwtBearer V3.0.0

没用最新包,也有其它方式做Token,不过既然选了Net Core,那就尽量微软系下去一路到底。

2)MySql.Data.EntityFrameworkCore V8.0.20

这个不用过多解释,连接数据库的包,Token为什么要用数据库呢?因为很简单,如何发放Token,一般都需要有一个用户表吧,里面存着用户名和密码,申请Token的时候先把用户名和密码提交过来,然后连接后台数据库校验,用户名和密码一致,之后我们再授予Token。

具体编码:

1)EF框架搭建,CodeFirst模式,这里采用原有的一个项目测试数据库,数据库是MySql,创建用户表的Sql如下

CREATE TABLE sys_user  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `user_no` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户编号',
  `user_pwd` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户密码',
  `status_no` int(11) NOT NULL COMMENT '用户状态',
  `user_name` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '用户名称',
  `gender` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '用户性别',
  `mobile_phone` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '用户联系方式',
  `email` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '用户邮箱',
  `create_by` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '创建人',
  `create_at` timestamp(0) NOT NULL COMMENT '创建时间',
  `update_by` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '更新人',
  `update_at` timestamp(0) NULL DEFAULT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE INDEX `uq_sys_user_no`(`user_no`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

新建表,并添加几个用户,之后添加用户实体:(单独新建一个命名空间,就是文件夹)

namespace ***.DapWebApi.Model
{
    [Table("sys_user")]
    public class SysUser
    {
        [Column("id")]
        public int Id { get; set; }
        [Column("user_no")]
        public string UserNo { get; set; }
        [Column("user_pwd")]
        public string UserPwd { get; set; }
        [Column("status_no")]
        public int StatusNo { get; set; }
        [Column("user_name")]
        public string UserName { get; set; }
        [Column("gender")]
        public string Gender { get; set; }
        [Column("mobile_phone")]
        public string MobilePhone { get; set; }
        [Column("email")]
        public string Email { get; set; }
        [Column("create_by")]
        public string CreateBy { get; set; }
        [Column("create_at")]
        public DateTime CreateAt { get; set; }
        [Column("update_by")]
        public string UpdateBy { get; set; }
        [Column("update_at")]
        public DateTime? UpdateAt { get; set; }
    }
}

新建针对MySql中所有数据库表主键Id列的通用Restful WebApi方法:

添加DbContext类,采用DI的方式,关键代码如下:

namespace ***DapWebApi.DataAccess.F***DbContext
{
    public class F***DbContext : DbContext
    {
        public F***DbContext(DbContextOptions<F***DbContext> options) : base(options) { }
        public DbSet<SysUser> SysUsers { get; set; }
    }
}

添加数据接入接口IDao,使用Restful风格,关键代码如下:

namespace ***.DapWebApi.DataAccess.OperateInterface
{
    public interface IDao 
    {
        bool Create<T>(T model) where T:class;
        IEnumerable<T> Get<T>() where T:class;
        T GetById<T>(int id) where T:class;
        bool Update<T>(T model) where T:class;
        bool DeleteById<T>(int id) where T:class;
    }
}
添加数据实现Dao,关键代码如下:
namespace***.DapWebApi.DataAccess.OperateImplement
{
    public class Dao : IDao
    {
        private ***DbContext _db;
        public Dao(***DbContext context)
        {
            _db=context;
        }
        public bool Create<T>(T model) where T : class
        {
            _db.Set<T>().Add(model);
            return _db.SaveChanges()>0;
        }
        public bool DeleteById<T>(int id) where T : class
        {
            _db.Remove(_db.Set<T>().Find(id));
            return _db.SaveChanges()>0;
        }
        public IEnumerable<T> Get<T>() where T : class
        {
            return _db.Set<T>().AsQueryable() as IEnumerable<T>;
        }
        public T GetById<T>(int id) where T : class
        {
            return _db.Set<T>().Find(id);
        }
        public bool Update<T>(T model) where T : class
        {
            _db.Set<T>().Update(model);
            return _db.SaveChanges()>0;
        }
    }
}
以上代码均采用了.Net Core默认的依赖注入方式。
2)编辑配置文件
打开appsetting.json添加以下节点:
"JwtSettings":{
    "Issuer":"http://localhost:5000",
    "Audience":"http://localhost:5000",
    "SecretKey":"1234567890987654321"
  },
  "ConnectionStrings": {
    "***Connection": "server=***;port=***;database=***;uid=***;pwd=***;CharSet=utf8"
  }
下半部分是数据库连接字符串,上半部分是Jwt的配置
Issuer:Token签发者,生成Token的服务器
Audience:Token签发对象,Token签发给谁
SecretKey:密钥串,Base64 Token信息前生成签名的密钥
3)添加服务提供对象的定位对象(感谢stackoverflow提供的实现方式)ServiceLocator,将其放入到Common对象中,关键代码:
namespace ***.DapWebApi.Common
{
    public class ServiceLocator
    {
        public static IServiceProvider Services{get;set;}
        public static void SetServices(IServiceProvider services)
        {
            Services=services;
        }
    }
}
之所以添加这一对象,因为一会要在Jwt的代码实现中通过IserviceProvider接口访问数据接口IDao,继而访问数据库。因为采用了DI的模式,不能直接创建IDao对象,所以采用这个模式访问数据库。
4)添加Jwt的模型实体
在Model层添加实体,用来记录Jwt的实体信息。
namespace ***.DapWebApi.Model
{
    public class JwtSettings
    {
        public string Issuer { get; set; }
        public string Audience { get; set; }
        public string SecretKey { get; set; }
    }
}
5)添加用户视图实体,用来精简数据和屏蔽不必要信息
namespace ***.DapWebApi.Model
{
    public class AuthorSysUserView
    {
        public int Id{get;set;}
        public string UserNo{get;set;}
        public string UserPwd{get;set;}
    }
}
我们这里最主要用到的就是Id,UserNo,UserPwd这三个属性。
6)配置并注入相关依赖:
打开app
  // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
    //add jwt
            services.Configure<JwtSettings>(Configuration.GetSection("JwtSettings"));
            var jwtSettings=new JwtSettings();
            Configuration.Bind("JwtSettings",jwtSettings);

   

    //add db connection and dao

            services.AddDbContext<***DbContext>(options => options.UseMySQL(Configuration.GetConnectionString("***Connection")));
            services.AddScoped<IDao, Dao>();

    //jwt setting

            services.AddAuthentication(options=>{
                options.DefaultAuthenticateScheme=JwtBearerDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme=JwtBearerDefaults.AuthenticationScheme;
            }).AddJwtBearer(o=>{
                o.TokenValidationParameters=new Microsoft.IdentityModel.Tokens.TokenValidationParameters{
                    //token source
                    ValidIssuer=jwtSettings.Issuer,
                    //token apply from
                    ValidAudience=jwtSettings.Audience,
                    //encrypt key
                    IssuerSigningKey=new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings.SecretKey))
                    //options
                    //ValidateIssuerSigningKey=true;
                    //ValidateLifetime=true,
                    //server time padding
                    //Clockskew=TimeSpan.Zero
                };
            });
            services.AddControllers();
    //add provider
            var provider=services.BuildServiceProvider();
            ServiceLocator.SetServices(provider);
        }
        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            app.UseAuthentication();
            app.UseHttpsRedirection();
            app.UseRouting();
            app.UseAuthorization();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }

7)添加授权的控制器,实现Token的下发

namespace ***.DapWebApi.Controllers
{
    [Route("api/[controller]")]
    public class AuthorizeController : Controller
    {
        private JwtSettings _jwtSettings;
        public AuthorizeController(IOptions<JwtSettings> _jwtSettingsAccesser)
        {
            _jwtSettings = _jwtSettingsAccesser.Value;
        }
        private IServiceProvider _provider;
        [HttpGet]
        public IActionResult Token([FromBody] AuthorSysUserView userView)
        {
            if (ModelState.IsValid)
            {
     //database access
                _provider = ServiceLocator.Services;
                IDao dao = _provider.GetService(typeof(IDao)) as IDao;
                if (dao != null)
                {
                    SysUser user = dao.GetById<SysUser>(userView.Id);
      //Md5Tool is a static class which change pwd to md5 string
                    if (user.UserNo == userView.UserNo && Md5Tool.GetMd5ByString(userView.UserPwd) == user.UserPwd)
                    {
       //add payload
       //添加JWT有效载荷,想要回传什么信息,就可以在这添加,不要太复杂,觉得这挺安全的,所有数据一股脑都扔到有效载荷里,程序跑死出错自己想办法去,这里它只是相当于Session或者Cookie的变种,并不适合承载大量数据
                        Claim[] claim = new Claim[]{
                        new Claim(ClaimTypes.Sid,userView.UserNo.ToString()),
                        new Claim(ClaimTypes.UserData,userView.Id.ToString())
                        };
                        //get key
                        var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtSettings.SecretKey));
                        //register token
                        var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
                        //other setting

       //这里只是示例,其实这里还可以添加一些其它控制信息,具体看项目需求,请根据需求查看官方文档对这里进行处理

                        //create token
                        var token = new JwtSecurityToken(_jwtSettings.Issuer, _jwtSettings.Audience, claim, DateTime.Now, DateTime.Now.AddHours(1), creds);
                        return Ok(new { token = new JwtSecurityTokenHandler().WriteToken(token) });
                    }
                }
            }
            return BadRequest();
        }
    }
}

8)WebApi 关于SysUser的控制器实现:

注意在控制器前添加了属性Authorize,那么这个控制器的访问就必须配备令牌Token,否则将被拒绝访问,我们的门卫大爷就上岗了,当然如果希望给某个Action添加豁免行为,也就是说某个请求不再希望进行Token验证,只需要把属性从这个控制器上转移到其它Action上就可了。
namespace ***.DapWebApi.Controllers
{
    [Authorize]
    [ApiController]
    [Route("api/sysuser")]
    public class SysUserController:ControllerBase
    {
        private IDao _dao=null;
        public SysUserController(IDao dao)
        {
            _dao=dao;
        }
        [HttpPost]
        public IActionResult Create(string userNo, string userPwd, int statusNo, string userName, string createBy)
        {
            if (string.IsNullOrWhiteSpace(userNo))
            {
                return Content("编号不能为空");
            }
            if (string.IsNullOrWhiteSpace(userPwd))
            {
                return Content("密码不能为空");
            }
            if (statusNo <= 0)
            {
                return Content("状态数据错误");
            }
            if (string.IsNullOrWhiteSpace(userName))
            {
                return Content("姓名不能为空");
            }
            if (string.IsNullOrWhiteSpace(createBy))
            {
                return Content("创建者不能为空");
            }
            SysUser user = new SysUser()
            {
                UserNo = userNo,
                UserPwd = userPwd,
                StatusNo = statusNo,
                UserName = userName,
                Gender = "男",
                MobilePhone = "130XXXXXXXX",
                Email = "godisME@Hoven.com",
                CreateBy = createBy,
                CreateAt = DateTime.Now,
                UpdateBy = createBy,
                UpdateAt = DateTime.Now
            };
            if (_dao.Create(user))
            {
                return Content("用户添加成功");
            }
            else
            {
                return Content("用户添加失败");
            }
        }
        [HttpGet]
        public IActionResult Gets()
        {
            IEnumerable<SysUser> users = _dao.Get<SysUser>();
            return new JsonResult(users);
        }
        [HttpGet("{id}")]
        public IActionResult Get(int id)
        {
            SysUser user=_dao.GetById<SysUser>(id);
            return new JsonResult(user);
        }
        [HttpPut]
        public IActionResult Update(int id, string userNo, string userPwd, int statusNo, string userName, string createBy)
        {
            if (id <= 0)
            {
                return Content("主键数据错误");
            }
            if (string.IsNullOrWhiteSpace(userNo))
            {
                return Content("编号不能为空");
            }
            if (string.IsNullOrWhiteSpace(userPwd))
            {
                return Content("密码不能为空");
            }
            if (statusNo <= 0)
            {
                return Content("状态数据错误");
            }
            if (string.IsNullOrWhiteSpace(userName))
            {
                return Content("姓名不能为空");
            }
            if (string.IsNullOrWhiteSpace(createBy))
            {
                return Content("创建者不能为空");
            }
            SysUser user = new SysUser()
            {
                Id = id,
                UserNo = userNo,
                UserPwd = userPwd,
                StatusNo = statusNo,
                UserName = userName,
                Gender = "男",
                MobilePhone = "130XXXXXXXX",
                Email = "godisME@Hoven.com",
                CreateBy = createBy,
                CreateAt = DateTime.Now,
                UpdateBy = createBy,
                UpdateAt = DateTime.Now
            };
            if (_dao.Update(user))
            {
                return Content("用户更新成功");
            }
            else
            {
                return Content("用户更新失败");
            }
        }
        [HttpDelete]
        public IActionResult Delete(int id)
        {
            if (id<=0)
            {
                return Content("主键数据错误");
            }
            if (_dao.DeleteById<SysUser>(id))
            {
                return Content("用户删除成功");
            }
            else
            {
                return Content("用户删除失败");
            }
        }
    }

以上就是所有的代码配置,其实其中除了JWT还包括一些其它的WebApi知识,东西比较多大家慢慢搭建,都搭建好了之后就可以调试运行了。项目结构如下图所示

进行调试,使用postman访问api,首先不申请Token访问,看看结果

很明显,结果为401,未授权,
接下来申请Token,这里测试的用户名为001,密码为123,id为1,通过body传入数据
成功获得Token,之后携带token访问之前的WebApi

可以看到,携带token后,WebApi可以正常访问并获取数据了。

这些都成功了,这次咱就来套煎饼果子算了……

VS Code WebApi系列——2、jwt结合数据库校验的更多相关文章

  1. VS Code WebApi系列——3、发布

    上两篇已经实现了WebApi及基于jwt的Token设置,那么功能做完了,该发布WebApi了.为什么要对发布进行一下说明呢,因为是基于vscode和.netcore的发布,所以可能会遇到莫名奇妙的问 ...

  2. VS Code WebApi系列——1、配置

    Knowledge should be Shared in Free. 最近在研究VS code下的webapi,看了很多文档,还是微软官方的例子好,不过不太适应国人习惯,所以写点东西. 直接了当 开 ...

  3. 1.什么是Code First(EF Code First 系列)

    EF4.1中开始支持Code First .这种方式在领域设计模式中非常有用.使用Code First模式,你可以专注于领域设计,根据需要,为你一个领域的对象创建类集合,而不是首先来设计数据库,然后来 ...

  4. Entityframework Code First 系列之数据注释

    上一篇<Entityframework Code First 系列之项目搭建>讲了搭建一个Code First的控制台项目.里面有一些内容并没有扩展出来讲,因为篇幅有限.这篇针对上面内容中 ...

  5. WebApi系列(从.Net FrameWork 到 .Net Core)

    一. 简介  1. 什么是WebApi? WebApi是一个很广泛的概念,在这里我们特指.Net平台下的Asp.Net WebApi框架,它是针对各种客户端(浏览器.APP等)来构建Http服务的一个 ...

  6. 第九节:JWT简介和以JS+WebApi为例基于JWT的安全校验

    一. 简介 1. 背景 传统的基于Session的校验存在诸多问题,比如:Session过期.服务器开销过大.不能分布式部署.不适合前后端分离的项目. 传统的基于Token的校验需要存储Key-Val ...

  7. 单元测试系列之八:Sonar 数据库表关系整理一(续)

    更多原创测试技术文章同步更新到微信公众号 :三国测,敬请扫码关注个人的微信号,感谢! 简介:Sonar平台是目前较为流行的静态代码扫描平台,为了便于使用以及自己二次开发,有必要对它的数据库结构进行学习 ...

  8. SpringBoot系列 - 集成JWT实现接口权限认证

    会飞的污熊 2018-01-22 16173 阅读 spring jwt springboot RESTful API认证方式 一般来讲,对于RESTful API都会有认证(Authenticati ...

  9. 9.2 翻译系列:数据注解特性之---Column【EF 6 Code First系列】

    原文链接:http://www.entityframeworktutorial.net/code-first/column-dataannotations-attribute-in-code-firs ...

随机推荐

  1. 当.Net成为大厂门槛代码小白该何去何从?

    掌握.Net已成为进入大厂的通行牌.越来越多的互联网软件公司开始使用.Net Core,根据去年数据显示腾讯.网易.顺丰.携程.中通.申通.同程艺龙.微医.233网校.问卷星.金蝶等关键业务已经在往. ...

  2. Spring MVC介绍和第一个例子

    1.Spring mvc概述 spring mvc是spring提供给web应用框架设计,实际上MVC框架是一个设计理念.它不仅存在java世界中而且广泛在于各类语言和开发中,包括web的前端应用.对 ...

  3. iOS开发添加新手引导

    往往项目中经常出现此类需求 用户通过点击引导按钮可响应页面附带按钮的点击事件. // // gzhGuideView.h // GuideView // // Created by 郭志贺 on 20 ...

  4. 题解 P4071 【[SDOI2016]排列计数】 (费马小定理求组合数 + 错排问题)

    luogu题目传送门! luogu博客通道! 这题要用到错排,先理解一下什么是错排: 问题:有一个数集A,里面有n个元素 a[i].求,如果将其打乱,有多少种方法使得所有第原来的i个数a[i]不在原来 ...

  5. [翔哥高手无敌之路]0-002.如何提取apk中的信息?

    面对一款apk软件,我们如何去获取它的信息,如何获取它的版本号,包名,或者ID,用户权限,这些信息都隐藏在apk包中的AndroidManifest.xml文件中,解开它我们就能获取任何想要的信息.但 ...

  6. [Wireshark]_003_电子邮件抓包分析

    电子邮件是我们的生活工作中经常使用的一种服务,用来联系世界各地的朋友,客户.下面我们就用Wireshark对电子邮件进行抓包. 准备工作: 邮件客户端一款(Outlook,Foxmail,KooMai ...

  7. 关于ubuntu下使用l2tpvpn和远程桌面windows系统的测试

    一.背景: 2019年9月下旬到10月上旬,到海南澄迈福山度假.随身带的笔记本电脑中windows10系统因硬盘故障挂了,在另一块硬盘上的ubuntu18.04系统正常.因媳妇需要在10月1日远程回公 ...

  8. js函数prototype属性学习(一)

    W3school上针对prototype属性是这么给出定义和用法的:使您有能力向对象添加属性和方法.再看w3school上给的那个实例,如下图: 仔细一看,原来最基本的作用就是对某些对象的属性.方法来 ...

  9. Take advantage of Checkra1n to Jailbreak iDevice for App analysis

    An unpatchable bootrom exploit called "checkm8" works on all iDevices up until the iPhone ...

  10. Java实现 洛谷 导弹拦截

    题目描述 某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统.但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度.某天,雷达捕捉到敌国的导弹 ...