sexta-feira, 15 de março de 2024

Criando uma API em GO com Swagger e MySQL para uma aplicação de TO-DO

Neste tutorial, vamos criar uma API em GO para uma aplicação de TO-DO, utilizando Swagger para documentação e MySQL como banco de dados. Vamos seguir a arquitetura hexagonal e orientação a objetos para manter o código organizado e fácil de manter. 

Passo 1: Configuração do ambiente


Certifique-se de ter o GO instalado em seu sistema. Você também precisará do MySQL instalado e configurado, você pode usar o post "Desenvolvendo uma API TO-DO com Nest.js, Swagger, MySQL e Docker" para usar o mesmo docker do MySQL e estrutura de tabela.

Passo 2: Crie o banco de dados e a tabela


Caso não esteja usando o mesmo docker para o MySQL conforme mencionado no passo anterior, vamos criar o banco de dados e a tabela necessária para nossa aplicação de TO-DO. Execute o seguinte script SQL:

CREATE DATABASE IF NOT EXISTS todo_db;
USE todo_db;

CREATE TABLE IF NOT EXISTS tasks (
    id INT AUTO_INCREMENT PRIMARY KEY,
    title VARCHAR(255) NOT NULL,
    description TEXT
);

Passo 3: Crie a estrutura do projeto


Crie a estrutura do projeto da seguinte forma:

go-todo-api/
├── docs/
├── src/
│   ├── handlers/
│   │   └── todo_handler.go
│   ├── models/
│   │   └── todo.go
│   ├── repositories/
│   │   └── todo_repository.go
│   ├── router/
│   │   └── router.go
│   └── config/
│       ├── db.go
│       └── swagger.go
├── main.mod
└── go.mod

Gere o arquivo go.mod executando o comando:

go init go-todo-api

Passo 4: Instale as dependências


Vamos precisar do pacote gorilla/mux para lidar com as rotas da API, swaggo/http-swagger para a documentação com Swagger e go-sql-driver/mysql para o acesso ao MySQL. Instale-os usando o seguinte comando:

go get -u github.com/gorilla/mux
go get -u github.com/swaggo/http-swagger/v2
go get -u github.com/go-sql-driver/mysql

Passo 5: Implemente a lógica da aplicação


Vamos implementar a lógica da aplicação seguindo a arquitetura hexagonal. Aqui está um exemplo básico de como seria a estrutura do código:

models/todo.go

package models

type Task struct {
    ID          int    `json:"id"`
    Title       string `json:"title"`
    Description string `json:"description"`
}

repositories/todo_repository.go

package repositories

import (
    "database/sql"
    "log"

    "go-todo-api/src/models"
)

type TodoRepository struct {
    db *sql.DB
}

func NewTodoRepository(db *sql.DB) TodoRepository {
    return TodoRepository{
        db: db,
    }
}

func (r *TodoRepository) Create(task *models.Task) error {
    query := "INSERT INTO tasks (title, description) VALUES (?, ?)"
    _, err := r.db.Exec(query, task.Title, task.Description)
    if err != nil {
        log.Printf("Erro ao inserir tarefa no banco de dados: %v", err)
        return err
    }
    return nil
}

func (r *TodoRepository) Update(id int, task *models.Task) error {
    query := "UPDATE tasks SET title = ?, description = ? WHERE id = ?"
    _, err := r.db.Exec(query, task.Title, task.Description, id)
    if err != nil {
        log.Printf("Erro ao atualizar tarefa no banco de dados: %v", err)
        return err
    }
    return nil
}

func (r *TodoRepository) Delete(id int) error {
    query := "DELETE FROM tasks WHERE id = ?"
    _, err := r.db.Exec(query, id)
    if err != nil {
        log.Printf("Erro ao excluir tarefa no banco de dados: %v", err)
        return err
    }
    return nil
}

func (r *TodoRepository) GetByID(id int) (*models.Task, error) {
    query := "SELECT id, title, description FROM tasks WHERE id = ?"
    row := r.db.QueryRow(query, id)

    var task models.Task
    err := row.Scan(&task.ID, &task.Title, &task.Description)
    if err != nil {
        log.Printf("Erro ao obter tarefa do banco de dados: %v", err)
        return nil, err
    }
    return &task, nil
}

func (r *TodoRepository) GetAll() ([]*models.Task, error) {
    query := "SELECT id, title, description FROM tasks"
    rows, err := r.db.Query(query)
    if err != nil {
        log.Printf("Erro ao obter tarefas do banco de dados: %v", err)
        return nil, err
    }
    defer rows.Close()

    var tasks []*models.Task
    for rows.Next() {
        var task models.Task
        err := rows.Scan(&task.ID, &task.Title, &task.Description)
        if err != nil {
            log.Printf("Erro ao ler linha de tarefa do banco de dados: %v", err)
            return nil, err
        }
        tasks = append(tasks, &task)
    }

    return tasks, nil
}


handlers/todo_handler.go

package handlers

import (
	"encoding/json"
	"net/http"
	"strconv"

	"github.com/gorilla/mux"
	"go-todo-api/src/models"
	"go-todo-api/src/repositories"
)

type TodoHandler struct {
	Repository repositories.TodoRepository
}

// @Summary      Create a task
// @Description  create a task
// @Tags         tasks
// @Accept       json
// @Produce      json
// @Param        task   body   models.Task true  "Task"
// @Success      201  {object}  models.Task
// @Failure      400  {object}  string
// @Failure      500  {object}  string
// @Router       /tasks [post]
func (h *TodoHandler) CreateTask(w http.ResponseWriter, r *http.Request) {
	var task models.Task
	err := json.NewDecoder(r.Body).Decode(&task)
	if err != nil {
		http.Error(w, "Erro ao decodificar JSON", http.StatusBadRequest)
		return
	}

	err = h.Repository.Create(&task)
	if err != nil {
		http.Error(w, "Erro ao criar tarefa", http.StatusInternalServerError)
		return
	}

	w.WriteHeader(http.StatusCreated)
	json.NewEncoder(w).Encode(task)
}

// @Summary      Update a task
// @Description  update a task
// @Tags         tasks
// @Accept       json
// @Produce      json
// @Param        id   path      int  true  "Task ID"
// @Param        task   body   models.Task true  "Task"
// @Success      200  {object}  models.Task
// @Failure      400  {object}  string
// @Failure      500  {object}  string
// @Router       /tasks/{id} [put]
func (h *TodoHandler) UpdateTask(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	id, err := strconv.Atoi(vars["id"])
	if err != nil {
		http.Error(w, "ID inválido", http.StatusBadRequest)
		return
	}

	var task models.Task
	err = json.NewDecoder(r.Body).Decode(&task)
	if err != nil {
		http.Error(w, "Erro ao decodificar JSON", http.StatusBadRequest)
		return
	}

	err = h.Repository.Update(id, &task)
	if err != nil {
		http.Error(w, "Erro ao atualizar tarefa", http.StatusInternalServerError)
		return
	}

	json.NewEncoder(w).Encode(task)
}

// @Summary      Delete a task
// @Description  delete a task
// @Tags         tasks
// @Accept       json
// @Produce      json
// @Param        id   path      int  true  "Task ID"
// @Success      204
// @Failure      400  {object}  string
// @Failure      500  {object}  string
// @Router       /tasks/{id} [delete]
func (h *TodoHandler) DeleteTask(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	id, err := strconv.Atoi(vars["id"])
	if err != nil {
		http.Error(w, "ID inválido", http.StatusBadRequest)
		return
	}

	err = h.Repository.Delete(id)
	if err != nil {
		http.Error(w, "Erro ao excluir tarefa", http.StatusInternalServerError)
		return
	}

	w.WriteHeader(http.StatusNoContent)
}

// @Summary      Show a task
// @Description  get task by ID
// @Tags         tasks
// @Accept       json
// @Produce      json
// @Param        id   path      int  true  "Task ID"
// @Success      200  {object}  models.Task
// @Failure      404  {object}  string
// @Router       /tasks/{id} [get]
func (h *TodoHandler) GetTaskByID(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	id, err := strconv.Atoi(vars["id"])
	if err != nil {
		http.Error(w, "ID inválido", http.StatusBadRequest)
		return
	}

	task, err := h.Repository.GetByID(id)
	if err != nil {
		http.Error(w, "Tarefa não encontrada", http.StatusNotFound)
		return
	}

	json.NewEncoder(w).Encode(task)
}

// @Summary 	 Get Task List
// @Description  get all tasks
// @Tags         tasks
// @Accept   	 json
// @Produce  	 json
// @Success      200  {object}  []models.Task
// @Failure      500  {object}  string
// @Router  	/tasks [get]
func (h *TodoHandler) GetAllTasks(w http.ResponseWriter, r *http.Request) {
	tasks, err := h.Repository.GetAll()
	if err != nil {
		http.Error(w, "Erro ao obter tarefas", http.StatusInternalServerError)
		return
	}

	json.NewEncoder(w).Encode(tasks)
}


router/router.go

package router

import (
    "github.com/gorilla/mux"
    "go-todo-api/src/handlers"
)

func NewRouter(handler *handlers.TodoHandler) *mux.Router {
    r := mux.NewRouter()
    s := r.PathPrefix("/todo/v1").Subrouter()
    s.HandleFunc("/tasks", handler.CreateTask).Methods("POST")
    s.HandleFunc("/tasks/{id}", handler.UpdateTask).Methods("PUT")
    s.HandleFunc("/tasks/{id}", handler.DeleteTask).Methods("DELETE")
    s.HandleFunc("/tasks/{id}", handler.GetTaskByID).Methods("GET")
    s.HandleFunc("/tasks", handler.GetAllTasks).Methods("GET")
    return s
}

A implementação da API está pronta, agora vamos as configurações para podermos gerar o Swagger, conexão com o banco de dados e execução da API

Passo 6: Configuração da conexão com o banco de dados

config/db.go

package config

import (
    "database/sql"
    "fmt"
    "log"

    _ "github.com/go-sql-driver/mysql"
)

var DB *sql.DB

func InitDB() {
    dataSourceName := "seu_user:sua_senha@tcp(localhost:3306)/todo_db"
    var err error
    DB, err = sql.Open("mysql", dataSourceName)
    if err != nil {
        log.Fatalf("Erro ao conectar ao banco de dados: %v", err)
    }

    err = DB.Ping()
    if err != nil {
        log.Fatalf("Erro ao pingar o banco de dados: %v", err)
    }

    fmt.Println("Conexão com o banco de dados estabelecida com sucesso")
}

func CloseDB() {
    err := DB.Close()
    if err != nil {
        log.Fatalf("Erro ao fechar conexão com o banco de dados: %v", err)
    }

    fmt.Println("Conexão com o banco de dados fechada com sucesso")
}

Passo 7: Configuração do Swagger


config/swagger.go

package config

import (
    "net/http"

    httpSwagger "github.com/swaggo/http-swagger/v2"
	_ "go-todo-api/docs"
)

func SetupSwagger() http.Handler {
    return httpSwagger.Handler(
        httpSwagger.URL("http://localhost:8080/todo/v1/swagger/doc.json"), // URL para acessar a documentação do Swagger
    )
}

Passo 8: Implementação da função main


Implemente a função main no arquivo main.go:

package main

import (
    "log"
    "net/http"

	db "go-todo-api/src/config"
	swagger "go-todo-api/src/config"
    "go-todo-api/src/handlers"
    "go-todo-api/src/repositories"
    "go-todo-api/src/router"

)

// @title           TODO API
// @version         1.0
// @description     This is a sample todo api.
// @termsOfService  http://swagger.io/terms/

// @license.name  Apache 2.0
// @license.url   http://www.apache.org/licenses/LICENSE-2.0.html

// @host      localhost:8080
// @BasePath  /todo/v1

// @externalDocs.description  OpenAPI
// @externalDocs.url          https://swagger.io/resources/open-api/
func main() {
	// Inicializa conexão com o banco de dados
    db.InitDB()
    defer db.CloseDB()

    // Configuração do repositório
    repo := repositories.NewTodoRepository(db.DB)

    // Configuração do handler
    handler := &handlers.TodoHandler{
        Repository: repo,
    }

    // Configuração do roteamento
    r := router.NewRouter(handler)

    // Configuração do Swagger
	r.PathPrefix("/swagger/").Handler(swagger.SetupSwagger()).Methods(http.MethodGet)


    // Inicialização do servidor
    log.Println("Servidor rodando em http://localhost:8080")
    log.Fatal(http.ListenAndServe(":8080", r))
}

Passo 9: Gerando a documentação da API


As documentações das rotas se encotram no handler e a documentação da API está no main, vamos executar o comando abaixo para gerar os arquivos na pasta docs:
Instalação do cmd swag

go install github.com/swaggo/swag/cmd/swag@latest

Geração da documentação da API

swag init

Passo 10: Executando a aplicação


Para executar a aplicação, basta rodar o seguinte comando abaixo:

go run main.go


Acesse o Swagger em http://localhost:8080/todo/v1/swagger/index.html para visualizar e testar as operações CRUD da sua API TO-DO.

Agora você tem uma API em GO para uma aplicação de TO-DO, com Swagger para documentação e MySQL como banco de dados. Você pode usar esse exemplo como base e expandir conforme necessário para atender às suas necessidades.

O projeto deste post está no meu github link.

segunda-feira, 8 de janeiro de 2024

Desenvolvendo uma API TO-DO com Nest.js, Swagger, MySQL e Docker

 Este guia apresenta o passo a passo para construir uma API de lista de tarefas (TO-DO) usando Nest.js, Swagger para documentação, integração com MySQL e a implementação de Docker para facilitar a configuração e execução.

Passo 1: Configurando o Ambiente

Antes de iniciarmos, certifique-se de ter o Node.js e o npm instalados. Em seguida, instale o Nest.js globalmente utilizando o seguinte comando:

npm install -g @nestjs/cli

Agora, crie um novo projeto Nest.js:

nest new nest-todo-api
cd nest-todo-api

Vamos remover os arquivos abaixo que não serão necessários.

rm ./src/app.controller.spec.ts
rm ./src/app.controller.ts
rm ./src/app.service.ts

Passo 2: Instalando Dependências

Instale as dependências necessárias para a nossa API:

npm install @nestjs/swagger swagger-ui-express mysql2 typeorm @nestjs/typeorm

Passo 3: Criando o Módulo, Controller, Service e a Entidade para Tarefas

Agora, vamos criar um módulo, controller, service para as tarefas e uma entidade para representar a tabela no banco de dados:

nest generate module task
nest generate controller task
nest generate service task
nest generate class task/task.entity

Passo 4: Configurando o Banco de Dados e Entidade

Vamos configurar a conexão com o MySQL utilizando o TypeORM. No arquivo src/app.module.ts, adicione o seguinte trecho para configurar o banco de dados:

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { TaskModule } from './task/task.module';

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'mysql',
      host: 'mysql',
      port: 3306,
      username: 'seu_usuario',
      password: 'sua_senha',
      database: 'todo_db',
      entities: [__dirname + '/**/*.entity{.ts,.js}'],
      synchronize: true,
    }),
    TaskModule,
  ],
})
export class AppModule {}

No arquivo task.entity.ts, adicione a seguinte definição de entidade:

import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';

@Entity()
export class Task {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  title: string;

  @Column({ nullable: true })
  description: string;
}

No arquivo task.module.ts, adicione a seguinte definição:

import { Module } from '@nestjs/common';
import { TaskController } from './task.controller';
import { TaskService } from './task.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Task } from './task.entity/task.entity';

@Module({
  imports: [TypeOrmModule.forFeature([Task])],
  controllers: [TaskController],
  providers: [TaskService]
})
export class TaskModule {}

Passo 5: Implementando Operações CRUD

O service é responsável por interagir com o banco de dados para executar operações relacionadas às tarefas. Adicione a seguinte implementação em task.service.ts:

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Task } from './task.entity/task.entity';

@Injectable()
export class TaskService {
  constructor(
    @InjectRepository(Task)
    private taskRepository: Repository&ltTask&gt,
  ) {}

  async findAll(): Promise&ltTask[]&gt {
    return await this.taskRepository.find();
  }

  async create(task: Task): Promise&ltTask&gt {
    return await this.taskRepository.save(task);
  }

  async update(id: number, updatedTask: Task): Promise&ltTask&gt {
    const existingTask = await this.taskRepository.findOne(id);

    if (!existingTask) {
      throw new Error('Tarefa não encontrada');
    }

    existingTask.title = updatedTask.title;
    existingTask.description = updatedTask.description;

    return await this.taskRepository.save(existingTask);
  }

  async remove(id: number): Promise&ltvoid&gt {
    const existingTask = await this.taskRepository.findOne(id);

    if (!existingTask) {
      throw new Error('Tarefa não encontrada');
    }

    await this.taskRepository.remove(existingTask);
  }
}

No arquivo task.controller.ts, implemente as operações CRUD:

import { Controller, Get, Post, Body, Put, Param, Delete } from '@nestjs/common';
import { TaskService } from './task.service';
import { Task } from './task.entity/task.entity';

@Controller('tasks')
export class TaskController {
  constructor(private readonly taskService: TaskService) {}

  @Get()
  findAll(): Promise&ltTask[]&gt {
    return this.taskService.findAll();
  }

  @Post()
  create(@Body() task: Task): Promise&ltTask&gt {
    return this.taskService.create(task);
  }

  @Put(':id')
  update(@Param('id') id: string, @Body() task: Task): Promise&ltTask&gt {
    return this.taskService.update(+id, task);
  }

  @Delete(':id')
  remove(@Param('id') id: string): Promise&ltvoid&gt {
    return this.taskService.remove(+id);
  }
}

Passo 6: Integrando Swagger

No arquivo main.ts, adicione as configurações do Swagger:

import { NestFactory } from '@nestjs/core';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  const config = new DocumentBuilder()
    .setTitle('API TO-DO Nest.js')
    .setDescription('Documentação da API de TO-DO')
    .setVersion('1.0.0')
    .build();

  const document = SwaggerModule.createDocument(app, config);
  SwaggerModule.setup('api', app, document);

  await app.listen(3000);
}
bootstrap();

Passo 7: Configurando Dockerfile e Compose

Crie o arquivo Dockerfile na raiz do projeto e adicione as configurações:

FROM node:20

WORKDIR /app

COPY package*.json ./

RUN npm install

COPY . .

EXPOSE 3000

CMD ["npm", "run", "start"]

Crie o arquivo docker-compose.yml na raiz do projeto e adicione as configurações:

version: '3'
services:
  # MySQL Service
  mysql:
    image: mysql:latest
    environment:
      MYSQL_ROOT_PASSWORD: root_senha
      MYSQL_DATABASE: todo_db
      MYSQL_USER: seu_usuario
      MYSQL_PASSWORD: sua_senha
    ports:
      - "3306:3306"
    networks:
      - app-network

  # Nest.js App Service
  nest-app:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "3000:3000"
    depends_on:
      - mysql
    networks:
      - app-network

networks:
  app-network:
    driver: bridge

Passo 8: Executando o Projeto

Execute o comando abaixo:

docker compose up

Acesse o Swagger em http://localhost:3000/api para visualizar e testar as operações CRUD da sua API TO-DO.

O projeto deste post está no meu github link.

quinta-feira, 25 de fevereiro de 2021

Unit Test com Node, Typescript e Jest

Vamos aproveitar o post anterior (AWS Lambda, Node e Typescript), e vamos incluir no projeto o "jest", com ele conseguimos criar unit test.

Passos:

1) Inserir dependências

npm install --save-dev babel-jest @babel/core @babel/preset-env @babel/preset-typescript @types/jest babel-jest jest


2)  No elemento "scripts" do arquivo "package.json" altere para "jest" o valor do elemento "test"

3) Vamos incluir o type "jest" no arquivo "tsconfig.json"

4) Para a compilação do projeto, vamos deixar de fora o diretório que irá conter os arquivos de testes, para isso, vamos colocar o elemento "exclude" no arquivo "tsconfig.json"

5) Agora vamos criar o arquivo de configuração do babel, na raiz do projeto crie o arquivo "babel.config.js" com as seguintes configs:

6) Crie o diretório "test" e o arquivo "hello.world.test.ts"

7) Agora execute o comando

npm run test


Site do Jest - link 

O projeto deste post está no meu github link.

sexta-feira, 19 de fevereiro de 2021

AWS Lambda, Node e Typescript

Neste post vou mostrar como criar um AWS Lambda utilizando Node e Typescript. 

O projeto será organizado para o exemplo deste post e para entendimento que o mesmo projeto possa ser reaproveitado (módulos) em outros lambdas, por exemplo, um projeto que componha todas as operações/funções (lambda) de uma API.

Passos: 

1) Instalação npm:

sudo apt-get install npm

2) Instalação Node:

sudo npm cache clean -f
sudo npm install -g n
sudo n stable

3) Instalação typescript:

sudo npm install -g typescript

4) - Criação do projeto:

mkdir LambdaNodeTypescript
cd LambdaNodeTypescript
tsc --init
npm init

5) Com o projeto criado, abra o mesmo com alguma ferramenta de desenvolvimento de sua preferencia, neste exemplo será utilizado o Visual Studio Code. Execute:

code .

6) Vamos continuar as configurações agora usando o terminal do Visual Studio Code. Vamos acrescentar as seguintes dependências do projeto para desenvolvimento com o Typescript. execute os seguintes comandos:

npm install @types/node --save-dev
npm install aws-sdk --save-dev
npm install @types/aws-lambda --save-dev


7) Depois vamos configurar a parte de build, agora vamos criar as classes que irão compor nosso projeto. Vamos criar um modulo de controller, para isso crie um dir "controller" e o seguinte arquivo "hello.world.controller.ts". Nesta classe que iremos deixar nossas regra de negocio.


8) Vamos agora criar o handler, crie um dir com o nome "handler", e crie o arquivo "hello.world.handler.ts".


9) Vamos criar o index, para podermos declarar o método de execução do lambda, que no caso do exemplo é o "execute".

10) Agora vamos configurar o build e test do projeto, para o build, vamos alterar o arquivo "tsconfig.json" da seguinte maneira:


Agora vamos alterar o arquivo "package.json":


11) Execute o comando para gerar o arquivo zip.

npm run all

12) Vamos criar um AWS Lambda, acesse a console, selecione "Lambda" e depois "Create function":

 

13) Em "Function Name" preencha com "LambdaNodeTypescript", selecione a ultima runtime do node disponível e selecione a role para execução e click em "Create Function"


14) Com a função criada, vamos fazer o upload do zip do projeto, em  "Action" selecione "Upload a .zip file"


15) Click em "Upload", selecione o arquivo zip do projeto e click em "Save"


16) Vamos apontar agora para o método "execute", vá em "Runtime settings" e click em "Edit" 

17) Em "Handler" altera para "index.execute" e click em "Save"

18) Vamos criar um evento de teste para o nosso Lambda, selecione "Configure test events"

19) Em "Event name" preencha com "test1" e passe um json em branco e click em "Create"

20) Vamos testar, certifique-se que o evento de teste criado anteriormente está selecionado e click em "Test".


O projeto deste post está no meu github link.

segunda-feira, 24 de fevereiro de 2020

Criando Dominio Compacto

A criação de um domínio compacto vem desabilitado, neste post irei demonstrar como habilitar e criar um domínio SOA compacto.

Crie os schemas utilizando o RCU. (os passos são iguais ao do post Criando um Dominio no Oracle SOA Suite 12c.)

cd $ORACLE_HOME/oracle_common/bin
./rcu



Execute:

cd $ORACLE_HOME/oracle_common/common/bin
export CONFIG_JVM_ARGS=-Dcom.oracle.cie.config.showProfile=true
./config.sh

Selecione "criar um domínio compacto", e selecione a localização do domínio.


Selecione "Oracle SOA Suite - <version>"


Daqui em diante, os passos são iguais ao do post Criando um Dominio no Oracle SOA Suite 12c.







quinta-feira, 7 de fevereiro de 2019

Spring Security Oauth2 com Ldap - Resource Server

No post anterior (link), criamos o projeto para a geração dos token com o Spring Security Oauth2. Agora vamos criar o projeto que irá conter um Controller restringindo na operação "/test" somente usuários que tiver a role ROLE_DEVELOPERS.

Vamos a configuração do projeto. Crie um projeto, e no pom.xml acrescente as seguintes dependências:


Crie uma classe para as configurações de base de dados, ex.: JdbcConfiguration


Crie uma classe que vai estender a classe ResourceServerConfigurerAdapter e iremos sobrescrever os 2 métodos configure. Nestes métodos estarão as configurações necessárias para a validação do token, clientId e do usuário logado e as regras do HTTP Security. Ex.: AuthorizationClientConfiguration

Acrescente a notação @EnableResourceServer para habilitar o filtro Spring Security que autentique as request por meio do token do Oauth2.


Observe que utilizamos o bean TokenStore configurado na classe JdbcConfiguration.

Crie uma classe que vai estender a classe GlobalMethodSecurityConfiguration e iremos sobrescrever 1 método. Ex.: MethodSecurityConfiguration

Acrescente a notação @EnableGlobalMethodSecurity para habilitar a segurança do método global Spring Security.


Crie uma classe que vai implementar a classe WebMvcConfigurer. Ex.: ResourceServerWebConfiguration

Acrescente a notação @EnableWebSecurity para ter as configurações do Spring Security.


Agora vamos criar o Controller, crie uma classe ex.: TestController, acrescente a notação @Controller. E crie um método conforme o exemplo. O ponto principal é a notação @PreAuthorize, onde definimos os scopes e roles que terão permissão de execução.


Vamos ao testes.
User: developer

Gerando um token:


Executando o método /test


User: tester


Executando o método /test e acesso negado:


Log:

O projeto deste post está no meu github link.

quarta-feira, 6 de fevereiro de 2019

Spring Security Oauth2 com Ldap - Authorization Server

Vou demonstrar neste post, como configurar o Spring Security Oauth2 com o LDAP (Veja como configurar o OpenLDAP aqui), neste post vamos configurar somente o Authorization Server.

Temos a opção do inMemory para o controle do client e token, mas vou demonstrar utilizando este controle com base de dados.

Rode o script OAUTH2_SPRING_SEC.sql no Oracle XE, e terá as seguintes tabelas geradas.


Faça um select na tabela OAUTH_CLIENT_DETAILS e vai ter o clientId para test.


Vamos a configuração do projeto. Crie um projeto, e no pom.xml acrescente as seguintes dependências:


A lib do ojdbc pode ser baixada em link. e para configurar a lib execute:

mvn install:install-file -DgroupId=com.oracle -DartifactId=ojdbc7 -Dversion=12.1.0 -Dpackaging=jar -Dfile=ojdbc7.jar

Agora vamos as classe de configuração:

Crie uma classe para as configurações de base de dados, ex.: JdbcConfiguration


Crie uma classe que vai estender a classe AuthorizationServerConfigurerAdapter e iremos sobrescrever os 3 métodos configure. Nestes métodos estarão as configurações necessárias para a validação do token, clientId e do usuário logado. Ex.: AuthorizationServerConfiguration

Acrescente a notação @EnableAuthorizationServer para habilitar o Authorization Server no contexto da aplicação.


Observe que utilizamos os beans DataSource e o TokenStore configurados na classe JdbcConfiguration.

Crie uma classe que vai estender a classe WebSecurityConfigurerAdapter e iremos sobrescrever 4 métodos. Ex.: ServerSecurityConfiguration

O primeiro método a ser sobrescrito vai conter as regras para HTTP Security.


O segundo método a ser sobrescrito contem as regras para a autenticação do usuário no ldap.


O terceiro método a ser sobrescrito serve para retornar o bean UserDetailsService que é utilizado na classe AuthorizationServerConfiguration.


E o quarto método a ser sobrescrito serve para retornar o bean AuthenticationManager que é utilizado na classe AuthorizationServerConfiguration.


Agora vamos aos métodos necessários para o correto funcionamento com o Ldap. O método abaixo irá preencher o atributo "authorities" com os grupos que o usuário  é membro.


O método abaixo, utilizado para a conexão com o Ldap.


Testando:
/oauth/token
grant_type: password



/oauth/check_token

/oauth/token
grant_type: refresh_token


Log:

Validação do clientId na base de dados:


Autenticação do usuário no Ldap.


O projeto deste post está no meu github link.