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. Java8中的Lambda表达式

    Lambda是什么 Lambda表达式,也可称为闭包,是java8的新特性,作用是取代大部分内部类,优化java代码结构,让代码变得更加简洁紧凑. Lambda的基本语法 (expression)-& ...

  2. Kubernetes基本概念与架构

    Kubernetes,面向云原生应用的新“云平台” Kubernetes:以google Brog为原型 Kubernetes的成长历程: l  2014年,Kubernetes正式由google开源 ...

  3. 读Pyqt4教程,带你入门Pyqt4 _009

    QLineEdit QLineEdit 窗口组件用来输入或者编辑单行纯文本,有撤销/重做,剪切/粘贴和拖放功能. #!/usr/bin/python # -*- coding: utf-8 -*- # ...

  4. Java和NodeJS解析XML对比

    Java解析XML 1.接收xml文件或者字符串,转为InputStream 2.使用DocumentBuilderFactory对象将InputStream转为document对象 Document ...

  5. Linux suid 提权

    SUID (Set owner User ID up on execution) 是给予文件的一个特殊类型的文件权限.在 Linux/Unix中,当一个程序运行的时候, 程序将从登录用户处继承权限.S ...

  6. [apue] 一个工业级、跨平台的 tcp 网络服务框架:gevent

    作为公司的公共产品,经常有这样的需求:就是新建一个本地服务,产品线作为客户端通过 tcp 接入本地服务,来获取想要的业务能力. 与印象中动辄处理成千上万连接的 tcp 网络服务不同,这个本地服务是跑在 ...

  7. Java实现 蓝桥杯VIP 算法训练 回文数

    import java.util.Scanner; public class 回文数 { static int time = 0; public static int change(String st ...

  8. 第九届蓝桥杯JavaC组省赛真题

    解题代码部分来自网友,如果有不对的地方,欢迎各位大佬评论 题目1.哪天返回 题目描述 小明被不明势力劫持.后被扔到x星站再无问津.小明得知每天都有飞船飞往地球,但需要108元的船票,而他却身无分文. ...

  9. java实现整数翻转

    ** 整数翻转** 以下程序把一个整数翻转(8765变为:5678),请补充缺少的代码. int n = 8765; int m = 0; while(n>0) { m = __________ ...

  10. MySQL基本DML

    DML: 数据操纵语言, 主要用来向数据库中添加. 删除. 修改数据用的.在开发中经常会用到,所以,在此也小小总结一下: CREATE DATABASE db2 DEFAULT CHARSET UTF ...