Baixe o app para aproveitar ainda mais
Prévia do material em texto
Arquitetura de MicroServices com Spring Cloud e Spring Boot Sumário 1. Introdução 2. Implementando o Config Server. 3. Subindo um Eureka Server e conectando-o ao Config Server. 4. Construindo um Servidor de Autorização OAuth2. 5. Implementando Serviço de Pedidos. Introdução Fala pessoal, estou começando hoje uma série de Stories onde vamos abordar a construção de uma Arquitetura de MicroServices utilizando os frameworks Spring Cloud e Spring Boot da Pivotal. Antes de começar decidi fazer uma breve introdução sobre as diferenças entre a Arquitetura de MicroServices e a Monolítica assim como reiterar alguns conceitos que surgem quando se utiliza a mesma. Serão stories semanais e o sumário estará presente em todas para facilitar a navegação entre os tópicos. Então chega de enrolação e vamos ao que interessa! Servidor de Configuração Como agora não teremos mais só uma aplicação, mas varias espalhadas por diversos servidores é necessário centralizar a configuração de todas as aplicações em um só lugar. A Pivotal possui um projeto chamado Spring Cloud Config que possibilita a criação de uma configuração externalizada em um sistema distribuído. Com o Config Server você tem um lugar central, um repositório git por exemplo, para gerenciar os arquivos de configuração de cada aplicativo que se encontram rodando em outros ambientes. As aplicações consultarão o Config Server para obter suas configurações na hora da inicialização. Podem ser desde configurações de acesso ao banco de dados até mesmo a porta em que desejamos que a aplicação suba. Service Registry Quando se tem muitas aplicações rodando em diferentes ambientes é difícil controlar qual o host ou porta onde cada um se encontra e se a mesma está online ou não. O Service Registry é um banco de dados preenchido com informações sobre como enviar pedidos para instâncias de microservice. Cada instância que sobe se cadastra no Service Registry informando que está Online para receber requisições. Além de deter as informações de acesso as instâncias, o Service Registry também realizará checagens de saúde da aplicação e o balanceamento de carga de instâncias da mesma aplicação. Para realizar essa função iremos utilizar o Eureka da Netflix. Circuit Braker Em uma aplicação distribuída é comum que haja chamadas entre diversos servidores na rede. Diferente da chamada em memória, as chamadas remotas podem falhar ou ficarem pendentes se o host destino estiver indisponível até que um tempo limite seja atingido. Se vários clientes tentarem acessar esse mesmo recurso indisponível podemos ter uma falha crítica e isso pode afetar o funcionamento de todo o sistema. O Circuit Break ficou popular por evitar esse tipo de cascata. Basicamente a request a um host que pode falhar é envolta em um Circuit Breaker e se essa chamada começar a falhar é retornado um erro conhecido e registrada alguma métrica informando que a request está falhando naquele local. Tendo métricas em mãos dos locais exatos onde a aplicação está falhando, se torna menos complicada a busca pela resolução de erros ou gargalos na aplicação. Para essa série de stories iremos utilizar o Hystrix da Netflix para tratar o Circuit Braker. Gateway Imagine que cada instância de um microservice sobe com uma porta diferente, e se você não tem um ponto de entrada único para sua aplicação como um todo, isso pode se tornar um caos na hora fornecer os recursos para um cliente web ou mobile. O gateway funciona como uma porta de entrada da sua aplicação, todo o trafego passa por ele antes de ser encaminhado para o microservice específico respeitando as rotas que são configuradas no mesmo. O Gateway geralmente recebe a requisição desejada pelo cliente e consulta no Service Registry qual instância de microservice responde por aquela rota. Se for uma rota segura, ele também irá realizar a autenticação junto ao servidor de autorização antes de fazer o redirecionamento. Implementando o Config Server Vamos criar nosso Servidor de Configuração, que tem o objetivo de centralizar toda a configuração da nossa rede de Microservices em um só lugar. Todos os arquivos de configuração do nosso sistema estarão em um repositório git e o Servidor de Configuração será o responsável por ler as informações no repositório e fornece- las às aplicações através de requests HTTP. Gerando a aplicação no Spring Initializr Acesse o site do Spring Initializr e preencha as configurações como na imagem. Basicamente escolhemos qual vai ser o gerenciador de dependências (Maven), a linguagem de programação (Java), as dependências necessárias para o projeto (Web, Actuator e Config Server) e configuramos os metadados do Maven. Por fim geramos o projeto clicando em Generate Project, Ctrl + Enter ou Command + Enter. (Atenção para a versão do Spring, estamos usando a 1.5 nesse projeto) Spring Initializr preenchido para gerar o Config Server Agora você deve ter em mãos um delivery-config-server.zip. Descompacte-o em uma pasta chamada delivery, onde ficaram todos os seus Microservices criados nesse projeto. Pasta principal do projeto Importe o projeto em sua IDE de preferência, aqui estou usando o Intellj. Como podemos ver na imagem abaixo essa é a estrutura de projeto padrão gerada pelo Spring Initialzr. Arvore de diretórios do Projeto Os arquivos listados abaixo podem ser descartados por não serem importantes para o projeto, ou seja ficamos com uma raiz de projeto tendo somente a pata src e o pom.xml 1. Diretório .mvn (Configurações específicas do maven); 2. Diretório static (Arquivos estáticos para uso em paginas web); 3. Diretório templates (Templates do Spring MVC); 4. Arquivo .gitignore (Arquivos ignorados pelo git, teremos um na pasta raiz do projeto delivery); 5. Arquivo mvnw (Usar versão e parâmetros expecíficos do maven); 6. mvnw.cmd (Usar versão e parâmetros específicos do maven no Windows); Arvore de diretórios do Projeto sem arquivos desnecessários Agora que já estamos com o projeto importado, vou abrir os arquivos gerados e explicar em detalhes cada um. Vamos começar pelo pom.xml. <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.coderef</groupId> <artifactId>delivery-config-server</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>delivery-config-server</name> <description>Config Server</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.4.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF- 8</project.reporting.outputEncoding> <java.version>1.8</java.version> <spring-cloud.version>Dalston.SR1</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version><type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> Vou abordar aqui só configurações específicas do projeto, para mais informações sobre o Maven consulte a documentação oficial aqui. Vemos logo de cara que possuímos o spring-boot-starter-parent como nosso parent. Por ser um projeto spring-boot existem uma série de configurações que são previamente feitas nesse projeto fazendo com que você não se preocupe com configuração e sim com a implementação de seu produto. Todas as configurações impostas pelo Spring Boot podem ser sobrepostas extendendo a classe que a implementa ou em casos mais simples usando o application.yml. Logo abaixo temos a declaração das dependências do projeto, explicarei cada uma delas abaixo: spring-boot-actuator: Se trata de um sub projeto do Spring Boot. Ele adiciona vários serviços de qualidade de produção à sua aplicação com pouco esforço de sua parte como o famoso /health por exemplo. spring-boot-starter-web: Essa dependência é uma das mais importantes. Usando-a você já tem um projeto totalmente configurado para trabalhar com qualquer serviço web, como fornecer recursos REST e um tomcat embedded por padrão para subir o projeto. spring-cloud-config-server: Aqui está a dependência mágica que irá transformar nosso MicroService em um Servidor de Configuração sem muito esforço adicional, apenas algumas configurações no application.yml. spring-boot-starter-test: Por último, e não menos importante, a dependência de testes do Spring. Ela torna nosso projeto apto para a implementação de testes unitários, de API, integração, carga entre diversos outros. Como eu disse acima cada dependência dessa fornece tudo que o seu projeto precisa para tratar de um certo nicho de frameworks. O Spring Boot hoje é bem completo, e você consegue encontrar quase todo tipo de framework pré inicializado para utilizar em sua aplicação. Hoje em nosso projeto, temos apenas uma classe chamada DeliveryConfigServerApplication.java, e acredite se quiser, é só dela que vamos precisar para montar nosso Servidor de Configuração. Essa classe é responsável por subir a nossa aplicação usando a seguinte linha de código encapsulada dentro de um método main : SpringApplication.run(DeliveryConfigServerApplication.class, args); Para que nosso projeto seja reconhecido como um Aplicativo Spring Boot precisamos adicionar também a anotação @SpringBootApplication. Nós queremos que essa aplicação seja um Servidor de Configuração, e nada mais fácil que informar isso utilizando uma anotação. Isso é possível utilizando a anotação @EnableConfigServer. http://maven.apache.org/guides/ Veja abaixo como fica a classe completa. package com.coderef.delivery; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.config.server.EnableConfigServer; @SpringBootApplication @EnableConfigServer public class DeliveryConfigServerApplication { public static void main(String[] args) { SpringApplication.run(DeliveryConfigServerApplication.class, args); } } Para finalizar precisamos configurar nosso o application.yml e o bootstrap.yml. application.yml: Aqui são declaradas as configurações que você gostaria de substituir no starter do Spring boot e serão usadas no seu projeto. bootstrap.yml: Esse arquivo é usado para realizar algumas configurações de inicialização do nosso parent, como o nome da aplicação no ecosistema do Spring Cloud e a conexão com um Servidor de Configuração. Como não há como o Servidor de Configuração utilizar ele mesmo para descrever sua configuração, ele será o único que descreveremos um application.yml. Obs:. Como optamos pelo padrão de arquivos .yml podemos deletar o arquivo application.properties. Crie o arquivo application.yml no diretório delivery-config-server/src/main/resources. server: port: 9090 spring: cloud: config: server: git: uri: https://github.com/rafaelcam/delivery-configs Aqui configuramos a porta em que a aplicação irá subir e qual o repositório git que será usado para fornecer os arquivos que o nosso Servidor de Configuração irá disponibilizar para os outros Microservices via HTTP. Esse repositório pode ser remoto ou local, eu estou utilizando um repositório remoto e público. Para configurar um repositório local, ou obter mais informações sobre como usar um repositório privado consulte a documentação do Spring Cloud Config. No arquivo bootstrap.yml vamos informar somente o nome da aplicação. Como estamos tratando de uma aplicação distribuída com o Spring Cloud, essa informação é muito importante porque funcionará como um DNS quando uma instância dessa aplicação for http://cloud.spring.io/spring-cloud-static/spring-cloud-config/1.3.1.RELEASE/ registrada no Service Registry. Os outros MicroServices também consultarão o Service Registry utilizando este nome, pois o host da instância pode ser dinâmico. Crie o arquivo bootstrap.yml no diretório delivery-config-server/src/main/resources. spring: application: name: educafacil-config-server OK! Nosso Servidor de Configuração está pronto, agora vamos realizar um teste simples para ver se tudo está funcionando corretamente. Antes de subir a aplicação vamos adicionar um arquivo de exemplo no repositório de configurações chamado sample-config-app.yml com uma propriedade qualquer. delivery: maxOrders: 10 minOrders: 1 Depois de efetuar o commit e o push para o GitHub nosso repositório de configuração está assim. Arquivo de configuração foi adicionado ao repositório remoto. Agora sim! Suba a aplicação no Tomcat embedded executando a classe principal do projeto, ela deverá estar em pé na porta 9090 que nós configuramos no application.yml. Executando a classe principal para subir a aplicação no Tomcat Embedded Se você está vendo no log da aplicação a mensagem Tomcat started on port(s): 9090 (http) quer dizer que está tudo certo. Abra um navegador qualquer e acesse a seguinte URL. http://localhost:9090/delivery-sample-config/default Você deve estar vendo na resposta do navegador a seguinte resposta contendo todas as propriedades que configuramos no sample-config-app.yml . { "name": "delivery-sample-config", "profiles": [ "default" ], "label": null, "version": null, "state": null, "propertySources": [ { "name": "https://github.com/rafaelcam/delivery-configs/delivery-sample-config.yml", "source": { "delivery.maxOrders": 10, "delivery.minOrders": 1 } } ] } É isso ai galera, era isso que eu tinha pra mostrar hoje. No proximo POST vamos continuar com a série de stories criando um Service Registry (Eureka) e o conectando ao Config Server que criamos hoje. http://localhost:9090/delivery-sample-config/default https://github.com/rafaelcam/delivery-configs/delivery-sample-config.yml Subindo um Eureka Server e conectando-o ao Config Server Olá pessoal, hoje vamos criar nosso Service Registry, que será responsável por gerenciar o status e a localização dos Microservices em nossa rede. Para isso usaremos o Eureka, uma ferramenta desenvolvida pela Netflix e disponibilizada para a comunidade. O Eureka é um serviço REST (Representational State Transfer) que é usado principalmente na nuvem AWS (Amazon Web Services) para localizar serviços com o objetivo de balanceamento de carga e failover de servidores. O conjunto de ferramentas Spring Cloud possui uma implementação do Eureka para que possamos subir um Eureka Server utilizando uma aplicação Spring Boot. Gerando a aplicação no Spring Initializr Acesse o site do Spring Initializre preencha as configurações como na imagem. Escolhemos qual vai ser o gerenciador de dependências (Maven), a linguagem de programação (Java), as dependências necessárias para o projeto (Web, Actuator, Config Client e Eureka Server) e configuramos os metadados do Maven. Por fim geramos o projeto clicando em Generate Project, Ctrl + Enter ou Command + Enter. (Atenção para a versão do Spring, estamos usando a 1.5 nesse projeto) Você se lembra da pasta delivery, que criamos para colocar o nosso config server? Então, é lá que iremos descompactar o nosso Eureka Server. Implementando a Aplicação Como na parte 2 dessa série de stories já entramos em detalhes sobre os arquivos que são gerados e os que são desnecessários não falaremos mais disso para que essa story fique mais concentrada no conteúdo proposto. Vamos direto ao ponto, observando o pom.xml e explicando as dependências inéditas. <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.coderef</groupId> <artifactId>delivery-eureka-server</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>delivery-eureka-server</name> <description>Eureka Server</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.4.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF- 8</project.reporting.outputEncoding> <java.version>1.8</java.version> <spring-cloud.version>Dalston.SR1</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka-server</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> Comparando com o projeto do Config Server, temos apenas duas novas dependências. Explicarei elas abaixo: spring-cloud-starter-config: Como estamos utilizando um Config Server para centralizar nossa aplicação, o Spring fornece esta biblioteca para que a conexão com esse servidor seja simples, somente configurando no arquivo bootstrap.yml qual o host do Config Server. spring-cloud-starter-eureka-server: Essa dependência é a mais importante. Ela torna possível transformar a aplicação Spring Boot em um Servidor Eureka somente com algumas configurações nos properties e uma anotação na classe principal. Como já sabemos, toda aplicação Spring Boot tem uma classe principal, temos que adicionar a ela a anotação @EnableEurekaServer para que seja iniciado um Servidor do Eureka. package com.coderef.delivery; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; @SpringBootApplication @EnableEurekaServer public class DeliveryEurekaServerApplication { public static void main(String[] args) { SpringApplication.run(DeliveryEurekaServerApplication.class, args); } } Agora precisamos configurar o nosso bootstrap.yml. Como disse anteriormente já entramos em detalhes para que serve cada arquivo nas stories anteriores, portanto irei somente explicar o conteúdo. Como a configuração da aplicação ficará no repositório do Config Server precisamos criar somente o bootstrap.yml localmente. Crie o arquivo bootstrap.yml no diretório delivery-eureka-server/src/main/resources. spring: application: name: delivery-eureka-server cloud: config: uri: http://localhost:9090 A configuração do nome da aplicação nós já conhecemos, porém temos algo novo neste bootstrap.yml. Configuramos a propriedade spring.cloud.config.uri que indica o host do Config Server e para que na hora da inicialização a aplicação peça a ele suas propriedades. Mas como o Config Server sabe qual é o arquivo em seu repositório que representa as configurações específicas dessa aplicação que está se conectando? Bem, é ai que entra uma das responsabilidades da propriedade spring.application.name. Quando a aplicação delivery-eureka-server estiver iniciando ela consultará a aplicação delivery-config-server, perguntando a ela se existe em seu repositório um arquivo com o nome configurado em spring.application.name e com a extensão properties ou yml. Mas espera um pouco, não criamos esse arquivo no repositório do Config Server ainda, estão vamos lá. No repositório de configurações do Config Server, no mesmo local onde criamos o sample- config-app.yml na parte 2 dessa série, crie o arquivo delivery-eureka-server.yml com o conteúdo abaixo: http://a-de-microservices-com-spring-cloud-e-spring-boot-parte-2-ff03d8d45dba/ server: port: 9091 eureka: instance: hostname: localhost client: registerWithEureka: true fetchRegistry: false serviceUrl: defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ server: wait-time-in-ms-when-sync-empty: 3000 Neste arquivo temos varias configurações novas, vamos passar uma a uma para descobrirmos do que se trata: eureka.instance.hostname: Host onde o será iniciado o servidor do Eureka. eureka.client.registerWithEureka: Mesmo sendo um Servidor do Eureka, essa aplicação não deixa de ser um Microservice como os outros, portanto ele deve se auto registrar no eureka. eureka.client.fetchRegistry: Os clientes do Eureka obtêm as informações de registro do servidor e as armazenam no local. Depois disso, os clientes usam essa informação para encontrar outros serviços. Como essa aplicação é o próprio Servidor do Eureka, desabilitamos esta busca. eureka.client.serviceUrl.defaultZone: Nada mais do que a localização do Eureka Server, já que essa aplicação vai se auto registrar. eureka.server.wait-time-in-ms-when-sync-empty: Tempo em milissegundos que o Eureka Server irá esperar entre as sincronizações com os clientes. Agora temos dois arquivos de configuração em nosso repositório, como você pode ver abaixo. Certo! Como você já deve ter percebido temos uma dependência entre nossos dois Microservices criados até agora. Dependência em uma Arquitetura de Microservices não é uma coisa boa, porém se trata de um servidor de configuração que futuramente será clusterizado na nuvem e não iremos mais subir ele localmente. Mas por enquanto sempre que quisermos subir Eureka Server precisamos antes iniciar o Config Server. Para testarmos se já esta tudo funcionando corretamente, inicie o Config Server e depois o Eureka Server executando a classe principal, comovimos na story anterior. Observe o log da inicialização do Eureka Server e confira se contém essas duas linhas: Fetching config from server at: http://localhost:9090 (Início do Log) Tomcat started on port(s): 9091 (http) (Fim do Log) Isso significa que o Eureka Server está iniciado e funcionando. Acesse o endereço http://localhost:9091/ e confira se o painel do Eureka aparece. Aqui temos muita informação, tanto de serviços registrados quanto do consumo da maquina host e etc. A seção mais importante para nós no momento é a Instances currently registered with Eureka. Aqui obtemos informações preciosas dos nossos Microservices registrados no Eureka, como por exemplo: O Status UP ou DOWN. Quantidade de instâncias do mesmo Microservice registradas. Importante lembrar que o próprio Eureka irá realizar o load balancer entre elas quando for solicitada uma instância pelo Gateway por exemplo. Host e Porta onde a instância está escutando. Como só temos o Microservice do próprio Eureka Server registrado, não temos uma visão do quanto essa tela é importante ainda, mas de agora pra frente todo Microservice que nós implementarmos será registrado no Eureka, revelando assim todo o seu poder. http://localhost:9090/ http://localhost:9091/%60 O Servidor de Autorização Olá pessoal, dando continuidade a nossa sequência de stories sobre Microservices, hoje vamos criar nosso servidor de autorização que vai ser responsável por controlar a autenticação e autorização de nossos recursos. Nessa story vamos utilizar o Spring Security OAuth uma ferramenta poderosa disponibilizada pela Pivotal. Antes de iniciar a implementação do nosso Servidor de Autorização, vamos conhecer alguns conceitos importantes, isso é necessário para entendermos como funciona e qual o objetivo das tecnologias utilizadas. Vamos lá? OAuth2 O Spring Security OAuth2 tem como base o OAuth2 um framework de autenticação e autorização aberto, poderoso e flexivel permitindo que sua aplicação não fique manipulando diretamente as credenciais dos usuários. Em um modelo tradicional de autenticação o cliente entra com suas credenciais diretamente e obtém acesso ao recurso protegido. Ficar trafegando as credenciais do usuário em cada requisição pode causar vários problemas e limitações, exemplo: Aplicações de terceiros terão que armazenar as credenciais do usuário para solicitar um acesso no futuro. O servidor de recursos tem que garantir e se preocupar sempre com um bom suporte a autenticação. Alteração das credenciais dos usuários no servidor de recursos afetará todas as aplicações de terceiros. O OAuth2 busca solucionar esses problemas definindo papéis e criando uma camada intermediária de autenticação, com isso a aplicação cliente solicita uma concessão de autorização ao usuário e caso seja concedida ela é enviada ao servidor de autorização, esse por sua vez autentica e valida o usuário, se tudo estiver correto um token é emitido para que o cliente possa acessar o servidor de recursos. Confuso, não é mesmo? Calma, vamos identificar e explicar passo a passo o fluxo abaixo: Resource Owner é o proprietário do recurso, ou seja, o usuário que irá entrar com as credenciais de acesso. Client é a aplicação que irá acessar o servidor de recursos. Ela pode ser uma aplicação mobile, desktop, etc. Authorization server é o servidor autenticação e autorização. É o que nós vamos implementa-lo nessa story. Resource server são os recursos protegidos. Fluxo do Oauth2 Fluxo de Autenticação O Client solicita autorização ao Resource Owner. O Client recebe uma concessão de autorização do Resource Owner, que é uma crendencial seguindo um dos padrões definidos pelo oAuth2. O Client solicita um token de acesso ao Authorization Server Authorization Server. O Authorization Server autentica o Client e valida a credencial informada pelo Resource Owner. Se for válida, um token de acesso é emitido.. O Client solicita o acesso ao recurso protegido do Resource Server informando o Token de acesso. O Resource Server valida o token de acesso. Se for válido ele atende a solicitação. Agora que deixamos claro o funcionamento de um Servidor de Autorização com oAuth2, vamos implementa-lo. Gerando a aplicação no Spring Initializr Acesse o site do Spring Initializr e preencha as configurações como na imagem. Escolhemos qual vai ser o gerenciador de dependências (Maven), a linguagem de programação (Java), as dependências necessárias para o projeto (Cloud OAuth2, Actuator, Config Client e Eureka Discovery, JPA, MySql) e configuramos os metadados do Maven. Por fim geramos o projeto clicando em Generate Project, Ctrl + Enter ou Command + Enter. (Atenção para a versão do Spring, estamos usando a 1.5 nesse projeto) https://start.spring.io/ Vamos descompactar esse projeto na pasta delivery, onde está o Config Server e o Eureka Server. Implementando a Aplicação Como de costume, não vamos discutir as configurações que já foram vistas nas stories anteriores, assim o conteúdo fica mais objetivo. Abaixo está o pom.xml gerado, vamos analisar as dependências inéditas. <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.coderef</groupId> <artifactId>delivery-auth-server</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>delivery-auth-server</name> <description>Oauth Server</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.4.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF- 8</project.reporting.outputEncoding> <java.version>1.8</java.version> <spring-cloud.version>Dalston.SR2</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-oauth2</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> Comparando com os projetos anteriores, temos apenas 3 dependências que ainda não vimos, são elas: spring-cloud-starter-oauth2: Essa é a principal dependência desse projeto. Ela oferece toda a implementação necessária para a aplicação funcionar como servidorde autorização Oauth2. spring-boot-starter-data-jpa: Como o próprio nome diz, usaremos essa dependência para trabalhar com nossa camada de persistência. Ela ja fornece tudo que precisamos como por exemplo: spring-data-jpa, spring-orm e o hibernate. mysql-connector-java: Aqui temos o driver JDBC do MySql(Banco de Dados que vamos utilizar). Antes de implementarmos as classes do projeto, vamos criar os arquivos de configuração, neles serão definidas algumas propriedades importantes que serão utilizadas na nossa aplicação. Vamos começar criando nosso bootstrap.yml, salve esse arquivo em delivery-auth- server/src/main/resouces. spring: application: name: delivery-auth-server cloud: config: uri: http://localhost:9090 Nele definimos a propriedade spring.cloud.config.uri com o host do Config Server onde a aplicação irá buscar as propriedades de configuração durante a inicialização com base no valor de spring.application.name como vimos na parte 3. Após criar o bootstrap.yml vamos criar o arquivo de configuração no repositório do Config Server assim como vimos na parte 2. Crie o arquivo delivery-auth-server.yml com o conteúdo abaixo: https://coderef.com.br/arquitetura-de-microservices-com-spring-cloud-e-spring-boot-parte-3-b84b3dce13a0 server: port: 9092 eureka: instance: hostname: localhost port: 9091 client: registerWithEureka: true fetchRegistry: false serviceUrl: defaultZone: http://${eureka.instance.hostname}:${eureka.instance.port}/eureka/ server: wait-time-in-ms-when-sync-empty: 3000 security: oauth2: client: access-token-validity-seconds: 1800 authorized-grant-types: password,refresh_token client-id: coderef client-secret: $2a$10$p9Pk0fQNAQSesI4vuvKA0OZanDD2 resource-ids: resources scope: read,write spring: datasource: data: classpath:/sql/data.sql driver-class-name: com.mysql.jdbc.Driver password: '1234' platform: mysql schema: classpath:/sql/schema.sql url: jdbc:mysql://localhost/oauth?verifyServerCertificate=false&useSSL=false&requireSSL=false username: root jpa: database-platform: org.hibernate.dialect.MySQLDialect generate-ddl: false hibernate: ddl-auto: none show-sql: true Como podemos perceber existem algumas configurações que ainda não conhecemos, primeiro vamos ver as relacionadas com a parte de segurança depois vamos conferir as configurações de persistência: Configurações de segurança: security.oauth2.client.access-token-validity-seconds: Tempo, em segundos, de duração do token de acesso. security.oauth2.client.authorized-grant-types: Qual o tipo do pedido de concessão de acesso. Se o Client vai solicitar um token utilizando usuário e senha ou se vai renovar um token já existente. security.oauth2.client.client-id: Identificador único que será usado pelo Client para solicitar concessão de acesso. security.oauth2.client.client-secret: Uma senha usada em conjunto com o client-id como autenticação básica do client que está tentando obter um token de acesso. security.oauth2.client.resource-ids: Identificador dos recursos disponíveis, se especificado aqui também deve ser especificado no servidor de recursos. security.oauth2.client.scope: Os escopos que o Client terá acesso. Configurações para o JPA: spring.datasource.data: Um arquivo .sql que será executado na inicialização da aplicação, ele será executado após a propriedade schema. spring.datasource.driver-class-name: Classe do driver JDBC. spring.datasource.password: Senha de acesso ao banco de dados. spring.datasource.platform: Plataforma de persistência. spring.datasource.schema: Arquivo com a estrutura do banco de dados. Como as classes que mapeiam os dados de segurança então nas bibliotecas do Spring, elas não são criadas automaticamente pelo JPA. spring.datasource.url: Url de acesso ao banco de dados. spring.datasource.username: Usuário de acesso ao banco de dados. spring.datasource.jpa.database-platform: Classe que possui a implementação que possibilita a geração SQL para a plataforma de persistência especificada, para que todo o SQL gerado pelo JPA seja compatível com o banco de dados escolhido. spring.datasource.jpa.generate-ddl: Informar se durante a inicialização será necessário gerar as estruturas do banco de dados com base nas anotações JPA. spring.datasource.jpa.hibernate.ddl-auto: Caso a propriedade generate-ddl esteja marcada como true, aqui deverá ser informada qual a política de geração do schema do banco de dados. spring.datasource.jpa.hibernate.show-sql: Se habilitada, será gerado um log com o sql gerado pelo JPA. Use somente em Homologação. Agora temos 3 arquivos de configuração no repositório: Bom, retornando a nossa aplicação. Se você percebeu durante nossa análise do arquivo de configuração delivery-auth-server.yml nós configuramos as propriedades spring.datasource.data e spring.datasource.schema que fazem referência a um arquivo .sql. para hospedar esses dois arquivos vamos criar uma pasta sql no mesmo lugar onde colocamos o arquivo bootstrap.yml. Primeiro vamos criar o arquivo que conterá a estrutura do nosso banco de dados de autenticação, além de possuir algumas tabelas importantes que serão utilizadas pelo Spring para gerenciar a sessão do usuário logado. Crie um arquivo schema.sql com o seguinte conteúdo: CREATE TABLE IF NOT EXISTS user ( username VARCHAR(50) NOT NULL PRIMARY KEY, email VARCHAR(50), password VARCHAR(500), activated BOOLEAN DEFAULT FALSE, activationkey VARCHAR(50) DEFAULT NULL, resetpasswordkey VARCHAR(50) DEFAULT NULL ); CREATE TABLE IF NOT EXISTS authority ( name VARCHAR(50) NOT NULL PRIMARY KEY ); CREATE TABLE IF NOT EXISTS user_authority ( username VARCHAR(50) NOT NULL, authority VARCHAR(50) NOT NULL, FOREIGN KEY (username) REFERENCES user (username), FOREIGN KEY (authority) REFERENCES authority (name), UNIQUE INDEX user_authority_idx_1 (username, authority) ); CREATE TABLE IF NOT EXISTS oauth_access_token ( token_id VARCHAR(256) DEFAULT NULL, token BLOB, authentication_id VARCHAR(256) DEFAULT NULL, user_name VARCHAR(256) DEFAULT NULL, client_id VARCHAR(256) DEFAULT NULL, authentication BLOB, refresh_token VARCHAR(256) DEFAULT NULL ); CREATE TABLE IF NOT EXISTS oauth_refresh_token ( token_id VARCHAR(256) DEFAULT NULL, token BLOB, authentication BLOB ); CREATE TABLE IF NOT EXISTS oauth_client_details ( client_id VARCHAR(255) PRIMARY KEY, resource_ids VARCHAR(255), client_secret VARCHAR(255), scope VARCHAR(255), authorized_grant_types VARCHAR(255), web_server_redirect_uri VARCHAR(255), authorities VARCHAR(255), access_token_validity INTEGER, refresh_token_validity INTEGER, additional_information VARCHAR(4096), autoapprove VARCHAR(255) ); Agora vamos criar o arquivo que ira apagar os Clients salvos e inserir os dados do primeiro usuário da aplicação, o administrador. Criaremos um arquivo com o nome data.sql com o seguinte conteúdo: DELETE FROM oauth_client_details; INSERT INTO user (username, email, password, activated) SELECT * FROM (SELECT 'admin', 'admin@admin.com', '$2a$10$r0RFDmpneBVryx.ihHK9gu6FFJQi4nTxQUqzdSTvrPpaKZMxigqpy', true) AS tmp WHERE NOT EXISTS ( SELECT username FROM user WHERE username = 'admin' ) LIMIT 1; INSERT INTO authority (name) SELECT * FROM (SELECT 'ROLE_USER') AS tmp WHERE NOT EXISTS ( SELECT name FROM authority WHERE name = 'ROLE_USER' ) LIMIT 1; INSERT INTO authority (name) SELECT * FROM (SELECT 'ROLE_ADMIN') AS tmp WHERE NOT EXISTS ( SELECT name FROM authority WHERE name = 'ROLE_ADMIN' ) LIMIT 1; INSERT INTO user_authority (username, authority)SELECT * FROM (SELECT 'admin', 'ROLE_USER') AS tmp WHERE NOT EXISTS ( SELECT username, authority FROM user_authority WHERE username = 'admin' and authority = 'ROLE_USER' ) LIMIT 1; INSERT INTO user_authority (username, authority) SELECT * FROM (SELECT 'admin', 'ROLE_ADMIN') AS tmp WHERE NOT EXISTS ( SELECT username, authority FROM user_authority WHERE username = 'admin' and authority = 'ROLE_ADMIN' ) LIMIT 1; Após criar esses dois arquivos temos a seguinte estrutura: Considerando que você já tenha importado o projeto na sua IDE vamos criar os seguintes pacotes e classes: Agora vamos implementar cada classe e ver seu conteúdo. AuthorizationServerConfiguration package com.coderef.delivery.config; import javax.sql.DataSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfig urer; import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServer ConfigurerAdapter; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizatio nServer; import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerE ndpointsConfigurer; import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore; import com.coderef.delivery.domain.Authorities; @Configuration @EnableAuthorizationServer public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter { private static PasswordEncoder encoder; @Value("${security.oauth2.client.client-id}") private String clientId; @Value("${security.oauth2.client.authorized-grant-types}") private String[] authorizedGrantTypes; @Value("${security.oauth2.client.resource-ids}") private String resourceIds; @Value("${security.oauth2.client.scope}") private String[] scopes; @Value("${security.oauth2.client.client-secret}") private String secret; @Value("${security.oauth2.client.access-token-validity-seconds}") private Integer accessTokenValiditySeconds; @Autowired DataSource dataSource; @Autowired @Qualifier("authenticationManagerBean") private AuthenticationManager authenticationManager; @Bean public JdbcTokenStore tokenStore() { return new JdbcTokenStore(dataSource); } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.authenticationManager(this.authenticationManager).tokenStore(tokenStore()); } @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.jdbc(dataSource) .withClient(clientId) .authorizedGrantTypes(authorizedGrantTypes) .authorities(Authorities.names()) .resourceIds(resourceIds) .scopes(scopes) .secret(secret) .accessTokenValiditySeconds(accessTokenValiditySeconds); } @Bean public PasswordEncoder passwordEncoder() { if (encoder == null) { encoder = new BCryptPasswordEncoder(); } return encoder; } } Nessa classe vimos a anotação @Configuration que diz ao Spring que essa classe é uma classe de configuração e ela deverá ser instanciada na inicialização da aplicação. A anotação @EnableAuthorizationServer que habilita o AuthorizationServer, disponibilizando um AuthorizationEndpoint e um TokenEndpoint. Vimos também que injetamos nossas propriedades que definimos anteriormente. Temos dois métodos configure o primeiro define o gerenciador de autenticação do AuthorizationEndpoint e o segundo registra um Client, segundo as configurações que definimos. CorsFilterConfiguration package com.coderef.delivery.config; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.filter.CorsFilter; @Configuration public class CorsFilterConfiguration { @Bean public FilterRegistrationBean corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); config.addAllowedOrigin("*"); config.addAllowedHeader("*"); config.addAllowedMethod("*"); source.registerCorsConfiguration("/**", config); FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source)); bean.setOrder(Ordered.HIGHEST_PRECEDENCE); return bean; } } Esse Filter será invocado no inicio de uma requisição ao Servidor de autorização, ele permite que uma requisição chegue ao Servidor independente das crendenciais, origin, header e method. ResourceServerConfiguration package com.coderef.delivery.config; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceSer ver; import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConf igurerAdapter; import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurit yConfigurer; @Configuration @EnableResourceServer public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter { @Value("${security.oauth2.client.resource-ids}") private String RESOURCE_ID; @Override public void configure(ResourceServerSecurityConfigurer resources) { resources.resourceId(RESOURCE_ID); } @Override public void configure(HttpSecurity http) throws Exception { http.requestMatchers() .antMatchers("/**") .and() .authorizeRequests() .anyRequest() .authenticated() .antMatchers(HttpMethod.GET, "/**").access("#oauth2.hasScope('read')") .antMatchers(HttpMethod.OPTIONS, "/**").access("#oauth2.hasScope('read')") .antMatchers(HttpMethod.POST, "/**").access("#oauth2.hasScope('write')") .antMatchers(HttpMethod.PUT, "/**").access("#oauth2.hasScope('write')") .antMatchers(HttpMethod.PATCH, "/**").access("#oauth2.hasScope('write')") .antMatchers(HttpMethod.DELETE, "/**").access("#oauth2.hasScope('write')"); } } Aqui definimos a anotação @EnableResourceServer que habilita um FilterSpring Security que autentica as requisições com base no token OAuth2 recebido. Temos um método configure que restringe as requisições com base nos escopos disponíveis. WebSecurityConfig package com.coderef.delivery.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.security.SecurityProperties; import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManag erBuilder; import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAda pter; import org.springframework.security.crypto.password.PasswordEncoder; import com.coderef.delivery.service.UserDetailsService; @Configuration @EnableWebSecurity @Order(SecurityProperties.ACCESS_OVERRIDE_ORDER) public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsService userDetailsService; @Autowired PasswordEncoder passwordEncoder; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder); } @Override @Bean public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Override public void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers("/oauth/register"); } } O Spring já possui uma classe interna anotada com @EnableWebSecurity para sobrescrever as configurações dessa classe anotamos a nossa com a mesma anotação e colocamos também a anotação @Order com a propriedade ACCESS_OVERRIDER_ORDER. Definimos também dois métodos configure um responsável pela codificação da senha que será informada pelo usuário a Aplicação Client e outro informando ao Spring Security que torne o endpoint /oauth/register público. AuthController package com.coderef.delivery.controller; import java.security.Principal; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/") public class AuthController { @RequestMapping("/user") public Principal getCurrentLoggedInUser(Principal user) { return user; } } Aqui implementamos uma Controller muito importante, pois ela será consultada sempre pelo Client para validar se o token informado ainda é válido, caso não tenha acesso ou o token informado seja inválido uma mensagem de não autorizado é retornada. Authorities package com.coderef.delivery.domain; public enum Authorities { ROLE_USER, ROLE_ADMIN; public static String[] names() { String[] names = new String[values().length]; for(int index = 0; index < values().length; index++) { names[index] = values()[index].name(); } return names; } } Nesse ENUM colocamos as duas regras de acesso que estamos utilizando na aplicação. Authority package com.coderef.delivery.model; import java.io.Serializable; import javax.persistence.Entity; import javax.persistence.Id; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; @Entity public class Authority implements Serializable { private static final long serialVersionUID = 1L; @Id @NotNull @Size(min = 0, max = 50) private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } } Aqui implementamos a classe que representa nossa tabela de regras no banco de dados. User package com.coderef.delivery.model; import java.io.Serializable; import java.util.Set; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.JoinTable; import javax.persistence.ManyToMany; import javax.validation.constraints.Size; import org.hibernate.validator.constraints.Email; @Entity public class User implements Serializable { private static final long serialVersionUID = 1L; @Id @Column(updatable = false, nullable = false) private String username; @Size(min = 0, max = 500) private String password; @Email @Size(min = 0, max = 50) private String email; private boolean activated; @Size(min = 0, max = 100) @Column(name = "activationkey") private String activationKey; @Size(min = 0, max = 100) @Column(name = "resetpasswordkey") private String resetPasswordKey; @ManyToMany @JoinTable( name = "user_authority", joinColumns = @JoinColumn(name = "username"), inverseJoinColumns = @JoinColumn(name = "authority")) private Set<Authority> authorities; public User() { } public User(String username, String password, String email, boolean activated, String firstName, String lastName, String activationKey, String resetPasswordKey, Set<Authority> authorities) { this.username = username; this.password = password; this.email = email; this.activated = activated; this.activationKey = activationKey; this.resetPasswordKey = resetPasswordKey; this.authorities = authorities; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public boolean isActivated() { return activated; } public void setActivated(boolean activated) { this.activated = activated; } public String getActivationKey() { return activationKey; } public void setActivationKey(String activationKey) { this.activationKey = activationKey; } public String getResetPasswordKey() { return resetPasswordKey; } public void setResetPasswordKey(String resetPasswordKey) { this.resetPasswordKey = resetPasswordKey; } public Set<Authority> getAuthorities() { return authorities; } public void setAuthorities(Set<Authority> authorities) { this.authorities = authorities; } } Essa classe será a representação da tabela de usuários que criamos anteriormente via SQL onde ficarão salvas as credenciais dos usuários. AuthorityRepository package com.coderef.delivery.repository; import org.springframework.data.jpa.repository.JpaRepository; import com.coderef.delivery.model.Authority; public interface AuthorityRepository extends JpaRepository<Authority, String>{ Authority findByName(String name); } Esse repository será responsável pelas consultas das regras do usuário no banco de dados. UserRepository package com.coderef.delivery.repository; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import com.coderef.delivery.model.User; public interface UserRepository extends JpaRepository<User, String> { @Query("SELECT u FROM User u WHERE LOWER(u.username) = LOWER(:username)") Optional<User> findByUsername(@Param("username") String username); } Aqui definimos as consultas referentes aos usuários. UserDetailsService package com.coderef.delivery.service; import java.util.ArrayList;import java.util.Collection; import java.util.Optional; import javax.transaction.Transactional; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import com.coderef.delivery.model.Authority; import com.coderef.delivery.model.User; import com.coderef.delivery.repository.UserRepository; @Service @Transactional public class UserDetailsService implements org.springframework.security.core.userdetails.UserDetailsService{ UserRepository userRepository; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { return userRepository.findByUsername(username) .map(user -> new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), getGrantedAuthorities(user))) .orElseThrow(() -> new UsernameNotFoundException("User "+username+" Not found")); } @Autowired public void setUserRepository(UserRepository userRepository) { this.userRepository = userRepository; } private Collection<GrantedAuthority> getGrantedAuthorities(User user){ Collection<GrantedAuthority> grantedAuthorities = new ArrayList<>(); for (Authority authority : user.getAuthorities()) { GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(authority.getName()); grantedAuthorities.add(grantedAuthority); } return grantedAuthorities; } } Aqui implementamos um @Service. O Spring Security fornece uma interface utilizada internamente chamada UserDetailsService que precisamos implementar quando queremos alterar o comportamento do UserDetail utilizado. DeliveryAuthServerApplication package com.coderef.delivery; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; @SpringBootApplication @EnableEurekaClient public class DeliveryAuthServerApplication { public static void main(String[] args) { SpringApplication.run(DeliveryAuthServerApplication.class, args); } } Por último implementamos nossa classe principal, que será responsável por inicializar nossa aplicação Spring Boot como vimos na parte 2. Aqui temos uma anotação nova a @EnableEurekaClient. Essa anotação torna nossa aplicação visível ao Eureka Server com base nas configurações que definimos no application.yml . Subindo a aplicação Agora chegou a hora subir o nosso Authorization Server para saber se tudo está funcionando corretamente. Antes de inciar nosso servidor de autorização será necessário subir o Config Server e depois o Eureka Server. Quando os dois estiverem online vamos executar a classe DeliveryAuthServerApplication.java. Finalizado, acesse o endereço http://localhost:9091/ nosso servidor vai aparecer listado no Eureka Server: https://coderef.com.br/arquitetura-de-microservices-com-spring-cloud-e-spring-boot-parte-3-b84b3dce13a0 Solicitando um Token de Acesso O framework Oauth2 define um padrão para a solicitação do token e um a resposta. Para solicitar um token o Client deverá estar registrado no servidor de autorização e deve enviar o client-id e o secret que configuramos no application.yml codificado no formato Base 64, respeitando o padrão client-id:secret. Para fazer essa requisição teremos que enviar os seguintes dados: Authorization: Basic Y29kZXJlZjokMmEkMTAkcDlQazBmUU5BUVNlc0k0dnV2S0EwT1phbkREMg== (verifique a imagem abaixo). URL: http://localhost:9092/oauth/token?grant_type=password&username=admin&passwo rd=123456 Method: POST. Content-Type: application/json. Para facilitar vamos utilizar o Postman, uma ferramenta poderosa com uma interface simples e fácil de manipular. Após instalar e abrir o Postman, os dados acima serão inseridos, tal como abaixo: http://localhost:9092/oauth/token?grant_type=password&username=admin&password=123456 http://localhost:9092/oauth/token?grant_type=password&username=admin&password=123456 Após clicar em Send um json será recebido: O Oauth2 tem um padrão de resposta, vou detalhar abaixo o que significa cada propriedade do JSON: access_token: token de acesso que o Client irá utilizar para solicitar acesso aos recursos protegidos. token_type: O tipo de token que o Client deverá enviar no Header das requisições para os recursos. refresh_token: A cada interação entre client e o resource server esse token pode ser utilizado para renovar o token sendo utilizado atualmente. Isso evita que o token expire enquanto o usuário ainda está interagindo com o site. expire_in: Tempo de vida do token, em segundos. scope: Escopo de permissão que o token terá para acessar os recursos. Microserviço de Pedido Olá pessoal, enfim voltamos com nossas publicações. Dando continuação a nossa sequencia de stories sobre microservice, nessa storie vamos criar nosso microserviço de pedido, que será responsável por criar, listar e apagar um pedido criado. Veremos também como integrar esse microserviço ao servidor de autorização criado na storie anterior, então bora lá. Gerando o Projeto Acesse o site do Spring Initializr e preencha as configurações como na imagem. Escolhemos qual vai ser o gerenciador de dependências (Maven), a linguagem de programação (Java), as dependências necessárias para o projeto (Cloud OAuth2, Actuator, Config Client e Eureka Discovery, JPA, MySql, web)e configuramos os metadados do Maven. Por fim geramos o projeto clicando em Generate Project, Ctrl + Enter ou Command + Enter. (Atenção para a versão do Spring, estamos usando a 1.5 nesse projeto) Vamos descompactar o projeto na pasta delivery, assim como fizemos antes. Implementando a aplicação Como sempre fazemos, iremos abrir o pom.xml e ver as dependências geradas, a maioria já conhecemos, mas vou frizar uma: <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.coderef</groupId> <artifactId>delivery-order-service</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>delivery-order-service</name> <description>Delivery order service</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.9.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF- 8</project.reporting.outputEncoding> <java.version>1.8</java.version> <spring-cloud.version>Edgware.RELEASE</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-oauth2</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency><groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> spring-cloud-starter-oauth2: Colocamos novamente essa dependência e iremos colocá-la novamente em projetos futuros, pois ela será necessária para implementarmos a conexão com o authorization server. Vamos criar os arquivos de configuração, neles serão definidas algumas propriedades importantes que serão utilizadas na nossa aplicação. Vamos começar criando nosso bootstrap.yml, salve esse arquivo em delivery-order-service/src/main/resouces. spring: application: name: delivery-order-service cloud: config: uri: http://localhost:9090 Como vimos na parte 3, nossa aplicação terá que buscar as configurações no Config Server, para isso especificamos novamente a propriedade spring.cloud.config.uri . Após criado nosso bootstrap.yml vamos criar nosso arquivo de configuração no repositório que o Config Server está usando tal, como vimos na parte 2, crie delivery-order-service.yml com: server: port: 9093 eureka: instance: hostname: localhost port: 9091 client: registerWithEureka: true fetchRegistry: false serviceUrl: defaultZone: http://${eureka.instance.hostname}:${eureka.instance.port}/eureka/ server: wait-time-in-ms-when-sync-empty: 3000 spring: datasource: driver-class-name: com.mysql.jdbc.Driver password: '1234' platform: mysql url: jdbc:mysql://localhost/delivery- order?verifyServerCertificate=false&useSSL=false&requireSSL=false username: root jpa: database-platform: org.hibernate.dialect.MySQLDialect generate-ddl: false hibernate: ddl-auto: create show-sql: true authserver: hostname: http://localhost:9092 security: oauth2: resource: userInfoUri: ${authserver.hostname}/user Vamos repassar algumas configurações que são novidades para nós até o momento: authserver.hostname: Parâmetro apenas para ser reutilizado em userInfoUri. security.oauth2.resource.userInfoUri: Url que o spring irá utilizar para validadar o token recebido na requisição. Agora temos 4 arquivos no repositório de configuração contando com o arquivo de exemplo: Considerando que você já esteja com o projeto aberto na sua IDE favorita, vamos criar a seguinte estrutura de pacotes com as seguintes classes: Desconsidere os diretórios .idea e .mvn . Agora vamos implementar cada classe e ver seu conteúdo. Order package com.coderef.delivery.model; import org.hibernate.validator.constraints.NotEmpty; import javax.persistence.*; import javax.validation.constraints.NotNull; import java.io.Serializable; @Entity @Table(name = "`order`") public class Order implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.AUTO) private Integer id; @NotEmpty(message = "Product required") private String product; @NotNull(message = "Price required") private Double price; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getProduct() { return product; } public void setProduct(String product) { this.product = product; } public Double getPrice() { return price; } public void setPrice(Double price) { this.price = price; } } Entidade que representará nosso pedido, iremos utilizar as anotações @NotEmpty e @NotNull na classe OrderService para validar as propriedades do nosso pedido. OrderRepository Essa interface será responsável por fazer a “ponte” entre nossa camada de negócio e o banco de dados, podemos ver que ela está estendendo a Interface CrudRepository ela é uma interface do Spring Data JPA que fornece uma funcionalidade CRUD completa para a entidade que está sendo gerenciada. package com.coderef.delivery.repository; import com.coderef.delivery.model.Order; import org.springframework.data.repository.CrudRepository; public interface OrderRepository extends CrudRepository<Order, Integer> { } OrderService Aqui estará nosso @Service nele poderíamos implementar ou chamar um fluxo de negócio relativo a nossa entidade. package com.coderef.delivery.service; import com.coderef.delivery.model.Order; import com.coderef.delivery.repository.OrderRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; @Service public class OrderService { @Autowired private OrderRepository orderRepository; public Order save(@Validated Order order) { return orderRepository.save(order); } public Order findById(Integer id){ return orderRepository.findOne(id); } public Iterable<Order> findAll(){ return orderRepository.findAll(); } https://docs.spring.io/spring-data/data-commons/docs/1.6.1.RELEASE/reference/html/repositories.html public void delete(Integer id) { orderRepository.delete(id); } } ExceptionHandlerController Essa controller será responsável por interceptar e tratar as exceções lançadas pela nossa aplicação: package com.coderef.delivery.exception; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import javax.validation.ConstraintViolationException; import java.util.stream.Collectors; @ControllerAdvice public class ExceptionHandlerController { @ExceptionHandler(ConstraintViolationException.class) public ResponseEntity<?> validateError(ConstraintViolationException ex){ return ResponseEntity.badRequest().body(ex.getConstraintViolations().stream().map(cv -> cv.getMessage()).collect(Collectors.toList())); } @ExceptionHandler(Exception.class) public ResponseEntity<?> otherErrors(Exception ex){ return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(ex.getMessage()); } } SecurityConfiguration Essa classe vai ativar nosso Resource Server e mapear cada role para seu method especifico: package com.coderef.delivery.security; import org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceSer ver; import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConf igurerAdapter; import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurit yConfigurer; @Configuration @EnableResourceServerpublic class SecurityConfiguration extends ResourceServerConfigurerAdapter { private final static String resourceId = "resources"; @Override public void configure(HttpSecurity http) throws Exception { http.requestMatchers() .antMatchers("/**") .and() .authorizeRequests() .anyRequest() .authenticated() .antMatchers(HttpMethod.GET, "/**").access("#oauth2.hasScope('read')") .antMatchers(HttpMethod.OPTIONS, "/**").access("#oauth2.hasScope('read')") .antMatchers(HttpMethod.POST, "/**").access("#oauth2.hasScope('write')") .antMatchers(HttpMethod.PUT, "/**").access("#oauth2.hasScope('write')") .antMatchers(HttpMethod.PATCH, "/**").access("#oauth2.hasScope('write')") .antMatchers(HttpMethod.DELETE, "/**").access("#oauth2.hasScope('write')"); } @Override public void configure(ResourceServerSecurityConfigurer resources){ resources.resourceId(resourceId); } } DeliveryOrderServiceApplication Por último implementamos nossa classe principal, que será responsável por inicializar nossa aplicação Spring Boot como vimos na parte 2. Aqui temos uma anotação nova a @EnableEurekaClient. Essa anotação torna nossa aplicação visível ao Eureka Server com base nas configurações que definimos no application.yml . package com.coderef.delivery; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; https://coderef.com.br/arquitetura-de-microservices-com-spring-cloud-e-spring-boot-parte-3-b84b3dce13a0 @SpringBootApplication @EnableEurekaClient public class DeliveryOrderServiceApplication { public static void main(String[] args) { SpringApplication.run(DeliveryOrderServiceApplication.class, args); } } Subindo a aplicação Agora chegou a hora subir o nosso MicroService, antes de iniciar será necessário subir o Config Server, Eureka Server e o Authorization Server. Quando os três estiverem online vamos executar a classe DeliveryOrderServiceApplication.java. Finalizado, acesse o endereço http://localhost:9091/ nosso servidor vai aparecer listado no Eureka Server: Criando um Pedido Antes de fazermos a requisição para o nosso MicroService, será necessário solicitarmos um token para nosso Authorization Server. Vamos abrir o Postman e criar uma nova requisição: http://localhost:9091/%60 https://www.getpostman.com/ URL: http://localhost:9092/oauth/token?grant_type=password&username=admin&passwo rd=123456 Authorization: Basic Y29kZXJlZjokMmEkMTAkcDlQazBmUU5BUVNlc0k0dnV2S0EwT1phbkREMg== Method: POST. Content-Type: application/json. Clique em Send. Uma resposta similar a imagem abaixo será retornada: Copiaremos o access_token e o token_type, pois será eles que iremos utilizar na próxima requisição. Agora que já temos um token vamos criar nosso primeiro pedido. Vamos até o Postman novamente e preencher: URL: http://localhost:9093/api/orders Mehod: POST Clique em Headers e preencha com: http://localhost:9092/oauth/token?grant_type=password&username=admin&password=123456 http://localhost:9092/oauth/token?grant_type=password&username=admin&password=123456 http://localhost:9093/api/orders Content-Type: application/json. Authorizarion: cole o token_type e o access_token com um espacinho entre os dois. Clique em body, depois em raw e selecione JSON(application/json), preencha o TextArea com um json similar ao conteúdo abaixo: { "product": "Apple", "price": "1.25" } Clique em Send, um pedido com id será retornado na requisição representando que o pedido foi criado com sucesso. Pronto, agora podemos realizar os mesmo passos para os endpoints de busca e apagar, não podemos esquecer que alguns possuem parâmetros na requisição e que se não forem passados pode ocorrer erro. Referências http://projects.spring.io/spring-security-oauth/ https://tools.ietf.org/pdf/draft-ietf-oauth-v2-31.pdf https://docs.spring.io/spring-data/data- commons/docs/1.6.1.RELEASE/reference/html/repositories.html Repositório https://github.com/diegosilva13/delivery https://docs.spring.io/spring-data/data-commons/docs/1.6.1.RELEASE/reference/html/repositories.html https://docs.spring.io/spring-data/data-commons/docs/1.6.1.RELEASE/reference/html/repositories.html
Compartilhar