JWT с Angular 8 и Springboot 2: строки JWT должны содержать ровно 2 символа периода - PullRequest
0 голосов
/ 12 апреля 2020

Я нашел этот учебник на github для реализации JWT с Angular 8 и springboot2. но, выполняя то же самое, я сталкиваюсь с исключением, приведенным ниже.

io.jsonwebtoken.MalformedJwtException: JWT strings must contain exactly 2 period characters. Found: 0

при дальнейшей отладке, это исключение исходит от DefaultJwtParser. java, который является одним из классов в библиотеке JWT

@Override
    public Jwt parse(String jwt) throws ExpiredJwtException, MalformedJwtException, SignatureException {

        Assert.hasText(jwt, "JWT String argument cannot be null or empty.");

        String base64UrlEncodedHeader = null;
        String base64UrlEncodedPayload = null;
        String base64UrlEncodedDigest = null;

        int delimiterCount = 0;

        StringBuilder sb = new StringBuilder(128);

        for (char c : jwt.toCharArray()) {

            if (c == SEPARATOR_CHAR) {

                CharSequence tokenSeq = Strings.clean(sb);
                String token = tokenSeq!=null?tokenSeq.toString():null;

                if (delimiterCount == 0) {
                    base64UrlEncodedHeader = token;
                } else if (delimiterCount == 1) {
                    base64UrlEncodedPayload = token;
                }

                delimiterCount++;
                sb.setLength(0);
            } else {
                sb.append(c);
            }
        }

        if (delimiterCount != 2) {
            String msg = "JWT strings must contain exactly 2 period characters. Found: " + delimiterCount;
            throw new MalformedJwtException(msg);
        }

это означает, что токен должен быть в формате 'Bearer ab c .def.ghi', но в соответствии с руководством он будет похож на 'Bearer Y2xpZW50OmNsaWVudA ==', пока я выполняю вход в систему.

Angular код

login(user: User): Observable<any> {
    const headers = new HttpHeaders(user ? {
      authorization:'Bearer '+ btoa(user.username + ':' + user.password)
    }:{});

    return this.http.get<any> (API_URL + "login", {headers:headers}).pipe(
      map(response => {
        console.log("map"+response);
        if(response) {
          localStorage.setItem('currentUser', JSON.stringify(response));
          this.currentUserSubject.next(response);
        }
        return response;
      })
    );
  }

Java код

@Component
public class JwtTokenProvider
{
    @Value("${app.jwt.secret}")
    private String jwtSecret;
    @Value("${app.jwt.token.prefix}")
    private String jwtTokenPrefix;
    @Value("${app.jwt.header.string}")
    private String jwtHeaderString;
    @Value("${app.jwt.expiration-in-ms}")
    private String jwtExpirationInMs;

    public String generateToken(Authentication authentication)
    {
        String authororities = authentication.getAuthorities().stream()
            .map(GrantedAuthority::getAuthority).collect(Collectors.joining());
        return Jwts.builder().setSubject(authentication.getName()).claim("roles", authororities)
            .setExpiration(new Date(System.currentTimeMillis() + jwtExpirationInMs))
            .signWith(SignatureAlgorithm.HS512, jwtSecret).compact();
    }

    public Authentication getAuthentication(HttpServletRequest request)
    {
        String token = resolveToken(request);
        if (token == null)
        {
            return null;
        }
        Claims claim = Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token).getBody();
        String username = claim.getSubject();
        List<GrantedAuthority> authorities = Arrays.stream(claim.get("roles").toString().split(","))
            .map(role -> role.startsWith("ROLE_") ? role : "ROLE_" + role)
            .map(SimpleGrantedAuthority::new).collect(Collectors.toList());
        return username != null
                        ? new UsernamePasswordAuthenticationToken(username, null, authorities)
                        : null;
    }

    public boolean validateToken(HttpServletRequest request)
    {
        String token = resolveToken(request);
        if (token == null)
        {
            return false;
        }
        Claims claim = Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token).getBody();
        if (claim.getExpiration().before(new Date()))
        {
            return false;
        }
        return true;
    }

    private String resolveToken(HttpServletRequest request)
    {
        String bearerToken = request.getHeader(jwtHeaderString);
        if (bearerToken != null && bearerToken.startsWith(jwtTokenPrefix))
        {
            return bearerToken.substring(7, bearerToken.length());
        }
        return null;
    }
}

Я уже пытался удалить «Носитель» из заголовка авторизации, но все не работает.

Пожалуйста, дайте мне знать, если нужны какие-либо другие детали

1 Ответ

0 голосов
/ 12 апреля 2020

Jwt определяется как xxx.yyy.zzz, где

  • xxx - информация заголовка вашего токена, например алгоритм ha sh,
  • yyy - полезная нагрузка, содержащая информацию о пользователе и
  • zzz - подпись для проверки токена. Подробнее об этом здесь:

https://jwt.io/introduction/

Вы просто отправляете username:password в виде строки ascii в кодировке base64. Это было бы частью полезной нагрузки yyy.

Spring ожидает xxx.yyy.zzz и выдает исключение, потому что без заголовка / подписи это не jwt.

Так что если вы хотите создать jwt в своем интерфейсе, вы нужно переписать твой код.

Кстати: токен никогда не должен содержать конфиденциальные данные, такие как пароль.

Обновление

Я бы предложил этот рабочий процесс входа в систему:

  • Пользователь вводит имя пользователя / пароль в форме входа
  • Ваш Angular Frontend выполняет POST-запрос с учетными данными, используя имя пользователя и пароль в качестве параметров. Пароль зашифрован, например, с помощью алгоритма bcrypt. Этот алгоритм хорош для этого варианта использования, так как он довольно медленный и, следовательно, сложнее грубой силы. Ваша озабоченность в отношении раздела сети браузера действительна, но заголовок Authorization можно увидеть там же в request header, прямо над частью параметра запроса.
  • Пружинный бэкэнд сравнивает учетные данные и возвращает токен jwt. Маркер может содержать информацию о пользователе, такую ​​как имя пользователя, роли или адрес электронной почты.
  • Внешний интерфейс хранит jwt, например, в localStorage браузера. Для каждого запроса токен добавляется с заголовком как Authorization: Bearer + ${token}
  • . Теперь бэкэнд может проверять токен при каждом запросе. Нет необходимости каждый раз передавать учетные данные.

Для этого требуется сервер аутентификации, который в настоящее время не поддерживается в Spring Security 5 (но это решение находится под пересмотром атм).

В качестве альтернативы вы можете использовать стороннего поставщика, такого как aws cognito , который может обрабатывать регистрацию пользователей или вход в систему с учетными записями в социальных сетях, таких как Google или Facebook.

Или вы можете использовать что-то вроде google oauth api для входа в систему. Пользователь входит в систему с помощью Google, который возвращает jwt, который вы можете использовать для бэкэнд-аутентификации.

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

...