Anpassen von Autorisierungs- und Tokenanforderungen mit Spring Security 5.1-Client

Anpassen von Autorisierungs- und Tokenanforderungen mit Spring Security 5.1 Client

1. Überblick

Manchmal weichen OAuth2-APIs etwas vom Standard ab. In diesem Fall müssen wir einige Anpassungen an den Standard-OAuth2-Anforderungen vornehmen.

Spring Security 5.1 bietet Unterstützung für das Anpassen von OAuth2-Autorisierungs- und Tokenanforderungen.

In diesem Lernprogramm erfahren Sie, wie Sie Anforderungsparameter und die Antwortbehandlung anpassen.

2. Benutzerdefinierte Autorisierungsanforderung

Zunächst passen wir die OAuth2-Autorisierungsanforderung an. Wir können Standardparameter ändern und der Autorisierungsanforderung nach Bedarf zusätzliche Parameter hinzufügen.

Dazuwe need to implement our own OAuth2AuthorizationRequestResolver:

public class CustomAuthorizationRequestResolver
  implements OAuth2AuthorizationRequestResolver {

    private OAuth2AuthorizationRequestResolver defaultResolver;

    public CustomAuthorizationRequestResolver(
      ClientRegistrationRepository repo, String authorizationRequestBaseUri) {
        defaultResolver = new DefaultOAuth2AuthorizationRequestResolver(repo, authorizationRequestBaseUri);
    }

    // ...
}

Beachten Sie, dass wirDefaultOAuth2AuthorizationRequestResolver verwendet haben, um die Basisfunktionalität bereitzustellen.

Wir werden auch dieresolve()-Methoden überschreiben, um unsere Anpassungslogik hinzuzufügen:

public class CustomAuthorizationRequestResolver
  implements OAuth2AuthorizationRequestResolver {

    //...

    @Override
    public OAuth2AuthorizationRequest resolve(HttpServletRequest request) {
        OAuth2AuthorizationRequest req = defaultResolver.resolve(request);
        if(req != null) {
            req = customizeAuthorizationRequest(req);
        }
        return req;
    }

    @Override
    public OAuth2AuthorizationRequest resolve(HttpServletRequest request, String clientRegistrationId) {
        OAuth2AuthorizationRequest req = defaultResolver.resolve(request, clientRegistrationId);
        if(req != null) {
            req = customizeAuthorizationRequest(req);
        }
        return req;
    }

    private OAuth2AuthorizationRequest customizeAuthorizationRequest(
      OAuth2AuthorizationRequest req) {
        // ...
    }

}

Wir werden unsere Anpassungen später mithilfe der MethodecustomizeAuthorizationRequest()hinzufügen, wie in den nächsten Abschnitten erläutert.

Nach der Implementierung unserer benutzerdefiniertenOAuth2AuthorizationRequestResolver müssen wir sie unserer Sicherheitskonfiguration hinzufügen:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.oauth2Login()
          .authorizationEndpoint()
          .authorizationRequestResolver(
            new CustomAuthorizationRequestResolver(
              clientRegistrationRepository(), "/oauth2/authorize-client"))
        //...
    }
}

Hier haben wiroauth2Login().authorizationEndpoint().authorizationRequestResolver() verwendet, um unsere benutzerdefiniertenOAuth2AuthorizationRequestResolver. zu injizieren

3. Anpassen der Standardparameter für Autorisierungsanforderungen

Lassen Sie uns nun die eigentliche Anpassung diskutieren. Wir könnenOAuth2AuthorizationRequest so oft ändern, wie wir wollen.

Für den Anfangwe can modify a standard parameter for each authorization request.

Wir können zum Beispiel unseren eigenen“state”-Parameter generieren:

private OAuth2AuthorizationRequest customizeAuthorizationRequest(
  OAuth2AuthorizationRequest req) {
    return OAuth2AuthorizationRequest
      .from(req).state("xyz").build();
}

4. Zusätzliche Parameter für Autorisierungsanforderung

We can also add extra parameters to our OAuth2AuthorizationRequest unter Verwendung deradditionalParameters()-Methode vonOAuth2AuthorizationRequest und Übergabe vonMap:

private OAuth2AuthorizationRequest customizeAuthorizationRequest(
  OAuth2AuthorizationRequest req) {
    Map extraParams = new HashMap();
    extraParams.putAll(req.getAdditionalParameters());
    extraParams.put("test", "extra");

    return OAuth2AuthorizationRequest
      .from(req)
      .additionalParameters(extraParams)
      .build();
}

Wir müssen auch sicherstellen, dass wir die altenadditionalParameters einschließen, bevor wir unsere neuen hinzufügen.

Sehen wir uns ein praktischeres Beispiel an, indem wir die mit dem Okta Authorization Server verwendete Autorisierungsanforderung anpassen.

4.1. Benutzerdefinierte Okta-Autorisierungsanforderung

Okta verfügt über zusätzliche optionale Parameter für die Autorisierungsanforderung, um dem Benutzer mehr Funktionen bereitzustellen. Zum Beispielidp, das den Identitätsanbieter angibt.

Der Identitätsanbieter ist standardmäßig Okta, aber wir können ihn mit dem Parameteridpanpassen:

private OAuth2AuthorizationRequest customizeOktaReq(OAuth2AuthorizationRequest req) {
    Map extraParams = new HashMap();
    extraParams.putAll(req.getAdditionalParameters());
    extraParams.put("idp", "https://idprovider.com");
    return OAuth2AuthorizationRequest
      .from(req)
      .additionalParameters(extraParams)
      .build();
}

5. Benutzerdefinierte Token-Anforderung

Jetzt erfahren Sie, wie Sie die OAuth2-Tokenanforderung anpassen.

Wir können die Token-Anforderung anpassen, indem wirOAuth2AccessTokenResponseClient anpassen.

Die Standardimplementierung fürOAuth2AccessTokenResponseClient istDefaultAuthorizationCodeTokenResponseClient.

Wir können die Tokenanforderung selbst anpassen, indem wir ein benutzerdefiniertesRequestEntityConverter bereitstellen, und wir können sogar die Behandlung der Tokenantwort anpassen, indem wirDefaultAuthorizationCodeTokenResponseClientRestOperations anpassen:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.tokenEndpoint()
          .accessTokenResponseClient(accessTokenResponseClient())
            //...
    }

    @Bean
    public OAuth2AccessTokenResponseClient accessTokenResponseClient(){
        DefaultAuthorizationCodeTokenResponseClient accessTokenResponseClient =
          new DefaultAuthorizationCodeTokenResponseClient();
        accessTokenResponseClient.setRequestEntityConverter(new CustomRequestEntityConverter());

        OAuth2AccessTokenResponseHttpMessageConverter tokenResponseHttpMessageConverter =
          new OAuth2AccessTokenResponseHttpMessageConverter();
        tokenResponseHttpMessageConverter.setTokenResponseConverter(new CustomTokenResponseConverter());
        RestTemplate restTemplate = new RestTemplate(Arrays.asList(
          new FormHttpMessageConverter(), tokenResponseHttpMessageConverter));
        restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());

        accessTokenResponseClient.setRestOperations(restTemplate);
        return accessTokenResponseClient;
    }
}

Wir können unsere eigenenOAuth2AccessTokenResponseClient usingtokenEndpoint().accessTokenResponseClient(). injizieren

Um die Token-Anforderungsparameter anzupassen, implementieren wirCustomRequestEntityConverter.. Um die Behandlung der Token-Antwort anzupassen, implementieren wirCustomTokenResponseConverter.

In den folgenden Abschnitten werden sowohlCustomRequestEntityConverter als auchCustomTokenResponseConverter erläutert.

6. Zusätzliche Parameter für Token-Anforderung

Jetzt erfahren Sie, wie Sie unserer Token-Anforderung zusätzliche Parameter hinzufügen, indem Sie ein benutzerdefiniertesConverter erstellen:

public class CustomRequestEntityConverter implements
  Converter> {

    private OAuth2AuthorizationCodeGrantRequestEntityConverter defaultConverter;

    public CustomRequestEntityConverter() {
        defaultConverter = new OAuth2AuthorizationCodeGrantRequestEntityConverter();
    }

    @Override
    public RequestEntity convert(OAuth2AuthorizationCodeGrantRequest req) {
        RequestEntity entity = defaultConverter.convert(req);
        MultiValueMap params = (MultiValueMap) entity.getBody();
        params.add("test2", "extra2");
        return new RequestEntity<>(params, entity.getHeaders(),
          entity.getMethod(), entity.getUrl());
    }

}

UnsereConverter transformierenOAuth2AuthorizationCodeGrantRequest inRequestEntity. 

Wir haben den StandardkonverterOAuth2AuthorizationCodeGrantRequestEntityConverter verwendet, um die Basisfunktionalität bereitzustellen, und dem Körper vonRequestEntityzusätzliche Parameter hinzugefügt.

7. Benutzerdefinierte Token-Antwortbehandlung

Jetzt passen wir die Behandlung der Token-Antwort an.

Wir können den Standard-Token-Response-KonverterOAuth2AccessTokenResponseHttpMessageConverter als Ausgangspunkt verwenden.

Wir werdenCustomTokenResponseConverter implementieren, um den Parameter“scope” anders zu behandeln:

public class CustomTokenResponseConverter implements
  Converter, OAuth2AccessTokenResponse> {
    private static final Set TOKEN_RESPONSE_PARAMETER_NAMES = Stream.of(
        OAuth2ParameterNames.ACCESS_TOKEN,
        OAuth2ParameterNames.TOKEN_TYPE,
        OAuth2ParameterNames.EXPIRES_IN,
        OAuth2ParameterNames.REFRESH_TOKEN,
        OAuth2ParameterNames.SCOPE).collect(Collectors.toSet());

    @Override
    public OAuth2AccessTokenResponse convert(Map tokenResponseParameters) {
        String accessToken = tokenResponseParameters.get(OAuth2ParameterNames.ACCESS_TOKEN);

        Set scopes = Collections.emptySet();
        if (tokenResponseParameters.containsKey(OAuth2ParameterNames.SCOPE)) {
            String scope = tokenResponseParameters.get(OAuth2ParameterNames.SCOPE);
            scopes = Arrays.stream(StringUtils.delimitedListToStringArray(scope, ","))
                .collect(Collectors.toSet());
        }

        //...
        return OAuth2AccessTokenResponse.withToken(accessToken)
          .tokenType(accessTokenType)
          .expiresIn(expiresIn)
          .scopes(scopes)
          .refreshToken(refreshToken)
          .additionalParameters(additionalParameters)
          .build();
    }

}

Der Token-Antwort-Konverter transformiertMap inOAuth2AccessTokenResponse.

In diesem Beispiel haben wir den Parameter“scope”als durch Kommas getrennte statt durch Leerzeichen getrennteString. analysiert

Lassen Sie uns ein weiteres praktisches Beispiel durchgehen, indem Sie die Token-Antwort mithilfe von LinkedIn als Autorisierungsserver anpassen.

7.1. LinkedIn Token Response Handling

Lassen Sie uns abschließend sehen, wie die Token-Antwort vonLinkedInbehandelt wird. Dies enthält nuraccess_token undexpires_in,, aber wir brauchen auchtoken_type.

Wir können einfach unseren eigenen Token-Response-Konverter implementieren undtoken_type manuell einstellen:

public class LinkedinTokenResponseConverter
  implements Converter, OAuth2AccessTokenResponse> {

    @Override
    public OAuth2AccessTokenResponse convert(Map tokenResponseParameters) {
        String accessToken = tokenResponseParameters.get(OAuth2ParameterNames.ACCESS_TOKEN);
        long expiresIn = Long.valueOf(tokenResponseParameters.get(OAuth2ParameterNames.EXPIRES_IN));

        OAuth2AccessToken.TokenType accessTokenType = OAuth2AccessToken.TokenType.BEARER;

        return OAuth2AccessTokenResponse.withToken(accessToken)
          .tokenType(accessTokenType)
          .expiresIn(expiresIn)
          .build();
    }
}

8. Fazit

In diesem Artikel haben wir gelernt, wie Sie OAuth2-Autorisierungs- und Tokenanforderungen anpassen, indem Sie Anforderungsparameter hinzufügen oder ändern.

Der vollständige Quellcode für die Beispiele istover on GitHub verfügbar.