Segurança

Introdução

Para fins de segurança, todos os recursos do Spring Security estão disponíveis.

A Spring Security é um framework maduro, poderoso e altamente personalizável que traz suporte a uma vasta quantidede de padrões técnicos de autenticação e autorização, além de proteções contra diversos ataques comuns.

A presente arquitetura orienta a utilização de OIDC (OpenID Connect), em que o microsserviço atua como entidade Resource Server, ou seja, recebendo autorização de acesso via token JWT (JSON Web Token) na requisições webservice. O Cloudsupport oferece diversas facilitades para uso do padrão OIDC, conforme explanado a seguir.

Visão Geral do OpenID Connect

OpenID Connect (OIDC) é um protocolo de autenticação baseado no OAuth 2.0, desenvolvido para permitir a verificação da identidade dos usuários de forma segura e simples. Ele permite que os usuários façam login em diferentes sites e aplicativos utilizando uma única conta de um provedor de identidade, como Google, Facebook, Azure AD, AWS Cognito, Keycloak (opensource) etc.

O OIDC é amplamente suportado por provedores de identidade e aplicativos, facilitando a integração e promovendo a interoperabilidade entre diferentes serviços.

São conceitos relevantes sobre o padrão OIDC:

  • Single Sign-On (SSO): Permite que os usuários façam login uma vez e obtenham acesso a vários sistemas e aplicativos sem precisar fazer login novamente.

  • Claims: Cada claim representa uma informação do usuário ou do processo de autenticação. As claims podem ser personalizadas de acordo com as necessidades da aplicação e podem incluir dados como nome, email, foto e outras informações do perfil do usuário.

  • Autenticação de Múltiplos Fatores (MFA): Embora não seja um recurso nativo do OIDC, ele pode ser integrado com soluções de MFA para fornecer uma camada adicional de segurança.

  • Sessões e Logout: Suporta o gerenciamento de sessões de usuário e o logout centralizado, permitindo que os usuários encerrem suas sessões em todos os aplicativos conectados de uma só vez.

  • Descoberta e Configuração Dinâmica: Fornece um endpoint de descoberta (/.well-known/openid-configuration) que permite que as aplicações obtenham automaticamente os detalhes de configuração do provedor de identidade, simplificando a integração.

  • Fluxos de Autenticação Flexíveis: Suporta diferentes fluxos de autenticação, permitindo que as aplicações escolham o fluxo mais adequado para suas necessidades.

As entidades reconhecidas pelo OIDC são:

  • Entidade End User:

    • Definida pelo padrão OIDC
    • Representa o usuário que deseja acessar um recurso protegido
  • Entidade Client ou Relying Party (RP):

    • Definida no padrão OIDC
    • Representa o aplicação na qual interage o End User, tipicamente a interface gráfica web ou móvel
    • Comunica com o Identity Provider (IdP) para realizar autenticação
    • Comunica com o Resource Server para acessar recursos protegidos
  • Entidade Identity Provider (IdP ou OP):

    • Definida no padrão OIDC
    • Representa o servidor que autentica o End User (valida as credenciais)
    • Implementa SSO (Single Sign-On)
    • Emite “tokens de identidade” (ID Token), que representam o resultado da autenticação
    • Emite “tokens de acesso” (Access Token e Refresh Token), utilizados para fins de autorização
  • Entidade Resource Server:

    • Definido no padrão OAuth2, incorporado ao OIDC
    • Representa o servidor que hospeda os recursos protegidos
    • Valida “tokens de acesso” (Access Token) como critério para conceder acesso ao recurso protegido

Os objetos de dados definidos no OIDC são:

  • ID Token:

    • Definido no padrão OIDC
    • Tem a função de “prova de autenticação”
    • É sempre no formato JWT (JSON Web Token)
    • Não deve ser utilizado para acessar um recurso protegido, pois:
      • Não há mecanismos de segurança para associar o ID Token com o Client
      • O “audience claim” é sempre vinculado a um Client, e não ao Resource Server
      • Não possui granted scopes
  • User Info:

    • Definido no padrão OIDC
    • Uso opcional, contém informações adicionais do perfil do usuário
    • Obtido através do OIDC UserInfo Endpoint
  • Access Token:

    • Definido no padrão OAuth2, incorporado ao OIDC
    • Pode ser JWT ou opaque token (uma string qualquer)
    • Não deve ser interpretado pelo Client, ainda que seja um JWT
    • É usado para acessar recursos protegidos, como APIs webservice
    • Geralmente contém informações sobre as permissões concedidas ao usuário
    • Se JWT, tipicamente:
      • São assinados digitalmente, por chave simétrica ou infraestrutura de PKI (Public Key Infrastructure)
      • São validados de maneira offline, ou seja, com base na assinatura e dados de expiração constantes no próprio _token
      • Têm validade de curta duração, na ordem de alguns minutos ou algumas horas
  • Refresh Token:

    • Definido no padrão OAuth2, incorporado ao OIDC
    • Utilizado para renovação do Access Token
    • Têm validade de longa duração, na ordem de dias a meses

Alguns endpoints definidos no OIDC podem ser utilizados pela aplicação:

  • OIDC UserInfo Endpoint:

    • Utilizado pelo Client ou Resource Server obter mais dados (claims) do usuário com base no Access Token
  • OAuth2 Introspection Endpoint:

    • Utilizado pelo Resource Server para validar OAuth2 Access Token do tipo opaque token
    • Não deve ser utilizado pelo Client

Configurando o OIDC

No contexto de backend o microsserviço atuará como Resource Server do OIDC/OAuth2, realizando a validação de tokens JWT recebidos via header HTTP em requisições webservice oriundas do frontend.

A configuração do Spring Security pode ser realizada manualmente caso necessário, conforme documentação oficial do Spring Framework e do Spring Boot. Entretanto ela é complexa.

O Cloudsupport oferece a propriedade abaixo que autoconfigura uma pilha de recursos de segurança do Spring Security habilitando o microsserviço como Resource Server JWT.

cloudsupport.security.scheme=jwt

Com apenas essa propriedade, os endpoints estão aptos para validação de autenticação e autorização conforme os recursos disponíveis no Spring Security, além dos facilitados adicionais do Cloudsuppport, descritos nas seções seguites.

As permissões do usuário, ou authorities na semântica do Spring Security, serão carregadas a partir do claim de nome roles do JWT, exemplo:

{
    "sub": "e2fcfbcf-4d3c-4d60-9fbb-75e4f12df7da",
    "iss": "https://oidc.domain.com/realm",
    "exp": 32503680000,
    "scope": "openid email profile",
    "name": "Meu Nome",
    "roles": [
        "atend",
        "atend:executor:manter",
        "atend:os:glosar",
        "atend:os:gerar",
        "atend:os:executar",
        "atend:os:encaminhar",
        "atend:os:cancelar",
        "atend:os:atestar"
    ]
}

Pode-se escolher outro claim através da propriedade cloudsupport.security.jwt.authorities-claim.

Por padrão a segurança JWT é suportada em todas as URLs mapeadas no microsserviço. Pode-se restringir os possíveis paths (separados por vírgula):

cloudsupport.security.jwt.paths=/atendimento

Para fins de desenvolvimento ou utilização do microsserviço em ambientes confiáveis, com por exemplo por trás de um API Gateway que atua como Resource Server, pode-se habilitar a propriedade abaixo, que permite ao microsserviço aceitar JWT sem validação da assinatura, inclusive tokens não assinados:

cloudsupport.security.jwt.always-trust=true

Detalhes Técnicos

A leitura desta seção é opcional, sendo útil para entender o que está por trás do modelo padrão de segurança da arquitetura.

Os recursos técnicos ativados no esquema de segurança “jwt” do Cloudsuppport são:

  • Um filtro SecurityFilterChain do Spring Security que:

    • Ativa OAuth2 ResourceServer via JWT
    • Desativa form login (statefull)
    • Desativa BASIC login
    • Desativa CSRF (Cross-site Request Forgery), vez que o microsserviço seria focado em API
    • Ativa CORS (veja abaixo o filtro autoconfigurado de CORS)
    • Atua em todas as rotas (padrão) ou aquelas definidas em cloudsupport.security.jwt.paths (lista separada por vírgula)
    • Não aplica requisito de autenticação obrigatória (deve ser mapeando por anotação em cada endpoint)
  • Filtro WebDetailsFilter do Cloudsuppport que define WebDetails como sendo o objeto details do AuthenticationToken do contexto do Spring Security.

  • Suporte a hidratação (injeção) de atributos no WebDetails por beans do tipo WebDetailsAttributesSource, a serem declarados pela aplicação, que permite a ela prover dados adicionais de maneira personalizável.

  • Um bean WebDetailsAttributesSource que injeta origin_ip, origin_proto, remote_addr e session_id no WebDetails.

  • Ativação de segurança de método no Spring Security (@PreAuthorize e @PostAuthorize).

  • Ativação do suporte a templating de meta-annotation em segurança de método no Spring Security.

  • Ativação de um JwtAuthenticationConverter que carrega as permissões (authorities) a partir do claim JWT definido na propriedade cloudsupport.security.jwt.authorities-claim (por padrão "roles").

  • Ativação do security scheme no Swagger de nome "JWT".

  • Ativação de um personalizador Swagger que automaticamente configura a documentação de requisito de segurança equivalente a @SecurityRequirement(name = "JWT") nos endpoints anotados com @HasAuthority, @HasAnyAuthority ou @IsAuthenticated, em que as permissões (authorities) são inclusas como scopes do SecurityRequirement do Swagger.

  • Ativação de um personalizador Swagger que automaticamente adiciona os nomes da permissões na documentação dos resumos dos endpoints (summary) anotados com @HasAuthority, @HasAnyAuthority ou @IsAuthenticated, uma vez que o Swagger UI não suporta nativamente a exibição das permissões (scopes).

Com o Cloudsupport, o código-fonte Java fica mais enxuto:

// Spring puro:
     
@PreAuthorize("hasAuthority('permission')")
@SecurityRequirement(name = "JWT")
@Tag(name = "group")
public void operation(
    @AuthenticationPrincipal(expression = 
        "#this instanceof T(org.springframework.security.oauth2.jwt.Jwt) ? claims['name'] : null")
    @Parameter(hidden = true)
    String name) {
     
}
     
// Com Cloudsupport:
     
@HasAuthority("permission")
public void operation(@LoggedInName String name) {
     
}

Convenções de Nomes

Sugere-se os seguinte padrão para nome de permissão de acesso (ou authority na semântica do Spring Security ou role no contexto OIDC):

microsservico:grupo:privilegio

Onde:

  • “microsservico”: Nome que representa o microsservico que trata a permissão.

  • “grupo”: Representa um determinado assunto de negócio. Tipicamente será o nome do grupo de endpoints.

  • “privilegio”: Representa a ação que pode ser realizada pelo usuário. Tipicamente será um nome que identifica o endpoint ou conjunto de endpoints relacionados.

As partes “grupo” e “privilegio” são opcionais.

Uma quantidade maior de níveis hierárquicos pode ser utilizada caso o microsserviço tenha uma complexidade maior.

Não é escopo do Cloudsupport definir um modelo de permissionamento baseado em ACLs (Access Control Lists). A solução proposta é baseada em RBAC (Role-Based Access Control). O controle de acesso com base nos dados pode ser tratado em nível de regras de negócio nas classes de serviço das funcionalidades.

Exemplos de permissões:

  • "atend": Concede acesso às funcionalidades de leitura/pesquisa do microsserviço de Atendimento.
  • "atend:executor:manter": Concede acesso às funcionalidades CRUD de executor.
  • "atend:os:glosar": Concede acesso à funcionalidade de glosa de ordem de serviço.
  • "atend:os:gerar": Concede acesso à funcionalidade de geração de ordem de serviço.
  • "atend:os:executar": Concede acesso à funcionalidade de execução de ordem de serviço.
  • "atend:os:encaminhar": Concede acesso à funcionalidade de encaminhamento de ordem de serviço.
  • "atend:os:cancelar": Concede acesso à funcionalidade de cancelamento de ordem de serviço.
  • "atend:os:atestar": Concede acesso à funcionalidade de ateste de ordem de serviço.

Validando Autenticação

Para proteger um endpoint com requisito de autenticação obrigatória, o Cloudsupport oferece a anotação @IsAuthenticated:

@Ws
public class CadastrarExecutorWsV1 extends BaseWs {

    @IsAuthenticated
    public void cadastrar(/* ... */) {
        // ...
    }
}

Vantagens:

  • Mais enxuta que o padrão @PreAuthorize, também suportado.

  • O Swagger será documentado automaticamente com o requisito de segurança, sem necessidade da anotação @SecurityRequirement(name = "JWT").

Programaticamente, a arquitetura oferece o método utilitário:

  • SecurityUtils.isAuthenticated(): Retorna booleano indicando se existe usuário autenticado no contexto da requisição HTTP ou da thread.

Em caso de erro na validação de autenticação, será emitida resposta para o frontend com base nas convenções de código descritas em Convenções HTTP e de retorno descritos em Tratamento de Erros.

Validando Autorização

Para proteger um endpoint com requisito de autorização obrigatória, o Cloudsupport oferece as anotações @HasAuthority e @HasAnyAuthority:

@Ws
public class CadastrarExecutorWsV1 extends BaseWs {

    @HasAuthority("atend:executor:cadastrar")
    public void cadastrar(/* ... */) {
        // ...
    }
}

ou

@Ws
public class CadastrarExecutorWsV1 extends BaseWs {

    @HasAnyAuthority({"'atend:admin'", "'atend:executor:cadastrar'"})
    public void cadastrar(/* ... */) {
        // ...
    }
}

Vantagens:

  • Mais enxuta que o padrão @PreAuthorize, também suportado.

  • O Swagger será documentado automaticamente com o requisito de segurança, sem necessidade da anotação @SecurityRequirement(name = "JWT").

  • O Swagger será documentado automaticamente para citar no nome da(s) permissão(ões) na descrição do endpoint, o que permite a visualização através do Swagger UI, que não tem suporte nativo para exibir essa informação.

Programaticamente, a arquitetura oferece os métodos utilitários:

  • SecurityUtils.hasAuthority(authority): Retorna booleano indicando se existe usuário autenticado e com a permissão informada no contexto da requisição HTTP ou da thread.

  • SecurityUtils.hasAnyAuthority(authorities[]): Retorna booleano indicando se existe usuário autenticado e com pelo menos alguma das permissões informadas no contexto da requisição HTTP ou da thread.

Em caso de erro na validação de autenticação, será emitida resposta para o frontend com base nas convenções de código descritas em Convenções HTTP e de retorno descritos em Tratamento de Erros.

Exemplo de informações automáticas exibidas no Swagger quando se utiliza as anotações da arquitetura:

Swagger

  • Indicador de requisito de segurança (cadeado à direita)
  • Permissão necessária (chave ao lado do título do endpoint com o nome da(s) permissão(ões) entre [])

Obtendo Dados do Usuário

A obtenção de atributo do usuário autenticado pode ser realizada por anotações em endpoints webservice:

@Ws
public class CadastrarExecutorWsV1 extends BaseWs {

    public void cadastrar(@LoggedInEmail String email) {
        // ...
    }
}

Vantagens:

  • Mais enxuta que via @AuthenticationPrincipal, também suportado, cuja sintaxe pode ser extensa:

      @AuthenticationPrincipal(expression = "#this instanceof T(org.springframework.security.oauth2.jwt.Jwt) ? claims['email'] : null")
    
  • O Swagger será documentado automaticamente com o requisito de segurança, sem necessidade da anotação @SecurityRequirement(name = "JWT").

  • O Swagger será documentado automaticamente para considerar o atributo como desprezado dos parâmetros HTTP, sem necessidade da anotação @Parameter(hidden = true), que se trata de argumento injetado automaticamente pelo Spring Security.

Lista de anotações da arquitetura:

Anotação Descrição
@LoggedIn Injeta o usuário autenticado. No esquema “jwt” da arquitetura, será um objeto Jwt. Equivale ao Spring @AuthenticationPrincipal mais Swagger @Parameter(hidden = true).
@LoggedInSubject Injeta o claim JWT "sub" (identificação do usuário no OIDC).
@LoggedInIssuer Injeta o claim JWT "iss" (identificação do emissor OIDC do token).
@LoggedInAccountId Injeta o claim JWT "account_id" (ex: número da conta).
@LoggedInPersonalId Injeta o claim JWT "personal_id" (ex: CPF).
@LoggedInUserId Injeta o claim JWT "user_id" (ex: matrícula, número de registro).
@LoggedInDepartmentId Injeta o claim JWT "department_id" (ex: código do departamento na organização).
@LoggedInUsername Injeta o claim JWT "username" (ex: login), raramente presente.
@LoggedInEmail Injeta o claim JWT "email".
@LoggedInPhoneNumber Injeta o claim JWT "phone_number".
@LoggedInName Injeta o claim JWT "name" (nome ou nome completo).
@LoggedInGivenName Injeta o claim JWT "given_name" (primeiro nome).
@LoggedInFamilyName Injeta o claim JWT "family_name" (sobrenome).
@LoggedInNickname Injeta o claim JWT "nickname" (apelido).
@LoggedInPicture Injeta o claim JWT "picture" (ex: URL).

Em todos os casos, as anotações @LoggedIn da arquitetura retornam null se o objeto principal do AuthenticationToken do Spring Security não for um objeto Jwt.

Programaticamente, a arquitetura oferece os métodos utilitários:

Método Descrição
SecurityUtils.getLoggedIn() Equivale a @LoggedIn.
SecurityUtils.getLoggedInSubject() Equivale a @LoggedInSubject.
SecurityUtils.getLoggedInIssuer() Equivale a @LoggedInIssuer.
SecurityUtils.getLoggedInAccountId() Equivale a @LoggedInAccountId.
SecurityUtils.getLoggedInPersonalId() Equivale a @LoggedInPersonalId.
SecurityUtils.getLoggedInUserId() Equivale a @LoggedInUserId.
SecurityUtils.getLoggedInDepartmentId() Equivale a @LoggedInDepartmentId.
SecurityUtils.getLoggedInUsername() Equivale a @LoggedInUsername.
SecurityUtils.getLoggedInPhoneNumber() Equivale a @LoggedInPhoneNumber.
SecurityUtils.getLoggedInEmail() Equivale a @LoggedInEmail.
SecurityUtils.getLoggedInName() Equivale a @LoggedInName.
SecurityUtils.getLoggedInGivenName() Equivale a @LoggedInGivenName.
SecurityUtils.getLoggedInFamilyName() Equivale a @LoggedInFamilyName.
SecurityUtils.getLoggedInNickname() Equivale a @LoggedInNickname.
SecurityUtils.getLoggedInPicture() Equivale a @LoggedInPicture.
SecurityUtils.getWebDetails() Retorna o objeto WebDetails, carregado pela arquitetura durante a autenticação, que contém atributos adicionais relativos ao ambiente web, além de possíveis atributos extras que podem ser injetados pela aplicação atráves de beans do tipo WebDetailsAttributesSource.
SecurityUtils.getWebDetail('key') Atalho para webDetails.getAttribute(key).
SecurityUtils.getOriginIp() Retorna o IP de origem da requisição, carregado a partir do cabeçalho “X-Forwarded-For”. Atalho para webDetails.getAttribute("origin_ip").
SecurityUtils.getOriginProto() Retorna o protocolo da requisição (http ou https), carregado a partir do cabeçalho “X-Forwarded-Proto”. Atalho para webDetails.getAttribute("origin_proto").
SecurityUtils.getLogin() Atalho para SecurityContextHolder.context.authentication.name (no caso de JWT, tem mesmo valor que o claim “sub”).

Tópicos Adicionais

WebDetails

O Cloudsupport, caso o esquema de segurança “jwt” esteja habilitado, carregará automaticamente um objeto do tipo WebDetails no contexto do Spring Security caso a autenticação JWT ocorrer com sucesso.

Esse objeto contém um Map de atributos, que pode ser utilizado para incluir informações adicionais relativas ao ambiente web da autenticação.

A arquitetura injeta por padrão os atributos de chave "origin_ip" e "origin_proto", carregados respectivamente dos cabeçalhos “X-Forwarded-For” e “X-Forwarded-Proto”. Além disso, os atributos "remote_addr" e "session_id" padrões do Spring são adicionados.

A aplicação pode declarar beans do tipo WebDetailsAttributesSource, que serão invocados pelo Cloudsupport durante a autenticação para adicionar atributos ao WebDetails.

Auditoria

O Cloudsupport inclui o Spring JPA Auditing, ativado por padrão nas entidades persistentes que herdam de BaseEntity.

As entidades auditadas pelo Spring JPA Auditing terão os campos createdBy e lastModifiedBy preenchidos, por padrão, quando utilizado o esquema de segurança “jwt” da arquitetura, com o valor do claim JWT “sub”.

Pode-se escolher o claim desejado na propriedade cloudsupport.auditing.auditor-jwt-claim, por exemplo:

cloudsupport.auditing.auditor-jwt-claim=user_id

A aplicação pode, alternativamente, implementar um bean do tipo AuditorAware.

Integração

A arquitetura oferece o componente WebClientComponent que permite o consumo de webservices com as seguintes características:

  • Relay do Bearer token (tipicamente o JWT), de forma a propagar a autenticação/autorização para o microsserviço de destino.

  • Relay dos cabeçalhos “X-Forwarded-For” e “X-Forwarded-Proto”, de forma a propagar o IP e protocolo de origem para ciência pelo microsserviço de destino.

As propagações são aplicadas caso sejam utilizados algum dos métodos:

  • WebClientComponent.authenticated()

  • WebClientComponent.authenticatedGet()

  • WebClientComponent.authenticatedPost()

Nota: Caso a aplicação declarar um bean do tipo WebClient.Builder, o WebClientComponent construirá seus clientes web internos por cima do cliente provido pelo bean da aplicação. Dessa forma, recursos adicionados pela aplicação, como por exemplo filtros de balanceamento de carga, também terão efeito quando do uso do WebClientComponent.

URLs Públicas

O Cloudsupport oferece a propriedade cloudsupport.security.allow-paths que autoconfigura uma pilha de recursos de segurança do Spring Security que permite o acesso público a um conjunto de padrões de URL.

Caso o valor da propriedade seja auto, serão permitidos acessos ao:

  • H2 Console (dinamicamente, ex: /h2-console)
  • Swagger UI (dinamicamente, ex: /swagger-ui)
  • OpenAPI (fixado em /v3/api-docs)
  • Actuator (dinamicamente, ex: /actuator)

Caso contrário, a aplicação pode elencar os paths, separados por vírgula.

Internamente, o Cloudsupport declara um SecurityFilterChain que:

  • Desativa CSRF
  • Ativa CORS (veja abaixo o filtro autoconfigurado de CORS)
  • Ativa iframe na mesma origem (devido ao H2 Console)

CORS

O Cloudsupport autoconfigura um bean do tipo CorsFilter que permite todas as origens, todos os headers e todos os métodos, sendo registrado para todos os paths, uma vez que o microsserviço é por padrão projetado para utilização em front mobile e com requisições autenticadas.

A aplicação pode declarar o filtro de CORS conforme sua necessidade, ou o CORS pode ser configurado em um proxy ou API Gateway.

Próximos Passos

A próxima leitura sugerida é a seção especial de Concorrência, que apresenta orientações para tratamento de processamentos simultâneos.