Skip to content

Instantly share code, notes, and snippets.

@faermanj
Last active July 12, 2023 14:12
Show Gist options
  • Save faermanj/270a8a8ab817f95fc2e350ec2d481bd2 to your computer and use it in GitHub Desktop.
Save faermanj/270a8a8ab817f95fc2e350ec2d481bd2 to your computer and use it in GitHub Desktop.
TDC 2022 - Aplicacoes Seguras com Quarkus

Aplicações Seguras com Quarkus e Keycloak

Julio @faermanj
https://faermanj.me
https://caravana.cluoud
https://gist.github.com/faermanj/270a8a8ab817f95fc2e350ec2d481bd2

Agenda

Aplicação estilo "microserviços" ("PetCare")

                                       ┌────────┐
                                   ┌───┴──────┐ │
┌────────┐      ┌─────────┐      ┌─┴────────┐ │ │
│  User  ├────► │   APP   ├────► │   API    │ ├─┘
│ Browser│      │ Vaadin  │      │  Quarkus ├─┘
└────────┘      └────┬────┘      └─┬──┬─────┘
                     │             │  │
                     │             │  │         ┌──────────────────┐
                     │             │  └────────►│  Database MySQL  │
                     │             │            └──────────────────┘
                     │             │
                     │             │            ┌──────────────────┐
                     │             └───────────►│  Auth*           │
                     │           OpenId Connect │  Keycloak        │
                     └─────────────────────────►└──────────────────┘

Criar o repositorio do projeto

git clone # URL do repositorio
cd # diretorio do projeto

Instalar o Quarkus CLI

https://quarkus.io/guides/cli-tooling

quarkus version

Criar o módulo API

quarkus create app petcare:ptc-api:0.0.1 \
    --java=17 \
    --package-name=petcare.api

Iniciando o módulo API

cd ptc-api
quarkus dev

Testando o módulo API

curl http://localhost:8080/hello
code src/main/java/petcare/api/GreetingResource.java

Quarkus Dev Console http://localhost:8080/q/dev

Adicionar as extensões do Quarkus

JSON

quarkus ext add resteasy-jackson

Hello JSON

@GET
@Path("/json")
@Produces(MediaType.APPLICATION_JSON)
public Map<String, String> getJSON() {
    return Map.of("message", "Hello RESTEasy");
}

Banco de Dados

quarkus ext add agroal
quarkus ext add jdbc-mysql

Datasource Resource

import javax.inject.Inject;
import javax.sql.DataSource;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.WebApplicationException;
import java.sql.SQLException;

@Path("/ds")
public class DatasourceResource {
    @Inject
    DataSource datasource;

    @GET
    public String getDS() {
        try (var con = datasource.getConnection();
             var stmt = con.createStatement();
             var rs = stmt.executeQuery("SELECT 1+1")) {
            rs.next();
            return rs.getString(1);
        } catch (SQLException e) {
            throw new WebApplicationException(e);
        }
    }
}
curl http://localhost:8080/ds

Quarkus Dev Service

application-dev.properties Referencia: https://quarkus.io/guides/all-config

# DevServices
quarkus.devservices.enabled=true
quarkus.live-reload.instrumentation=true

# Database  
quarkus.datasource.devservices.port=13371
quarkus.datasource.username=admin
quarkus.datasource.password=Masterkey123
quarkus.hibernate-orm.database.generation=drop-and-create

# Logging
quarkus.hibernate-orm.log.sql=true
quarkus.http.access-log.enabled=true

# Utilities
mysql -h127.0.0.1 -P13371 -uadmin -pMasterkey123 default

Mapeamento Objeto-Relacional

quarkus ext add hibernate-orm
quarkus ext add hibernate-orm-panache

Pet.java

@Entity
public class Pet extends PanacheEntity {
    String name;
    String cuteName;
    LocalDate birthday;
    // Constructor
    // Getters
}

JPA Resource

@Path("/jpa")
public class JPAResource {
    @Inject
    EntityManager em;

    @Path("/new")
    @GET
    @Transactional
    public Pet getNew(){
        var pet = new Pet("Dexter",
                "Maxwell", 
                LocalDate.of(2020,1,1));
        em.persist(pet);
        return pet;
    }
}
curl http://localhost:8080/jpa/new

Panache Resource

@Path("/panache")
public class PanacheResource {
    @Path("new")
    @GET
    @Transactional
    public Pet getNew(){
        var pet = new Pet("Bjorn",
                "Pikeu",
                LocalDate.of(2021, 1, 1));
        pet.persist();
        return pet;
    }

    @Path("find")
    @GET
    @Transactional
    public List<Pet> findByName(@QueryParam("name") String name){
        return Pet.find("name", name).list();
    }
}
curl http://localhost:8080/panache/new
curl http://localhost:8080/panache/find?name=Bjorn

Autenticação e autorização

quarkus ext add oidc
quarkus ext add keycloak-authorization

application-dev.properties

# Keycloak
quarkus.keycloak.devservices.enabled=true
quarkus.keycloak.devservices.shared=true
quarkus.keycloak.devservices.port=13370

quarkus.oidc.client-id=quarkus-app
quarkus.oidc.client-secret=secret

quarkus.keycloak.devservices.realm-name=petcare-realm
quarkus.keycloak.devservices.users.alice=Masterkey123
quarkus.keycloak.devservices.roles.alice=user

Testando a autenticação

curl -s -X POST 'http://localhost:13370/realms/petcare-realm/protocol/openid-connect/token' \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'username=alice' \
--data-urlencode 'password=Masterkey123' \
--data-urlencode 'grant_type=password' \
--data-urlencode 'client_id=quarkus-app' \
--data-urlencode 'client_secret=secret'  | jq

WhoAmIResource

import io.quarkus.security.identity.SecurityIdentity;
import org.jboss.resteasy.annotations.cache.NoCache;

import javax.annotation.security.RolesAllowed;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;

@Path("/user/whoami")
public class WhoAmIResource {

    @Inject
    SecurityIdentity securityIdentity;

    @GET
    @RolesAllowed("user")
    @NoCache
    public String me() {
        var username = securityIdentity.getPrincipal().getName();
        return username;
    }
}

Enviando uma requisição com o token

export ACCESS_TOKEN=$(curl -s --location --request POST 'http://localhost:13370/realms/petcare-realm/protocol/openid-connect/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'username=alice' \
--data-urlencode 'password=Masterkey123' \
--data-urlencode 'grant_type=password' \
--data-urlencode 'client_id=quarkus-app' \
--data-urlencode 'client_secret=secret'  | jq -r '.access_token')

export ACCESS_HEADER="Authorization: Bearer $ACCESS_TOKEN"

curl -v --location \
    --request GET 'http://localhost:8080/user/whoami' \
    --header "$ACCESS_HEADER"

Recursos Estáticos

src/main/resources/META-INF/resources/[index.html]

Templates

quarkus ext add resteasy-qute

src/main/resources/templaters/greeting.html


GreetingResource

@Path("/greeting")
public class GreetingResource {
    @Inject
    Template greeting;

    @GET
    @Produces(MediaType.TEXT_HTML)
    public TemplateInstance get(@QueryParam("name") @DefaultValue("fulano") String name) {
        return greeting.data("name", name);
    }
}

Packaging to Prod

./mvnw package
# export
java -jar target/quarkus-app/quarkus-run.jar

Containers, native images & more!

APP Vaadin

cd ..
gh repo clone vaadin/base-starter-flow-quarkus
mv base-starter-flow-quarkus ptc-app

application.properties

# HTTP
quarkus.http.host=0.0.0.0
quarkus.http.port=8081
quarkus.http.cors=true
quarkus.http.limits.max-body-size=200M

# Disable DevSevices by default
quarkus.datasource.jdbc=false
quarkus.devservices.enabled=false
quarkus dev

localhost:8081 MainView

quarkus ext add rest-client
quarkus ext add rest-client-jackson
quarkus ext add oidc
quarkus ext add keycloak-authorization

REST Client

WhoAmI View

import com.vaadin.flow.component.html.Label;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.Route;
import org.eclipse.microprofile.rest.client.inject.RestClient;

import javax.inject.Inject;

@Route("user/whoami")
public class WhoAmIView extends VerticalLayout {
    @Inject
    public WhoAmIView(@RestClient WhoAmIService whoAmIService) {
        add(new Label("Hello " + whoAmIService.whoami()));
    }
}

Client Interface

import javax.ws.rs.GET;
import javax.ws.rs.Path;

import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;


@RegisterRestClient(configKey = "whoami")
@RegisterClientHeaders(AuthClientHeaders.class)
public interface WhoAmIService {
    @GET
    String whoami();
}

Auth Headers

import javax.enterprise.context.Dependent;
import javax.inject.Inject;
import javax.ws.rs.core.MultivaluedHashMap;
import javax.ws.rs.core.MultivaluedMap;

import org.eclipse.microprofile.jwt.JsonWebToken;
import org.eclipse.microprofile.rest.client.ext.ClientHeadersFactory;

@Dependent
public class AuthClientHeaders implements ClientHeadersFactory {
    @Inject
    JsonWebToken accessToken;

    @Override
    public MultivaluedMap<String, String> update(
            MultivaluedMap<String, String> incomingHeaders,
            MultivaluedMap<String, String> clientOutgoingHeaders) {
        var token = "Bearer " + accessToken.getRawToken();
        return new MultivaluedHashMap<String, String>(){{
            add("Authorization", token);
        }};
    }
}

application-dev.properties

quarkus.oidc.auth-server-url=http://localhost:13370/realms/petcare-realm
quarkus.oidc.client-id=quarkus-app
quarkus.oidc.credentials.secret=secret
quarkus.oidc.application-type=web-app
quarkus.oidc.authentication.user-info-required=true

quarkus.http.auth.permission.authenticated.paths=/user/*
quarkus.http.auth.permission.authenticated.policy=authenticated

rest-client.base-uri=http://localhost:8080
quarkus.rest-client.whoami.uri=${rest-client.base-uri}/user/whoami

quarkus.rest-client.ping.hostname-verifier=io.quarkus.restclient.NoopHostnameVerifier
quarkus.tls.trust-all=true

http://localhost:8081/user/whoami

Obrigado!

Julio Faerman

@faermanj

jufaerma@redhat.com

Referências

https://quarkus.io/guides/all-config

https://quarkus.io/guides/dev-services

https://lordofthejars.github.io/quarkus-cheat-sheet/

https://gist.github.com/faermanj/270a8a8ab817f95fc2e350ec2d481bd2

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment