Spring Boot - OAuth2 com JWT

Neste capítulo, você aprenderá em detalhes sobre os mecanismos do Spring Boot Security e OAuth2 com JWT.

Servidor de Autorização

O Authorization Server é um componente arquitetônico supremo para Web API Security. O Authorization Server atua como um ponto de autorização de centralização que permite que seus aplicativos e endpoints HTTP identifiquem os recursos de seu aplicativo.

Servidor de Recursos

O Resource Server é um aplicativo que fornece o token de acesso aos clientes para acessar os pontos de extremidade HTTP do Resource Server. É uma coleção de bibliotecas que contém os pontos de extremidade HTTP, recursos estáticos e páginas da web dinâmicas.

OAuth2

OAuth2 é uma estrutura de autorização que permite ao aplicativo Web Security acessar os recursos do cliente. Para construir um aplicativo OAuth2, precisamos nos concentrar no tipo de concessão (código de autorização), ID do cliente e segredo do cliente.

 

Token JWT

O token JWT é um token da Web JSON, usado para representar as reivindicações protegidas entre duas partes. Você pode aprender mais sobre o token JWT em www.jwt.io/ .

Agora, vamos construir um aplicativo OAuth2 que permite o uso do Authorization Server, Resource Server com a ajuda de um token JWT.

Você pode usar as seguintes etapas para implementar o Spring Boot Security com o token JWT acessando o banco de dados.

Primeiro, precisamos adicionar as seguintes dependências em nosso arquivo de configuração de construção.

Os usuários do Maven podem adicionar as seguintes dependências em seu arquivo pom.xml.

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-security</artifactId>
</dependency>

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
   <groupId>org.springframework.security.oauth</groupId>
   <artifactId>spring-security-oauth2</artifactId>
</dependency>

<dependency>
   <groupId>org.springframework.security</groupId>
   <artifactId>spring-security-jwt</artifactId>
</dependency>

<dependency>
   <groupId>com.h2database</groupId>
   <artifactId>h2</artifactId>
</dependency>

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-test</artifactId>
   <scope>test</scope>
</dependency>

<dependency>
   <groupId>org.springframework.security</groupId>
   <artifactId>spring-security-test</artifactId>
   <scope>test</scope>
</dependency>

Os usuários do Gradle podem adicionar as seguintes dependências no arquivo build.gradle.

compile('org.springframework.boot:spring-boot-starter-security')
compile('org.springframework.boot:spring-boot-starter-web')
testCompile('org.springframework.boot:spring-boot-starter-test')
testCompile('org.springframework.security:spring-security-test')

compile("org.springframework.security.oauth:spring-security-oauth2")
compile('org.springframework.security:spring-security-jwt')
compile("org.springframework.boot:spring-boot-starter-jdbc")
compile("com.h2database:h2:1.4.191")

Onde,

  • Spring Boot Starter Security - Implementa o Spring Security

  • Spring Security OAuth2 - Implementa a estrutura OAUTH2 para habilitar o Authorization Server e o Resource Server.

  • Spring Security JWT - Gera o token JWT para segurança da Web

  • Spring Boot Starter JDBC - Acessa o banco de dados para garantir que o usuário esteja disponível ou não.

  • Spring Boot Starter Web - Grava endpoints HTTP.

  • H2 Database - Armazena as informações do usuário para autenticação e autorização.

O arquivo de configuração de compilação completo é fornecido abaixo.

<?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.tutorialspoint</groupId>
   <artifactId>websecurityapp</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <packaging>jar</packaging>
   <name>websecurityapp</name>
   <description>Demo project for Spring Boot</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>
   </properties>

   <dependencies>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-jdbc</artifactId>
      </dependency>
      
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-security</artifactId>
      </dependency>
      
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-web</artifactId>
      </dependency>
      
      <dependency>
         <groupId>org.springframework.security.oauth</groupId>
         <artifactId>spring-security-oauth2</artifactId>
      </dependency>
      
      <dependency>
         <groupId>org.springframework.security</groupId>
         <artifactId>spring-security-jwt</artifactId>
      </dependency>
      
      <dependency>
         <groupId>com.h2database</groupId>
         <artifactId>h2</artifactId>
      </dependency>
      
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-test</artifactId>
         <scope>test</scope>
      </dependency>
      
      <dependency>
         <groupId>org.springframework.security</groupId>
         <artifactId>spring-security-test</artifactId>
         <scope>test</scope>
      </dependency>
   </dependencies>

   <build>
      <plugins>
         <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
         </plugin>
      </plugins>
   </build>
   
</project>

Gradle – build.gradle

buildscript {
   ext {
      springBootVersion = '1.5.9.RELEASE'
   }
   repositories {
      mavenCentral()
   }
   dependencies {
      classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
   }
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'

group = 'com.tutorialspoint'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8

repositories {
   mavenCentral()
}

dependencies {
   compile('org.springframework.boot:spring-boot-starter-security')
   compile('org.springframework.boot:spring-boot-starter-web')
   testCompile('org.springframework.boot:spring-boot-starter-test')
   testCompile('org.springframework.security:spring-security-test')
   compile("org.springframework.security.oauth:spring-security-oauth2")
   compile('org.springframework.security:spring-security-jwt')
   compile("org.springframework.boot:spring-boot-starter-jdbc")
   compile("com.h2database:h2:1.4.191")  
}

Agora, no aplicativo Spring Boot principal, adicione as anotações @EnableAuthorizationServer e @EnableResourceServer para atuar como um servidor Auth e um servidor de recursos no mesmo aplicativo.

Além disso, você pode usar o código a seguir para escrever um ponto de extremidade HTTP simples para acessar a API com Spring Security usando o token JWT.

package com.tutorialspoint.websecurityapp;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@EnableAuthorizationServer
@EnableResourceServer
@RestController
public class WebsecurityappApplication {
   public static void main(String[] args) {
      SpringApplication.run(WebsecurityappApplication.class, args);
   }
   @RequestMapping(value = "/products")
   public String getProductName() {
      return "Honey";   
   }
}

Use o código a seguir para definir a classe POJO para armazenar as informações do usuário para autenticação.

package com.tutorialspoint.websecurityapp;

import java.util.ArrayList;
import java.util.Collection;
import org.springframework.security.core.GrantedAuthority;

public class UserEntity {
   private String username;
   private String password;
   private Collection<GrantedAuthority> grantedAuthoritiesList = new ArrayList<>();
   
   public String getPassword() {
      return password;
   }
   public void setPassword(String password) {
      this.password = password;
   }
   public Collection<GrantedAuthority> getGrantedAuthoritiesList() {
      return grantedAuthoritiesList;
   }
   public void setGrantedAuthoritiesList(Collection<GrantedAuthority> grantedAuthoritiesList) {
      this.grantedAuthoritiesList = grantedAuthoritiesList;
   }
   public String getUsername() {
      return username;
   }
   public void setUsername(String username) {
      this.username = username;
   }
}

Agora, use o código a seguir e defina a classe CustomUser que estende a classe org.springframework.security.core.userdetails.User para autenticação Spring Boot.

package com.tutorialspoint.websecurityapp;

import org.springframework.security.core.userdetails.User;

public class CustomUser extends User {
   private static final long serialVersionUID = 1L;
   public CustomUser(UserEntity user) {
      super(user.getUsername(), user.getPassword(), user.getGrantedAuthoritiesList());
   }
}

Você pode criar a classe @Repository para ler as informações do usuário do banco de dados e enviá-las para o serviço de usuário personalizado e também adicionar a autoridade concedida “ROLE_SYSTEMADMIN”.

package com.tutorialspoint.websecurityapp;

import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Repository;

@Repository
public class OAuthDao {
   @Autowired
   private JdbcTemplate jdbcTemplate;

   public UserEntity getUserDetails(String username) {
      Collection<GrantedAuthority> grantedAuthoritiesList = new ArrayList<>();
      String userSQLQuery = "SELECT * FROM USERS WHERE USERNAME=?";
      List<UserEntity> list = jdbcTemplate.query(userSQLQuery, new String[] { username },
         (ResultSet rs, int rowNum) -> {
         
         UserEntity user = new UserEntity();
         user.setUsername(username);
         user.setPassword(rs.getString("PASSWORD"));
         return user;
      });
      if (list.size() > 0) {
         GrantedAuthority grantedAuthority = new SimpleGrantedAuthority("ROLE_SYSTEMADMIN");
         grantedAuthoritiesList.add(grantedAuthority);
         list.get(0).setGrantedAuthoritiesList(grantedAuthoritiesList);
         return list.get(0);
      }
      return null;
   }
}

Você pode criar uma classe de serviço de detalhes de usuário personalizado que estenda org.springframework.security.core.userdetails.UserDetailsService para chamar a classe de repositório DAO conforme mostrado.

package com.tutorialspoint.websecurityapp;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

@Service
public class CustomDetailsService implements UserDetailsService {
   @Autowired
   OAuthDao oauthDao;

   @Override
   public CustomUser loadUserByUsername(final String username) throws UsernameNotFoundException {
      UserEntity userEntity = null;
      try {
         userEntity = oauthDao.getUserDetails(username);
         CustomUser customUser = new CustomUser(userEntity);
         return customUser;
      } catch (Exception e) {
         e.printStackTrace();
         throw new UsernameNotFoundException("User " + username + " was not found in the database");
      }
   }
}

Em seguida, crie uma classe @configuration para habilitar o Web Security, definindo o codificador de Senha (BCryptPasswordEncoder) e definindo o bean AuthenticationManager. A classe de configuração de segurança deve estender a classe WebSecurityConfigurerAdapter.

package com.tutorialspoint.websecurityapp;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
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.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
   @Autowired
   private CustomDetailsService customDetailsService;

   @Bean
   public PasswordEncoder encoder() {
      return new BCryptPasswordEncoder();
   }
   @Override
   @Autowired
   protected void configure(AuthenticationManagerBuilder auth) throws Exception {
      auth.userDetailsService(customDetailsService).passwordEncoder(encoder());
   }
   @Override
   protected void configure(HttpSecurity http) throws Exception {
      http.authorizeRequests().anyRequest().authenticated().and().sessionManagement()
         .sessionCreationPolicy(SessionCreationPolicy.NEVER);
   }
   @Override
   public void configure(WebSecurity web) throws Exception {
      web.ignoring();
   }
   @Override
   @Bean
   public AuthenticationManager authenticationManagerBean() throws Exception {
      return super.authenticationManagerBean();
   }
}

Agora, defina a classe de configuração OAuth2 para adicionar o ID do cliente, o segredo do cliente, defina o JwtAccessTokenConverter, a chave privada e a chave pública para a chave do assinante do token e a chave do verificador e configure o ClientDetailsServiceConfigurer para a validade do token com escopos.

package com.tutorialspoint.websecurityapp;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;

@Configuration
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {
   private String clientid = "tutorialspoint";
   private String clientSecret = "my-secret-key";
   private String privateKey = "private key";
   private String publicKey = "public key";

   @Autowired
   @Qualifier("authenticationManagerBean")
   private AuthenticationManager authenticationManager;
   
   @Bean
   public JwtAccessTokenConverter tokenEnhancer() {
      JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
      converter.setSigningKey(privateKey);
      converter.setVerifierKey(publicKey);
      return converter;
   }
   @Bean
   public JwtTokenStore tokenStore() {
      return new JwtTokenStore(tokenEnhancer());
   }
   @Override
   public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
      endpoints.authenticationManager(authenticationManager).tokenStore(tokenStore())
      .accessTokenConverter(tokenEnhancer());
   }
   @Override
   public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
      security.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");
   }
   @Override
   public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
      clients.inMemory().withClient(clientid).secret(clientSecret).scopes("read", "write")
         .authorizedGrantTypes("password", "refresh_token").accessTokenValiditySeconds(20000)
         .refreshTokenValiditySeconds(20000);

   }
}

Agora, crie uma chave privada e uma chave pública usando o openssl.

Você pode usar os seguintes comandos para gerar a chave privada.

openssl genrsa -out jwt.pem 2048
openssl rsa -in jwt.pem

Você pode usar Para geração de chave pública, use os comandos abaixo.

openssl rsa -in jwt.pem -pubout

Para a versão do Spring Boot posterior a 1.5, adicione a propriedade abaixo em seu arquivo application.properties para definir a ordem do filtro de recursos OAuth2.

security.oauth2.resource.filter-order=3

Os usuários do arquivo YAML podem adicionar a propriedade abaixo no arquivo YAML.

security:
   oauth2:
      resource:
         filter-order: 3

Agora, crie o arquivo schema.sql e data.sql nos recursos do classpath src/main/resources/directory para conectar o aplicativo ao banco de dados H2.

O arquivo schema.sql é mostrado -

CREATE TABLE USERS (ID INT PRIMARY KEY, USERNAME VARCHAR(45), PASSWORD VARCHAR(60));

O arquivo data.sql é mostrado -

INSERT INTO USERS (ID, USERNAME,PASSWORD) VALUES (
   1, '[email protected]','$2a$08$fL7u5xcvsZl78su29x1ti.dxI.9rYO8t0q5wk2ROJ.1cdR53bmaVG');

INSERT INTO USERS (ID, USERNAME,PASSWORD) VALUES (
   2, '[email protected]','$2a$08$fL7u5xcvsZl78su29x1ti.dxI.9rYO8t0q5wk2ROJ.1cdR53bmaVG');

Note - A senha deve ser armazenada no formato do codificador Bcrypt na tabela do banco de dados.

Você pode criar um arquivo JAR executável e executar o aplicativo Spring Boot usando os seguintes comandos Maven ou Gradle.

Para Maven, você pode usar o comando fornecido abaixo -

mvn clean install

Após “BUILD SUCCESS”, você pode encontrar o arquivo JAR no diretório de destino.

Para Gradle, você pode usar o comando conforme mostrado -

gradle clean build

Depois de “BUILD SUCCESSFUL”, você pode encontrar o arquivo JAR no diretório build / libs.

Agora, execute o arquivo JAR usando o comando mostrado aqui -

java –jar <JARFILE>

O aplicativo é iniciado na porta 8080 do Tomcat.

Agora acesse a URL do método POST via POSTMAN para obter o token OAUTH2.

http://localhost:8080/oauth/token

Agora, adicione os cabeçalhos de solicitação da seguinte forma -

  • Authorization - Autenticação básica com seu ID de cliente e segredo do cliente.

  • Content Type - application / x-www-form-urlencoded

Agora, adicione os parâmetros de solicitação da seguinte forma -

  • grant_type = senha
  • nome de usuário = seu nome de usuário
  • senha = sua senha

Agora, acesse a API e obtenha o access_token conforme mostrado -

Agora, acesse a API do servidor de recursos com token de acesso do portador no cabeçalho da solicitação, conforme mostrado.

Então você pode ver a saída conforme mostrado abaixo -