Lidar com segurança no Zuul, com OAuth2 e JWT

Lidar com segurança no Zuul, com OAuth2 e JWT

1. Introdução

Simplificando, uma arquitetura de microsserviço nos permite dividir nosso sistema e nossa API em um conjunto de serviços independentes, que podem ser implantados de forma totalmente independente.

Embora isso seja excelente do ponto de vista da implantação e gerenciamento contínuos, ele pode se tornar rapidamente complicado quando se trata de usabilidade da API. Com diferentes pontos de extremidade para gerenciar, os aplicativos dependentes precisarão gerenciar o CORS (compartilhamento de recursos de origem cruzada) e um conjunto diversificado de pontos de extremidade.

O Zuul é um serviço de ponta que permite rotear solicitações HTTP recebidas em vários microsserviços de back-end. Por um lado, isso é importante para fornecer uma API unificada para os consumidores de nossos recursos de back-end.

Basicamente, o Zuul nos permite unificar todos os nossos serviços, sentando-se na frente deles e agindo como um proxy. Ele recebe todos os pedidos e os encaminha para o serviço correto. Para um aplicativo externo, nossa API aparece como uma área de superfície unificada da API.

Neste tutorial, vamos falar sobre como podemos usá-lo para esse propósito exato, em conjunto com umOAuth 2.0 and JWTs, para ser a linha de frente para proteger nossos serviços da web. Especificamente, usaremos o fluxoPassword Grant para obter um token de acesso aos recursos protegidos.

Uma observação rápida, mas importante, é que estamos usando o fluxo de concessão de senha apenas para explorar um cenário simples; a maioria dos clientes provavelmente usará o fluxo de Concessão de autorização em cenários de produção.

2. Adicionando dependências do Zuul Maven

Vamos começar adicionando Zuul aour project. Fazemos isso adicionando o artefatospring-cloud-starter-netflix-zuul:


    org.springframework.cloud
    spring-cloud-starter-netflix-zuul
    2.0.2.RELEASE

3. Ativando o Zuul

O aplicativo que gostaríamos de rotear por meio do Zuul contém um servidor de autorização OAuth 2.0 que concede tokens de acesso e um servidor de recursos que os aceita. Esses serviços residem em dois pontos de extremidade separados.

Gostaríamos de ter um único endpoint para todos os clientes externos desses serviços, com diferentes caminhos que se ramificam para diferentes endpoints físicos. Para isso, apresentaremos o Zuul como um serviço de ponta.

Para fazer isso, vamos criar um novo aplicativo Spring Boot, chamadoGatewayApplication. Vamos então simplesmente decorar esta classe de aplicativo com a anotação@EnableZuulProxy, o que fará com que uma instância Zuul seja gerada:

@EnableZuulProxy
@SpringBootApplication
public class GatewayApplication {
    public static void main(String[] args) {
    SpringApplication.run(GatewayApplication.class, args);
    }
}

4. Configurando rotas Zuul

Antes de prosseguirmos, precisamos configurar algumas propriedades do Zuul. A primeira coisa que vamos configurar é a porta na qual Zuul está escutando as conexões de entrada. Isso precisa ir para o arquivo/src/main/resources/application.yml:

server:
    port: 8080

Agora, as coisas divertidas: configurar as rotas reais para as quais o Zuul encaminhará. Para fazer isso, precisamos observar os seguintes serviços, seus caminhos e as portas em que eles escutam.

O Authorization Server é implantado em: http://localhost:8081/spring-security-oauth-server/oauth

O Resource Server é implantado em: http://localhost:8082/spring-security-oauth-resource

O Authorization Server é um provedor de identidade OAuth. Existe para fornecer tokens de autorização ao Servidor de Recursos, que por sua vez fornece alguns pontos de extremidade protegidos.

O servidor de autorização fornece um token de acesso ao cliente, que depois usa o token para executar solicitações no servidor de recursos, em nome do proprietário do recurso. Uma rápida execução deOAuth terminology nos ajudará a manter esses conceitos em vista.

Agora vamos mapear algumas rotas para cada um desses serviços:

zuul:
  routes:
    spring-security-oauth-resource:
      path: /spring-security-oauth-resource/**
      url: http://localhost:8082/spring-security-oauth-resource
    oauth:
      path: /oauth/**
      url: http://localhost:8081/spring-security-oauth-server/oauth

Nesse ponto, qualquer solicitação que chegar a Zuul emlocalhost:8080/oauth/ será roteada para o serviço de autorização em execução na porta 8081. Qualquer solicitação paralocalhost:8080/spring-security-oauth-resource/ será roteada para o servidor de recursos em execução no 8082.

5. Protegendo caminhos de tráfego externo do Zuul

Mesmo que nosso serviço de borda Zuul agora esteja roteando as solicitações corretamente, ele está fazendo isso sem nenhuma verificação de autorização. O servidor de autorização atrás de/oauth/* cria um JWT para cada autenticação bem-sucedida. Naturalmente, é acessível anonimamente.

O Servidor de Recursos - localizado em/spring-security-oauth-resource/**, por outro lado, deve sempre ser acessado com um JWT para garantir que um Cliente autorizado esteja acessando os recursos protegidos.

Primeiro, vamos configurar o Zuul para passar pelo JWT para os serviços que ficam por trás dele. No nosso caso aqui, esses serviços precisam validar o token.

Fazemos isso adicionandosensitiveHeaders: Cookie,Set-Cookie.

Isso completa nossa configuração do Zuul:

server:
  port: 8080
zuul:
  sensitiveHeaders: Cookie,Set-Cookie
  routes:
    spring-security-oauth-resource:
      path: /spring-security-oauth-resource/**
      url: http://localhost:8082/spring-security-oauth-resource
    oauth:
      path: /oauth/**
      url: http://localhost:8081/spring-security-oauth-server/oauth

Depois de resolvermos isso, precisamos lidar com a autorização no limite. No momento, o Zuul não validará o JWT antes de repassá-lo aos nossos serviços de recebimento de dados. Esses serviços validarão o próprio JWT, mas o ideal é que o serviço de borda faça isso primeiro e rejeite todas as solicitações não autorizadas antes que se propaguem mais profundamente em nossa arquitetura.

Vamos configurar o Spring Security para garantir que a autorização seja verificada no Zuul.

Primeiro, precisaremos trazer as dependências do Spring Security para nosso projeto. Queremosspring-security-oauth2espring-security-jwt:


    org.springframework.security.oauth
    spring-security-oauth2
    2.3.3.RELEASE


    org.springframework.security
    spring-security-jwt
    1.0.9.RELEASE

Agora vamos escrever uma configuração para as rotas que queremos proteger estendendoResourceServerConfigurerAdapter:

@Configuration
@Configuration
@EnableResourceServer
public class GatewayConfiguration extends ResourceServerConfigurerAdapter {
    @Override
    public void configure(final HttpSecurity http) throws Exception {
    http.authorizeRequests()
          .antMatchers("/oauth/**")
          .permitAll()
          .antMatchers("/**")
      .authenticated();
    }
}

A classeGatewayConfiguration define como o Spring Security deve lidar com solicitações HTTP de entrada por meio do Zuul. Dentro do métodoconfigure, primeiro combinamos o caminho mais restritivo usandoantMatcherse permitimos o acesso anônimo porpermitAll.

Ou seja, todas as solicitações que chegam em/oauth/** devem ter permissão para passar sem a verificação de tokens de autorização. Isso faz sentido porque esse é o caminho a partir do qual os tokens de autorização são gerados.

Em seguida, combinamosall other paths with /** e, por meio de uma chamada paraauthenticated, insistimos que todas as outras chamadas deveriam conter tokens de acesso.

6. Configurando a chave usada para validação de JWT

Agora que a configuração está estabelecida, todas as solicitações roteadas para o caminho/oauth/** serão permitidas de forma anônima, enquanto todas as outras solicitações exigirão autenticação.

No entanto, há uma coisa que está faltando aqui: o segredo real necessário para verificar se o JWT é válido. Para fazer isso, precisamos fornecer a chave (que é simétrica neste caso) usada para assinar o JWT. Em vez de escrever o código de configuração manualmente, podemos usarspring-security-oauth2-autoconfigure.

Vamos começar adicionando o artefato ao nosso projeto:


    org.springframework.security.oauth.boot
    spring-security-oauth2-autoconfigure
    2.1.2.RELEASE

Em seguida, precisamos adicionar algumas linhas de configuração ao nosso arquivoapplication.yaml para definir a chave usada para assinar o JWT:

security:
  oauth2:
    resource:
      jwt:
        key-value: 123

A linhakey-value: 123 define a chave simétrica usada pelo Authorization Server para assinar o JWT. Esta chave será usada porspring-security-oauth2-autoconfigure para configurar a análise de token.

É importante notar que, em um sistema de produção,we shouldn’t use a symmetric key, specified in the source code of the application. That naturalmente precisa ser configurado externamente.

7. Testando o serviço de borda

7.1. Obtendo um token de acesso

Agora vamos testar como nosso serviço de borda Zuul se comporta - com alguns comandos curl.

Primeiro, veremos como podemos obter um novo JWT do servidor de autorização, usando opassword grant.

Aqui trocamos umusername and password in for an Access Token. Neste caso, usamos ‘john’ como nome de usuário e ‘123’ como senha:

curl -X POST \
  http://localhost:8080/oauth/token \
  -H 'Authorization: Basic Zm9vQ2xpZW50SWRQYXNzd29yZDpzZWNyZXQ=' \
  -H 'Cache-Control: no-cache' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'grant_type=password&password=123&username=john'

Essa chamada gera um token JWT que podemos usar para solicitações autenticadas em nosso Servidor de Recursos.

Observe o campo de cabeçalho“Authorization: Basic…”. Isso existe para informar ao servidor de autorização qual cliente está se conectando a ele.

É para o cliente (neste caso, a solicitação cURL) qual nome de usuário e senha são para o usuário:

{
    "access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpX...",
    "token_type":"bearer",
    "refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpX...",
    "expires_in":3599,
    "scope":"foo read write",
    "organization":"johnwKfc",
    "jti":"8e2c56d3-3e2e-4140-b120-832783b7374b"
}

7.2. Testando uma solicitação do servidor de recursos

Em seguida, podemos usar o JWT recuperado do servidor de autorização para agora executar uma consulta no servidor de recursos:

curl -X GET \
curl -X GET \
  http:/localhost:8080/spring-security-oauth-resource/users/extra \
  -H 'Accept: application/json, text/plain, */*' \
  -H 'Accept-Encoding: gzip, deflate' \
  -H 'Accept-Language: en-US,en;q=0.9' \
  -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXV...' \
  -H 'Cache-Control: no-cache' \

O serviço de borda Zuul agora validará o JWT antes de rotear para o servidor de recursos.

Isso então extrai os campos-chave do JWT e verifica uma autorização mais granular antes de responder à solicitação:

{
    "user_name":"john",
    "scope":["foo","read","write"],
    "organization":"johnwKfc",
    "exp":1544584758,
    "authorities":["ROLE_USER"],
    "jti":"8e2c56d3-3e2e-4140-b120-832783b7374b",
    "client_id":"fooClientIdPassword"
}

8. Segurança entre camadas

É importante observar que o JWT está sendo validado pelo serviço de borda Zuul antes de ser passado para o servidor de recursos. Se o JWT for inválido, a solicitação será negada no limite do serviço de borda.

Se o JWT é realmente válido, por outro lado, a solicitação é passada adiante. O Servidor de Recursos valida o JWT novamente e extrai os principais campos, como escopo do usuário, organização (neste caso, um campo personalizado) e autoridades. Ele usa esses campos para decidir o que o usuário pode ou não fazer.

Para ser claro, em muitas arquiteturas, não precisaremos realmente validar o JWT duas vezes - essa é uma decisão que você terá que tomar com base em seus padrões de tráfego.

Por exemplo, em alguns projetos de produção, os Servidores de Recursos individuais podem ser acessados ​​diretamente, bem como por meio do proxy - e podemos verificar o token nos dois locais. Em outros projetos, o tráfego pode vir apenas através do proxy; nesse caso, verificar o token é suficiente.

9. Sumário

Como vimos, o Zuul fornece uma maneira fácil e configurável de abstrair e definir rotas para serviços. Juntamente com o Spring Security, ele permite autorizar solicitações nos limites do serviço.

Finalmente, como sempre, o código está disponívelover on Github.