声明:
- 该博客资源来源自互联网,如有侵权还望告知(侵删)。
- 该博客资源仅供学习参考,对本人没有任何收益!
一、身份认证的三种方式
1.1 单一服务器模式
一般过程如下:
- 用户向服务器发送用户名和密码。
- 验证服务器后,相关数据(如用户名,用户角色等)将保存在当前会话(session)中。
- 服务器向用户返回session_id,session信息都会写入到用户的Cookie(名为jSessionID的Cookie)。
- 用户的每个后续请求都将通过在Cookie中取出session_id传给服务器。
- 服务器收到session_id并对比之前保存的数据,确认用户的身份。
缺点:
- 单点性能压力,无法扩展。
- 分布式架构中,需要session共享方案,session共享方案存在性能瓶颈。
session共享方案:
session广播 :也叫Session同步或Session复制,让集群内每个tomcat的session完全同步,不推荐。 redis代替session :推荐,性能高, 通常是Cookie+Redis实现。
Session共享就是单点登录的一种实现方案。
1.2 SSO(单点登录)
??单点登录(Single Sign On),简称就是SSO 。它的解释是:在多个应用系统中,只需要登录一次,就可以访问其他相互信任的应用系统。
??例如,网页登录了淘宝账号,天猫,钉钉等阿里系应用都不用再二次登录了。SSO核心意义就是:一处登录,处处登录;一处注销,处处注销。就是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统,即用户只需要记住一组用户名和密码就可以登录所有有权限的系统。
单点登录的常见实现方案有: Cookie+Redis , CAS 、OAuth2 (第三方登录授权)、JWT 等。
简单的案例演示:
如下图所示,图中有3个系统(微服务),分别是业务A、业务B、和认证中心。业务A、业务B没有登录模块。而认证中心只有认证模块,没有其他的业务模块。 【图片来自网络】
SSO一般过程如下:
- 当业务A、业务B需要登录时,将跳到认证中心系统。
- 认证中心从用户信息数据库中获取用户信息并校验用户信息,认证中心系统完成登录。
- 然后将用户信息存入缓存(例如redis)。
- 当用户访问业务A或业务B,需要判断用户是否登录时,将跳转到认证中心系统进行用户身份验证,认证中心判断缓存中是否存在用户身份信息。
- 这样,只要其中一个系统完成登录,其他的应用系统也就随之登录了。
优点: 用户身份信息独立管理,更好的分布式管理。 缺点: 认证中心服务器的访问压力较大。
1.3 Token
参考博主:chrisghb
1.3.1 token 验证过程
??基于Token的身份验证是无状态 的,我们不将用户信息存在服务器中。这种概念解决了在服务端存储信息时的许多问题。NoSession意味着你的程序可以根据需要去增减机器,而不用去担心用户是否登录。 【图片来自网络】
基于Token的身份验证的过程如下:
- 用户通过用户名和密码发送请求。
- 服务器端程序验证。
- 服务器端程序返回一个带签名的token 给客户端。
- 客户端储存token,并且每次访问API都携带Token到服务器端的。
- 服务端验证token,校验成功则返回请求数据,校验失败则返回错误码。
1.3.2 使用token的案例演示(时序图)
该部分图片来自 chrisghb 博主(简书)
登录 业务请求 Token过期,刷新 Token
Refresh Token 如果过期,就要求用户重新登录认证。
1.3.3 token 优缺点
优点:
- 无状态: token是无状态的(服务器端不会记住用户状态 )。
- 安全性:token是有时效的,一段时间之后用户需要重新验证。
- 可拓展:Tokens能够创建与其它程序共享权限的程序。
- 多平台跨域:
- 基于标准化:你的API可以采用标准化的 JSON Web Token (JWT)。
缺点:
- 占用带宽(每次请求服务器资源都要携带Token)
- 无法在服务器端销毁
二、JWT
2.1 JWT令牌
JWT 是JSON Web Token 的缩写,即JSON Web令牌 ,是一种自包含令牌 。 JWT 官网
??JWT主要用在多web服务器下实现无状态分布式身份验证 (单点登录),JWT官网有一张图描述了JWT的认证过程; JWT 的作用:
- JWT 最重要的作用就是对 token信息的防伪作用。
注意:
- 不应该在jwt的payload部分存放敏感信息,因为该部分是客户端可解密的部分。
- 保护好secret私钥,该私钥非常重要。
- 尽可能的使用https协议。
2.2 JWT 的组成
JWT头 、有效载荷 、验证签名 。
一个JWT是一个很长的字符串,字符之间通过". "分隔符分为三个子串。
2.2.1 HEADER
JWT 头部分 是一个描述 JWT 元数据的 JSON 对象,通常如下所示。
{
"alg": "HS256",
"typ": "JWT"
}
最后,使用Base64 URL算法将上述JSON对象转换为字符串保存。 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
2.2.2 PAYLOAD
有效载荷 部分,是JWT的主体内容部分,也是一个JSON对象,包含需要传递的数据。
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
有效载荷部分包含三个部分:
标准中注册的声明 (建议但不强制使用) :
sub :jwt所面向的用户(主题)iss :jwt签发者aud :接收jwt的一方iat :jwt的签发时间exp :jwt的过期时间,这个过期时间必须要大于签发时间nbf :定义在什么时间之前,该jwt都是不可用的jti :jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击
公共的声明 :
公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密。
私有的声明 :
私有声明是提供者和消费者所共同定义的声明(自定义),一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息(即可以被查看的)。
{
"sub": "1234567890",
"iat": 1516239022,
"name": "John Doe",
"admin": true,
"head_img": "helen.jpg"
}
请注意:
- 默认情况下 JWT 的 PAYLOAD 部分是
未加密 的,任何人都可以解读其内容,因此不要构建隐私信息字段去存放保密信息,以防止信息泄露。 - JSON对象也使用 Base64 URL 算法转换为字符串保存。(上面的payload经过转换后的字符串为)
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJhZG1pbiI6dHJ1ZSwiaGVhZF9pbWciOiJoZWxlbi5qcGcifQ
2.2.3 VERIFY SIGNATURE
验证签名 部分是对上面两部分数据签名,通过指定的算法生成哈希,以确保数据不会被篡改。
??首先,需要指定一个密码(secret )(该密码仅仅保存在服务器中,并且不能向用户公开)。然后,使用指定的签名算法(默认情况下为HMAC SHA256)根据以下过程生成签名:
String encodedStr = base64UrlEncode(header) + '.' + base64UrlEncode(payload);
String signature = HMACSHA256(encodedStr, 'secret');
- 在计算出签名哈希后,JWT头,有效载荷和签名哈希的三个部分组合成一个字符串,每个部分用"."分隔,就构成整个
JWT对象 。
2.2.4 拓展:Base64URL算法
??如前所述,JWT头和有效载荷序列化的算法都用到了Base64URL。该算法和常见Base64算法类似,稍有差别。
??作为令牌的JWT可以放在URL中(例如api.example/?token=xxx)。 Base64中用的三个字符是"+","/“和”=",由于在URL中有特殊含义,因此Base64URL中对他们做了替换:"=“去掉,”+“用”-“替换,”/“用”_"替换,这就是Base64URL算法。
注意: base64编码,并不是加密,只是把明文信息变成了你不认识的字符串。但是其实只要用一些工具就可以把base64编码解成明文,所以不要在JWT中放入涉及私密的信息。
2.3 JWT 的使用
客户端接收服务器返回的JWT,将其存储在 Cookie 或 localStorage 中。
此后,客户端将在与服务器交互中都会带JWT。
注意:
- 如果将它存储在Cookie中,就可以自动发送,但是不能跨域(前端js取不到跨域Cookie),因此一般是将 JWT 放入 HTTP 请求的 Header Authorization 字段中。
- 当跨域时,也可以将 JWT 放置于 POST 请求的数据主体中。
2.4 JWT 的生成和解析(demo)
2.4.1 jwt生成
private static long tokenExpiration = 24 * 60 * 60 * 1000;
private static String tokenSignKey = "ccbx456jlqeukkcxmae";
@Test
public void testCreateToken() {
JwtBuilder jwtBuilder = Jwts.builder();
String token = jwtBuilder.
setHeaderParam("typ", "JWT")
.setHeaderParam("alg", "HS256")
.setSubject("ccbx")
.setIssuer("JackCC")
.setAudience("hblg")
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + tokenExpiration))
.setNotBefore(new Date(System.currentTimeMillis() + 20 * 1000))
.setId(UUID.randomUUID().toString())
.claim("nickName", "JackMa")
.claim("avatar", "1.jpg")
.signWith(SignatureAlgorithm.HS256, tokenSignKey)
.compact();
System.out.println(token);
}
生成的jwt对象(下面的换行是我自己手动换行的,从 . 开始换行)
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.
eyJzdWIiOiJjY2J4IiwiaXNzIjoiSmFja0NDIiwiYXVkIjoiaGJsZyIsImlhdCI6MTY0MTU2NjA4OCwiZXhwIjoxNjQxNjUyNDg4LCJuYmYiOjE2NDE1NjYxMDgsImp0aSI6IjcyNjA1YzA0LTI3MDgtNDk4MC1iNzdlLTE0M2VkOThlMTEwNCIsIm5pY2tOYW1lIjoiSmFja01hIiwiYXZhdGFyIjoiMS5qcGcifQ.
eCygtpG_cEPP3lsU1sf3M3zmQQnGUM0CzKqKBwOh8Ds
2.4.2 jwt解析
@Test
public void testGetInfo() {
String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJjY2J4IiwiaXNzIjoiSmFja0NDIiwiYXVkIjoiaGJsZyIsImlhdCI6MTY0MTU2NjA4OCwiZXhwIjoxNjQxNjUyNDg4LCJuYmYiOjE2NDE1NjYxMDgsImp0aSI6IjcyNjA1YzA0LTI3MDgtNDk4MC1iNzdlLTE0M2VkOThlMTEwNCIsIm5pY2tOYW1lIjoiSmFja01hIiwiYXZhdGFyIjoiMS5qcGcifQ.eCygtpG_cEPP3lsU1sf3M3zmQQnGUM0CzKqKBwOh8Ds";
JwtParser parser = Jwts.parser();
Jws<Claims> claimsJws = parser.setSigningKey(tokenSignKey).parseClaimsJws(token);
JwsHeader header = claimsJws.getHeader();
String algorithm = header.getAlgorithm();
String type = header.getType();
System.out.println(algorithm + "..." + type);
System.out.println("********************************");
Claims claims = claimsJws.getBody();
String subject = claims.getSubject();
String issuer = claims.getIssuer();
String audience = claims.getAudience();
Date issuedAt = claims.getIssuedAt();
Date expiration = claims.getExpiration();
Date notBefore = claims.getNotBefore();
String id = claims.getId();
System.out.println(subject);
System.out.println(issuer);
System.out.println(audience);
System.out.println(issuedAt);
System.out.println(expiration);
System.out.println(notBefore);
System.out.println(id);
String nickname = (String) claims.get("nickName");
String avatar = (String) claims.get("avatar");
System.out.println("昵称:" + nickname);
System.out.println("头像:" + avatar);
System.out.println("********************************");
String signature = claimsJws.getSignature();
System.out.println(signature);
}
解析结果:
有不足还望指正,感谢,祝升职加薪。
|