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. Pyqt5_QWidget

    QWidget常用方法: Qwidget.size()#获得客户区的大小 QWidget.width().QWidget.height()#获得客户区的宽度与高度 #设置不可以变宽.高 QWidget ...

  2. Flexible 应用

    Flexibl.js 为我们做了一项工作,媒体查询工作,节约了许多操作 举个例子,移动端的页面设计稿是750px,我们自己换算rem单位,比如我想把屏幕划分为15等份,我就750/15=50,然后用所 ...

  3. MySQL所有的安装部署方式

    目录 一.前言 二.关于MySQL的安装 三.部署规划 3.1 服务器规划 3.2 数据库目录规划 四.准备工具 五.通用二进制包安装MySQL 5.1 上传MySQL通用二进制安装包到node7的/ ...

  4. 基于 kubeadm 搭建高可用的kubernetes 1.18.2 (k8s)集群一 环境准备

    本k8s集群参考了 Michael 的 https://gitee.com/pa/kubernetes-ha-kubeadm-private 这个项目,再此表示感谢! Michael的项目k8s版本为 ...

  5. css 盒模型、box-sizing 学习笔记

    默认情况下,给元素设置的高度和宽度是元素内容区的宽度和高度,给元素加padding 和 border ,元素的实际宽度和高度的计算方式是下面的两个公式: 元素的宽度= 元素的内容区宽度 + 内边距宽度 ...

  6. 使用setTimeout()代替setInterval()

    背景: 在JavaScript中,有两种定时器:setTimeout()和setInterval():setTimeout()只执行一次定时操作,setInterval()执行无限次定时操作:但是大多 ...

  7. 容器技术之Dockerfile(二)

    前文我们聊到了什么是dockerfile,它的主要作用以及dockerfile的一些基本指令的使用方法,回顾请参考https://www.cnblogs.com/qiuhom-1874/p/13019 ...

  8. Java实现 LeetCode 643 子数组最大平均数 I(滑动窗口)

    643. 子数组最大平均数 I 给定 n 个整数,找出平均数最大且长度为 k 的连续子数组,并输出该最大平均数. 示例 1: 输入: [1,12,-5,-6,50,3], k = 4 输出: 12.7 ...

  9. Java实现 LeetCode 350 两个数组的交集 II(二)

    350. 两个数组的交集 II 给定两个数组,编写一个函数来计算它们的交集. 示例 1: 输入: nums1 = [1,2,2,1], nums2 = [2,2] 输出: [2,2] 示例 2: 输入 ...

  10. Java实现 LeetCode 257 二叉树的所有路径

    257. 二叉树的所有路径 给定一个二叉树,返回所有从根节点到叶子节点的路径. 说明: 叶子节点是指没有子节点的节点. 示例: 输入: 1 / \ 2 3 \ 5 输出: ["1->2 ...