У меня проблемы с проверкой токена доступа, полученного от Azure, в моем веб-приложении на Java.signature.verifySignature()
библиотеки jose4j
просто возвращает false
.Может кто-нибудь помочь мне понять, что я делаю неправильно?
Я успешно настроил фронтальное веб-приложение для входа в систему через Azure Active Directory, на которое оно получает токен доступа в виде подписанного Json.Веб-токен.Я намерен прикрепить этот JWT в заголовке "Authoriation": "token <signed-access-token>"
во всех запросах к другим моим веб-приложениям, работающим с базами данных.Я хотел бы создать Spring Filter для проверки подписи JWT для каждого из этих веб-приложений, обращающихся к базе данных, чтобы убедиться, что токен действительно был выпущен Azure.
У меня есть код для моего небольшого доказательстваКонцепция в этом репозитории github: repo .Но вот мой фильтр:
package com.doug.example.oauthclient.microservice.config;
//...
import javax.servlet.FilterChain;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import org.jose4j.jwa.AlgorithmConstraints;
import org.jose4j.jwk.JsonWebKey;
import org.jose4j.jwk.JsonWebKeySet;
import org.jose4j.jwk.VerificationJwkSelector;
import org.jose4j.jws.AlgorithmIdentifiers;
import org.jose4j.jws.JsonWebSignature;
import org.jose4j.lang.JoseException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
import org.springframework.web.client.RestOperations;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.filter.GenericFilterBean;
public class AzureJwtFilter extends GenericFilterBean {
private final static Logger LOG = LoggerFactory.getLogger(AzureJwtFilter.class);
private final static String AUTHORIZATION_HEADER_NAME = "Authorization";
private final static String TOKEN_PREFIX = "token ";
private String azurePublicKeyUrl = "https://login.microsoftonline.com/<my-ad>.onmicrosoft.com/discovery/v2.0/keys";
private RestOperations restTemplate = new RestTemplate();
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) {
HttpServletRequest httpRequest = (HttpServletRequest)request;
if(httpRequest.getHeader(AUTHORIZATION_HEADER_NAME) == null ||
!httpRequest.getHeader(AUTHORIZATION_HEADER_NAME).startsWith(TOKEN_PREFIX) ) {
throw new AuthenticationCredentialsNotFoundException("Missing or Invalid Authorization Header in Request");
}
String authorizationToken = httpRequest.getHeader(AUTHORIZATION_HEADER_NAME).replace(TOKEN_PREFIX, "");
String[] tokenParts = authorizationToken.split("\\.");
if(tokenParts.length != 3) {
throw new InvalidTokenException("The authorization token did not have 3 parts");
}
try {
String publicKeySetJson = restTemplate.getForObject(
new URI(azurePublicKeyUrl), String.class);
JsonWebKeySet publicKeySet = new JsonWebKeySet(publicKeySetJson);
JsonWebSignature signature = new JsonWebSignature();
signature.setAlgorithmConstraints(new AlgorithmConstraints(
AlgorithmConstraints.ConstraintType.WHITELIST,
AlgorithmIdentifiers.RSA_USING_SHA256));
signature.setCompactSerialization(authorizationToken);
VerificationJwkSelector publicKeySelector = new VerificationJwkSelector();
JsonWebKey jsonWebKey = publicKeySelector.select(
signature, publicKeySet.getJsonWebKeys());
signature.setKey(jsonWebKey.getKey());
if(!signature.verifySignature()) { // <<== ALWAYS FAILS VERIFICATION :(
throw new RuntimeException("JSON Web Token Signature Invalid");
}
} catch (URISyntaxException e) {
LOG.error("Invalid URL \"" + azurePublicKeyUrl + "\"", e);
} catch (JoseException e) {
LOG.error("An error occurred when validating the JWT signature", e);
throw new RuntimeException(e);
}
try {
filterChain.doFilter(request, response);
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
}
}
}
Вот как выглядят метаданные декодированных токенов Base64:
{"typ":"JWT","nonce":"AQABAAAAAADX8GCi6Js6SK82TsD2Pb7r9xWDjnazKO0nBJFdLLawrH4SsyXGtZpR4VSgvoX7ADMIjFSLUAOhd_xJnYCQw85rt3-pFp1UoMW8B9zL3Mjp6SAA","alg":"RS256","x5t":"iBjL1Rcqzhiy4fpxIxdZqohM2Yk","kid":"iBjL1Rcqzhiy4fpxIxdZqohM2Yk"}
Вот как выглядит тело токенов, декодированных Base64:
{"aud":"https://graph.microsoft.com","iss":"https://sts.windows.net/.../","iat":12345,"nbf":12345,"exp":1528136094,"acr":"1","aio":"Y2dg.../33rf...","amr":["pwd"],"app_displayname":"localhost","appid":"...","appidacr":"1","family_name":"Snoop","given_name":"Dougg","ipaddr":"...","name":"Snoop, Dougg - Dougg","oid":"...","onprem_sid":"...","platf":"3","puid":"...","scp":"User.Read","signin_state":["inknownntwk","kmsi"],"sub":"...","tid":"...","unique_name":"...","upn":"...","uti":"...","ver":"1.0"}
Теперь я видел некоторые упоминания о том, как nonce
может отбросить проверку ( ссылка ), но я не понимаю, как это могло произойти.Я имею в виду, что первые 2 части JWT подписаны, почему свойства, которые они содержат, влияют на действительность подписи?Разве Microsoft не подписывает токен, а затем добавляет одноразовый номер после того, как подпись уже вычислена?