Вопрос для уточнения: аутентификация Microservice JWT [включая решение для аутентификации архитектуры микросервиса]

Java
Вопрос для уточнения: аутентификация Microservice JWT [включая решение для аутентификации архитектуры микросервиса]

гид

Многие друзья часто спрашивают меня и говорят:

  • «Брат Сюн, чем именно занимается JWT?»
  • «Есть ли хороший способ аутентификации микросервиса?»
  • «Брат Сюн, мне так трудно, JWT, кажется, не понимает, можешь мне объяснить?»
img

Что ж, сегодня я объясню аутентификацию JWT с помощью статьи братьев.

Говоря о JWT, мы должны сначала поговорить об аутентификации.Аутентификацию очень легко реализовать в сервисе одиночной архитектуры, но в микросервисной архитектуре, когда сервисов больше, это сделать не очень просто. Потому что мы вряд ли будем делать аутентификацию для каждого сервиса!

1. Схема архитектуры аутентификации микросервиса

Архитектура микросервисной системы должна иметь роль шлюза. В системе шлюза шлюз больше всего подходит для проверки авторизации. Схема архитектуры аутентификации микросервиса выглядит следующим образом:

Глядя на диаграмму архитектуры, мы можем использовать JWT для реализации проверки подлинности! Это сценарий JWT

Примечание: Должна быть необходимость рождать технологии!

Многие студенты не понимают определенную технологию, потому что она вам не нужна. Я часто сталкиваюсь с определенной технологией, и чувствую: «О, оказывается, предыдущую проблему такого-то можно решить с ее помощью!». Я сразу все понял, но сначала не понял, почему это такая очевидная проблема, ведь многие студенты просто не могли этого понять! Наконец-то разобрались, знания многих студентов отложены в долгий ящик, а не на землю!

Без лишних слов, давайте к делу.

2. Что такое JWT? Полное имя JSON Web Token

JWT — это, по сути, спецификация написания строк, которая используется для безопасной и надежной передачи информации между пользователями и серверами.

JSON Web Token (JWT) — очень легкая спецификация. Эта спецификация позволяет нам использовать JWT вБезопасная и надежная передача информации между пользователями и серверами. JWT на самом деле представляет собой строку, состоящую из трех частей:

  • голова
  • нагрузка
  • подписать

2.1 Заголовок подробного объяснения трех элементов JWT

Заголовок используется для описания самой основной информации о JWT, такой как его тип и алгоритм, используемый для его подписи. Это также может быть представлено как объект JSON.

{"typ":"JWT","alg":"HS256"}

В шапке указано, что алгоритм подписи - алгоритм HS256. Делаем кодировку BASE64base64.xpcha.com/ закодированная строка выглядит следующим образом:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

Краткий факт: Base64 — это представление двоичных данных, основанное на 64 печатных символах. Поскольку 2 в 6-й степени равно 64, каждые 6 бит являются единицей, соответствующей определенному печатному символу. Три байта имеют 24 бита, соответствующие 4 единицам Base64, то есть 3 байта должны быть представлены 4 печатными символами. JDK предоставляет очень удобныйBASE64Encoder а также BASE64Decoder, который можно использовать для простого кодирования и декодирования на основе BASE64.

2.2 Полезная нагрузка подробного объяснения трех элементов JWT

Полезная нагрузка — это место, где хранится достоверная информация. Название, по-видимому, относится конкретно к товарам, перевозимым в самолете, и эта достоверная информация состоит из трех частей.

(1) Утверждения, зарегистрированные в стандарте (рекомендуется, но не является обязательным)

iss: jwt签发者
sub: jwt所面向的用户
aud: 接收jwt的一方
exp: jwt的过期时间,这个过期时间必须要大于签发时间
nbf: 定义在什么时间之前,该jwt都是不可用的.
iat: jwt的签发时间
jti: jwt的唯一身份标识,主要用来作为一次性token。

(2) Публичное заявление

Публичное заявление может добавлять любую информацию, как правило, информацию о пользователе или другую необходимую информацию для нужд бизнеса, но не рекомендуется добавлять конфиденциальную информацию, потому что эта часть может быть расшифрована на стороне клиента.

(3) Частное заявление

Частный оператор — это оператор, определяемый совместно поставщиком и потребителем.Как правило, не рекомендуется хранить конфиденциальную информацию, поскольку base64 является симметричным дешифрованием, что означает, что эта часть информации может быть классифицирована как информация открытого текста.

Имеется в виду пользовательское требование. Например, admin и имя в предыдущем примере структуры относятся к пользовательскому утверждению. Отличие этих требований от требований, предусмотренных стандартом JWT, заключается в том, что: требование, предусмотренное JWT, получатель JWT знает, как проверить эти стандартные заявления после получения JWT (неизвестно, можно ли это проверить) ; в то время как частные утверждения не проходят проверку, если получателю явно не указано проверить эти утверждения и правила.

Определите полезную нагрузку:

{"sub":"1234567890","name":"hore_brother","admin":true}

Затем он шифруется base64, чтобы получить вторую часть Jwt.

eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9

2.3 Подробное объяснение трех элементов JWT (подпись)

Третья часть jwt — это визовая информация, состоящая из трех частей:

заголовок (после base64)

полезная нагрузка (после base64)

secret

Эта часть требует использования заголовка, зашифрованного с помощью base64, и полезной нагрузки, зашифрованной с помощью base64.Строка, сформированная соединением, затем шифруется с помощью метода шифрования, объявленного в заголовке, с комбинацией секретов с солью, которая затем составляет третью часть jwt.

TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

Объедините эти три части с . в полную строку, чтобы сформировать окончательный jwt:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

Уведомление: Секрет хранится на стороне сервера, а также на стороне сервера выполняется выдача и генерация jwt.Секрет используется для выдачи jwt и проверки jwt.Поэтому это закрытый ключ вашего сервера и не должны раскрываться ни при каком сценарии. Как только клиент узнает этот секрет, это означает, что клиент может самостоятельно выпустить jwt.

3. Как выпустить и проверить JWT? Использование JJWT

JJWT — это библиотека Java, обеспечивающая сквозное создание и проверку JWT. Всегда бесплатный и с открытым исходным кодом (лицензия Apache, версия 2.0), JJWT прост в использовании и понимании. Он был разработан как гибкий интерфейс, основанный на архитектуре, которая скрывает большую часть своей сложности.

Официальная документация:github.com/jwtk/jjwt

3.1 Создать токен

(1) Добавьте зависимости к pom.xml в новом проекте:

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.0</version>
</dependency>

(2) Создайте тестовый класс, код выглядит следующим образом

JwtBuilder builder= Jwts.builder()
    .setId("888")   //设置唯一编号
    .setSubject("雄哥666")//设置主题  可以是JSON数据
    .setIssuedAt(new Date())//设置签发日期
    .signWith(SignatureAlgorithm.HS256,"kkb123");//设置签名 使用HS256算法,并设置SecretKey(字符串)
//构建 并返回一个字符串 
System.out.println( builder.compact() );

Запустите результат печати:

eyJhbGciOiJIUzINiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiLlsI_mb0iLCJpYXQiOjE1NTc5MDQxODF9.ThecMfgYjtoys3JX7dpx3hu6pUm0piZ0tXreFU

Запустите его еще раз, и вы обнаружите, что результаты каждого запуска разные, потому что наша полезная нагрузка включает время.

3.2 Разобрать токен

Мы только что создали токен.В веб-приложении эта операция выполняется сервером, а затем отправляется клиенту.Клиент должен иметь этот токен при отправке запроса на сервер в следующий раз (это как держать билет ) , сервер должен проанализировать информацию в токене (например, идентификатор пользователя) при получении этого токена и запросить базу данных в соответствии с этой информацией, чтобы вернуть соответствующий результат.

    String compactJwt="eyJhbGciOiJIUzINiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiLlsI_mb0iLCJpYXQiOjE1NTc5MDQxODF9.ThecMfgYjtoys3JX7dpx3hu6pUm0piZ0tXreFU";
    Claims claims = Jwts.parser().setSigningKey("kkb123").parseClaimsJws(compactJwt).getBody();
    System.out.println(claims);

Запустите эффект печати:

{jti=888, sub=雄哥666, iat=1557904181}

Если вы попытаетесь изменить токен или ключ подписи, вы обнаружите, что во время выполнения будет сообщено об ошибке, поэтому синтаксический анализ токена также проверяет токен.

3.3 Установите время истечения срока действия

Во многих случаях мы не хотим, чтобы выпущенный токен был постоянным, поэтому мы можем добавить к токену срок действия.

(1) Создайте токен и установите срок действия

    //当前时间
    long currentTimeMillis = System.currentTimeMillis();
    Date date = new Date(currentTimeMillis);
    JwtBuilder builder= Jwts.builder()
            .setId("888")   //设置唯一编号
            .setSubject("雄哥666")//设置主题  可以是JSON数据
            .setIssuedAt(new Date())//设置签发日期
            .setExpiration(date)
            .signWith(SignatureAlgorithm.HS256,"kkb123");//设置签名 使用HS256算法,并设置SecretKey(字符串)
    //构建 并返回一个字符串
    System.out.println( builder.compact() );

объяснять:

  .setExpiration(date)//用于设置过期时间 ,参数为Date类型数据

Запустите, эффект печати выглядит следующим образом:

eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiLlsI_nmb0iLCJpYXOjE1NTc5MDUzMDgsImV4cCI6MTU1NzkwNTMwOH0.4q5AaTyBRf8SB9B3Tl-I53PrILGyicJ

(2) Разобрать ТОКЕН

    String compactJwt="eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiLlsI_nm0iLCJpYXQiOjE1NTc5MDUzMDgsImV4cCI6MTU1NzkwNTMwOH0.4q5AaTyBRf8SB9B3Tl-I53PrILGyicJ";
    Claims claims = Jwts.parser().setSigningKey("kkb123").parseClaimsJws(compactJwt).getBody();
    System.out.println(claims);

Эффект печати:

Если текущее время превышает время истечения, будет сообщено об ошибке.

3.4 Пользовательские претензии

В примере, который мы только что сохранили, хранятся только две части информации: идентификатор и тема.Если вы хотите хранить больше информации (например, роли), вы можете определить пользовательские утверждения.

Создайте тестовый класс и настройте методы тестирования:

Создать токен:

@Test
public void createJWT(){
    //当前时间
    long currentTimeMillis = System.currentTimeMillis();
    currentTimeMillis+=1000000L;
    Date date = new Date(currentTimeMillis);
    JwtBuilder builder= Jwts.builder()
            .setId("888")   //设置唯一编号
            .setSubject("雄哥666")//设置主题  可以是JSON数据
            .setIssuedAt(new Date())//设置签发日期
            .setExpiration(date)//设置过期时间
            .claim("roles","admin")//设置角色
            .signWith(SignatureAlgorithm.HS256,"kkb123");//设置签名 使用HS256算法,并设置SecretKey(字符串)
    //构建 并返回一个字符串
    System.out.println( builder.compact() );
}

Запустите эффект печати:

eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiLpm4Tlk6U2NjYiLCJpYXQiOjE2MDU0ODE5MTgsImV4cCI6MTYwNTQ4MjkxOCwicm9sZXMiOiJhZG1pbiJ9.zSs542P4nUHRMxOgL12sksWPRFavR8B0Fikb3lvxb_k

Разобрать ТОКЕН:

//解析
@Test
public void parseJWT(){
    String compactJwt="eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiLlsI_nmb0CJpYXQiOjE1NTc5MDU4MDIsImV4cCI6MTU1NzkwNjgwMiwicm9sZXMiOiJhZG1pbiJ9.AS5Y2fNCwUzQQxXh_QQWMpaB75YqfuK";
    Claims claims = Jwts.parser().setSigningKey("kkb123").parseClaimsJws(compactJwt).getBody();
    System.out.println(claims);
}

текущий результат:

В-четвертых, полная схема реализации кода аутентификации микросервиса

На самом деле, когда вы понимаете JWT, реализация аутентификации придет сама собой! Хорошо понял, логика аутентификации очень проста

4.1 Анализ идей технической архитектуры

  1. Пользователь входит в шлюз и начинает входить в систему, а фильтр шлюза выносит решение.Если это вход в систему, он направляется в микрослужбу фонового управления для входа.
  2. Пользователь успешно входит в систему, а микрослужба фонового управления выдает информацию JWT TOKEN и возвращает ее пользователю.
  3. Пользователь снова входит в шлюз, чтобы начать доступ, и фильтр шлюза получает ТОКЕН, переносимый пользователем.
  4. Фильтр шлюза анализирует TOKEN, чтобы определить, есть ли у него разрешение. Если да, то он будет освобожден. Если нет, он вернет ошибку, не связанную с проверкой подлинности.

4.2 Маркер выпуска системных микросервисов

(1) JwtUtil

/**
 * JWT工具类
 */
public class JwtUtil {

    //有效期为
    public static final Long JWT_TTL = 3600000L;// 60 * 60 *1000  一个小时
	//设置秘钥明文
    public static final String JWT_KEY = "kkb123";

    /**
     * 创建token
     * @param id
     * @param subject
     * @param ttlMillis
     * @return
     */
    public static String createJWT(String id, String subject, Long ttlMillis) {

        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);
        if(ttlMillis==null){
            ttlMillis=JwtUtil.JWT_TTL;
        }
        long expMillis = nowMillis + ttlMillis;
        Date expDate = new Date(expMillis);
        SecretKey secretKey = generalKey();

        JwtBuilder builder = Jwts.builder()
                .setId(id)              //唯一的ID
                .setSubject(subject)   // 主题  可以是JSON数据
                .setIssuer("admin")     // 签发者
                .setIssuedAt(now)      // 签发时间
                .signWith(signatureAlgorithm, secretKey) //使用HS256对称加密算法签名, 第二个参数为秘钥
                .setExpiration(expDate);// 设置过期时间
        return builder.compact();
    }

    /**
     * 生成加密后的秘钥 secretKey
     * @return
     */
    public static SecretKey generalKey() {
        byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
        SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
        return key;
    }
}

(2) Метод входа в систему, если пользователь успешно вошел в систему, будет выдан ТОКЕН.

    /**
     * 登录
     * @param admin
     * @return
     */
    @PostMapping("/login")
    public Result login(@RequestBody Admin admin){
        boolean login = adminService.login(admin);
        if(login){  //如果验证成功
            Map<String,String> info = new HashMap<>();
            info.put("username",admin.getLoginName());
            String token = JwtUtil.createJWT(UUID.randomUUID().toString(), admin.getLoginName(), null);
            info.put("token",token);
            return new Result(true, StatusCode.OK,"登录成功",info);
        }else{
            return new Result(false,StatusCode.LOGINERROR,"用户名或密码错误");
        }
    }

4.3 Токен проверки фильтра шлюза

(1) Добавить зависимость jjwt

<!--鉴权-->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.0</version>
</dependency>

(2) Создайте класс JWTUtil.

/**
 * jwt校验工具类
 */
public class JwtUtil {

    //有效期为
    public static final Long JWT_TTL = 3600000L;// 60 * 60 *1000  一个小时
	//设置秘钥明文
    public static final String JWT_KEY = "kkb123";

    /**
     * 生成加密后的秘钥 secretKey
     *
     * @return
     */
    public static SecretKey generalKey() {
        byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
        SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
        return key;
    }

    /**
     * 解析
     *
     * @param jwt
     * @return
     * @throws Exception
     */
    public static Claims parseJWT(String jwt) throws Exception {
        SecretKey secretKey = generalKey();
        return Jwts.parser()
                .setSigningKey(secretKey)
                .parseClaimsJws(jwt)
                .getBody();
    }
}

(3) Создайте фильтр для проверки токена

/**
 * 鉴权过滤器 验证token
 */
@Component
public class AuthorizeFilter implements GlobalFilter, Ordered {
    private static final String AUTHORIZE_TOKEN = "token";

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
		//1. 获取请求
        ServerHttpRequest request = exchange.getRequest();
        //2. 则获取响应
        ServerHttpResponse response = exchange.getResponse();
        //3. 如果是登录请求则放行
        if (request.getURI().getPath().contains("/admin/login")) {
            return chain.filter(exchange);
        }
        //4. 获取请求头
        HttpHeaders headers = request.getHeaders();
        //5. 请求头中获取令牌
        String token = headers.getFirst(AUTHORIZE_TOKEN);

        //6. 判断请求头中是否有令牌
        if (StringUtils.isEmpty(token)) {
            //7. 响应中放入返回的状态吗, 没有权限访问
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            //8. 返回
            return response.setComplete();
        }

        //9. 如果请求头中有令牌则解析令牌
        try {
            JwtUtil.parseJWT(token);
        } catch (Exception e) {
            e.printStackTrace();
            //10. 解析jwt令牌出错, 说明令牌过期或者伪造等不合法情况出现
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            //11. 返回
            return response.setComplete();
        }
        //12. 放行
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

4.4 Заключительный тест

  1. Если вы напрямую обращаетесь без токена: верните ошибку 401, неавторизованный

  2. Если передан правильный токен, будет возвращен правильный результат запроса.

Суммировать

Получить JWT на самом деле не сложно, верно!

Эта статья впервые опубликована Nuggets, пожалуйста, не пересылайте ее на другие платформы без разрешения! Пожалуйста, уважайте оригинальность, спасибо