Flutter开发环境配置踩坑之cmdline-tools component is missing_ZXHL_hxf的博客-CSDN博客

mikel阅读(759)

来源: Flutter开发环境配置踩坑之cmdline-tools component is missing_ZXHL_hxf的博客-CSDN博客

1、sdk中添加工具

 

2、添加完还是不行的话,请检查SDK路径。

我的SDK路径之前从C盘移到D盘,不是在默认路径中,而Flutter doctor无法认到现用的SDK路径,所以安装完工具也一直过不去,后面重新配置了flutter的sdk路径就可以了。

使用下面这条指令可以配置flutter的sdk路径:

flutter config –Android-sdk /path/to/Android/sdk
其中 /path/to/android/sdk 是你自己的sdk路径,如我的是 D:Android\Sdk

————————————————
版权声明:本文为CSDN博主「xiaofeng-huang」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/ZXHL_hxf/article/details/121208026

Blazor入手教程(三)列表和条件渲染 - 小小爵 - 博客园

mikel阅读(591)

来源: Blazor入手教程(三)列表和条件渲染 – 小小爵 – 博客园

Blazor和vue的列表渲染以及条件渲染大致类似,有区别的就是Blazor没有像vue里面v-show一样的功能。当然要实现也比较容易,只需要控制节点的diplay样式即可。

 

 

组件Virtualize

 

Virtualize(虚拟化) 组件是一种代替列表渲染的内置组件,.Net5才有,.net core是没有的。

他用来渲染大量数据时使用,功能有点类似于前端中常见的图片懒加载。

我这里暂时没有升级到.Net5所以这块暂时没有,以后补上。

 

Blazor入手教程(二)css和class绑定 - 小小爵 - 博客园

mikel阅读(972)

来源: Blazor入手教程(二)css和class绑定 – 小小爵 – 博客园

Css和class绑定

 

Blazor中的css和class绑定还是比较便利的。方式也和vue 十分类似,感觉唯一区别就是Blazor中拼接时不用像vue那样用+加号拼接字符串

 

 

@page "/cssbinding"
<style scoped>
    .active{
        width:80px;
        height:80px;
    }
    .active2 {
        background-color:#ff6a00;
    }
</style>
<h3>绑定单个css属性</h3>
<div style="font-size:@(fontsize)px">文字大小</div>
<h3 style="margin-top:100px">绑定多个css属性</h3>
<div style="@style.ToString()"></div>
<h3 style="margin-top:100px">绑定多个class</h3>
<div class="@getClass()"></div>
@code {
    public class Style
    {
        public int height { get; set; } = 100;
        public int width { get; set; } = 100;
        public string color { get; set; } = "#ccc";
        public override string ToString()
        {
            return $"width:{width}px;height:{height}px;background-color:{color}";
        }
    }
    public int fontsize =30;
    public Style style = new Style();
    public string[] classArray = new string[] { "active", "active2" };
    public string getClass()
    {
        return string.Join(" ", classArray);
    }
}

 

 

Blazor入手教程(一)前言 - 小小爵 - 博客园

mikel阅读(849)

来源: Blazor入手教程(一)前言 – 小小爵 – 博客园

Blazor入手教程(一)前言

 

结论

最近在学习blazor。得出了这么一个结论:

Blazor是一门很值得学习的技术,未来.net下将会有相当多的 web应用使用blazor开发。十分看好这一技术,原因有这么两点:

1,开发效率高。选择了Blazor就等于选择了全栈开发。以往的经验告诉我,全栈开发的效率更高,全栈省去了相当一部分的沟通成本,而且使用blazor时可以重用很多以往的C#代码,这能提高不少效率。

2,社区发展势头很好。已经有很多很不错的组件库AntDesign-blazor,Bootsrap Blazor等等。且微软对blazor也是相当支持的,从它把blazor的文档放到最前面以及文档的详细程度就可以看出。

 

两种模式

Blazor 提供了两种模式,服务端模式(server-inside) 和客户端模式(client-inside)

服务端基于SignalR ,以websocket上连接来保持状态和UI的一致。

客户端模式是基于Webassemely,可以理解为在浏览器执行你的编写的dll文件。

个人感觉Server-inside的应用场景较少,Client-inside才是会被广泛应用的模式,所以本篇主要介绍客户端模式的blazor。

 

和Vue 的对比学习

因为之前有相当一段时间的全栈开发经历(vue),所以在学习的过程发现和blazor和vue有许多相通的地方,比如组件,api,生命周期,数据驱动的思想 等等。如果你之前有学习过vue,那么上手应该是很快的(两天上手毫不夸张)。如果没有,那么在学习过程中一定要先习惯这种数据驱动视图的开发思想。

 

Nginx 的简单使用 (IIS,Asp.Net) - 小小爵 - 博客园

mikel阅读(663)

来源: Nginx 的简单使用 (IIS,Asp.Net) – 小小爵 – 博客园

Nginx 的一些常见功能(windows,AspNet ,IIS

 

 

下载

官方网站https://nginx.org/en/download.html

下载,解压缩是这个样子

 

 

 

启动:

启动方式有两种

方式一:双击nginx.exe

 

方式二:进入cmd 到该目录下,运行 start nginx

 

 

启动闪退,查看错误日志,原因是80端口已经被占用了,所以我们找到nginx.conf文件,换了一个监听的端口8085

 

 

 

 

 

 

 

错误日志文件

 

 

 

 

 

 

 

 

,再次启动,启动成功,启动成功。发现多了一个nginx.pid文件,在浏览器里面访问localhost:8035

 

这个样子就是成功了

 

这里是日志文件

记录了每次请求

 

 

 

接下来,我们做第一个nginx常用的功能:

使用ngxix负载均衡

把请求分散到多个主机

 

 

 

这里我们新建了一个aspnetmvc项目,功能很简单,

 

 

发布之后,复制两个一样的文件夹,只改动web。Config里面的serverid。分别部署到8001端口,8002端口

 

 

8001的配置文件

 

8002的配置文件

 

 

 

 

 

Serverid 1

 

Serverid 2

 

 

 

 

 

我们的目标是:访问8035端口时,将请求通过nginx转到8001和8002

 

 

配置前:

8085

 

8001

 

8002

 

开始配置

 

 

1,在server节点上面加入 upstream节点,起个名字 my_web_server,

 

在大括号里面加入要映射的地址,格式是 server + 地址 +权重,权重越高,访问越多

 

2,修改server节点,里面的server_name 和上面保持一致

 

3,修改 location 节点 名字也要和上面保持一致

 

 

 

齐活。

 

保存配置文件,在cmd中重启nginx配置文件

 

 

重启命令 nginx –s reload

 

 

然后我们再去访问8085 ,配置成功

 

 

 

 

可以看到一次1,一次二,目前是两个不加任何策略配置

接下来我们加上ip_hash 的策略

 

 

接着访问多次8085

,发现他只会映射到8001上面去,这个就是ip_hash策略的作用:根据不通ip的哈希值,指定到固定端口上面去,ip_hash是很常用的一种策略,可以解决一点点的session不共享问题

 

 

 

其他的几种策略这里不再赘述,

可以去看看这里 https://blog.csdn.net/balalamm/article/details/79916483

 

 

 

使用nginx代理二级目录

 

 

我们经常会遇到这样的 结构

 

比如说这是某个应用的接口地址http://api.example.com/,他有v1和v2两个版本

放在不同的服务器或者端口上,我们想使用下面的url来请求

http://api.example.com/v1/  和  http://api.example.com/v2/

 

这时候我们就可以是用nginx来处理

 

我们的目标是

 

请求   http://localhost:8085/v1/    转到8001端口

 

请求   http://localhost:8085/v2/      转到8002端口

 

修改配置文件,这里暂时用不到upstream 节点了

 

修改成这个样子

 

加一个location 就行,是不是很简单,注意8001/后面的斜杠 /一定不能少

 

效果图  v1/

 

 

V2/

 

 

Nginx的功能是十分强大,以上只是基础,其他的功能比如:使用nginx防盗链,请求日志记录,拦截请求等等,使用nginx实现要比在应用程序中实现简单快捷很多,总而言之就是nginx很有学习的必要。

 

附录:nginx常量对照表(原文 https://blog.csdn.net/echizao1839/article/details/80872378

 

$http_user_agent, $http_cookie, 等等。下面是nginx支持的所有内置变量:

$arg_name:请求中的的参数名,即“?”后面的arg_name=arg_value形式的arg_name

$args:请求中的参数值

$binary_remote_addr:客户端地址的二进制形式, 固定长度为4个字节

$body_bytes_sent:传输给客户端的字节数,响应头不计算在内;这个变量和Apache的mod_log_config模块中的“%B”参数保持兼容

$bytes_sent:传输给客户端的字节数 (1.3.8, 1.2.5)

$connection:TCP连接的序列号 (1.3.8, 1.2.5)

$connection_requests:TCP连接当前的请求数量 (1.3.8, 1.2.5)

$content_length:“Content-Length” 请求头字段

$content_type:“Content-Type” 请求头字段

$cookie_name:cookie名称

$document_root:当前请求的文档根目录或别名

$document_uri:同 $uri

$host:优先级如下:HTTP请求行的主机名>”HOST”请求头字段>符合请求的服务器名

$hostname:主机名

$http_name:匹配任意请求头字段; 变量名中的后半部分“name”可以替换成任意请求头字段,如在配置文件中需要获取http请求头:“Accept-Language”,那么将“-”替换为下划线,大写字母替换为小写,形如:$http_accept_language即可。

$https:如果开启了SSL安全模式,值为“on”,否则为空字符串。

$is_args:如果请求中有参数,值为“?”,否则为空字符串。

$limit_rate:用于设置响应的速度限制,详见 limit_rate

$msec:当前的Unix时间戳 (1.3.9, 1.2.6)

$nginx_version:nginx版本

$pid:工作进程的PID

$pipe:如果请求来自管道通信,值为“p”,否则为“.” (1.3.12, 1.2.7)

$proxy_protocol_addr:获取代理访问服务器的客户端地址,如果是直接访问,该值为空字符串。(1.5.12)

$query_string:同 $args

$realpath_root:当前请求的文档根目录或别名的真实路径,会将所有符号连接转换为真实路径。

$remote_addr:客户端地址

$remote_port:客户端端口

$remote_user:用于HTTP基础认证服务的用户名

$request:代表客户端的请求地址

$request_body:客户端的请求主体,此变量可在location中使用,将请求主体通过proxy_pass, fastcgi_pass, uwsgi_pass, 和 scgi_pass传递给下一级的代理服务器。

$request_body_file:将客户端请求主体保存在临时文件中。文件处理结束后,此文件需删除。如果需要之一开启此功能,需要设置client_body_in_file_only。如果将次文件传递给后端的代理服务器,需要禁用request body,即设置proxy_pass_request_body off,fastcgi_pass_request_body off, uwsgi_pass_request_body off, or scgi_pass_request_body off 

$request_completion:如果请求成功,值为”OK”,如果请求未完成或者请求不是一个范围请求的最后一部分,则为空。

$request_filename:当前连接请求的文件路径,由root或alias指令与URI请求生成。

$request_length:请求的长度 (包括请求的地址, http请求头和请求主体) (1.3.12, 1.2.7)

$request_method:HTTP请求方法,通常为“GET”或“POST

$request_time:处理客户端请求使用的时间 (1.3.9, 1.2.6); 从读取客户端的第一个字节开始计时。

$request_uri:这个变量等于包含一些客户端请求参数的原始URI,它无法修改,请查看$uri更改或重写URI,不包含主机名,例如:”/cnphp/test.php?arg=freemouse”。

$scheme:请求使用的Web协议, “http” 或 “https

$sent_http_name:可以设置任意http响应头字段; 变量名中的后半部分“name”可以替换成任意响应头字段,如需要设置响应头Content-length,那么将“-”替换为下划线,大写字母替换为小写,形如:$sent_http_content_length 4096即可。

$server_addr:服务器端地址,需要注意的是:为了避免访问linux系统内核,应将ip地址提前设置在配置文件中。

$server_name:服务器名,www.cnphp.info

$server_port:服务器端口

$server_protocol:服务器的HTTP版本, 通常为 “HTTP/1.0” 或 “HTTP/1.1

$status:HTTP响应代码 (1.3.2, 1.2.2)

$tcpinfo_rtt, $tcpinfo_rttvar, $tcpinfo_snd_cwnd, $tcpinfo_rcv_space:客户端TCP连接的具体信息

$time_iso8601:服务器时间的ISO 8610格式 (1.3.12, 1.2.7)

$time_local:服务器时间(LOG Format 格式) (1.3.12, 1.2.7)

$uri:请求中的当前URI(不带请求参数,参数位于$args),可以不同于浏览器传递的$request_uri的值,它可以通过内部重定向,或者使用index指令进行修改,$uri不包含主机名,如”/foo/bar.html”。

Aspnet Mvc 前后端分离项目手记(四)vue项目的搭建(一)(iview) - 小小爵 - 博客园

mikel阅读(531)

来源: Aspnet Mvc 前后端分离项目手记(四)vue项目的搭建(一)(iview) – 小小爵 – 博客园

一项目创建

1,搭建vue-cli脚手架(依赖npm)

没有安装npm的同学,请先使用npm install -g vue-cli ,然后再进行这一步

安装的过程中有几项

? Project name p1        //项目名
? Project description 1   //描述
? Author jimsfriend          //作者
? Vue build standalone
? Install vue-router? Yes   //是否使用路由(请选择Yes)
? Use ESLint to lint your code? No  //这里是是否使用严格模式,一定要选否,不要问我为什么,严格模式很痛苦!
? Set up unit tests No            //这里随便
? Setup e2e tests with Nightwatch? No       //这里随便

然后就是下载依赖包,得等会儿

 

二 项目结构

1,build:webpack配置文件,可以不用动

2,config,配置文件

dev.env.js看名字叫生产环境.js,没啥重要的用途,可以不用关注

这个也是

比较重要的是config/index.js文件

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
var path = require('path')
module.exports = {
  build: { // production 环境
    env: require('./prod.env'), // 使用 config/prod.env.js 中定义的编译环境
    index: path.resolve(__dirname, '../dist/index.html'), // 编译输入的 index.html 文件
    assetsRoot: path.resolve(__dirname, '../dist'), // 编译输出的静态资源路径
    assetsSubDirectory: 'static'// 编译输出的二级目录
    assetsPublicPath: '/'// 编译发布的根目录,可配置为资源服务器域名或 CDN 域名
    productionSourceMap: true// 是否开启 cssSourceMap
    // Gzip off by default as many popular static hosts such as
    // Surge or Netlify already gzip all static assets for you.
    // Before setting to `true`, make sure to:
    // npm install --save-dev compression-webpack-plugin
    productionGzip: false// 是否开启 gzip
    productionGzipExtensions: ['js''css'// 需要使用 gzip 压缩的文件扩展名
  },
  dev: { // dev 环境
    env: require('./dev.env'), // 使用 config/dev.env.js 中定义的编译环境
    port: 8080, // 运行测试页面的端口
    assetsSubDirectory: 'static'// 编译输出的二级目录
    assetsPublicPath: '/'// 编译发布的根目录,可配置为资源服务器域名或 CDN 域名
    proxyTable: {}, // 需要 proxyTable 代理的接口(可跨域)
    // CSS Sourcemaps off by default because relative paths are "buggy"
    // with this option, according to the CSS-Loader README
    // (https://github.com/webpack/css-loader#sourcemaps)
    // In our experience, they generally work as expected,
    // just be aware of this issue when enabling this option.
    cssSourceMap: false // 是否开启 cssSourceMap
  }
}

 

 这段代码是抄了园友,更加详细的原文地址    https://www.cnblogs.com/whkl-m/p/6627864.html

 3,dist: 编译后的打包文件默认是没有的,

 

项目编译打包:

cmd 到project1目录,运行:npm run build ,然后对多出来一个dist文件,这个dist文件就是编译后的项目,发布的时候只需把dist放在 web宿主 (如 IIS)上即可

 4,mode_modules,node模块,就是一些依赖包,

5 ,src,主要编写代码文件,后面会单独介绍

6,static,也是静态文件存放文件

7,剩下的都是写不怎么重要的,省略了。

 

二 项目运行,进入project1 目录  在cmd中运行 npm run dev  回车即可

 

然后就运行起来,访问  http://localhost:8080/ 

 

 

三 安装iview,

 

也是project1目录下,运行 npm install iview –save

装好之后基本上依赖项和环境就Ok,然后就开干了

Iview的文档地址    http://v1.iviewui.com/docs/guide/install,一定要注意版本,不同的版本api不同

 

 

先了解一下src的项目结构

 

assets,放一些静态文件,比如图片图标啥的

components , 存放组件 ,理解为抽取出来的一些公共的自定义组件

js,这个是我自己新建的文件夹放一些公共的js文件比如 用axios粉装的Http请求 , 常用的工具类等等

router ,里面有一个index.js 文件,是路由文件 。就像ASPNET MVC 中的路由表,只不过他是显式的定义出来,而mvc中默认使用ctroller前缀和action名作为路由。

views文件夹,放主要的页面,代码量最多的地方

App.vue,入口的组件

main.js入口文件

清楚之后,我们来写一个登录页

先搞一个_layout文件放布局页,是不是和mvc里面的_layout很像?没错他们都是一个意思

 

这里的<router-view></router-view> 和mvc中的RendBody()是一个意思

 

然后搞 account文件夹,来放登陆注册这种页面 ,搞一个login.vue文件,就是登录页了

 

搞完之后就去访问 /account/login,你会发现啥都没有,因为还没有再router/index.js里面定义

,定义一个 account_login ,名字随便起 from后面是文件夹的路径

,下面的path是在浏览器中访问的地址

再次访问,已经有了

,写不动了,今天先写这么多,下一节写路由的使用

 

PHP中比较两个时间的大小与日期的差值_jimlong的专栏-CSDN博客_php时间比大小

mikel阅读(530)

来源: PHP中比较两个时间的大小与日期的差值_jimlong的专栏-CSDN博客_php时间比大小

在这里我们全用到时间戳

mktime(hour,minute,second,month,day,year,[is_dst])
其参数可以从右向左省略,任何省略的参数都会被设置成本地日期和时间的当前值。
参数 描述
hour 可选。规定小时。
minute 可选。规定分钟。
second 可选。规定秒。
month 可选。规定用数字表示的月。
day 可选。规定天。
year 可选。规定年。在某些系统上,合法值介于 1901 – 2038 之间。不过在 php教程 5 中已经不存在这个限制了。
is_dst 可选。如果时间在日光节约时间(dst)期间,则设置为1,否则设置为0,若未知,则设置为-1。自 5.1.0 起,is_dst 参数被废弃。因此应该使用新的时区处理特性

在日常生活中我们要经常比较时间的早晚,对于我们来说判断时间的大小很简单。但是时间的比较不只是单纯的数字大小的比较,因此相对来说还是比较复杂。那么在php中通过什么方式来比较两个时间的大小呢?

要比较两个时间的大小,我们需要将时间转化为时间戳格式,然后再进行比较这是最常用的方法。常用到的函数是:strtotime()
语法格式:strtotime(time,now)
如果time是绝对时间,则now参数不起作用
如果time是相对时间,则相对应的参数则对应函数就是now来提供,如果没有提供now参数,那么相对应的时间就是当前的本地时间。

实例:比较两个绝对时间的大小
代码:
<?php
$zero1=date(“y-m-d h:i:s”);
$zero2=”2010-11-29 21:07:00′;
echo “zero1的时间为:”.$zero1.”<br>”;
echo “zero2的时间为:”.$zero2.”<br>”;
if(strtotime($zero1)<strtotime($zero2)){undefined
echo “zero1早于zero2′;
}else{undefined
echo “zero2早于zero1′;
}
?>

输出结果:
zero1的时间为:2010-11-30 21:12:55
zero2的时间为:2010-11-29 21:07:00
zero2早于zero1

注:可以根据实例发散思维

计算两个日期的差值
奥运会倒计时,亚运会倒计时,生日倒计时这些倒计时都可以通过计算两个日期的差值来实现,同样需要用到strottime()函数。
实现倒计时需要将两个时间的差值整数化,需要用到函数ceil()
ceil()函数的作用是求不小于给定实数的最小整数

实例:倒计时小程序
实例代码:
<?php
$zero1=strtotime (date(“y-m-d h:i:s”)); //当前时间
$zero2=strtotime (“2011-2-03 24:00:00′);  //过年时间
$guonian=ceil(($zero2-$zero1)/86400); //60s*60min*24h
echo “离过年还有<strong>$guonian</strong>天!”;
?>

输出结果:
离过年还有66天!

strtotime()函数解析

定义和用法
strtotime() 函数将任何英文文本的日期时间描述解析为 unix 时间戳。

语法
strtotime(time,now)参数 描述
time 规定要解析的时间字符串。
now 用来计算返回值的时间戳。如果省略该参数,则使用当前时间。

说明
该函数预期接受一个包含美国英语日期格式的字符串并尝试将其解析为 unix 时间戳(自 january 1 1970 00:00:00 gmt 起的秒数),其值相对于 now 参数给出的时间,如果没有提供此参数,则用系统当前时间

JSON Web Token 入门教程

mikel阅读(427)

作者: 阮一峰

日期: 2018年7月23日

JSON Web Token(缩写 JWT)是目前最流行的跨域认证解决方案,本文介绍它的原理和用法。

一、跨域认证的问题

互联网服务离不开用户认证。一般流程是下面这样。

1、用户向服务器发送用户名和密码。

2、服务器验证通过后,在当前对话(session)里面保存相关数据,比如用户角色、登录时间等等。

3、服务器向用户返回一个 session_id,写入用户的 Cookie。

4、用户随后的每一次请求,都会通过 Cookie,将 session_id 传回服务器。

5、服务器收到 session_id,找到前期保存的数据,由此得知用户的身份。

这种模式的问题在于,扩展性(scaling)不好。单机当然没有问题,如果是服务器集群,或者是跨域的服务导向架构,就要求 session 数据共享,每台服务器都能够读取 session。

举例来说,A 网站和 B 网站是同一家公司的关联服务。现在要求,用户只要在其中一个网站登录,再访问另一个网站就会自动登录,请问怎么实现?

一种解决方案是 session 数据持久化,写入数据库或别的持久层。各种服务收到请求后,都向持久层请求数据。这种方案的优点是架构清晰,缺点是工程量比较大。另外,持久层万一挂了,就会单点失败。

另一种方案是服务器索性不保存 session 数据了,所有数据都保存在客户端,每次请求都发回服务器。JWT 就是这种方案的一个代表。

二、JWT 的原理

JWT 的原理是,服务器认证以后,生成一个 JSON 对象,发回给用户,就像下面这样。


{
  "姓名": "张三",
  "角色": "管理员",
  "到期时间": "2018年7月1日0点0分"
}

以后,用户与服务端通信的时候,都要发回这个 JSON 对象。服务器完全只靠这个对象认定用户身份。为了防止用户篡改数据,服务器在生成这个对象的时候,会加上签名(详见后文)。

服务器就不保存任何 session 数据了,也就是说,服务器变成无状态了,从而比较容易实现扩展。

三、JWT 的数据结构

实际的 JWT 大概就像下面这样。

它是一个很长的字符串,中间用点(.)分隔成三个部分。注意,JWT 内部是没有换行的,这里只是为了便于展示,将它写成了几行。

JWT 的三个部分依次如下。

  • Header(头部)
  • Payload(负载)
  • Signature(签名)

写成一行,就是下面的样子。


Header.Payload.Signature

下面依次介绍这三个部分。

3.1 Header

Header 部分是一个 JSON 对象,描述 JWT 的元数据,通常是下面的样子。


{
  "alg": "HS256",
  "typ": "JWT"
}

上面代码中,alg属性表示签名的算法(algorithm),默认是 HMAC SHA256(写成 HS256);typ属性表示这个令牌(token)的类型(type),JWT 令牌统一写为JWT

最后,将上面的 JSON 对象使用 Base64URL 算法(详见后文)转成字符串。

3.2 Payload

Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据。JWT 规定了7个官方字段,供选用。

  • iss (issuer):签发人
  • exp (expiration time):过期时间
  • sub (subject):主题
  • aud (audience):受众
  • nbf (Not Before):生效时间
  • iat (Issued At):签发时间
  • jti (JWT ID):编号

除了官方字段,你还可以在这个部分定义私有字段,下面就是一个例子。


{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

注意,JWT 默认是不加密的,任何人都可以读到,所以不要把秘密信息放在这个部分。

这个 JSON 对象也要使用 Base64URL 算法转成字符串。

3.3 Signature

Signature 部分是对前两部分的签名,防止数据篡改。

首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。


HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用”点”(.)分隔,就可以返回给用户。

3.4 Base64URL

前面提到,Header 和 Payload 串型化的算法是 Base64URL。这个算法跟 Base64 算法基本类似,但有一些小的不同。

JWT 作为一个令牌(token),有些场合可能会放到 URL(比如 api.example.com/?token=xxx)。Base64 有三个字符+/=,在 URL 里面有特殊含义,所以要被替换掉:=被省略、+替换成-/替换成_ 。这就是 Base64URL 算法。

四、JWT 的使用方式

客户端收到服务器返回的 JWT,可以储存在 Cookie 里面,也可以储存在 localStorage。

此后,客户端每次与服务器通信,都要带上这个 JWT。你可以把它放在 Cookie 里面自动发送,但是这样不能跨域,所以更好的做法是放在 HTTP 请求的头信息Authorization字段里面。


Authorization: Bearer <token>

另一种做法是,跨域的时候,JWT 就放在 POST 请求的数据体里面。

五、JWT 的几个特点

(1)JWT 默认是不加密,但也是可以加密的。生成原始 Token 以后,可以用密钥再加密一次。

(2)JWT 不加密的情况下,不能将秘密数据写入 JWT。

(3)JWT 不仅可以用于认证,也可以用于交换信息。有效使用 JWT,可以降低服务器查询数据库的次数。

(4)JWT 的最大缺点是,由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或者更改 token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。

(5)JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT 的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。

(6)为了减少盗用,JWT 不应该使用 HTTP 协议明码传输,要使用 HTTPS 协议传输。

六、参考链接

(完)

Aspnet Mvc 前后端分离项目手记(二)关于token认证 - 小小爵 - 博客园

mikel阅读(561)

来源: Aspnet Mvc 前后端分离项目手记(二)关于token认证 – 小小爵 – 博客园

在前后端分离的项目中,首先我们要解决的问题就是身份认证

以往的时候,我们使用cookie+session,或者只用cookie来保持会话。

 

一,先来复习一下cookie和session

首先我们来复习一下在aspnet中cookie和session的关系,做一个简单试验

这是一个普通的view没有任何处理

 

 

可以看到,没有任何东西(cookie),然后当我们写入一个session之后

\

 

 

 

会发现多了一个名为ASP.NET_SessionId的cookie。我们都知道在aspnet中,session是保存在服务器端的内存中的,而http协议是无状态的,那么他是怎么确定不同请求的session

没错,session是借助cookie来实现的:cookie中保存着 session的key,当我们清除掉浏览器缓存时,会发现session也找不到了,就是这个原因。

使用session来保持会话有几个很严重的缺点:1 session容易丢失;2无法支持分布式;3,cookie 对跨域的支持不好

 

所以就用到了我们今天说的token

二,token 

1,token的产生

一般是用户登录成功,服务器端产生一个token并返给前端,前端将token保存在cookie或者localStorage里面,然后每次请求时都带上这个token,一般都带在请求头里面

 2,token的内容

一般的token里面必须有的是:1,会话用户的标识:比如userid。2,token的过期时间,如果想更完整一点,可以加上token的颁发者,签名等等

3,token的生成算法,一般是由服务器端将token的主要内容,过期时间等等做非对称加密,然后进行签名算法(防止客户端更改),具体看后面jwt

 

4,token校验

当服务器端收到请求时,首先会校验token,校验有两种不同的方式

 一, token产生后保存在服务器端(redis或者其他比较速度快的缓存中) 。优点:可控性强,可以用这个来做单点登录,比如另一个地方登录,就remove掉之前的token。缺点:实现麻烦一点,而且要占服务器压力

二, token产生后服务器端不保存,只负责校验。 优点:大大降低了服务器的压力,实现起来,也要相对简单一点。缺点:token一旦颁发,服务器端就不可控了,只能等它过期。

    具体用哪种看具体的需求。如果不是做可控性要求很强,个人建议第二种。

 

5 jwt 

jwt 全名Json Web Tokens,算是一种token的规范吧

园子里面有很不不错的介绍 ,比如这篇:阮一峰 jwt介绍   http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html

 

组成有三部分

  • Header(头部,一般包含了token的签名方式)
  • Payload(负载,也就是具体的有效部分)
  • Signature(签名,将前两部分进行签名算法,防止客户端篡改)

实现方式,将header部分和payload部分分别进行base64算法,然后用点号“.”隔开拼接,然后进行签名算法,然后在将三部分拼接(点号隔开)就得到了jwt

注意 ,jwt默认是采用base64编码的,也就是说 客户端也能解码得出具体内容的,所以除非特殊情况,重要敏感字段一定不能放在token中

 

以下是具体实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Web;
namespace Rk.JWT
{
 
    public class Jwt
    {
        //参考自 阮一峰 jwt介绍  http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html
 
        public static string SALT = "OXpcRP8jmCfMKumY";
     
         
        /// <summary>
        ///
        /// </summary>
        /// <param name="ExraPayload">额外的信息</param>
        /// <returns></returns>
        public static string Create(Dictionary<string,object> ExraPayload)
        {
            var Header = new Dictionary<stringstring>();
            Header.Add("tp""MD5");
            var Payload = new Dictionary<stringobject>();
            //JWT 规定了7个官方字段,供选用。
            Payload.Add("iss""signBy"); //颁发人
            Payload.Add("jti", Guid.NewGuid().ToString()); //jwt的id
            Payload.Add("exp",System.DateTime.Now.AddMinutes(20));//过期时间
            Payload.Add("nbf", System.DateTime.Now);//生效时间
            Payload.Add("iat", System.DateTime.Now);//签发时间
            Payload.Add("sub""subject");//主题
            Payload.Add("aud""audience");//受众
            foreach (var item in ExraPayload)
            {
                if (Payload.ContainsKey(item.Key))
                {
                    throw new Exception($"{item.Key}键值已被占用 不能使用 ");
                }
                else
                {
                    Payload.Add(item.Key, item.Value);
                }
            }
            string base64Header = Base64Url(Newtonsoft.Json.JsonConvert.SerializeObject(Header));
            string base64Payload = Base64Url(Newtonsoft.Json.JsonConvert.SerializeObject(Payload));
            string tmp = base64Header + "." + base64Payload;
 
            string sign = Md5(tmp+ SALT);//加盐,重要
            return base64Header+"."+ base64Payload+"."+ sign;
        }
        //校验是否合法,是否过期
        public static bool Check(string token)
        {
            string base64Header = token.Split('.')[0];
            string base64Payload = token.Split('.')[1];
            string sign = token.Split('.')[2];
            string tmp = base64Header + "." + base64Payload;
            var signCheck = Md5(base64Header + "." + base64Payload + SALT);
            if(signCheck!= sign)
            {
                return false;
            }
            var dic = Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<stringobject>>(Base64UrlDecode(base64Payload));
            if(  Convert.ToDateTime(dic["exp"])<System.DateTime.Now)
            {
                //过期了
                return false;
            }
            return true;
        }
        //校验是否合法,是否过期
        public static Dictionary<string,object> GetPayLoad(string token)
        {
    
            string base64Payload = token.Split('.')[1];
         
            var dic = Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<stringobject>>(Base64UrlDecode(base64Payload));
           
            return dic;
        }
        public static string Base64Url(string input)
        {
            //JWT 作为一个令牌(token),有些场合可能会放到 URL(比如 api.example.com/?token=xxx)。
            //Base64 有三个字符+、/和=,在 URL 里面有特殊含义,所以要被替换掉:=被省略、+替换成-,/替换成_ 。
            string output = "";
                byte[] bytes = Encoding.UTF8.GetBytes(input);
            try
            {
                output = Convert.ToBase64String(bytes).Replace('+''-').Replace('/''_').TrimEnd('=') ;
             
            }
            catch (Exception e)
            {
                throw e;
            }
            return output;
        }
        public static string Base64UrlDecode(string input)
        {
            string output = "";
         
            input = input.Replace('-''+').Replace('_''/');
            switch (input.Length % 4)
            {
                case 2:
                    input += "==";
                    break;
                case 3:
                    input += "=";
                    break;
            }
            byte[] bytes = Convert.FromBase64String(input);
            try
            {
                output = Encoding.UTF8.GetString(bytes);
            }
            catch
            {
                output = input;
            }
            return output;
        }
        public static string Md5(string input,int bit=16)
        {
            
            MD5CryptoServiceProvider md5Hasher = new MD5CryptoServiceProvider();
            byte[] hashedDataBytes;
            hashedDataBytes = md5Hasher.ComputeHash(Encoding.GetEncoding("gb2312").GetBytes(input));
            StringBuilder tmp = new StringBuilder();
            foreach (byte in hashedDataBytes)
            {
                tmp.Append(i.ToString("x2"));
            }
            if (bit == 16)
                return tmp.ToString().Substring(8, 16);
            else
            if (bit == 32) return tmp.ToString();//默认情况
            else return string.Empty;
           
        }
    }
    
}

 

使用方式

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class HomeController : BaseController
   {
       public ActionResult Login(string username, string pwd)
       {
           /// 1, todo 验证用户名密码正确
           //2,//在token中加入用户id,创建token
           var dic = new Dictionary<stringobject>();
           dic.Add("userid""20125521225858");
           string token = JWT.Jwt.Create(dic);
           //验证token是否正确是否过期
           var isChecked = JWT.Jwt.Check(token);
           return Content("");
       }
   }

 

下一篇我们将会聊一聊 rest 风格url在前后端分离项目中的使用

MVC 统一验证Token demo - _小马哥 - 博客园

mikel阅读(399)

来源: MVC 统一验证Token demo – _小马哥 – 博客园

复制代码
/// <summary>
        /// 获取token
        /// </summary>
        /// <param name="staffId"></param>
        /// <returns></returns>
        public JsonResult GetToken(string staffId)
        {
            ResultMsg resultMsg = null;

            //判断参数是否合法
            if (string.IsNullOrEmpty(staffId))
            {
                resultMsg = new ResultMsg();
                resultMsg.StatusCode = (int)StatusCodeEnum.ParameterError;
                resultMsg.Info = "staffId不合法";
                resultMsg.Data = new Token();
                return Json(resultMsg, JsonRequestBehavior.AllowGet);
            }

            //插入缓存
            Token token = (Token)HttpRuntime.Cache.Get(staffId);
            if (HttpRuntime.Cache.Get(staffId.ToString()) == null)
            {
                token = new Token();
                token.StaffId = staffId;
                token.SignToken = Guid.NewGuid();
                token.ExpireTime = DateTime.Now.AddDays(1);
                HttpRuntime.Cache.Insert(token.StaffId.ToString(), token, null, token.ExpireTime, TimeSpan.Zero);
            }

            //返回token信息
            resultMsg = new ResultMsg();
            resultMsg.StatusCode = (int)StatusCodeEnum.Success;
            resultMsg.Info = "";
            resultMsg.Data = token;
            return  Json(resultMsg, JsonRequestBehavior.AllowGet);

        }
复制代码
复制代码
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Net;
using System.Text;
using System.Web;
using System.Web.Mvc;
using WebApplication_Token.Models;

namespace WebApplication_Token.Controllers
{
    public class VerificationTokenController : Controller
    {
        protected override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            ResultMsg resultMsg = null;
            var request = Request;
            var method = request.HttpMethod;
            string staffid = string.Empty, timestamp = string.Empty, nonce = string.Empty, signature = string.Empty;

            if (!string.IsNullOrEmpty(request.Headers["staffid"]))
            {
                staffid = HttpUtility.UrlDecode(request.Headers.GetValues("staffid").FirstOrDefault());
            }
            if (!string.IsNullOrEmpty(request.Headers["timestamp"]))
            {
                timestamp = HttpUtility.UrlDecode(request.Headers.GetValues("timestamp").FirstOrDefault());
            }
            if (!string.IsNullOrEmpty(request.Headers["nonce"]))
            {
                nonce = HttpUtility.UrlDecode(request.Headers.GetValues("nonce").FirstOrDefault());
            }
            if (!string.IsNullOrEmpty(request.Headers["signature"]))
            {
                signature = HttpUtility.UrlDecode(request.Headers.GetValues("signature").FirstOrDefault());
            }

            //GetToken方法不需要进行签名验证
            if (filterContext.ActionDescriptor.ActionName == "GetToken")
            {
                base.OnActionExecuting(filterContext);
                return;
            }

            //判断请求头是否包含以下参数
            if (string.IsNullOrEmpty(staffid) || string.IsNullOrEmpty(timestamp) || string.IsNullOrEmpty(nonce) || string.IsNullOrEmpty(signature))
            {
                resultMsg = new ResultMsg();
                resultMsg.StatusCode = (int)HttpStatusCode.PartialContent;
                resultMsg.Info = "请求头缺少参数";
                resultMsg.Data = new Token();
                filterContext.Result = Json(resultMsg, JsonRequestBehavior.AllowGet);//返回json数据
                base.OnActionExecuting(filterContext);
                return;
            }

            //判断token是否有效
            Token token = (Token)HttpRuntime.Cache.Get(staffid);
            
            string signtoken = string.Empty;
            if (token == null)
            {
                resultMsg = new ResultMsg();
                resultMsg.StatusCode = (int)StatusCodeEnum.ParameterError;
                resultMsg.Info = "token为null";
                resultMsg.Data = new Token();
                filterContext.Result = Json(resultMsg, JsonRequestBehavior.AllowGet);//返回json数据
                base.OnActionExecuting(filterContext);
                return;
            }
            else
            {
                signtoken = token.SignToken.ToString();
            }

            bool timespanvalidate = token.ExpireTime > Convert.ToDateTime(timestamp);
            if (!timespanvalidate)
            {
                resultMsg = new ResultMsg();
                resultMsg.StatusCode = (int)HttpStatusCode.PartialContent;
                resultMsg.Info = "token已过期";
                resultMsg.Data = new Token();
                filterContext.Result = Json(resultMsg, JsonRequestBehavior.AllowGet);//返回json数据
                base.OnActionExecuting(filterContext);
                return;
            }

            //根据请求类型拼接参数
            NameValueCollection coll = Request.Form;
            string[] requestItem = coll.AllKeys;
            Dictionary<string, string> sArray = new Dictionary<string, string>();
            int j = 0;
            for (j = 0; j < requestItem.Length; j++)
            {
                sArray.Add(requestItem[j], Request.Form[requestItem[j]]);
            }
            var queryStr = GetQueryString(sArray);
            var _signature = GetSingnature(timestamp, queryStr.Item1, staffid, signtoken, queryStr.Item2);

            if(signature!= _signature)
            {
                resultMsg = new ResultMsg();
                resultMsg.StatusCode = (int)HttpStatusCode.PartialContent;
                resultMsg.Info = "token不合法";
                resultMsg.Data = new Token();
                filterContext.Result = Json(resultMsg, JsonRequestBehavior.AllowGet);//返回json数据
                base.OnActionExecuting(filterContext);
                return;
            }

        }

        /// <summary>
        /// 获取签名字符串
        /// </summary>
        /// <param name="parames"></param>
        /// <returns></returns>
        public Tuple<string, string> GetQueryString(Dictionary<string, string> parames)
        {
            // 第一步:把字典按Key的字母顺序排序
            IDictionary<string, string> sortedParams = new SortedDictionary<string, string>(parames);
            IEnumerator<KeyValuePair<string, string>> dem = sortedParams.GetEnumerator();

            // 第二步:把所有参数名和参数值串在一起
            StringBuilder query = new StringBuilder("");//签名字符串
            StringBuilder queryStr = new StringBuilder("");//url参数
            if (parames == null || parames.Count == 0)
            {
                return new Tuple<string, string>("", "");
            }

            while (dem.MoveNext())
            {
                string key = dem.Current.Key;
                string value = dem.Current.Value;
                if (!string.IsNullOrEmpty(key))
                {
                    query.Append(key).Append(value);
                    queryStr.Append("&").Append(key).Append("=").Append(value);
                }
            }

            return new Tuple<string, string>(query.ToString(), queryStr.ToString().Substring(1, queryStr.Length - 1));
        }

        /// <summary>
        /// 根据参数计算签名
        /// </summary>
        /// <param name="timeStamp">发起请求时的时间戳(单位:毫秒)</param>
        /// <param name="nonce">随机数</param>
        /// <param name="staffId">当前请求用户StaffId</param>
        /// <param name="signToken">signToken</param>
        /// <param name="data">参数url</param>
        /// <returns></returns>
        public string GetSingnature(string timeStamp, string nonce, string staffId,string signToken, string data)
        {
            var hash = System.Security.Cryptography.MD5.Create();
            //拼接签名
            var signStr = timeStamp + nonce + staffId + signToken + data;
            //将字符串中字符按升序排序
            var sortStr = string.Concat(signStr.OrderBy(c => c));
            var bytes = Encoding.UTF8.GetBytes(sortStr);
            //使用MD5加密
            var md5Val = hash.ComputeHash(bytes);
            //把二进制转大写十六进制
            StringBuilder result = new StringBuilder();
            foreach (var c in md5Val)
            {
                result.Append(c.ToString("X2"));
            }
            return result.ToString().ToUpper();

        }
    }
}
复制代码