Há muitas perguntas aqui que tratam da mecânica de autenticação e autorização de APIs RESTful, mas nenhuma delas parece entrar em detalhes de como implementar serviços seguros no nível do aplicativo.
Por exemplo, digamos que meu aplicativo da web (eu tenho Java em mente, mas isso se aplica a qualquer back-end realmente) tem um sistema de autenticação seguro que permite que os usuários da API efetuem login com um nome de usuário e senha. Quando o usuário faz uma solicitação, a qualquer momento durante o pipeline de processamento de solicitações, posso chamar um getAuthenticatedUser()
método que retornará o usuário nulo se o usuário não estiver conectado ou um objeto de domínio do Usuário que represente o usuário conectado.
A API permite que usuários autenticados acessem seus dados, por exemplo, um GET /api/orders/
retornará a lista de pedidos desse usuário. Da mesma forma, um GET /api/tasks/{task_id}
retornará dados relacionados a essa tarefa específica.
Vamos supor que existem vários objetos de domínio diferentes que podem ser associados à conta de um usuário (pedidos e tarefas são dois exemplos, também podemos ter clientes, faturas etc.). Queremos que apenas usuários autenticados possam acessar dados sobre seus próprios objetos, portanto, quando um usuário faz uma ligação /api/invoices/{invoice_id}
, precisamos verificar se o usuário está autorizado a acessar esse recurso antes de servi-lo.
Minha pergunta é: existem padrões ou estratégias para lidar com esse problema de autorização? Uma opção que estou considerando é criar uma interface auxiliar (ou seja SecurityUtils.isUserAuthorized(user, object)
), que pode ser chamada durante o processamento de solicitações para garantir que o usuário esteja autorizado a buscar o objeto. Isso não é ideal, pois polui o código do terminal do aplicativo com muitas dessas chamadas, por exemplo
Object someEndpoint(int objectId) {
if (!SecurityUtils.isUserAuthorized(loggedInUser, objectDAO.get(objectId)) {
throw new UnauthorizedException();
}
...
}
... e depois há a questão de implementar esse método para cada tipo de domínio que pode ser um pouco trabalhoso. Essa pode ser a única opção, mas eu gostaria de ouvir suas sugestões!
Respostas:
Por favor, pelo amor de Deus, não crie uma
SecurityUtils
classe!Sua classe se tornará 10 mil linhas de código espaguete em questão de meses! Você precisaria passar um
Action
tipo (criar, ler, atualizar, destruir, listar, etc.) em seuisUserAuthorized()
método, que rapidamente se tornaria umaswitch
declaração de mil linhas com uma lógica cada vez mais complexa que seria difícil de testar na unidade. Não faça isso.Geralmente, o que faço, pelo menos no Ruby on Rails, é que cada objeto de domínio seja responsável por seus próprios privilégios de acesso, tendo uma classe de política para cada modelo . Em seguida, o controlador pergunta à classe de política se o usuário atual da solicitação tem acesso ao recurso ou não. Aqui está um exemplo no Ruby, já que eu nunca implementei algo assim em Java, mas a ideia deve aparecer de maneira limpa:
Mesmo se você tiver recursos aninhados complexos, algum recurso deve 'possuir' os recursos aninhados, para que a lógica de nível superior seja inerentemente reduzida. No entanto, esses recursos aninhados precisam de suas próprias classes de política, caso possam ser atualizados independentemente do recurso 'pai'.
Na minha inscrição, que é para o departamento da minha universidade, tudo gira em torno do
Course
objeto. Não é umUser
aplicativo centralizado. No entanto,User
s estão matriculadosCourse
, para que eu possa simplesmente garantir que:@course.users.include? current_user && (whatever_other_logic_I_need)
para qualquer recurso que um particular
User
precise modificar, uma vez que quase todos os recursos estão vinculados a aCourse
. Isso é feito na classe de política noowns_whatever
métodoNão fiz muita arquitetura Java, mas parece que você pode criar uma
Policy
interface, na qual os diferentes recursos que precisam ser autenticados devem implementar a interface. Então, você tem todos os métodos necessários que podem se tornar tão complexos quanto você precisa que sejam por objeto de domínio . O importante é amarrar a lógica ao modelo em si, mas, ao mesmo tempo, mantê-la em uma classe separada (princípio de responsabilidade única (SRP)).Suas ações do controlador podem ser algo como:
fonte
Uma solução mais conveniente é usar anotações para marcar métodos que requerem alguma forma de autorização. Isso se destaca do seu código comercial e pode ser tratado pelo Spring Security ou pelo código AOP personalizado. Se você usar essas anotações nos seus métodos de negócios, e não nos pontos de extremidade, poderá obter uma exceção quando um usuário não autorizado tentar chamá-los, independentemente do ponto de entrada.
fonte
@PreAuthorization("hasRole('ADMIN') and #requestingUser.company.uuid == authentication.details.companyUuid")
anotação em que o#requestingUser
segmento faça referência a um objeto colado com o fieldNamerequestingUser
que possui um métodogetCompany()
cujo objeto retornado possui outro métodogetUuid()
. Oauthentication
refere-se aoAuthentication
objeto armazenado no contexto de segurança.@PreAuthorize("hasPermission(#user, 'allowDoSomething')")
e implemente seu avaliador de permissão personalizado ou escreva um manipulador de expressão personalizado e raiz . Se você quiser alterar o comportamento de anotações disponíveis hava uma olhada esta discussãoUse segurança baseada em capacidade.
Uma capacidade é um objeto imperdoável que atua como evidência de que se pode executar uma determinada ação. Nesse caso:
Isso torna impossível tentar fazer algo que o usuário atual não está autorizado a fazer.
Dessa forma, é impossível
fonte