热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

VSCodeWebApi系列——2、jwt结合数据库校验

Knowledgeshouldbesharedfree.我们都知道WebApi最重要的作用就是为外部服务提供相应的数据接口和服务,所以一般WebApi都会连接后台数据库,那么最重要

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 { getset; }



        [Column("user_no")]

        public string UserNo { getset; }



        [Column("user_pwd")]

        public string UserPwd { getset; }



        [Column("status_no")]

        public int StatusNo { getset; }



        [Column("user_name")]

        public string UserName { getset; }



        [Column("gender")]

        public string Gender { getset; }



        [Column("mobile_phone")]

        public string MobilePhone { getset; }



        [Column("email")]

        public string Email { getset; }



        [Column("create_by")]

        public string CreateBy { getset; }



        [Column("create_at")]

        public DateTime CreateAt { getset; }



        [Column("update_by")]

        public string UpdateBy { getset; }



        [Column("update_at")]

        public DateTimeUpdateAt { getset; }

    }

}

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

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



namespace ***DapWebApi.DataAccess.F***DbContext

{

    public class F***DbContext : DbContext

    {

        public F***DbContext(DbContextOptions<F***DbContextoptions) : base(options) { }



        public DbSet<SysUserSysUsers { getset; }

    }

}

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



namespace ***.DapWebApi.DataAccess.OperateInterface

{

    public interface IDao 

    {

        bool Create<T>(T modelwhere T:class;



        IEnumerable<TGet<T>() where T:class;



        T GetById<T>(int idwhere T:class;



        bool Update<T>(T modelwhere T:class;



        bool DeleteById<T>(int idwhere 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 modelwhere T : class

        {

            _db.Set<T>().Add(model);

            return _db.SaveChanges()>0;

        }



        public bool DeleteById<T>(int idwhere T : class

        {

            _db.Remove(_db.Set<T>().Find(id));

            return _db.SaveChanges()>0;

        }



        public IEnumerable<TGet<T>() where T : class

        {

            return _db.Set<T>().AsQueryable() as IEnumerable<T>;

        }



        public T GetById<T>(int idwhere T : class

        {

            return _db.Set<T>().Find(id);

        }



        public bool Update<T>(T modelwhere 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 { getset; }

        public string Audience { getset; }

        public string SecretKey { getset; }

    }

}

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<IDaoDao>();

    //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 appIWebHostEnvironment 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([FromBodyAuthorSysUserView 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(keySecurityAlgorithms.HmacSha256);

                        //other setting

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

                        //create token

                        var token = new JwtSecurityToken(_jwtSettings.Issuer_jwtSettings.AudienceclaimDateTime.NowDateTime.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 userNostring userPwdint statusNostring userNamestring 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<SysUserusers = _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 idstring userNostring userPwdint statusNostring userNamestring 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结合数据库校验



推荐阅读
  • 本文介绍了lua语言中闭包的特性及其在模式匹配、日期处理、编译和模块化等方面的应用。lua中的闭包是严格遵循词法定界的第一类值,函数可以作为变量自由传递,也可以作为参数传递给其他函数。这些特性使得lua语言具有极大的灵活性,为程序开发带来了便利。 ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • 知识图谱——机器大脑中的知识库
    本文介绍了知识图谱在机器大脑中的应用,以及搜索引擎在知识图谱方面的发展。以谷歌知识图谱为例,说明了知识图谱的智能化特点。通过搜索引擎用户可以获取更加智能化的答案,如搜索关键词"Marie Curie",会得到居里夫人的详细信息以及与之相关的历史人物。知识图谱的出现引起了搜索引擎行业的变革,不仅美国的微软必应,中国的百度、搜狗等搜索引擎公司也纷纷推出了自己的知识图谱。 ... [详细]
  • 本文介绍了指针的概念以及在函数调用时使用指针作为参数的情况。指针存放的是变量的地址,通过指针可以修改指针所指的变量的值。然而,如果想要修改指针的指向,就需要使用指针的引用。文章还通过一个简单的示例代码解释了指针的引用的使用方法,并思考了在修改指针的指向后,取指针的输出结果。 ... [详细]
  • 在Android中解析Gson解析json数据是很方便快捷的,可以直接将json数据解析成java对象或者集合。使用Gson解析json成对象时,默认将json里对应字段的值解析到java对象里对应字段的属性里面。然而,当我们自己定义的java对象里的属性名与json里的字段名不一样时,我们可以使用@SerializedName注解来将对象里的属性跟json里字段对应值匹配起来。本文介绍了使用@SerializedName注解解析json数据的方法,并给出了具体的使用示例。 ... [详细]
  • 基于layUI的图片上传前预览功能的2种实现方式
    本文介绍了基于layUI的图片上传前预览功能的两种实现方式:一种是使用blob+FileReader,另一种是使用layUI自带的参数。通过选择文件后点击文件名,在页面中间弹窗内预览图片。其中,layUI自带的参数实现了图片预览功能。该功能依赖于layUI的上传模块,并使用了blob和FileReader来读取本地文件并获取图像的base64编码。点击文件名时会执行See()函数。摘要长度为169字。 ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • 高质量SQL书写的30条建议
    本文提供了30条关于优化SQL的建议,包括避免使用select *,使用具体字段,以及使用limit 1等。这些建议是基于实际开发经验总结出来的,旨在帮助读者优化SQL查询。 ... [详细]
  • 在project.properties添加#Projecttarget.targetandroid-19android.library.reference.1..Sliding ... [详细]
  • 猜字母游戏
    猜字母游戏猜字母游戏——设计数据结构猜字母游戏——设计程序结构猜字母游戏——实现字母生成方法猜字母游戏——实现字母检测方法猜字母游戏——实现主方法1猜字母游戏——设计数据结构1.1 ... [详细]
  • CentOS 7部署KVM虚拟化环境之一架构介绍
    本文介绍了CentOS 7部署KVM虚拟化环境的架构,详细解释了虚拟化技术的概念和原理,包括全虚拟化和半虚拟化。同时介绍了虚拟机的概念和虚拟化软件的作用。 ... [详细]
  • 本文介绍了一种解析GRE报文长度的方法,通过分析GRE报文头中的标志位来计算报文长度。具体实现步骤包括获取GRE报文头指针、提取标志位、计算报文长度等。该方法可以帮助用户准确地获取GRE报文的长度信息。 ... [详细]
  • PDF内容编辑的两种小方法,你知道怎么操作吗?
    本文介绍了两种PDF内容编辑的方法:迅捷PDF编辑器和Adobe Acrobat DC。使用迅捷PDF编辑器,用户可以通过选择需要更改的文字内容并设置字体形式、大小和颜色来编辑PDF文件。而使用Adobe Acrobat DC,则可以通过在软件中点击编辑来编辑PDF文件。PDF文件的编辑可以帮助办公人员进行文件内容的修改和定制。 ... [详细]
  • CentOS 6.5安装VMware Tools及共享文件夹显示问题解决方法
    本文介绍了在CentOS 6.5上安装VMware Tools及解决共享文件夹显示问题的方法。包括清空CD/DVD使用的ISO镜像文件、创建挂载目录、改变光驱设备的读写权限等步骤。最后给出了拷贝解压VMware Tools的操作。 ... [详细]
  • 本文介绍了前端人员必须知道的三个问题,即前端都做哪些事、前端都需要哪些技术,以及前端的发展阶段。初级阶段包括HTML、CSS、JavaScript和jQuery的基础知识。进阶阶段涵盖了面向对象编程、响应式设计、Ajax、HTML5等新兴技术。高级阶段包括架构基础、模块化开发、预编译和前沿规范等内容。此外,还介绍了一些后端服务,如Node.js。 ... [详细]
author-avatar
cssic_630
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有