base64是一种编码方式,不是加密方式。
所谓Base64,就是说选出64个字符:小写字母a-z、大写字母A-Z、数字0-9、符号"+“、”/“(再加上作为垫字的”=",实际上是使用65个字符),作为一个基本字符集。然后,其他文件(视频、文本、字符串…)里的所有符号都转换成这个字符集中的字符。
所谓的垫字的=号,即base64三个字节一分,最后不够分的时候拿等号占位一下,也就是说等号只能出现在末尾,且最多两个。(缺三字节那就是前面刚好够分,所以最多可能有两个==)
在Linux下,编码为:
echo -n 'Hello World' | base64
SGVsbG8gV29ybGQ=
解码为:
echo -n 'SGVsbG8gV29ybGQ=' | base64 -d
Hello World
其中:
echo 命令带换行
echo -n 即不换行输出
echo -n '{"alg":"HS256","typ":"JWT"}' | base64
以上是通过管道将echo的结果传给后面的指令,当然可以直接base64,配合Ctrl+D结束输入。
也可以对文件进行base64编码和解码:
#base64编码
# base64 待编码的文件名 > 编码后的文件名
base64 1.mp3 > mymp3 #打开就是一堆64个字符组成的文件
#base64 解码
#base64 -d 待解码的文件名 >解码后的文件名
base64 -d mymp3>88.mp3
Base64Url是一种在Base64的基础上编码形成新的编码方式,为了编码能在网络中安全顺畅传输,需要对Base64进行的编码,特别是互联网中。
Base64Url编码的步骤是:
1)去除尾部的"="
2)把"+"替换成"-"
3)斜线"/"替换成下划线"_"
互联网服务离不开用户认证,基于session的流程是:
这种模式的问题在于,扩展性(scaling)不好。单机当然没有问题,如果是服务器集群,或者是跨域的服务导向架构,就要求 session 数据共享,每台服务器都能够读取 session。
举例来说,A 网站和 B 网站是同一家公司的关联服务。现在要求,用户只要在其中一个网站登录,再访问另一个网站就会自动登录(单点登录),请问怎么实现?
解决方案一:服务端做session数据持久化
即服务端将写入数据库或别的持久层。各种服务收到请求后,都向这个持久层请求数据。这种方案的优点是架构清晰,缺点是工程量比较大。另外,持久层万一挂了,就会单点失败。
解决方案二:服务端不再保存 session 数据了,所有数据都保存在客户端
如JWT,服务器不存数据,客户端存,服务器解析就行了,解析出来JWT合法,则允许访问系统。
以上是JWT实现登录的原理图,即客户端认证通过后,被下发一个令牌,客户端发起请求时携带这个令牌,服务端可以通过算法和密钥进行令牌合法性校验,不再依赖数据库,Memcached的等存储系统,因此可以做到跨服务器验证,只要密钥和算法相同,不同服务器程序生成的Token可以互相验证通过。这就是JWT和session的区别,用JWT时,服务端啥也不用存,就做个校验。
直白讲就是服务端用解析 token 的计算时间换取 session 的存储空间,从而减轻服务器的压力,减少频繁的查询数据库。
JSON Web Token
(JWT)是一个开放标准(RFC 7519),它定义了一种紧凑且独立的方式,用于在各方之间作为JSON对象安全地传输信息。 此信息可以通过数字签名进行验证和信任。JWT可以使用密钥(使用HMAC算法)或使用RSA或ECDSA的公钥/私钥对进行签名。
相关文档:
JWT的主要用途有:
一个JWT由三部分组成,各部分以点分隔:xxxxx.yyyyy.zzzzz格式
Header(头部)
-----base64Url编码的Json字符串
Payload(载荷)
—base64url编码的Json字符串
Signature(签名)
—使用指定算法,通过Header和Playload加盐计算的字符串
举例:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
头部由两部分组成:
示例:
{
"alg":"HS256",
"typ":"JWT"
}
编码:
echo -n '{"alg":"HS256","typ":"JWT"}' | base64
得到的就是JWT的第一部分。
payload就像车厢,里面拉了很多东西,比如用户名。 payload(有效负载),其中包含claims(声明)。Claims是关于一个实体(通常是用户)和其他数据类型的声明。Claims又有三种类型:
1)
Registered(已注册的声明):这些是一组预定义声明,不是强制性的,但建议使用,以提供一组有用的,可互操作的声明。 其中一些是:iss(发行人),exp(到期时间),sub(主题),aud(观众)and others。(请注意,声明名称只有三个字符,因为JWT意味着紧凑。)
2)
Public(公开声明):这些可以由使用JWT的人随意定义。 但为避免冲突,应在IANA JSON Web Token Registry中定义它们,或者将其定义为包含防冲突命名空间的URI。
3)
private (私人声明):这些声明是为了在同意使用它们的各方之间共享信息而创建的,并且既不是注册声明也不是公开声明。
示例:
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
Signature是用来保证数据安全的,是对前两部分head、payload的签名,防止数据篡改。首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道
,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
算出Signature值后,再把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用"点"(.)分隔,就可以返回给用户。这就是一个JWT值。
客户端收到服务器返回的 JWT,可以储存在 Cookie 里面,也可以储存在 localStorage(本地存储)。此后,客户端每次与服务器通信,都要带上这个 JWT。你可以把它放在 Cookie 里面自动发送,但是这样不能跨域,所以更好的做法是放在 HTTP 请求的头信息Authorization字段里面。即:
Authorization: Bearer jwt
另一种做法是,跨域的时候,JWT 就放在 POST 请求的数据体里面。
当放本地存储localStorage的时候,每次请求,需要前端同事从localStorage里取出来,放到请求头里,再请求后端的controller,controller中httpServletRequest.getHeader()从请求头里获取出来,然后校验合法性,合法则允许访问。
JWT 的最大缺点是,由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或者更改 token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑(JWT的登出问题)。(后面再说redis)就是因为服务端无状态了,所以正常情况下, 修改了密码后就会跳转到登录页面重新登录,修改成功后清空浏览器保存的token了,重新认证,拿新的token(服务端无状态,改不了令牌)。JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT 的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。为了减少盗用,JWT 不应该使用 HTTP 80 协议明码传输,要使用 HTTPS 443 协议传输。