JWT(SSO方案)+身份认证的三种方式

声明:

  1. 该博客资源来源自互联网,如有侵权还望告知(侵删)。
  2. 该博客资源仅供学习参考,对本人没有任何收益!

一、身份认证的三种方式

1.1 单一服务器模式


一般过程如下:

  1. 用户向服务器发送用户名和密码。
  2. 验证服务器后,相关数据(如用户名,用户角色等)将保存在当前会话(session)中。
  3. 服务器向用户返回session_id,session信息都会写入到用户的Cookie(名为jSessionID的Cookie)。
  4. 用户的每个后续请求都将通过在Cookie中取出session_id传给服务器。
  5. 服务器收到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, CASOAuth2(第三方登录授权)、JWT 等。

简单的案例演示:

如下图所示,图中有3个系统(微服务),分别是业务A、业务B、和认证中心。业务A、业务B没有登录模块。而认证中心只有认证模块,没有其他的业务模块。

【图片来自网络】

SSO一般过程如下:

  1. 当业务A、业务B需要登录时,将跳到认证中心系统。
  2. 认证中心从用户信息数据库中获取用户信息并校验用户信息,认证中心系统完成登录。
  3. 然后将用户信息存入缓存(例如redis)。
  4. 当用户访问业务A或业务B,需要判断用户是否登录时,将跳转到认证中心系统进行用户身份验证,认证中心判断缓存中是否存在用户身份信息。
  5. 这样,只要其中一个系统完成登录,其他的应用系统也就随之登录了。

优点: 用户身份信息独立管理,更好的分布式管理。
缺点: 认证中心服务器的访问压力较大。

1.3 Token

参考博主:chrisghb

1.3.1 token 验证过程

  基于Token的身份验证是无状态的,我们不将用户信息存在服务器中。这种概念解决了在服务端存储信息时的许多问题。NoSession意味着你的程序可以根据需要去增减机器,而不用去担心用户是否登录。

【图片来自网络】

基于Token的身份验证的过程如下:

  1. 用户通过用户名和密码发送请求。
  2. 服务器端程序验证。
  3. 服务器端程序返回一个带签名的token 给客户端。
  4. 客户端储存token,并且每次访问API都携带Token到服务器端的。
  5. 服务端验证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令牌

JWTJSON 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", //声明加密的算法 默认直接使用 HMAC SHA256(写为 HS256)
  "typ": "JWT" 	  //声明类型,这里是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" #私有的声明
}

请注意:

  1. 默认情况下 JWT 的 PAYLOAD 部分是未加密的,任何人都可以解读其内容,因此不要构建隐私信息字段去存放保密信息,以防止信息泄露。
  2. JSON对象也使用 Base64 URL 算法转换为字符串保存。(上面的payload经过转换后的字符串为)
    eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJhZG1pbiI6dHJ1ZSwiaGVhZF9pbWciOiJoZWxlbi5qcGcifQ

2.2.3 VERIFY SIGNATURE

验证签名部分是对上面两部分数据签名,通过指定的算法生成哈希,以确保数据不会被篡改。

  首先,需要指定一个密码(secret)(该密码仅仅保存在服务器中,并且不能向用户公开)。然后,使用指定的签名算法(默认情况下为HMAC SHA256)根据以下过程生成签名:

String encodedStr = base64UrlEncode(header) + '.' + base64UrlEncode(payload);
//根据 encodedStr 和 secret 和 指定算法 生成签名
String signature = HMACSHA256(encodedStr, 'secret'); // HMACSHA256 为默认的签名算法
  1. 在计算出签名哈希后,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。

注意:

  1. 如果将它存储在Cookie中,就可以自动发送,但是不能跨域(前端js取不到跨域Cookie),因此一般是将 JWT 放入 HTTP 请求的 Header Authorization 字段中。
  2. 当跨域时,也可以将 JWT 放置于 POST 请求的数据主体中。

2.4 JWT 的生成和解析(demo)

2.4.1 jwt生成

//过期时间,毫秒,24小时
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)) //20秒后可用
            .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() {
    //要解析的jwt
    String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJjY2J4IiwiaXNzIjoiSmFja0NDIiwiYXVkIjoiaGJsZyIsImlhdCI6MTY0MTU2NjA4OCwiZXhwIjoxNjQxNjUyNDg4LCJuYmYiOjE2NDE1NjYxMDgsImp0aSI6IjcyNjA1YzA0LTI3MDgtNDk4MC1iNzdlLTE0M2VkOThlMTEwNCIsIm5pY2tOYW1lIjoiSmFja01hIiwiYXZhdGFyIjoiMS5qcGcifQ.eCygtpG_cEPP3lsU1sf3M3zmQQnGUM0CzKqKBwOh8Ds";
    //获取jwt解析器
    JwtParser parser = Jwts.parser();
    Jws<Claims> claimsJws = parser.setSigningKey(tokenSignKey).parseClaimsJws(token);

    //解析JWT头
    JwsHeader header = claimsJws.getHeader();
    String algorithm = header.getAlgorithm();
    String type = header.getType();
    System.out.println(algorithm + "..." + type); //HS256...JWT
    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);    //ccbx
    System.out.println(issuer);     //JackCC
    System.out.println(audience);   //hblg
    System.out.println(issuedAt);   //Fri Jan 07 22:34:48 CST 2022
    System.out.println(expiration); //Sat Jan 08 22:34:48 CST 2022
    System.out.println(notBefore);  //Fri Jan 07 22:35:08 CST 2022
    System.out.println(id);         //72605c04-2708-4980-b77e-143ed98e1104
    String nickname = (String) claims.get("nickName");
    String avatar = (String) claims.get("avatar");

    System.out.println("昵称:" + nickname);   //昵称:JackMa
    System.out.println("头像:" + avatar);     //头像:1.jpg
    System.out.println("********************************");

    //解析签名哈希
    String signature = claimsJws.getSignature();
    System.out.println(signature);  //eCygtpG_cEPP3lsU1sf3M3zmQQnGUM0CzKqKBwOh8Ds
}

解析结果:

有不足还望指正,感谢,祝升职加薪。

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值