Spring Security 5 - Login do OAuth2

Spring Security 5 - Login do OAuth2

1. Visão geral

Spring Security 5 apresenta uma nova classeOAuth2LoginConfigurer que podemos usar para configurar um servidor de autorização externo.

Neste artigo,we’ll explore some of the various configuration options available for the oauth2Login() element.

2. Dependências do Maven

Além das dependências padrão do Spring e Spring Security, também precisaremos adicionar as dependênciasspring-security-oauth2-clientespring-security-oauth2-jose:


    org.springframework.security
    spring-security-oauth2-client


   org.springframework.security
   spring-security-oauth2-jose

Em nosso exemplo, as dependências são gerenciadas pelo pai inicial do Spring Boot, versão2.x,, que corresponde à versão5.x dos artefatos Spring Security.

3. Configuração de clientes

Em um projeto Spring Boot, tudo o que precisamos fazer é adicionar algumas propriedades padrão para cada cliente que desejamos configurar.

Vamos configurar nosso projeto para login com clientes registrados no Google e no Facebook como provedores de autenticação.

3.1. Obtenção de credenciais de cliente

Para obter as credenciais do cliente para autenticação OAuth2 do Google, vá paraGoogle API Console - seção “Credenciais”.

Aqui, criaremos credenciais do tipo “OAuth2 Client ID” para nosso aplicativo da web. Isso resulta no Google configurando um ID de cliente e um segredo para nós.

Também precisamos configurar um URI de redirecionamento autorizado no Google Console, que é o caminho para o qual os usuários serão redirecionados após fazer login com sucesso no Google.

Por padrão, Spring Boot configura este URI de redirecionamento como/login/oauth2/code/{registrationId}.. Portanto, para o Google, adicionaremos o URI:

http://localhost:8081/login/oauth2/code/google

Para obter as credenciais do cliente para autenticação com o Facebook, precisamos registrar um aplicativo no siteFacebook for Developers e configurar o URI correspondente como um “URI de redirecionamento OAuth válido”:

http://localhost:8081/login/oauth2/code/facebook

3.3. Configuração de segurança

Em seguida, precisamos adicionar credenciais de cliente no arquivoapplication.properties. The Spring Security properties are prefixed with “spring.security.oauth2.client.registration” followed by the client name, then the name of the client property:

spring.security.oauth2.client.registration.google.client-id=
spring.security.oauth2.client.registration.google.client-secret=

spring.security.oauth2.client.registration.facebook.client-id=
spring.security.oauth2.client.registration.facebook.client-secret=

Adding these properties for at least one client will enable the Oauth2ClientAutoConfiguration class que configura todos os beans necessários.

A configuração automática de segurança da web é equivalente a definir um elementooauth2Login() simples:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
         .anyRequest().authenticated()
         .and()
         .oauth2Login();
    }
}

Aqui, podemos ver que o elementooauth2Login() é usado de maneira semelhante aos elementoshttpBasic()eformLogin() já conhecidos.

Agora, quando tentamos acessar um URL protegido, o aplicativo exibe uma página de login gerada automaticamente com dois clientes:

image

3.4. Outros Clientes

Note that in addition to Google and Facebook, the Spring Security project also contains default configurations for GitHub and Okta. Essas configurações padrão fornecem todas as informações necessárias para autenticação, que é o que nos permite inserir apenas as credenciais do cliente.

Se quisermos usar um provedor de autenticação diferente não configurado no Spring Security, precisaremos definir a configuração completa, com informações como URI de autorização e URI de token. Here dá uma olhada nas configurações padrão no Spring Security para ter uma ideia das propriedades necessárias.

4. Configuração em um projeto sem inicialização

4.1. Criando um BeanClientRegistrationRepository

If we’re not working with a Spring Boot application, we’ll need to define a ClientRegistrationRepository bean que contém uma representação interna das informações do cliente pertencentes ao servidor de autorização:

@Configuration
@EnableWebSecurity
@PropertySource("classpath:application.properties")
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    private static List clients = Arrays.asList("google", "facebook");

    @Bean
    public ClientRegistrationRepository clientRegistrationRepository() {
        List registrations = clients.stream()
          .map(c -> getRegistration(c))
          .filter(registration -> registration != null)
          .collect(Collectors.toList());

        return new InMemoryClientRegistrationRepository(registrations);
    }
}

Aqui estamos criando umInMemoryClientRegistrationRepository com uma lista de objetosClientRegistration.

4.2. Construindo ObjetosClientRegistration

Vamos ver o métodogetRegistration() que constrói esses objetos:

private static String CLIENT_PROPERTY_KEY
  = "spring.security.oauth2.client.registration.";

@Autowired
private Environment env;

private ClientRegistration getRegistration(String client) {
    String clientId = env.getProperty(
      CLIENT_PROPERTY_KEY + client + ".client-id");

    if (clientId == null) {
        return null;
    }

    String clientSecret = env.getProperty(
      CLIENT_PROPERTY_KEY + client + ".client-secret");

    if (client.equals("google")) {
        return CommonOAuth2Provider.GOOGLE.getBuilder(client)
          .clientId(clientId).clientSecret(clientSecret).build();
    }
    if (client.equals("facebook")) {
        return CommonOAuth2Provider.FACEBOOK.getBuilder(client)
          .clientId(clientId).clientSecret(clientSecret).build();
    }
    return null;
}

Aqui, estamos lendo as credenciais do cliente de um arquivoapplication.properties semelhante e, em seguida, usando o enumCommonOauth2Provider já definido no Spring Security para o restante das propriedades do cliente para clientes Google e Facebook.

Cada instânciaClientRegistration corresponde a um cliente.

4.3. Registrando oClientRegistrationRepository

Finalmente, temos que criar um beanOAuth2AuthorizedClientService com base no beanClientRegistrationRepository e registrar ambos com o elementooauth2Login():

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests().anyRequest().authenticated()
      .and()
      .oauth2Login()
      .clientRegistrationRepository(clientRegistrationRepository())
      .authorizedClientService(authorizedClientService());
}

@Bean
public OAuth2AuthorizedClientService authorizedClientService() {

    return new InMemoryOAuth2AuthorizedClientService(
      clientRegistrationRepository());
}

Como evidenciado aqui,we can use the clientRegistrationRepository() method of oauth2Login() to register a custom registration repository.

Também teremos que definir uma página de login personalizada, pois ela não será mais gerada automaticamente. Veremos mais informações sobre isso na próxima seção.

Vamos continuar com a personalização de nosso processo de login.

5. Personalizandooauth2Login()

Existem vários elementos que o processo OAuth 2 usa e que podemos personalizar usando os métodosoauth2Login().

Observe que todos esses elementos têm configurações padrão no Spring Boot e a configuração explícita não é necessária.

Vamos ver como podemos personalizar isso em nossa configuração.

5.1. Página de login personalizada

Mesmo que o Spring Boot gere uma página de login padrão para nós, geralmente queremos definir nossa própria página personalizada.

Vamos começar configurando um novo URL de login para o elementooauth2Login() usando o MétodologinPage():

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
      .antMatchers("/oauth_login")
      .permitAll()
      .anyRequest()
      .authenticated()
      .and()
      .oauth2Login()
      .loginPage("/oauth_login");
}

Aqui, configuramos nosso URL de login como/oauth_login.

A seguir, vamos definir umLoginController com um método que mapeia para este URL:

@Controller
public class LoginController {

    private static String authorizationRequestBaseUri
      = "oauth2/authorization";
    Map oauth2AuthenticationUrls
      = new HashMap<>();

    @Autowired
    private ClientRegistrationRepository clientRegistrationRepository;

    @GetMapping("/oauth_login")
    public String getLoginPage(Model model) {
        // ...

        return "oauth_login";
    }
}

This method has to send a map of the clients available and their authorization endpoints to the view, que obteremos do beanClientRegistrationRepository:

public String getLoginPage(Model model) {
    Iterable clientRegistrations = null;
    ResolvableType type = ResolvableType.forInstance(clientRegistrationRepository)
      .as(Iterable.class);
    if (type != ResolvableType.NONE &&
      ClientRegistration.class.isAssignableFrom(type.resolveGenerics()[0])) {
        clientRegistrations = (Iterable) clientRegistrationRepository;
    }

    clientRegistrations.forEach(registration ->
      oauth2AuthenticationUrls.put(registration.getClientName(),
      authorizationRequestBaseUri + "/" + registration.getRegistrationId()));
    model.addAttribute("urls", oauth2AuthenticationUrls);

    return "oauth_login";
}

Finalmente, precisamos definir nossa páginaoauth_login.html:

Login with:

Client

Esta é uma página HTML simples que exibe links para autenticação com cada cliente.

Depois de adicionar um pouco de estilo, podemos ter uma página de login muito melhor:

image

5.2. Comportamento de sucesso e falha de autenticação personalizada

Podemos controlar o comportamento pós-autenticação usando diferentes métodos:

  • defaultSuccessUrl() efailureUrl() - para redirecionar o usuário para um determinado URL

  • successHandler()efailureHandler() - para executar a lógica personalizada após o processo de autenticação

Vamos ver como podemos definir URLs personalizados para redirecionar o usuário para:

.oauth2Login()
  .defaultSuccessUrl("/loginSuccess")
  .failureUrl("/loginFailure");

Se o usuário visitou uma página segura antes de autenticar, ele será redirecionado para essa página após o login; caso contrário, eles serão redirecionados para/loginSuccess.

Se quisermos que o usuário seja sempre enviado para a URL/loginSuccess, independentemente se ele estava em uma página segura antes ou não, podemos usar o métododefaultSuccessUrl(“/loginSuccess”, true).

Para usar um manipulador personalizado, teríamos que criar uma classe que implemente as interfacesAuthenticationSuccessHandler ouAuthenticationFailureHandler, sobrescrever os métodos herdados e, em seguida, definir os beans usando os métodossuccessHandler()e failureHandler () .

5.3. Endpoint de autorização personalizada

O ponto de extremidade de autorização é o ponto de extremidade que o Spring Security usa para acionar uma solicitação de autorização para o servidor externo.

Primeiro,let’s set new properties for the authorization endpoint:

.oauth2Login()
  .authorizationEndpoint()
  .baseUri("/oauth2/authorize-client")
  .authorizationRequestRepository(authorizationRequestRepository());

Aqui, modificamosbaseUri para/oauth2/authorize-client em vez do/oauth2/authorization. padrão. Também estamos definindo explicitamente um beanauthorizationRequestRepository() que temos que definir:

@Bean
public AuthorizationRequestRepository
  authorizationRequestRepository() {

    return new HttpSessionOAuth2AuthorizationRequestRepository();
}

Em nosso exemplo, usamos a implementação fornecida pelo Spring para nosso bean, mas também podemos fornecer uma personalizada.

5.4. Terminal de token personalizado

O tokenendpoint processa tokens de acesso.

Let’s explicitly configure the tokenEndpoint() com a implementação do cliente de resposta padrão:

.oauth2Login()
  .tokenEndpoint()
  .accessTokenResponseClient(accessTokenResponseClient());

E aqui está o bean de cliente de resposta:

@Bean
public OAuth2AccessTokenResponseClient
  accessTokenResponseClient() {

    return new NimbusAuthorizationCodeTokenResponseClient();
}

Essa configuração é a mesma que a padrão e está usando a implementação Spring, que é baseada na troca de um código de autorização com o provedor.

Obviamente, também poderíamos substituir um cliente de resposta personalizado.

5.5. Endpoint de redirecionamento personalizado

Este é o ponto de extremidade para o qual redirecionar após a autenticação com o provedor externo.

Vamos ver como podemos alterar obaseUri para o endpoint de redirecionamento:

.oauth2Login()
  .redirectionEndpoint()
  .baseUri("/oauth2/redirect")

O URI padrão élogin/oauth2/code.

Observe que, se mudarmos, também temos que atualizar a propriedaderedirectUriTemplate de cadaClientRegistratione adicionar o novo URI como um URI de redirecionamento autorizado para cada cliente.

5.6. Endpoint de informações do usuário personalizado

O endpoint de informações do usuário é o local que podemos aproveitar para obter informações do usuário.

We can customize this endpoint using the userInfoEndpoint() method. Para isso, podemos usar métodos comouserService()ecustomUserType() para modificar a forma como as informações do usuário são recuperadas.

6. Acesso às informações do usuário

Uma tarefa comum que desejamos realizar é encontrar informações sobre o usuário conectado. Para isso,we can make a request to the user information endpoint.

Primeiro, teremos que obter o cliente correspondente ao token de usuário atual:

@Autowired
private OAuth2AuthorizedClientService authorizedClientService;

@GetMapping("/loginSuccess")
public String getLoginInfo(Model model, OAuth2AuthenticationToken authentication) {
    OAuth2AuthorizedClient client = authorizedClientService
      .loadAuthorizedClient(
        authentication.getAuthorizedClientRegistrationId(),
          authentication.getName());
    //...
    return "loginSuccess";
}

A seguir, enviaremos uma solicitação ao endpoint de informações do usuário do cliente e recuperaremos ouserAttributes Map:

String userInfoEndpointUri = client.getClientRegistration()
  .getProviderDetails().getUserInfoEndpoint().getUri();

if (!StringUtils.isEmpty(userInfoEndpointUri)) {
    RestTemplate restTemplate = new RestTemplate();
    HttpHeaders headers = new HttpHeaders();
    headers.add(HttpHeaders.AUTHORIZATION, "Bearer " + client.getAccessToken()
      .getTokenValue());
    HttpEntity entity = new HttpEntity("", headers);
    ResponseEntity response = restTemplate
      .exchange(userInfoEndpointUri, HttpMethod.GET, entity, Map.class);
    Map userAttributes = response.getBody();
    model.addAttribute("name", userAttributes.get("name"));
}

Ao adicionar a propriedadename como um atributoModel, podemos exibi-la na visualizaçãologinSuccess como uma mensagem de boas-vindas ao usuário:

image

Além dename,,userAttributes Map também contém propriedades comoemail, family_name,picture, locale.

7. Conclusão

Neste artigo, vimos como podemos usar o elementooauth2Login() no Spring Security para autenticar com diferentes provedores, como Google e Facebook. Também analisamos alguns cenários comuns de personalização desse processo.

O código-fonte completo dos exemplos pode ser encontradoover on GitHub.