SSO单点登录

单点登录问题

在单体架构中的登录以及登录身份认证过程

在微服务架构中,按照相同的方式,再来分析下登录,以及登录身份认证过程:

假设用户在没有登录的情况下,发起查看购物车的请求:

  • 当请求转发给订服务的时候,订单服务,订单服务发现用户的请求中没有包含JsessionId的Cookie,认为用户没登录,于是给前端返回需要登录的响应码,前端自动跳转到登录页面。
  • 在登录界面,用户提交用户名和密码,向登录服务发起请求,登录服务验证用户发送的用户名和密码无误后,创建session,并返回包含JsessionId的Cookie
  • 登录成功后,用户再次发起查看订单的请求,此时虽然携带了包含JsessionId的Cookie,但是这个JsessionId对应却并不是订单服务中的某一个Session的id,即在订单服务中找不到与此JssionId对应的Session,于是订单服务还是认为用户未登录。

经过以上分析,我们发现,在微服务架构中适用于单体应用的登录以及登录验证逻辑,在微服务架构中不适用了。这就引出了微服务架构(分布式系统的单点登录问题)。所以,在微服务架构中,我们就需要解决单点登录问题——Single Sign On,简称为 SSO,是比较流行的企业业务整合的解决方案之一。SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。

如何解决单点登录问题呢?有如下两种思路

  • 共享会话: 在用户登录之后,将用户登录数据放在Redis中,只需判断用户在Redis中是否有登录数据,就可判断其是否登录
  • 基于JWT

但是共享会话有自己的弊端:1)用户的登录会话数据存储在Redis中,如果同时登录人数较多占用大量内存;2)访问Redis还要经过底层的网络通信,需要通信时间。而相比较之下,JWT方案不需要在服务器端存储用户登录数据,而且验证登录只需在自己的java进程内存中验证JWT无需经过网络通信。所以我们选择使用JWT

JWT介绍

JWT全称 Json·Web·Token,是一个开放标准(RFC·7519),它定义了一种紧凑的,自包含的方式,用于作为JSON对象在各方之间安全的传输信息。该信息可以被验证和信任,因为它是数字签名的。JWT是目前最流行的分布式系统登录身份认证解决方案。

使用场景

下列场景中使用JWT是很有用的:

  • Information Exchange(信息交换):对于安全的在各方之间传输信息而言,Json·Web·Token无疑是一种很好的方式。因为JWT可以被签名,例如,用公钥/私钥配对,你可以确定发送人就是他们所说的那个人。另外,由于签名是使用头和有效负载计算的,您还可以验证内容有没有被篡改。
  • 基于JWT实现数据传输是一种比较成熟的机制(标准化,轻量级)且相对安全,所以我们基于JWT实现单点登录功能。我们实现单点登录的思路如下:

基本格式

JSON·Web·Token由三部分组成,他们之间用圆点(·)连接,这三部分分别是:

  • Header
  • Payload
  • Signature

Header由两部分信息组成:

  • type:声明类型,这里是jwt
  • alg:声明加密的算法 通常直接使用 HMAC、RSA

Payload就是存放有效信息的地方(不强制)

  • iss: jwt签发者
  • sub: jwt所面向的用户
  • aud: 接收jwt的一方
  • exp: jwt的过期时间,这个过期时间必须要大于签发时间
  • nbf: 定义在什么时间之前,该jwt都是不可用的
  • iat: jwt的签发时间
  • jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击
  • claim:jwt存放信息的地方

Signature就是签名信息

因此,一个典型的JWT看起来是这个样子的:xxxxxxxx·yyyyyyyyyy·zzzzzzzzzzz

具体如下:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ3bGd6cyIsImV4cCI6MTU4Nzk3MzY1NywidXNlciI6Ijk2MkYxODkwNTVFMzRFNzVERjVGMzQ0QTgxODNCODdGIn0.APehq9dxRiilgTOGyuz9qtZxvPDIJ5QIIVUCLYeX1QE

API

引入如下依赖:

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.7.18</version>
</dependency>

使用如下项目中的工具类:

JwtTool

public class JwtTool {
    private static final Duration JWT_TOKEN_TTL = Duration.ofMinutes(24 * 60 * 30);
    private static final String PAYLOAD_USER_KEY = "user";
    private static final String UserType = "userType";

    // 生成数字签名使用的秘钥
    private byte[] key;

    public JwtTool(String keyStr) {
        key = keyStr.getBytes();
    }

    /**
     * 创建 jwttoken
     *
     * @param currentUserId 用户id
     * @param name          用户姓名/昵称
     * @param avatar        用户头像
     * @param userType      1: 普通用户  2: 服务人员 3: 后台人员        
     * @return jwt token
     */
    public String createToken(Long currentUserId, String name, String avatar, int userType) {
        // 名称base64编码,防止token无法解析
        String encodeName = StringUtils.isEmpty(name) ? null : Base64Utils.encodeStr(name);
        // 1.生成jws
        return JWT.create()
                // 向jwt中放入数据,对应的json字段名称为user
                .setPayload(PAYLOAD_USER_KEY, new CurrentUserInfo(currentUserId, encodeName, avatar, userType))
                // 设置过期时间
                .setExpiresAt(new Date(System.currentTimeMillis() + JWT_TOKEN_TTL.toMillis()))
                .setCharset(Charset.forName("UTF-8"))
                // 设置签名所使用的秘钥
                .setKey(key)
                .sign();
    }

    /**
     * 从访问token中获取用户信息
     *
     * @param token 访问token
     * @return 用户信息
     */
    public CurrentUserInfo parseToken(String token) {
        try {
            JWT jwt = JWT.of(token).setKey(key);
            // 获取jwt中的用户数据,这里会自动校验数字签名
            JSONObject payload = (JSONObject) jwt.getPayload(PAYLOAD_USER_KEY);
            // 获取并封装数据
            CurrentUserInfo userInfo = new CurrentUserInfo();
            userInfo.setId(payload.getLong("id"));
            String name = payload.getStr("name");
            String avatar = payload.getStr("avatar");
            String userType = payload.getStr(UserType);
            if (userType != null) {
                userInfo.setUserType(Integer.parseInt(userType));
            }
            if (StringUtils.hasText(name)) {
                String decodeName = Base64Utils.decodeStr(name);
                userInfo.setName(decodeName);
            }
            userInfo.setAvatar(avatar);

            return userInfo;
        } catch (Exception e) {
            return null;
        }
    }

生成jwt字符串

 @Test
    public void createJWT() {
        String key = "jwt-test";
        JwtTool jwtTool = new JwtTool(key);
        /**
         *  第一个参数表示用户id
         *  第二个参数表示用户名
         *  第三个参数表示头像
         *  第四个参数表示用户类型: 因为我们有不同的类型的用户,后端工作人员,普通用户,服务提供人员
         *  这四个参数代表用户登录后要放入到JWT中的数据
         */
        String admin = jwtTool.createToken(1L, "admin", "http://xxx", 1);
        System.out.println(admin);
    }

解析jwt中的数据

    @Test
    public void parseJWT() {
        String key = "jwt-test";
        JwtTool jwtTool = new JwtTool(key);
        String jwtStr = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjp7ImlkIjoxLCJuYW1lIjoiWVdSdGFXND0iLCJhdmF0YXIiOiJodHRwOi8veHh4IiwidXNlclR5cGUiOjF9LCJleHAiOjE3NDk1NTA4OTN9.Tjh1leMEf4iReYe4rO_L1yQXitMHY_Zwh74Xw4ZneYY";
        CurrentUserInfo currentUserInfo = jwtTool.parseToken(jwtStr);
        System.out.println(currentUserInfo);
    }

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇