Aquí hay muchas preguntas que tratan sobre la mecánica de autenticación y autorización de las API RESTful, pero ninguna de ellas parece entrar en detalles sobre cómo implementar servicios seguros a nivel de aplicación.
Por ejemplo, supongamos que mi aplicación web (tengo en mente Java pero esto se aplica a cualquier backend) tiene un sistema de autenticación seguro que permite a los usuarios de la API iniciar sesión con un nombre de usuario y contraseña. Cuando el usuario realiza una solicitud, en cualquier momento durante el proceso de procesamiento de la solicitud, puedo llamar a un getAuthenticatedUser()
método que devolverá el usuario nulo si el usuario no ha iniciado sesión o un objeto de dominio de Usuario que representa al usuario conectado.
La API permite a los usuarios autenticados acceder a sus datos, por ejemplo, GET para /api/orders/
devolver la lista de pedidos de ese usuario. Del mismo modo, GET to /api/tasks/{task_id}
devolverá datos relacionados con esa tarea específica.
Supongamos que hay varios objetos de dominio diferentes que pueden asociarse con la cuenta de un usuario (los pedidos y las tareas son dos ejemplos, también podríamos tener clientes, facturas, etc.). Solo queremos que los usuarios autenticados puedan acceder a los datos sobre sus propios objetos, por lo que cuando un usuario realiza una llamada, /api/invoices/{invoice_id}
debemos verificar que el usuario esté autorizado para acceder a ese recurso antes de que lo sirvamos.
Mi pregunta es, entonces, ¿existen patrones o estrategias para tratar este problema de autorización? Una opción que estoy considerando es crear una interfaz auxiliar (es decir SecurityUtils.isUserAuthorized(user, object)
), a la que se pueda llamar durante el procesamiento de la solicitud para garantizar que el usuario esté autorizado para recuperar el objeto. Esto no es ideal ya que contamina el código de punto final de la aplicación con muchas de estas llamadas, por ejemplo
Object someEndpoint(int objectId) {
if (!SecurityUtils.isUserAuthorized(loggedInUser, objectDAO.get(objectId)) {
throw new UnauthorizedException();
}
...
}
... y luego está la cuestión de implementar este método para cada tipo de dominio que podría ser un poco difícil. ¡Esta podría ser la única opción, pero me interesaría escuchar sus sugerencias!
Respuestas:
Por favor, por el amor de Dios, ¡no cree una
SecurityUtils
clase!¡Tu clase se convertirá en 10k líneas de código de espagueti en cuestión de meses! Debería tener un
Action
tipo (crear, leer, actualizar, destruir, enumerar, etc.) pasado a suisUserAuthorized()
método, que rápidamente se convertiría en unaswitch
declaración de mil líneas con una lógica cada vez más compleja que sería difícil de probar. No lo hagasEn general, lo que hago, al menos en Ruby on Rails, es que cada objeto de dominio sea responsable de sus propios privilegios de acceso al tener una clase de política para cada modelo . Luego, el controlador pregunta a la clase de política si el usuario actual para la solicitud tiene acceso al recurso o no. Aquí hay un ejemplo en Ruby, ya que nunca he implementado algo así en Java, pero la idea debería ser clara:
Incluso si tiene recursos anidados complejos, algunos recursos deben 'poseer' los recursos anidados, por lo que la lógica de nivel superior inherentemente se dispara. Sin embargo, esos recursos anidados necesitan sus propias clases de políticas en caso de que puedan actualizarse independientemente del recurso 'principal'.
En mi solicitud, que es para el departamento de mi universidad, todo gira en torno al
Course
objeto. No es unaUser
aplicación céntrica. Sin embargo, losUser
s están inscritosCourse
, así que simplemente puedo asegurarme de que:@course.users.include? current_user && (whatever_other_logic_I_need)
para cualquier recurso que un particular
User
necesite modificar, ya que casi todos los recursos están vinculados a aCourse
. Esto se hace en la clase de política delowns_whatever
método.No he hecho mucha arquitectura Java, pero parece que podrías hacer una
Policy
interfaz, donde los diferentes recursos que necesitan ser autenticados deben implementar la interfaz. Luego, tiene todos los métodos necesarios que pueden volverse tan complejos como los necesita por objeto de dominio . Lo importante es vincular la lógica al modelo en sí, pero al mismo tiempo mantenerla en una clase separada (principio de responsabilidad única (SRP)).Las acciones de su controlador podrían verse así:
fuente
Una solución más conveniente es usar anotaciones para marcar métodos que requieren algún tipo de autorización. Esto se destaca de su código comercial y puede ser manejado por Spring Security o código AOP personalizado. Si utiliza estas anotaciones en sus métodos comerciales en lugar de puntos finales, puede estar seguro de obtener una excepción cuando un usuario no autorizado intente llamarlos independientemente del punto de entrada.
fuente
@PreAuthorization("hasRole('ADMIN') and #requestingUser.company.uuid == authentication.details.companyUuid")
anotación en la que el#requestingUser
segmento haga referencia a un objeto pegado con fieldNamerequestingUser
que tiene un métodogetCompany()
cuyo objeto devuelto tiene otro métodogetUuid()
. Seauthentication
refiere alAuthentication
objeto almacenado en el contexto de seguridad.@PreAuthorize("hasPermission(#user, 'allowDoSomething')")
e implemente su evaluador de permisos personalizado o escriba un controlador de expresión personalizado y una raíz . Si desea modificar el comportamiento de las anotaciones disponibles, eche un vistazo a este hiloUse seguridad basada en capacidades.
Una capacidad es un objeto inolvidable que actúa como evidencia de que uno puede realizar una determinada acción. En este caso:
Esto hace que sea imposible intentar hacer algo que el usuario actual no está autorizado a hacer.
De esa manera es imposible
fuente