A autenticação por token no Db2, introduzida na 11.5.4, permite que um cliente se ligue com um token em vez usar uma palavra-passe. Isso torna-se útil em integrações com curso a SSO e em fluxos de aplicação em que a identidade já foi estabelecida a montante.
Este artigo é deliberadamente prático. Cada passo abaixo mostra os ficheiros exatos a criar, os comandos a executar e as verificações a fazer. O lab foi pensado para ser reproduzido numa máquina Linux com uma instância Db2.
Autenticação no Db2
Antes de abordarmos a autenticação por token, é importante enumerar os aspetos de segurança importantes para o Db2:
- Autenticação valida e estabelece a identidade
- Autorização determina o que é permitido fazer sob uma determinada identidade
- Estabelecimento da ligação é o handshake que transporta tanto as credenciais como a política de segurança
Numa ligação Db2 tradicional, o cliente fornece um nome de utilizador e uma palavra-passe, o Db2 valida-os e o Authorization ID resultante passa a ser a identidade da sessão. A autenticação por token modifica o primeiro passo: em vez de uma palavra-passe, o cliente fornece um token assinado de forma a provar a sua identidade.
Essa diferença tem impactos operacionais importantes. O aplicativo já não tem de gerir a palavra-passe. Em vez disso, o Db2 valida um token emitido e assinado noutro sistema e depois mapeia um dos claims do token para o authid Db2 usado pela sessão.
A forma como as autorizações são validadas permanece inalterada. O Db2 continua a aplicar privilégios, roles e autorizações administrativas da base de dados e instância à identidade que foi estabelecida.
JWT
Um JSON Web Token (JWT) é uma forma compacta de transportar claims sobre uma identidade e assiná-los para que o recetor possa confiar no conteúdo.
O JWT tem três partes:
- um header, que descreve o algoritmo de assinatura
- um payload, que transporta claims como issuer, subject e expiração
- uma signature, que prova que o token foi assinado pr uma entidade confiável
Para este lab, os claims importantes são:
iss, que identifica o issuerusername, que mapeamos para o authid do Db2iat, que indica quando o token foi criadoexp, que indica quando o token deixa de ser válido
O Db2 precisa apenas da informação suficiente para confiar no issuer, extrair o claim mapeado e rejeitar tokens expirados ou mal formados. É por isso que o resto do artigo usa um JWT assinado simples em vez de uma plataforma de estabelecimento de identidade completa.
O que este lab constrói
No fim do lab terá:
- um par de chaves local para assinar JWTs
- um keystore PKCS#12 com o certificado do issuer para validação no Db2
- um ficheiro
db2token.cfgque confia no issuer e mapeia um claim para o authid do Db2 - o Db2 configurado para aceitar ligações de servidor baseadas em token
- uma base de dados que aceita uma ligação CLP baseada em token
- um exemplo JDBC que usa o mesmo token
- um teste de falha reproduzível para tokens inválidos ou expirados
Pré-requisitos
Use um servidor Db2 com versão 11.5.4 ou posterior.
Também necessita de:
- Acesso shell à conta do proprietário da instância Db2
opensslgsk8capicmd_64- O CLP do Db2
- Suporte de execução de Java e o driver JDBC do Db2 se pretender executar o exemplo Java
1. Criar o par de chaves do issuer
O issuer é o sistema que assina o JWT. Para o lab vamos mantê-lo local para que o fluxo seja fácil de reproduzir.
Faça login como o proprietário da instância Db2, crie um diretório de trabalho e efetue a geração de uma chave RSA de 3072 bits:
mkdir -p "$HOME/db2-token-lab"
cd "$HOME/db2-token-lab"
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:3072 -out jwt-issuer.keyCrie um certificado auto-assinado a partir dessa chave:
openssl req -new -x509 \
-key jwt-issuer.key \
-out jwt-issuer.crt \
-days 365 \
-subj "/CN=PYXIS-ISSUER/O=Pyxis/L=Lisbon/C=PT"O subject do certificado não é a string do issuer do JWT. É apenas a identidade do certificado em que o Db2 vai confiar.
2. Criar o keystore que o Db2 vai usar
O Db2 precisa de um keystore local PKCS#12 com o certificado do issuer.
Crie o keystore:
gsk8capicmd_64 -keydb -create \
-db "$HOME/db2-token-lab/jwtkeys.p12" \
-pw "Db2Token123!" \
-stashImporte o certificado do issuer nesse keystore com uma etiqueta que vamos referenciar mais à frente:
gsk8capicmd_64 -cert -add \
-db "$HOME/db2-token-lab/jwtkeys.p12" \
-pw "Db2Token123!" \
-label db2jwt \
-file "$HOME/db2-token-lab/jwt-issuer.crt" \
-format asciiVerifique a etiqueta se quiser confirmar que foi guardada corretamente:
gsk8capicmd_64 -cert -list \
-db "$HOME/db2-token-lab/jwtkeys.p12" \
-pw "Db2Token123!"O Db2 vai usar este keystore para validar a assinatura do JWT.
3. Criar o db2token.cfg
Agora, crie o ficheiro de configuração de tokens no diretório de configuração da instância:
cat > "$HOME/sqllib/cfg/db2token.cfg" <<EOF
VERSION=1
TOKEN_TYPES_SUPPORTED=JWT
JWT_KEYDB=$HOME/db2-token-lab/jwtkeys.p12
JWT_IDP_ISSUER=PYXIS-ISSUER
JWT_IDP_AUTHID_CLAIM=username
JWT_IDP_RSA_CERTIFICATE_LABEL=db2jwt
EOF
chmod 600 "$HOME/sqllib/cfg/db2token.cfg"Explicando o conteúdo:
VERSION=1declara o formato do ficheiro de configuraçãoTOKEN_TYPES_SUPPORTED=JWTdiz ao Db2 que tokens JWT são permitidosJWT_KEYDBaponta para o keystore localJWT_IDP_ISSUERtem de coincidir exatamente com o claimissJWT_IDP_AUTHID_CLAIMdiz ao Db2 qual é o claim que contém o authidJWT_IDP_RSA_CERTIFICATE_LABELidentifica o certificado no keystore
Verifique o ficheiro:
cat "$HOME/sqllib/cfg/db2token.cfg"4. Ativar a autenticação por token no servidor
Se a instância ainda não estiver a usar TCP/IP, ative agora a utilização do protocolo configurando a variável DB2COMM do Registry:
db2set DB2COMM=TCPIPDefina o modo de autenticação de ligações ao servidor para encriptação baseada em token:
db2 update dbm cfg using srvcon_auth SERVER_ENCRYPT_TOKENReinicie a instância para seja aplicada a nova configuração:
db2stop force
db2startConfirme o parâmetro depois do reinício:
db2 get dbm cfg | egrep -i "svcename|srvcon_auth"Determine a porta TCP real em que o Db2 está a escutar. Vamos guardar o valor na variável de ambiente DB2_PORT:
SVCENAME=$(db2 get dbm cfg | awk -F= '/TCP\/IP Service name/ {gsub(/[[:space:]]/, "", $2); print $2}')
if [[ "$SVCENAME" =~ ^[0-9]+$ ]]; then
DB2_PORT="$SVCENAME"
else
DB2_PORT=$(awk -v svc="$SVCENAME" '$1 == svc && $2 ~ /tcp/ {split($2, a, "/"); print a[1]; exit}' /etc/services)
fi
echo "$DB2_PORT"
ss -ltn | grep "$DB2_PORT"Se DB2_PORT vier vazio, o nome de serviço vindo de svcename não está mapeado em /etc/services e o JDBC continuará sem saber que porta usar.
5. Criar uma base de dados de teste
Crie uma base de dados para a demonstração:
db2 create database TOKDBLigue-se uma vez para confirmar que a base de dados está disponível e confira autorização de connect ao utilizador DEMO1:
db2 connect to TOKDB
db2 "grant connect on database to user DEMO1"
db2 connect reset
db2 terminate6. Gerar um JWT assinado
O token precisa de conter pelo menos:
issa corresponder ao nome do issuer emdb2token.cfg- um claim que mapeia para o authid
- a hora de emissão
- a hora de expiração
Guarde o seguinte script como make-jwt.sh no diretório do lab:
#!/usr/bin/env bash
set -euo pipefail
if [ "$#" -lt 4 ]; then
echo "Usage: $0 <private-key> <issuer> <authid> <ttl-seconds>" >&2
exit 1
fi
key_file="$1"
issuer="$2"
authid="$3"
ttl_seconds="$4"
now=$(date +%s)
exp=$((now + ttl_seconds))
header='{"alg":"RS256","typ":"JWT"}'
payload=$(printf '{"username":"%s","sub":"%s","iss":"%s","iat":%s,"exp":%s}' \
"$authid" "$authid" "$issuer" "$now" "$exp")
b64url() {
openssl base64 -A | tr '+/' '-_' | tr -d '='
}
header_b64=$(printf '%s' "$header" | b64url)
payload_b64=$(printf '%s' "$payload" | b64url)
unsigned_token="${header_b64}.${payload_b64}"
signature_b64=$(printf '%s' "$unsigned_token" \
| openssl dgst -sha256 -sign "$key_file" -binary \
| b64url)
printf '%s.%s\n' "$unsigned_token" "$signature_b64"Torne-o executável:
chmod +x make-jwt.shEfetue a geração do token para o authid DEMO1 atribuindo-lhe validade de uma hora:
./make-jwt.sh "$HOME/db2-token-lab/jwt-issuer.key" PYXIS-ISSUER DEMO1 3600 > token.jwtInspecione o ficheiro do token:
TOKEN=$(cat token.jwt)
base64 -d <<< $(echo $TOKEN | cut -d'.' -f1 | tr '_-' '/+')
base64 -d <<< $(echo $TOKEN | cut -d'.' -f2 | tr '_-' '/+')O claim username é importante porque db2token.cfg mapeia esse claim para o authorization ID do Db2.
7. Ligar a partir do CLP do Db2
Use o token numa ligação explícita do CLP:
db2 connect to TOKDB accesstoken "$(cat token.jwt)" accesstokentype jwtSe a ligação tiver sucesso, valide a identidade mapeada:
db2 "values current user"Deverá ver o authid mapeado do token em maiúsculas, que nesta demonstração é DEMO1.
Termine a ligação e a sessão de CLP:
db2 connect reset
db2 terminate8. Ligar a partir de JDBC
A demonstração JDBC demonstra a utilização do token a partir de um aplicativo Java.
Guarde o seguinte código como TokenAuthDemo.java:
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import com.ibm.db2.jcc.DB2BaseDataSource;
import com.ibm.db2.jcc.DB2SimpleDataSource;
public class TokenAuthDemo {
public static void main(String[] args) throws Exception {
if (args.length < 3) {
throw new IllegalArgumentException("Usage: TokenAuthDemo <jwt> <host> <port>");
}
String token = args[0];
String host = args[1];
int port = Integer.parseInt(args[2]);
DB2SimpleDataSource ds = new DB2SimpleDataSource();
ds.setDriverType(4);
ds.setServerName(host);
ds.setPortNumber(port);
ds.setDatabaseName("TOKDB");
ds.setLoginTimeout(15);
ds.setSecurityMechanism(DB2BaseDataSource.TOKEN_SECURITY);
ds.setAccessToken(token);
ds.setAccessTokenType("JWT");
try (Connection conn = ds.getConnection();
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("VALUES CURRENT USER")) {
while (rs.next()) {
System.out.println("CURRENT USER = " + rs.getString(1));
}
}
}
}Compile com o driver JDBC do Db2 no classpath:
javac -cp "$HOME/sqllib/java/db2jcc4.jar" TokenAuthDemo.javaSe obtiver erros como package com.ibm.db2.jcc does not exist, o classpath não está a apontar para o ficheiro jar real do driver JDBC do Db2. Numa instalação típica do Db2 no servidor, o db2jcc4.jar fica disponível em $HOME/sqllib/java/.
Execute com o mesmo token:
java -cp ".:$HOME/sqllib/java/db2jcc4.jar" TokenAuthDemo "$(cat token.jwt)" localhost "$DB2_PORT"Se a configuração estiver correta, o programa mostra o utilizador mapeado pelo token.
O exemplo define um timeout de login de 15 segundos para falhar em vez de ficar pendurado indefinidamente.
Se obtiver Connection refused, o Db2 não está a escutar no host/porta usados pelo JDBC. Verifique o valor de svcename, confirme que a instância foi reiniciada, resolva a porta real e valide com ss -ltn que o listener está realmente ativo.
9. Teste com vários cenários de falha
Testamos agora com vários cenários de falha.
Issuer errado
Crie um token com uma string de issuer diferente:
./make-jwt.sh "$HOME/db2-token-lab/jwt-issuer.key" BAD-ISSUER DEMO1 3600 > bad-issuer.jwt
db2 connect to TOKDB accesstoken "$(cat bad-issuer.jwt)" accesstokentype jwtO teste deve falhar porque BAD-ISSUER não coincide com JWT_IDP_ISSUER.
Token expirado
Crie um token com um tempo de vida negativo:
./make-jwt.sh "$HOME/db2-token-lab/jwt-issuer.key" PYXIS-ISSUER DEMO1 -60 > expired.jwt
db2 connect to TOKDB accesstoken "$(cat expired.jwt)" accesstokentype jwtO teste deve falhar porque o token já expirou.
Claim de authid errado
Crie um token para outro utilizador e tente ligar-se à base de dados:
./make-jwt.sh "$HOME/db2-token-lab/jwt-issuer.key" PYXIS-ISSUER OTHERUSER 3600 > wrong-user.jwt
db2 connect to TOKDB accesstoken "$(cat wrong-user.jwt)" accesstokentype jwtO teste falha. A ligação só pode ter sucesso se esse authid for aceitável no seu ambiente. Caso contrário, esta é uma forma clara de mostrar que o mapeamento de claim para authid importa.
Resolução de problemas
Se a ligação não funcionar, verifique primeiro se:
- o
db2token.cfgestá no diretório de configuração da instância - o claim
isscoincide comJWT_IDP_ISSUER - o token está assinado com a mesma chave do certificado confiável
- o token ainda não expirou
- o Db2 está a escutar na porta TCP/IP esperada
srvcon_authestá definido paraSERVER_ENCRYPT_TOKEN- a instância foi reiniciada após as alterações
Se a ligação por JDBC falhar mas o teste por CLP funcionar, verifique:
- a versão do driver
- a definição
TOKEN_SECURITY - o valor
accessTokenType
Principais conclusões
- A autenticação por token no Db2 está disponível a partir da 11.5.4.
- O servidor usa
db2token.cfgpara confiar no issuer e mapear claims para authids. - O CLP e o JDBC conseguem autenticar com um JWT.
Documentação IBM útil
Para aprofundar, as referências IBM mais úteis para este tema são: