
Se estivermos falando de um único projeto web, as informações sobre o estado de uma determinada sessão de interação entre o cliente e o servidor são fáceis de manter usando a autenticação do usuário em seu login. Mas se tal sistema independente evoluir, transformando-se em vários sistemas, o desenvolvedor se depara com a questão de manter informações sobre o estado de cada um desses sistemas separados. Na prática, a pergunta fica assim: "O usuário desses sistemas terá que entrar em cada um deles separadamente e também sair deles?"
Existe uma boa regra prática sobre os sistemas que crescem em complexidade com o tempo e como esses sistemas interagem com seus usuários. Ou seja, a responsabilidade de resolver problemas associados à complicação da arquitetura do projeto recai sobre o sistema, e não sobre seus usuários. Não importa o quão complexos sejam os mecanismos internos do projeto web. Deve parecer um sistema unificado para o usuário. Em outras palavras, um usuário que trabalha com um sistema web que consiste em muitos componentes deve perceber o que está acontecendo como se estivesse trabalhando com um sistema. Em particular, estamos falando sobre autenticação em tais sistemas usando SSO (Single Sign-On) - uma tecnologia de single sign-on.
Como faço para criar sistemas que usam SSO? Você pode pensar na boa e velha solução baseada em cookies aqui, mas esta solução está sujeita a limitações. As restrições se aplicam aos domínios dos quais os cookies são instalados. Ele pode ser contornado apenas coletando todos os nomes de domínio de todos os subsistemas do aplicativo da Web em um domínio de nível superior.
No ambiente atual, essas soluções são prejudicadas pela ampla adoção de arquiteturas de microsserviço. O gerenciamento de sessão tornou-se mais difícil em um momento em que diferentes tecnologias eram usadas no desenvolvimento de projetos da web e quando diferentes serviços às vezes eram hospedados em domínios diferentes. Além disso, os serviços da web que costumavam ser escritos em Java começaram a escrever usando os recursos da plataforma Node.js. Isso tornava mais difícil trabalhar com cookies. Descobriu-se que as sessões agora não são tão fáceis de gerenciar.
Essas dificuldades levaram ao desenvolvimento de novos métodos de registro em sistemas, em particular, estamos falando sobre a tecnologia de logon único.
Tecnologia de logon único
O princípio básico no qual a tecnologia de logon único se baseia é que um usuário pode efetuar login em um sistema de um projeto que consiste em vários sistemas e ser autorizado em todos os outros sistemas sem ter que efetuar login novamente. Ao mesmo tempo, estamos falando de uma saída centralizada de todos os sistemas.
Nós, para fins educacionais, vamos implementar a tecnologia SSO na plataforma Node.js.
É importante ressaltar que a implantação desta tecnologia em escala corporativa exigirá muito mais esforço do que iremos colocar no desenvolvimento do nosso sistema de treinamento. É por isso que existem soluções SSO especializadas projetadas para projetos de grande escala.
Como o logon SSO é organizado?
No centro da implementação de SSO está um único servidor de autenticação independente que é capaz de aceitar informações para autenticar usuários. Por exemplo - endereço de e-mail, nome de usuário, senha. Outros sistemas não fornecem ao usuário mecanismos diretos para efetuar login neles. Eles autorizam o usuário indiretamente, recebendo informações sobre ele do servidor de autenticação. Os mecanismos de autorização indireta são implementados usando tokens.
Aqui está o repositório de código para o projeto simple-sso, cuja implementação descreverei aqui. Estou usando o framework Node.js, mas você pode implementar o mesmo usando algo diferente. Vamos fazer uma análise passo a passo das ações do usuário que trabalha com o sistema e dos mecanismos que o compõem.
Passo 1
O usuário tenta acessar um recurso protegido no sistema (vamos chamar esse recurso de "consumidor SSO", "consumidor sso"). O consumidor SSO descobre que o usuário não está logado e redireciona o usuário para o "servidor SSO" ("sso-server") usando seu próprio endereço como parâmetro de consulta. Um usuário autenticado com sucesso será redirecionado para este endereço. Este mecanismo é fornecido pelo middleware Express:
const isAuthenticated = (req, res, next) => {
// , ,
// - SSO-
// URL URL,
// ,
const redirectURL = `${req.protocol}://${req.headers.host}${req.path}`;
if (req.session.user == null) {
return res.redirect(
`http://sso.ankuranand.com:3010/simplesso/login?serviceURL=${redirectURL}`
);
}
next();
};
module.exports = isAuthenticated;
Passo 2
O servidor SSO descobre que o usuário não está conectado e o redireciona para a página de login:
const login = (req, res, next) => {
// req.query url,
// , sso-.
//
//
const { serviceURL } = req.query;
// URL.
if (serviceURL != null) {
const url = new URL(serviceURL);
if (alloweOrigin[url.origin] !== true) {
return res
.status(400)
.json({ message: "Your are not allowed to access the sso-server" });
}
}
if (req.session.user != null && serviceURL == null) {
return res.redirect("/");
}
// -
//
if (req.session.user != null && serviceURL != null) {
const url = new URL(serviceURL);
const intrmid = encodedId();
storeApplicationInCache(url.origin, req.session.user, intrmid);
return res.redirect(`${serviceURL}?ssoToken=${intrmid}`);
}
return res.render("login", {
title: "SSO-Server | Login"
});
};
Vou fazer alguns comentários aqui sobre segurança.
Verificamos o
serviceURL
parâmetro de solicitação de entrada para o servidor SSO. Isto permite-nos saber se este URL está registado no sistema e se o serviço que representa pode utilizar os serviços de um servidor SSO.
Esta é a aparência de uma lista de URLs para serviços que têm permissão para usar o servidor SSO:
const alloweOrigin = {
"http://consumer.ankuranand.in:3020": true,
"http://consumertwo.ankuranand.in:3030": true,
"http://test.tangledvibes.com:3080": true,
"http://blog.tangledvibes.com:3080": fasle,
};
etapa 3
O usuário insere um nome de usuário e uma senha que são enviados ao servidor SSO na solicitação de login.

Página de login
Passo 4
O servidor de autenticação SSO verifica as informações do usuário e cria uma sessão entre ele e o usuário. Esta é a chamada "sessão global". Um token de autorização é criado imediatamente. O token é uma sequência de caracteres aleatórios. Como exatamente essa string é gerada não importa. O principal é que linhas semelhantes não se repetem para usuários diferentes, e que tal linha seria difícil de forjar.
Etapa 5
O servidor SSO pega o token de autorização e o passa para o local de origem do usuário recém-conectado (ou seja, ele passa o token para o consumidor SSO).
const doLogin = (req, res, next) => {
// .
// ,
// userDB - , ,
const { email, password } = req.body;
if (!(userDB[email] && password === userDB[email].password)) {
return res.status(404).json({ message: "Invalid email and password" });
}
//
const { serviceURL } = req.query;
const id = encodedId();
req.session.user = id;
sessionUser[id] = email;
if (serviceURL == null) {
return res.redirect("/");
}
const url = new URL(serviceURL);
const intrmid = encodedId();
storeApplicationInCache(url.origin, id, intrmid);
return res.redirect(`${serviceURL}?ssoToken=${intrmid}`);
};
Novamente, algumas notas de segurança:
- Este token deve sempre ser considerado como um mecanismo intermediário, ele é usado para obter outro token.
- Se você estiver usando um JWT como um token intermediário, tente não incluir segredos nele.
Etapa 6
O consumidor SSO recebe um token e contata o servidor SSO para verificar o token. O servidor verifica o token e retorna outro token com informações do usuário. Esse token é usado pelo consumidor SSO para criar uma sessão com o usuário. Esta sessão é chamada de local.
Aqui está o código de middleware usado no consumidor SSO baseado em Express:
const ssoRedirect = () => {
return async function(req, res, next) {
// , req queryParameter, ssoToken,
// , .
const { ssoToken } = req.query;
if (ssoToken != null) {
// ssoToken , .
const redirectURL = url.parse(req.url).pathname;
try {
const response = await axios.get(
`${ssoServerJWTURL}?ssoToken=${ssoToken}`,
{
headers: {
Authorization: "Bearer l1Q7zkOL59cRqWBkQ12ZiGVW2DBL"
}
}
);
const { token } = response.data;
const decoded = await verifyJwtToken(token);
// jwt,
// global-session-id id ,
// .
req.session.user = decoded;
} catch (err) {
return next(err);
}
return res.redirect(`${redirectURL}`);
}
return next();
};
};
Depois de receber uma solicitação de um consumidor SSO, o servidor verifica o token quanto à existência e data de expiração. O token verificado é considerado válido.
Em nosso caso, o servidor SSO, após a verificação bem-sucedida do token, retorna um JWT assinado com informações sobre o usuário.
const verifySsoToken = async (req, res, next) => {
const appToken = appTokenFromRequest(req);
const { ssoToken } = req.query;
// ssoToken .
// ssoToken - , .
if (
appToken == null ||
ssoToken == null ||
intrmTokenCache[ssoToken] == null
) {
return res.status(400).json({ message: "badRequest" });
}
// appToken -
const appName = intrmTokenCache[ssoToken][1];
const globalSessionToken = intrmTokenCache[ssoToken][0];
// appToken , SSO-
if (
appToken !== appTokenDB[appName] ||
sessionApp[globalSessionToken][appName] !== true
) {
return res.status(403).json({ message: "Unauthorized" });
}
// ,
const payload = generatePayload(ssoToken);
const token = await genJwtToken(payload);
// ,
delete intrmTokenCache[ssoToken];
return res.status(200).json({ token });
};
Aqui estão algumas notas de segurança.
- Todos os aplicativos que usarão este servidor para autenticação devem ser registrados no servidor SSO. Eles precisam receber códigos que serão usados para verificá-los quando fizerem solicitações ao servidor. Isso permite um nível mais alto de segurança ao se comunicar entre o servidor SSO e os consumidores SSO.
- É possível gerar diferentes arquivos rsa "privados" e "públicos" para cada aplicativo e permitir que cada um verifique seus JWTs internamente com suas respectivas chaves públicas.
Além disso, você pode definir uma política de segurança no nível do aplicativo e organizar seu armazenamento centralizado:
const userDB = {
"info@ankuranand.com": {
password: "test",
userId: encodedId(), // , .
appPolicy: {
sso_consumer: { role: "admin", shareEmail: true },
simple_sso_consumer: { role: "user", shareEmail: false }
}
}
};
Depois que o usuário efetua login com sucesso no sistema, as sessões são criadas entre ele e o servidor SSO, bem como entre ele e cada subsistema. A sessão estabelecida entre o usuário e o servidor SSO é chamada de sessão global. Uma sessão estabelecida entre um usuário e um subsistema que fornece ao usuário alguns serviços é chamada de sessão local. Depois que a sessão local for estabelecida, o usuário poderá trabalhar com os recursos do subsistema fechados para recursos externos.

Configurando sessões locais e globais
Um rápido tour pelo consumidor SSO e servidor SSO
Vamos fazer um rápido tour pelo consumidor SSO e a funcionalidade do servidor SSO.
▍ Consumidor SSO
- O subsistema de consumidor SSO não autentica o usuário redirecionando-o para o servidor SSO.
- Este subsistema recebe o token transmitido a ele pelo servidor SSO.
- Ele interage com o servidor para verificar a validade do token.
- Ela recebe o JWT e valida esse token usando a chave pública.
- Este subsistema estabelece uma sessão local.
▍SSO Server
- O servidor SSO valida as informações de login do usuário.
- O servidor cria uma sessão global.
- Ele cria um token de autorização.
- Um token de autorização é enviado ao consumidor SSO.
- O servidor verifica a validade dos tokens passados para ele pelos consumidores SSO.
- O servidor envia um SSO JWT ao consumidor com informações do usuário.
Organização de logout centralizado
Da mesma forma como o SSO foi implementado, a tecnologia SSO pode ser implementada. Aqui você só precisa levar em consideração as seguintes considerações:
- Se houver uma sessão local, também deverá existir uma sessão global.
- Se existir uma sessão global, isso não significa necessariamente que exista uma sessão local.
- Se a sessão local for destruída, a sessão global também deverá ser destruída.
Resultado
Como resultado, pode-se observar que existem muitas implementações prontas da tecnologia de logon único que você pode integrar em seu sistema. Todos eles têm suas próprias vantagens e desvantagens. O desenvolvimento de tal sistema de forma independente, desde o início, é um processo iterativo durante o qual você precisa analisar as características de cada um dos sistemas. Isso inclui métodos de login, armazenamento de informações do usuário, sincronização de dados e muito mais.
Seus projetos usam mecanismos SSO?
