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
doCloudsuppport
que defineWebDetails
como sendo o objeto details doAuthenticationToken
do contexto do Spring Security. -
Suporte a hidratação (injeção) de atributos no
WebDetails
por beans do tipoWebDetailsAttributesSource
, a serem declarados pela aplicação, que permite a ela prover dados adicionais de maneira personalizável. -
Um bean
WebDetailsAttributesSource
que injetaorigin_ip
,origin_proto
,remote_addr
esession_id
noWebDetails
. -
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 doSecurityRequirement
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:
- 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.