WedX - журнал о программировании и компьютерных науках

JWT выпускает тот же токен

Я делаю API для отдыха с Jersey. Я использую java-jwt(https://github.com/auth0/java-jwt) для своего работа по генерации токенов. Пожалуйста, проверьте приведенный ниже код.

UserJSONInfo — класс методов REST

@Path ("/user_info")
public class UserInfoJSONService 
{
    @POST
    @Path("/authenticateUser")
    @Produces(MediaType.APPLICATION_JSON)
    public Response authenticateUser(Credentials credentials)
    {
        UserInfoService service = new UserInfoService();

        try{
        UserInfo authenticateUser = service.authenticateUser(credentials.getUserName(), credentials.getPassword());
        String generateToken = service.generateToken(AuthKey.authorizationSecret);

        Authentication auth = new Authentication();
        auth.setIdUser(authenticateUser.getIduser());
        auth.setToken(generateToken);

        return Response.status(Response.Status.OK).entity(auth).build();
        //return authenticateUser;
        }
        catch(IndexOutOfBoundsException e)
        {
            throw new WebApplicationException(Response.Status.BAD_REQUEST);
        } catch (JWTCreationException ex) {
            throw new WebApplicationException(Response.Status.UNAUTHORIZED);
        } catch (UnsupportedEncodingException ex) {
            throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
        }
    }
}

UserInfoService — сервисный уровень

public class UserInfoService {

    private static UserInfoDAOInterface userDAOInterface;

    public UserInfoService() {
        userDAOInterface = new UserInfoDAOImpl();
    }

    public Session getSession() {
        Session session = userDAOInterface.openCurrentSession();
        return session;
    }

    public Transaction getTransaction(Session session) {
        Transaction transaction = userDAOInterface.openTransaction(session);
        return transaction;
    }



    public UserInfo authenticateUser(String userName, String password)
    {
        return authenticate(userName, password);
    }

    private UserInfo authenticate(String userName, String password) throws IndexOutOfBoundsException
    {
        Session session = userDAOInterface.openCurrentSession();
        Transaction transaction = null;
        UserInfo user = new UserInfo();

        try {
            transaction = userDAOInterface.openTransaction(session);
            user = userDAOInterface.authenticate(userName, password, session);
            transaction.commit();
//        } catch (Exception ex) {
//            //ex.printStackTrace();
//            System.out.println("OK");
//        
        } finally {
            session.close();
        }

        return user;
    }

    public String generateToken(String secret) throws JWTCreationException, UnsupportedEncodingException
    {
        Token token = new Token();
        return token.issueTokenHMAC256(secret);
    }

}

AuthKey – просто содержит secret

public interface AuthKey {
    public static String authorizationSecret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
}

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="https://java.sun.com/xml/ns/javaee" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://java.sun.com/xml/ns/javaee https://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
    <servlet>
        <servlet-name>ExampleServlet</servlet-name>
        <servlet-class>test.ExampleServlet</servlet-class>
    </servlet>
    <servlet>
        <servlet-name>Jersey RESTful Application</servlet-name>
        <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
        <init-param>
            <param-name>jersey.config.server.provider.packages</param-name>
            <param-value>rest</param-value>
        </init-param>
        <init-param>
            <param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
            <param-value>true</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>ExampleServlet</servlet-name>
        <url-pattern>/ExampleServlet</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>Jersey RESTful Application</servlet-name>
        <url-pattern>/rest/*</url-pattern>
    </servlet-mapping>    
    <session-config>
        <session-timeout>
            30
        </session-timeout>
    </session-config>
    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>
</web-app>

Я сохранил свои классы генерации токенов как еще один проект Java и импортировал их сюда как библиотеку (я использую Netbeans). Ниже приведен код

package com.xyz.security;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTCreationException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import java.io.UnsupportedEncodingException;

/**
 *
 * @author Yohan
 */
public class Token {

    /**
     * Generate the HMAC256 Token
     * @param secret
     *          Secret to generate the token
     * @return 
     *      Token as a String
     * @throws UnsupportedEncodingException
     *      UTF-8 encoding not supported
     * @throws JWTVerificationException 
     *      Invalid Signing configuration / Couldn't convert Claims.
     */
    public String issueTokenHMAC256(String secret) throws UnsupportedEncodingException, JWTCreationException
    {

        String token="";
        try {
            Algorithm algorithm = Algorithm.HMAC256(secret);
            token = JWT.create()
                    .withIssuer("auth0")
                    .sign(algorithm);


        } catch (UnsupportedEncodingException exception) {
    //UTF-8 encoding not supported
            exception.printStackTrace();
        } catch (JWTCreationException exception) {
    //Invalid Signing configuration / Couldn't convert Claims.
            exception.printStackTrace();
        }

        return token;
    }


    /**
     * Validate a HMAC256 Token
     * @param token
     *          Token you need to validate
     * @param secret
     *          Secret used to generate the token
     * @return
     *          Returns `true` if token is valid.
     * @throws UnsupportedEncodingException
     *          UTF-8 encoding not supported
     * @throws JWTVerificationException 
     *          Invalid Signing configuration / Couldn't convert Claims.
     */
    public boolean validateTokenHMAC256(String token, String secret) throws UnsupportedEncodingException, JWTVerificationException
    {       
        Algorithm algorithm = Algorithm.HMAC256(secret);
            JWTVerifier verifier = JWT.require(algorithm)
                .withIssuer("auth0")
                .build(); //Reusable verifier instance
            DecodedJWT jwt = verifier.verify(token);

        return true;
    }
}

Теперь проблема в том, что каждый раз, когда я генерирую token при входе пользователя в систему, я получаю одно и то же token. Я использую POSTMAN для проверки методов REST, я открыл 3 вкладки и попытался войти в систему для 3 разных пользователей. Проблема в том, что я получаю тот же токен! Это правильно или что-то не так? В таком случае, как я могу это исправить?

23.03.2017

  • Как вы сгенерировали этот авторизационный секрет в AuthKey? 08.04.2020

Ответы:


1

О какой схеме аутентификации на основе токенов идет речь

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

С токеном вы должны иметь возможность идентифицировать пользователя, который ориентируется на ваш API. Следовательно, нет смысла иметь один токен для всех аутентифицированных пользователей.

Решение вашей проблемы

После того, как вы используете JWT, у вас может быть претензия с именем пользователя. Также рассмотрите возможность добавления даты истечения срока действия вашего токена (утверждение exp). Вы же не хотите, чтобы ваш токен был действителен вечно, не так ли?

С java-jwt используйте следующее:

try {

    Algorithm algorithm = Algorithm.HMAC256("secret");
    Date expirationDate = Date.from(ZonedDateTime.now().plusMinutes(60).toInstant());
    String token = JWT.create()
                      .withExpiresAt(expirationDate)
                      .withClaim("username", username)
                      .sign(algorithm);

} catch (UnsupportedEncodingException e){
    // UTF-8 encoding not supported
} catch (JWTCreationException e){
    // Invalid signing configuration / Couldn't convert claims
}

При проверке токена вы сможете получить утверждение username и узнать, для кого вы выдали токен:

try {

    Algorithm algorithm = Algorithm.HMAC256("secret");
    JWTVerifier verifier = JWT.require(algorithm).build();
    DecodedJWT jwt = verifier.verify(token);

    Claim usernameClaim = jwt.getClaim("username");
    String username = usernameClaim.asString();

} catch (UnsupportedEncodingException e){
    // UTF-8 encoding not supported
} catch (JWTVerificationException e){
    // Invalid signature/claims
}

Обработка обновления токена с помощью JWT

Принимать только действительные (с неистекшим сроком действия) токены для обновления. Клиент несет ответственность за обновление токенов до истечения срока действия, указанного в exp претензия.

Чтобы избежать бесконечного обновления токена, вы можете следить за обновлением токена, добавляя два утверждения к вашему токену (имена утверждений зависят от вас):

  • refreshLimit: указывает, сколько раз можно обновить токен.
  • refreshCount: Указывает, сколько раз токен обновлялся.

Поэтому обновляйте токен только в том случае, если выполняются следующие условия:

  • Токен не просрочен (exp >= now).
  • Количество обновлений маркера меньше, чем количество обновлений маркера (refreshCount < refreshLimit).

И при обновлении токена:

  • Обновите дату истечения срока действия (exp = now + some-amount-of-time).
  • Увеличьте количество обновлений токена (refreshCount++).

После того, как токен подписан и подпись проверена на стороне сервера, клиент не может изменить содержимое токена.

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

Другой подход заключается в выпуске отдельного долгоживущего токена обновления, который используется для выпуска краткосрочных токенов JWT.

Лучший подход зависит от ваших требований.

Обработка отзыва токена с помощью JWT

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

Утверждение jti должно использоваться для хранения идентификатора токена в самом токене. . При проверке токена убедитесь, что он не был отозван, проверив значение jti претензии к идентификаторам токенов, которые у вас есть на стороне сервера.

В целях безопасности отзывайте все токены для пользователя, когда он меняет свой пароль.


Подробнее об аутентификации на основе токенов в JAX-RS см. в этом ответе.

23.03.2017
  • Спасибо. При проверке то же самое может войти и в мой validateTokenHMAC256(), верно? 23.03.2017
  • Спасибо. Я также хотел спросить, вместо того, чтобы делать как JWT.create().withClaim........ и так далее, было бы очень легко настроить, если мы получим объект JWT и продолжим, верно? Тогда это будет как jwtobject.withClaim.... .Я разговариваю по телефону, поэтому не могу проверить, но это возможно, не так ли? 23.03.2017
  • @PeakGen Извините, я не понимаю, что вы имеете в виду. Что-то не так с подходом, описанным выше? Вот как работает API, который вы используете. 23.03.2017
  • Спасибо. Все работает нормально. Другой вопрос, когда срок действия токена истек, как я могу с этим справиться? Пользователь должен войти в систему, вернуться к экрану входа и войти каждый час? 24.03.2017
  • @PeakGen Если запрос выполняется с недопустимым токеном, сервер должен ответить кодом состояния 403, а клиент должен обработать это (например, показать страницу входа). Вы также можете разрешить своим клиентам обновлять свои токены. Для этого нет стандартов, но я обновил свой ответ тем, что я обычно делаю. 24.03.2017
  • Спасибо. Я задал еще один вопрос, и у меня есть ответ, но ваш вклад также высоко ценится. Ссылка - stackoverflow.com/questions /43090518/ 30.03.2017
  • Новые материалы

    Объяснение документов 02: BERT
    BERT представил двухступенчатую структуру обучения: предварительное обучение и тонкая настройка. Во время предварительного обучения модель обучается на неразмеченных данных с помощью..

    Как проанализировать работу вашего классификатора?
    Не всегда просто знать, какие показатели использовать С развитием глубокого обучения все больше и больше людей учатся обучать свой первый классификатор. Но как только вы закончите..

    Работа с цепями Маркова, часть 4 (Машинное обучение)
    Нелинейные цепи Маркова с агрегатором и их приложения (arXiv) Автор : Бар Лайт Аннотация: Изучаются свойства подкласса случайных процессов, называемых дискретными нелинейными цепями Маркова..

    Crazy Laravel Livewire упростил мне создание электронной коммерции (панель администратора и API) [Часть 3]
    Как вы сегодня, ребята? В этой части мы создадим CRUD для данных о продукте. Думаю, в этой части я не буду слишком много делиться теорией, но чаще буду делиться своим кодом. Потому что..

    Использование машинного обучения и Python для классификации 1000 сезонов новичков MLB Hitter
    Чему может научиться машина, глядя на сезоны новичков 1000 игроков MLB? Это то, что исследует это приложение. В этом процессе мы будем использовать неконтролируемое обучение, чтобы..

    Учебные заметки: создание моего первого пакета Node.js
    Это мои обучающие заметки, когда я научился создавать свой самый первый пакет Node.js, распространяемый через npm. Оглавление Глоссарий I. Новый пакет 1.1 советы по инициализации..

    Забудьте о Matplotlib: улучшите визуализацию данных с помощью умопомрачительных функций Seaborn!
    Примечание. Эта запись в блоге предполагает базовое знакомство с Python и концепциями анализа данных. Привет, энтузиасты данных! Добро пожаловать в мой блог, где я расскажу о невероятных..


    Для любых предложений по сайту: [email protected]