У меня возникают проблемы с проверкой токена доступа, который я получаю из Azure, в моем веб-приложении Java. jose4j библиотеки signature.verifySignature() просто возвращает false. Может ли кто-нибудь помочь мне понять, что я делаю неправильно?
Я успешно настроил интерфейсное веб-приложение для входа в систему через Azure Active Directory, после чего оно получает токен доступа в виде подписанного веб-токена Json. Я намерен прикрепить этот JWT в заголовок "Authoriation": "token <signed-access-token>" во всех запросах к другим моим веб-приложениям, работающим с базой данных. Я хотел бы создать Spring Filter для проверки подписи JWT для каждого из этих веб-приложений, работающих с базой данных, чтобы убедиться, что токен действительно был выпущен Azure.
У меня есть код моего небольшого доказательства концепции в этой ветке репозитория github: репо. Но вот мой фильтр:
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 может нарушить проверку (ссылка на сайт), но я не понимаю, как это могло произойти. Я имею в виду, что первые две части JWT подписаны, почему свойства, которые они содержат, влияют на действительность подписи? Если Microsoft не подписывает токен, а затем добавляет одноразовый номер после того, как подпись уже вычислена?
Вы когда-нибудь понимали это? Я испытываю то же самое; если я установлю для ресурса идентификатор приложения, я смогу проверить токен. Но если я установлю ресурс на graph.microsoft.com, я не смогу проверить токен.
Извините, я давно с этим не связывался, но что вы имеете в виду под the resource?
В AD v1.0 вы передаете ресурс в параметрах для получения токена доступа; ресурс эквивалентен aud (аудитории).
О, интересно, думаю, я не использовал v1.0, похоже, я использую v2.0, и токен доступа, который я получаю, когда включаю свой идентификатор приложения, - это тот, который я не могу проверить. В версии 1.0 вы можете проверить токен доступа для конкретного приложения? POST-запрос, который я отправляю для токена доступа, направлен на https://login.microsoftonline.com/<my organization>/oauth2/v2.0/token с переменными application / x-www-form-urlencoded для grant_type=authorization_code, client_id=<App ID>, code=<Authorization code>, redirect_url=https://<app host name> и client_secret=<App secret> ...




Поскольку я не могу проверить подпись токена, я отправляю его в api графика Microsoft в качестве токена-носителя, чтобы проверить его (graph.microsoft.com/v1.0/me) ...