Pular para o conteúdo principal

archbase-security

Módulo de segurança com autenticação JWT, autorização baseada em permissões e suporte a múltiplos contextos de aplicação.

Instalação

<dependency>
<groupId>br.com.archbase</groupId>
<artifactId>archbase-security</artifactId>
<version>${archbase.version}</version>
</dependency>

Visão Geral

FuncionalidadeDescrição
JWTAccess e refresh tokens com configuração flexível
PermissõesControle de acesso por Resource/Action/Tenant
ContextosAutenticação multi-contexto (STORE, CUSTOMER, DRIVER, ADMIN)
API TokensTokens para integrações serviço-serviço
IntervalosControle de acesso por horário/dia da semana
GruposOrganização de usuários com permissões compartilhadas

Anotações de Segurança

@HasPermission

Controle de acesso baseado em Resource + Action:

@RestController
@RequestMapping("/api/pedidos")
public class PedidoController {

// No nível do controller - aplica a todos os métodos
@HasPermission(action = "VIEW", resource = "PEDIDO")
@GetMapping
public Page<Pedido> listar(Pageable pageable) {
return pedidoRepository.findAll(pageable);
}

// Sobrepor no nível do método
@PostMapping
@HasPermission(action = "CREATE", resource = "PEDIDO")
public Pedido criar(@RequestBody CriarPedidoRequest request) {
return pedidoService.criar(request);
}

@DeleteMapping("/{id}")
@HasPermission(action = "DELETE", resource = "PEDIDO")
public void deletar(@PathVariable UUID id) {
pedidoService.deletar(id);
}
}

Parâmetros:

ParâmetroTipoDescrição
actionStringAção permitida (VIEW, CREATE, UPDATE, DELETE, etc.)
resourceStringNome do recurso (PEDIDO, CLIENTE, PRODUTO)
tenantIdStringID do tenant (opcional, suporta SpEL)
companyIdStringID da empresa (opcional)
projectIdStringID do projeto (opcional)

Exemplo com Tenant:

@HasPermission(
action = "ADMIN",
resource = "PEDIDO",
tenantId = "#tenantId" // SpEL - usa o parâmetro do método
)
public Page<Pedido> listarPorTenant(@PathVariable String tenantId, Pageable pageable) {
return pedidoService.listarPorTenant(tenantId, pageable);
}

@RequireProfile

Controle baseado em perfis do Archbase:

@RequireProfile({"ADMIN", "MANAGER"})
public void metodoAdminOuManager() {
// Acessível por usuários com profile ADMIN OU MANAGER
}

@RequireProfile(value = {"ADMIN", "FINANCE"}, requireAll = true)
public void metodoRestrito() {
// Usuário deve ter AMBOS os profiles: ADMIN E FINANCE
}

Parâmetros:

ParâmetroTipoDefaultDescrição
valueString[]-Perfis necessários
requireAllbooleanfalseSe true, exige TODOS os perfis
allowSystemAdminbooleantruePermite bypass para administradores
requireActiveUserbooleantrueVerifica se usuário está ativo

@RequireRole

Controle baseado em roles customizadas:

@RequireRole("STORE_MANAGER")
public void metodoGerenteLoja() {
// Método para gerentes de loja
}

@RequireRole(value = {"OWNER", "PARTNER"}, requirePlatformAdmin = true)
public void metodoPlataforma() {
// Requer ser admin da plataforma E ter role OWNER ou PARTNER
}

@RequireRole(value = "MERCHANT", ownerOnly = true)
public void metodoProprietario() {
// Apenas proprietários podem acessar
}

Parâmetros:

ParâmetroTipoDefaultDescrição
valueString[]-Roles necessárias
requireAllbooleanfalseSe true, exige TODAS as roles
requirePlatformAdminbooleanfalseRequer admin da plataforma
ownerOnlybooleanfalseApenas proprietários
contextString-Contexto específico para validação

@RequirePersona

Controle baseado em personas de negócio:

@RequirePersona("CUSTOMER")
public void metodoCliente() {
// Acessível apenas por clientes
}

@RequirePersona(value = "STORE_ADMIN", context = "STORE_APP")
public void metodoAdminLoja() {
// Para admins de loja no contexto do app da loja
}

@RequirePersona(
value = {"DRIVER", "DISPATCHER"},
context = "DELIVERY_APP",
contextData = "{\"region\": \"#region\"}"
)
public void metodoDelivery(@PathVariable String region) {
// Motoristas ou despachantes da região especificada
}

Combinando Anotações

@RequireProfile("MANAGER")
@HasPermission(resource = "FINANCIAL", action = "READ")
public FinancialReport getRelatorioFinanceiro() {
// Usuário deve ter:
// 1. Profile MANAGER
// 2. Permissão FINANCIAL:READ
}

@RequirePersona("STORE_ADMIN")
@HasPermission(resource = "ORDER", action = "CANCEL")
public void cancelarPedido(UUID orderId) {
// Admin da loja COM permissão para cancelar pedidos
}

Autenticação Contextual

O Archbase oferece autenticação unificada para múltiplos contextos de aplicação.

Contextos Disponíveis

ContextoDescrição
STORE_APPAplicativo de loja/estabelecimento
CUSTOMER_APPAplicativo do cliente final
DRIVER_APPAplicativo de motorista/entregador
WEB_ADMINPainel administrativo web

Login com Contexto

POST /api/v1/auth/login
Content-Type: application/json

{
"email": "usuario@exemplo.com",
"password": "senha123",
"context": "STORE_APP",
"contextData": "{\"storeId\": \"123\"}"
}

Login Flexível (Email ou Telefone)

POST /api/v1/auth/login-flexible
Content-Type: application/json

{
"identifier": "usuario@exemplo.com ou 11999999999",
"password": "senha123",
"context": "CUSTOMER_APP"
}

Login Social

POST /api/v1/auth/login-social
Content-Type: application/json

{
"provider": "google",
"token": "token-do-google",
"context": "CUSTOMER_APP"
}

Registro com Dados Adicionais

POST /api/v1/auth/register
Content-Type: application/json

{
"name": "João Silva",
"email": "joao@exemplo.com",
"password": "senha123",
"role": "USER",
"additionalData": {
"phone": "+5511999999999",
"storeId": "123",
"cpf": "12345678900"
}
}

Listar Contextos

GET /api/v1/auth/contexts

Response:
{
"supportedContexts": ["STORE_APP", "CUSTOMER_APP", "DRIVER_APP", "WEB_ADMIN"],
"defaultContext": "WEB_ADMIN"
}

AuthenticationBusinessDelegate

Interface para adicionar lógica de negócio customizada durante autenticação:

@Component
@Primary
public class MinhaAppAuthenticationDelegate implements AuthenticationBusinessDelegate {

@Override
public String onUserRegistered(User user, Map<String, Object> registrationData) {
// Criar entidade de negócio (ex: Loja, Cliente)
Loja loja = lojaService.criar(
(String) registrationData.get("nomeLoja"),
(String) registrationData.get("cnpj")
);

// Associar usuário à loja
lojaService.adicionarUsuario(loja.getId(), user.getId());

return loja.getId();
}

@Override
public AuthenticationResponse enrichAuthenticationResponse(
AuthenticationResponse baseResponse,
String context,
HttpServletRequest request) {

// Enriquecer resposta baseado no contexto
switch (context) {
case "STORE_APP":
return baseResponse.withAdditionalData(Map.of(
"lojaId", getLojaId(baseResponse.getUserId()),
"permissoes", getPermissoesLoja(baseResponse.getUserId())
));
case "CUSTOMER_APP":
return baseResponse.withAdditionalData(Map.of(
"clienteId", getClienteId(baseResponse.getUserId()),
"favoritos", getFavoritos(baseResponse.getUserId())
));
default:
return baseResponse;
}
}

@Override
public boolean supportsContext(String context) {
return Set.of("STORE_APP", "CUSTOMER_APP").contains(context);
}

@Override
public List<String> getSupportedContexts() {
return List.of("STORE_APP", "CUSTOMER_APP");
}

@Override
public void preAuthenticate(String email, String context) {
// Validações pré-autenticação
if ("STORE_APP".equals(context)) {
validarLojaAtiva(email);
}
}

@Override
public void postAuthenticate(User user, String context) {
// Ações pós-autenticação
auditoria.registrarLogin(user.getId(), context);
}

@Override
public String onSocialLogin(String provider, Map<String, Object> providerData) {
// Integração com login social
return socialLoginService.processar(provider, providerData);
}
}

AuthenticationResponseEnricher

Interface alternativa para enriquecer respostas:

@Component
public class CustomResponseEnricher implements AuthenticationResponseEnricher {

@Override
public AuthenticationResponse enrich(
AuthenticationResponse baseResponse,
String context,
HttpServletRequest request) {

Map<String, Object> additionalData = new HashMap<>();
additionalData.put("timestamp", Instant.now());
additionalData.put("version", "1.0");

return baseResponse.withAdditionalData(additionalData);
}

@Override
public boolean supports(String context) {
return "CUSTOMER_APP".equals(context);
}

@Override
public int getOrder() {
return 100; // Ordem de execução
}
}

Intervalos de Acesso

Controle de acesso baseado em horários e dias da semana.

AccessInterval

@Entity
public class AccessInterval extends PersistenceEntityBase<AccessInterval, UUID> {

private LocalTime startTime; // Horário de início
private LocalTime endTime; // Horário de fim
private DayOfWeek dayOfWeek; // Dia da semana
}

AccessSchedule

@Entity
public class AccessSchedule extends PersistenceEntityBase<AccessSchedule, UUID> {

private String cronExpression; // Expressão cron
private String description; // Descrição do horário
}

Configuração

archbase:
security:
access-schedule:
enabled: true
timezone: America/Sao_Paulo

Grupos e Perfis

Group

@Entity
public class Group extends PersistenceEntityBase<Group, UUID> {

private String name;
private String description;
private List<UserGroup> members;
private List<Permission> permissions;
}

UserGroup

@Entity
public class UserGroup extends PersistenceEntityBase<UserGroup, UUID> {

private User user;
private Group group;
private LocalDateTime joinedAt;
}

UserProfile

@Entity
public class UserProfile extends PersistenceEntityBase<UserProfile, UUID> {

private String name;
private List<Permission> permissions;
private List<User> users;
}

Contexto de Segurança

Obter Usuário Autenticado

@Service
public class PedidoService {

public Pedido criar(PedidoRequest request) {
// Obter usuário do contexto
ArchbaseAuthentication auth =
SecurityContextHolder.getAuthentication();

UUID usuarioId = auth.getUserId();
String username = auth.getUsername();

Pedido pedido = new Pedido(usuarioId, request);
return pedidoRepository.save(pedido);
}
}

Obter Permissões

@GetMapping("/minhas-permissoes")
public List<Permission> minhasPermissoes() {
ArchbaseAuthentication auth =
SecurityContextHolder.getAuthentication();

return auth.getPermissions();
}

Serviços Utilitários

CryptoUtil

// Gerar hash de senha
String hashedPassword = CryptoUtil.hashPassword("plainPassword");

// Verificar senha
boolean matches = CryptoUtil.verifyPassword("plainPassword", hashedPassword);

// Gerar token seguro
String secureToken = CryptoUtil.generateSecureToken();

// Criptografar dados
String encrypted = CryptoUtil.encrypt("dados-sensiveis");
String decrypted = CryptoUtil.decrypt(encrypted);

TokenGeneratorUtil

// Token UUID padrão
String token = TokenGeneratorUtil.generateToken();

// Token com tamanho específico
String customToken = TokenGeneratorUtil.generateToken(32);

// Token alfanumérico
String alphaToken = TokenGeneratorUtil.generateAlphanumericToken(16);

ArchbaseEmailService

public interface ArchbaseEmailService {
void sendPasswordResetEmail(String email, String token);
void sendWelcomeEmail(String email, String name);
void sendNotificationEmail(String email, String subject, String body);
}

ArchbaseTokenCleanupService

@Service
public class TokenCleanupJob {

@Scheduled(cron = "0 0 2 * * ?") // 2h da manhã
public void cleanupExpiredTokens() {
cleanupService.cleanupExpiredAccessTokens();
cleanupService.cleanupExpiredRefreshTokens();
cleanupService.cleanupExpiredPasswordResetTokens();
}
}

Testando com Segurança

@SpringBootTest
@WithMockArchbaseUser(
userId = "123e4567-e89b-12d3-a456-426614174000",
username = "testuser",
permissions = {"VIEW:PEDIDO", "CREATE:PEDIDO"}
)
class PedidoControllerTest {

@Autowired
private MockMvc mockMvc;

@Test
void deveListarPedidos() throws Exception {
mockMvc.perform(get("/api/pedidos"))
.andExpect(status().isOk());
}
}

Próximos Passos