电子劳动合同来了 足不出户也能签约】日前,北京市人力资源和社会保障局公开发布《关于推进电子劳动合同相关工作的实施意见》,这标志着延续多年的纸质劳动合同将逐步进入电子化时代。专家认为,这项重要的制度创新,将在全国起到示范引领作用。据了解,电子劳动合同与纸质劳动合同具有同等法律效力。推行电子劳动合同有利于用人单位降本增效,提高人力资源管理效率。电子劳动合同来了 足不出户也能签约 (经济日报)
声明:转载此文是出于传递更多信息之目的。若有来源标注错误或侵犯了您的合法权益,请作者持权属证明与本网联系,我们将及时更正、删除,谢谢。
来源: 中国搜索
者 | 阿文
责编 | 屠敏
出品 | CSDN(ID:CSDNnews)
最近几年,随着RESTful API开始风靡,使用HTTP header来传递认证令牌似乎变得理所应当,通过 RESTful 的API 接口设计简化了系统架构, 减少了耦合性, 可以让所有模块各自独立的进行改进。
不过,在实际的REST API 接口设计过程中,我们需要考虑如何让鉴权变得更安全可靠,例如不会被第三方恶意请求或者保证传输过程中的数据安全以及防止重复提交,本文就一起聊一聊。
传统的Session 认证方式
首先,我们说一些传统的认证方式,众所周知,HTTP 协议是一种无状态的协议,而这就意味着如果用户向我们的应用提供了用户名和密码来进行用户认证,那么下一次请求时,用户还要再一次进行用户认证才行,因为我们并不能知道是哪个用户发出的请求,所以为了让我们的应用能识别是哪个用户发出的请求,我们只能在服务器存储一份用户登录的信息,这份登录信息会在响应时传递给浏览器,告诉其保存为cookie,以便下次请求时发送给我们的应用,这样我们的应用就能识别请求来自哪个用户了,这就是传统的基于session认证。
而这种方式有很多问题:
首先,占用资源,这种方式需要每个用户经过认证之后,都要在服务端做一次记录,以方便用户下次请求的鉴别,通常而言session都是保存在内存中,而随着认证用户的增多,服务端的开销会明显增大。
其次,扩展性差: 客户端认证之后,服务端做认证记录,如果认证的记录被保存在内存中的话,这意味着用户下次请求还必须要请求在这台服务器上,这样才能拿到授权的资源,在一些分布式的场景下会限制了负载均衡器的能力,会限制了应用的扩展能力。
第三,容易遭受攻击: 这种基于cookie来进行用户识别的认证方式, 很容易被截获,用户就会很容易受到跨站请求伪造的攻击。
基于Token 的鉴权方式
由于session 认证的诸多问题,因此出现了基于token 的鉴权方式,这种方式不需要在服务端去保留用户的认证信息或者会话信息。这就意味着基于token认证机制的应用不需要去考虑用户在哪一台服务器登录了,这就为应用的扩展提供了便利。
基于token 鉴权的工作流程如下:
首先,客户端通过用户名密码来请求对应的API接口
第二,服务器会验证用户的信息
第三,服务器通过验证后会发送token给客户端
第四,客户端存储token,并在每次请求时附送上这个token值
第五,服务端验证token值,并返回数据
这种方式的典型代表就是JWT(Json web token , 它是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519),该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。
它的特点如下:
体积小(一串字符串)。因而传输速度快
传输方式多样。可以通过 HTTP 头部(推荐)/URL/POST 参数等方式传输
严谨的结构化。它自身(在 payload 中)就包含了所有与用户相关的验证消息,如用户可访问路由、访问有效期等信息,服务器无需再去连接数据库验证信息的有效性,并且 payload 支持应用定制
支持跨域验证,多应用于单点登录
JWT通常由三部分组成:
头信息(header)
消息体(payload)
签名(signature)
如下所示:
// Header { "alg": "HS256", "typ": "JWT" }
// Payload { // reserved claims "iss": "a.com", "exp": "1d", // public claims "http://a.com": true, // private claims "company": "A", "awesome": true }
// $Signature HS256(Base64(Header) + "." + Base64(Payload), secretKey)
// JWT JWT=Base64(Header) + "." + Base64(Payload) + "." + $Signature
其工作流程如下:
首先,客户端通过发送HTTP 请求把账号密码发送给服务短,通常使用的是POST请求, 服务器会校验账号与密码是否合法,如果一致,则根据密钥生成一个 token 并返回,客户端收到这个 token 并保存在本地。在这之后,需要访问一个受保护的路由或资源时,只要附加上 token(通常使用 Header 的 Authorization 属性)发送到服务器,服务器就会检查这个 token 是否有效,并做出响应。
服务端接收到 token 之后,会逆向构造过程,解码出 JWT 的三个部分,这一步可以得到 sign 的算法及 payload,结合服务端配置的 secretKey,可以再次进行 $Signature 的生成得到新的 $Signature,与原有的 $Signature 比对以验证 token 是否有效,完成用户身份的认证,验证通过才会使用 payload 的数据。
如何保证接口安全性
要想实现接口的安全性,我们可以做到以下几点:
首先,我们需要采用HTTPS 对传输过程中的数据进行加密,避免使用HTTP 这种明文传输的协议,防止数据直接暴露在公网中,在使用HTTPS的同时要保证时间安全可靠的加密方法和SSL 协议,目前主流的是TLS1.2 和最新的TLS1.3。同时要对证书进行校验,因为即使是HTTPS协议,证书也是能够被伪造的。
其次,对接口设计一般会加入 token、timestamp和sign 这些参数,不同的参数有自己不同的用途:
timestamp,即时间戳,它是客户端调用接口时传入的当前时间戳,时间戳的目的是用于防止DoS攻击。每次调用接口时接口都会判断服务器当前系统时间和接口中传的的timestamp的差值,如果这个差值超过某个设置的时间,例如设置的时间是3分钟,那么这个请求将被拦截掉,如果在设置的超时时间范围内,是不能阻止DoS攻击的。timestamp机制只能减轻DoS攻击的时间,缩短攻击时间。如果黑客修改了时间戳的值可通过sign签名机制来处理。
sign,即签名,通常用于参数签名,防止参数被非法篡改,最常见的是修改金额等重要敏感参数, sign的值一般是将所有非空参数按照升续排序然后+token+key+timestamp+nonce(随机数)拼接在一起,然后使用某种加密算法进行加密,这种方式的好处就是,当被劫持后,修改其中的参数值,然后再继续调用接口,虽然参数的值被修改了,但是因为攻击者并不清楚sign是如何计算出来的,所以即可是篡改参数的值,但没法修改sign的值,当服务器调用接口前会按照sign的规则重新计算出sign的值然后和接口传递的sign参数的值做比较,如果相等表示参数值没有被篡改,如果不等,表示参数被非法篡改了,则不会返回真实的响应信息。
此外,接口设计时候要实现幂等性操作,所谓的幂等性操作就是为了防止重复性运算,我们可以将生成的签名和key保存到redis 中,并且设置超时时间,过期自动删除,当有重复的值存在则不会处理,就可以防止重复提交,从而保证请求结果一致性。
其使用流程如下:
接口调用方(客户端)向接口提供方(服务器)申请接口调用账号,申请成功后,接口提供方会给接口调用方一个AppKey和一个APP Secret参数
调用方申请App Key 和 App Secret 在生成请求时,将参数拼接后进行加密,例如使用HMAC-SHA256 或MD5加密,然后将 App Key, 加密结果追加到请求上。sign=加密(appId + timestamp + key)
服务收到请求后,根据App Key识别出调用方,解密得到参数以及对时间进行对比,判断是否超时,然后从字典中查询到对应的App Secret,与请求参数拼接、加密,与请求中的签名进行对比,签名结果相同的为合法请求。
以上就是给API 接口设计的一些建议,仅供参考,在实际的应用中还可以追加一些公共的参数,例如Host、接口的版本等等参数去进行校验保证接口的安全性。
雯 发自 凹非寺
量子位 | 公众号 QbitAI
取整求个无符号整数的平均值,居然也能整出花儿来?
这不,微软大神Raymond Chen最近的一篇长文直接引爆外网技术平台,引发无数讨论:
无数人点进去时无比自信:不就是一个简单的相加后除二的小学生编程题吗?
unsigned average(unsigned a, unsigned b)
{
return (a + b) / 2;
}
但跟着大神的一路深挖,却逐渐目瞪狗呆……
先从开头提到的小学生都会的方法看起,这个简单的方法有个致命的缺陷:
如果无符号整数的长度为32位,那么如果两个相加的值都为最大长度的一半,那么仅在第一步相加时,就会发生内存溢出。
也就是average(0x80000000U, 0x80000000U)=0。
不过解决方法也不少,大多数有经验的开发者首先能想到的,就是预先限制相加的数字长度,避免溢出。
具体有两种方法:
1、当知道相加的两个无符号整数中的较大值时,减去较小值再除二,以提前减少长度:
unsigned average(unsigned low, unsigned high)
{
return low + (high - low) / 2;
}
2、对两个无符号整数预先进行除法,同时通过按位与修正低位数字,保证在两个整数都为奇数时,结果仍然正确。
(顺带一提,这是一个被申请了专利的方法,2016年过期)
unsigned average(unsigned a, unsigned b)
{
return (a / 2) + (b / 2) + (a & b & 1);
}
这两个都是较为常见的思路,不少网友也表示,自己最快想到的就是2016年专利方法。
同样能被广大网友快速想到的方法还有SWAR(SIMD within a register):
unsigned average(unsigned a, unsigned b)
{
return (a & b) + (a ^ b) / 2;// 变体 (a ^ b) + (a & b) * 2
以及C++ 20版本中的std: : midpoint函数。
接下来,作者提出了第二种思路:
如果无符号整数是32位而本机寄存器大小是64位,或者编译器支持多字运算,就可以将相加值强制转化为长整型数据。
unsigned average(unsigned a, unsigned b)
{
// Suppose "unsigned" is a 32-bit type and
// "unsigned long long" is a 64-bit type.
return ((unsigned long long)a + b) / 2;
}
不过,这里有一个需要特别注意的点:
必须要保证64位寄存器的前32位都为0,才不会影响剩余的32位值。
像是x86-64和aarch64这些架构会自动将32位值零扩展为64位值:
// x86-64: Assume ecx=a, edx=b, upper 32 bits unknown
mov eax, ecx ; rax=ecx zero-extended to 64-bit value
mov edx, edx ; rdx=edx zero-extended to 64-bit value
add rax, rdx ; 64-bit addition: rax=rax + rdx
shr rax, 1 ; 64-bit shift: rax=rax >> 1
; result is zero-extended
; Answer in eax
// AArch64 (ARM 64-bit): Assume w0=a, w1=b, upper 32 bits unknown
uxtw x0, w0 ; x0=w0 zero-extended to 64-bit value
uxtw x1, w1 ; x1=w1 zero-extended to 64-bit value
add x0, x1 ; 64-bit addition: x0=x0 + x1
ubfx x0, x0, 1, 32 ; Extract bits 1 through 32 from result
; (shift + zero-extend in one instruction)
; Answer in x0
而Alpha AXP、mips64等架构则会将32位值符号扩展为64位值。
这种时候,就需要额外增加归零的指令,比如通过向左进位两字的删除指令rldicl:
// Alpha AXP: Assume a0=a, a1=b, both in canonical form
insll a0, #0, a0 ; a0=a0 zero-extended to 64-bit value
insll a1, #0, a1 ; a1=a1 zero-extended to 64-bit value
addq a0, a1, v0 ; 64-bit addition: v0=a0 + a1
srl v0, #1, v0 ; 64-bit shift: v0=v0 >> 1
addl zero, v0, v0 ; Force canonical form
; Answer in v0
// MIPS64: Assume a0=a, a1=b, sign-extended
dext a0, a0, 0, 32 ; Zero-extend a0 to 64-bit value
dext a1, a1, 0, 32 ; Zero-extend a1 to 64-bit value
daddu v0, a0, a1 ; 64-bit addition: v0=a0 + a1
dsrl v0, v0, #1 ; 64-bit shift: v0=v0 >> 1
sll v0, #0, v0 ; Sign-extend result
; Answer in v0
// Power64: Assume r3=a, r4=b, zero-extended
add r3, r3, r4 ; 64-bit addition: r3=r3 + r4
rldicl r3, r3, 63, 32 ; Extract bits 63 through 32 from result
; (shift + zero-extend in one instruction)
; result in r3
或者直接访问比本机寄存器更大的SIMD寄存器,当然,从通用寄存器跨越到SIMD寄存器肯定也会增加内存消耗。
如果电脑的处理器支持进位加法,那么还可以采用第三种思路。
这时,如果寄存器大小为n位,那么两个n位的无符号整数的和就可以理解为n+1位,通过RCR(带进位循环右移)指令,就可以得到正确的平均值,且不损失溢出的位。
带进位循环右移
// x86-32
mov eax, a
add eax, b ; Add, overflow goes into carry bit
rcr eax, 1 ; Rotate right one place through carry
// x86-64
mov rax, a
add rax, b ; Add, overflow goes into carry bit
rcr rax, 1 ; Rotate right one place through carry
// 32-bit ARM (A32)
mov r0, a
adds r0, b ; Add, overflow goes into carry bit
rrx r0 ; Rotate right one place through carry
// SH-3
clrt ; Clear T flag
mov a, r0
addc b, r0 ; r0=r0 + b + T, overflow goes into T bit
rotcr r0 ; Rotate right one place through carry
那如果处理器不支持带进位循环右移操作呢?
也可以使用内循环(rotation intrinsic):
unsigned average(unsigned a, unsigned b)
{
#if defined(_MSC_VER)
unsigned sum;
auto carry=_addcarry_u32(0, a, b, &sum);
sum=(sum & ~1) | carry;
return _rotr(sum, 1);
#elif defined(__clang__)
unsigned carry;
sum=(sum & ~1) | carry;
auto sum=__builtin_addc(a, b, 0, &carry);
return __builtin_rotateright32(sum, 1);
#else
#error Unsupported compiler.
#endif
}
结果是,x86架构下的代码生成没有发生什么变化,MSCver架构下的代码生成变得更糟,而arm-thumb2的clang 的代码生成更好了。
// _MSC_VER
mov ecx, a
add ecx, b ; Add, overflow goes into carry bit
setc al ; al=1 if carry set
and ecx, -2 ; Clear bottom bit
movzx ecx, al ; Zero-extend byte to 32-bit value
or eax, ecx ; Combine
ror ear, 1 ; Rotate right one position
; Result in eax
// __clang__
mov ecx, a
add ecx, b ; Add, overflow goes into carry bit
setc al ; al=1 if carry set
shld eax, ecx, 31 ; Shift left 64-bit value
// __clang__ with ARM-Thumb2
movs r2, #0 ; Prepare to receive carry
adds r0, r0, r1 ; Calculate sum with flags
adcs r2, r2 ; r2 holds carry
lsrs r0, r0, #1 ; Shift sum right one position
lsls r1, r2, #31 ; Move carry to bit 31
adds r0, r1, r0 ; Combine
Raymond Chen1992年加入微软,迄今为止已任职25年,做UEX-Shell,也参与Windows开发,Windows系统的很多最初UI架构就是他搞起来的。
他在MSDN 上建立的blogThe Old New Thing也是业内非常出名的纯技术向产出网站。
这篇博客的评论区们也是微软的各路大神出没,继续深入探讨。
有人提出了新方法,在MIPS ASM共有36个循环:
unsigned avg(unsigned a, unsigned b
{
return (a & b) + (a ^ b) / 2;
}
// lw $3,8($fp) # 5
// lw $2,12($fp) # 5
// and $3,$3,$2 # 4
// lw $4,8($fp) # 5
// lw $2,12($fp) # 5
// xor $2,$4,$2 # 4
// srl $2,$2,1 # 4
// addu $2,$3,$2 # 4
有人针对2016年专利法表示,与其用(a / 2) + (b / 2) + (a & b & 1)的方法,为啥不直接把 (a & 1) & ( b & 1 ) ) 作为进位放入加法器中计算呢?
还有人在评论区推荐了TopSpeed编译器,能够通过指定合适的代码字节和调用约定来定义一个内联函数,以解决“乘除结果是16位,中间计算值却不是”的情况。
只能说,学无止境啊。
原文:
https://devblogs.microsoft.com/oldnewthing/20220207-00/?p=106223
参考链接:
https://news.ycombinator.com/item?id=30252263
*请认真填写需求信息,我们会在24小时内与您取得联系。