Аутентификация токена AAD в RestApi Spring boot 3

Я хотел бы разрешить доступ к ресурсам в моем приложении RestApi Spring Boot 3 с помощью токена носителя, полученного от Azure AD.

ПОМ:

          <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-oauth2-client</artifactId>
    </dependency>
    <dependency>
        <groupId>com.azure.spring</groupId>
        <artifactId>spring-cloud-azure-starter-active-directory</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>

Я создал приложение на портале Azure (согласно некоторым руководствам по Azure) и могу получить токен доступа через почтальона.

Это мои свойства весенней загрузки:

Когда я пытаюсь получить доступ к ресурсу через почтальона, включая полученный токен доступа, я получаю информацию для входа:

Нужна ли мне дополнительная конфигурация безопасности Spring, которая позволит мне получить доступ к этому ресурсу, или есть какая-то другая проблема?

Я пробовал со следующими, но без каких-либо изменений.

Я перехожу с SpringBoot 2.1.3 на 3.0.7. Используя старое решение, я смог правильно авторизоваться через токен доступа:

      public class AuzreAuthFilter extends OncePerRequestFilter {
private static final Logger log = LoggerFactory.getLogger(AADAuthenticationFilter.class);

private static final String CURRENT_USER_PRINCIPAL = "CURRENT_USER_PRINCIPAL";
private static final String CURRENT_USER_PRINCIPAL_GRAPHAPI_TOKEN = "CURRENT_USER_PRINCIPAL_GRAPHAPI_TOKEN";

private static final String TOKEN_HEADER = "Authorization";
private static final String TOKEN_TYPE = "Bearer ";

private AADAuthenticationProperties aadAuthProps;
private ServiceEndpointsProperties serviceEndpointsProps;
private AzureUserPrincipalManager principalManager;

public AuzreAuthFilter(AADAuthenticationProperties aadAuthProps,
                       ServiceEndpointsProperties serviceEndpointsProps,
                       ResourceRetriever resourceRetriever) {
    this.aadAuthProps = aadAuthProps;
    this.serviceEndpointsProps = serviceEndpointsProps;
    this.principalManager = new AzureUserPrincipalManager(serviceEndpointsProps, aadAuthProps, resourceRetriever);
}

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
                                FilterChain filterChain) throws ServletException, IOException {

    final String authHeader = request.getHeader(TOKEN_HEADER);

    if (authHeader != null && authHeader.startsWith(TOKEN_TYPE)) {
        try {
            final String idToken = authHeader.replace(TOKEN_TYPE, "");
            UserPrincipal principal = (UserPrincipal) request
                    .getSession().getAttribute(CURRENT_USER_PRINCIPAL);

            final ClientCredential credential =
                    new ClientCredential(aadAuthProps.getClientId(), aadAuthProps.getClientSecret());

            final AzureADGraphClient client =
                    new AzureADGraphClient(credential, aadAuthProps, serviceEndpointsProps);

            if (principal == null) {
                principal = principalManager.buildUserPrincipal(idToken);
                request.getSession().setAttribute(CURRENT_USER_PRINCIPAL, principal);
                request.getSession().setAttribute(CURRENT_USER_PRINCIPAL_GRAPHAPI_TOKEN, idToken);
            }

            final Authentication authentication = new PreAuthenticatedAuthenticationToken(
                    principal, null, client.convertGroupsToGrantedAuthorities(principal.getUserGroups()));

            authentication.setAuthenticated(true);
            log.info("Request token verification success. {}", authentication);
            SecurityContextHolder.getContext().setAuthentication(authentication);
        } catch (ParseException | BadJOSEException | JOSEException ex) {
            log.error("Failed to initialize UserPrincipal.", ex);
            throw new ServletException(ex);
        }
    }

    filterChain.doFilter(request, response);
}

}

      public class AzureUserPrincipalManager {
private final JWKSource<SecurityContext> keySource;

/**
 * Creates a new {@link UserPrincipalManager} with a predefined {@link JWKSource}.
 * <p>
 * This is helpful in cases the JWK is not a remote JWKSet or for unit testing.
 *
 * @param keySource - {@link JWKSource} containing at least one key
 */
public AzureUserPrincipalManager(JWKSource<SecurityContext> keySource) {
    this.keySource = keySource;
}

/**
 * Create a new {@link UserPrincipalManager} based of the {@link ServiceEndpoints#getAadKeyDiscoveryUri()} and
 * {@link AADAuthenticationProperties#getEnvironment()}.
 *
 * @param serviceEndpointsProps -  used to retrieve the JWKS URL
 * @param aadAuthProps          - used to retrieve the environment.
 * @param resourceRetriever     - configures the {@link RemoteJWKSet} call.
 */
public AzureUserPrincipalManager(ServiceEndpointsProperties serviceEndpointsProps,
                                 AADAuthenticationProperties aadAuthProps,
                                 ResourceRetriever resourceRetriever) {
    try {
        keySource = new RemoteJWKSet<>(new URL(serviceEndpointsProps
                .getServiceEndpoints(aadAuthProps.getEnvironment()).getAadKeyDiscoveryUri()), resourceRetriever);
    } catch (MalformedURLException e) {
        log.error("Failed to parse active directory key discovery uri.", e);
        throw new IllegalStateException("Failed to parse active directory key discovery uri.", e);
    }
}

public UserPrincipal buildUserPrincipal(String idToken) throws ParseException, JOSEException, BadJOSEException {
    final JWSObject jwsObject = JWSObject.parse(idToken);
    final ConfigurableJWTProcessor<SecurityContext> validator =
            getAadJwtTokenValidator(jwsObject.getHeader().getAlgorithm());
    final JWTClaimsSet jwtClaimsSet = validator.process(idToken, null);
    final JWTClaimsSetVerifier<SecurityContext> verifier = validator.getJWTClaimsSetVerifier();
    verifier.verify(jwtClaimsSet, null);

    return new UserPrincipal(jwsObject, jwtClaimsSet);
}

private ConfigurableJWTProcessor<SecurityContext> getAadJwtTokenValidator(JWSAlgorithm jwsAlgorithm) {
    final ConfigurableJWTProcessor<SecurityContext> jwtProcessor = new DefaultJWTProcessor<>();

    final JWSKeySelector<SecurityContext> keySelector =
            new JWSVerificationKeySelector<>(jwsAlgorithm, keySource);
    jwtProcessor.setJWSKeySelector(keySelector);

    jwtProcessor.setJWTClaimsSetVerifier(new DefaultJWTClaimsVerifier<SecurityContext>() {
        @Override
        public void verify(JWTClaimsSet claimsSet, SecurityContext ctx) throws BadJWTException {
            super.verify(claimsSet, ctx);
            final String issuer = claimsSet.getIssuer();
            if (issuer == null || !issuer.contains("https://sts.windows.net/")
                    && !issuer.contains("https://login.microsoftonline.com/")) {
                throw new BadJWTException("Invalid token issuer");
            }
        }
    });
    return jwtProcessor;
}

}

2 ответа

Токены доступа могут авторизовать запросы к серверам ресурсов OAuth2 (не запросы к клиентам OAuth2, которые защищены сеансами) => вы должны зависеть отspring-boot-starter-oauth2-resource-serverвместоspring-boot-starter-oauth2-clientи имеютspring.security.oauth2.resource-serverсвойства вместоspring.security.oauth2.clientте.

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

В дополнение к настроенным вами зависимостям и свойствам вам также необходимо настроить Spring Security:

      @Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    return http
        .apply(AadResourceServerHttpSecurityConfigurer.aadResourceServer()).and()
        // TODO: Add the rest of your HttpSecurity configuration here ...
}
Другие вопросы по тегам