architecture - the - Autorización REST
famous architects 2018 (3)
Estoy construyendo un sitio basado en la comunidad en Rails para los miembros de una organización del mundo real. Estoy tratando de adherirme a las mejores prácticas de diseño RESTful, y la mayor parte es más o menos por el libro. El problema que hace que mi cerebro funcione en círculos RESTful es la autorización. La autenticación es un problema fácil y de larga resolución con soluciones RESTful ampliamente aceptadas, pero la autorización RESTful parece ser un poco de arte negro. Estoy tratando de encontrar el enfoque que proporcionará el marco más general y flexible para controlar el acceso a los recursos y, al mismo tiempo, ser lo más sencillo posible, a la vez que se ajusta a una arquitectura RESTful. (También, un pony.)
Consideraciones:
- Necesito controlar el acceso a una variedad de recursos, como Usuarios, Páginas, Mensajes, etc.
- La autorización para un recurso dado debe ser más detallada que la simple CRUD.
- Deseo permitirme y a otros editar las reglas de autorización desde la aplicación.
- Se debe permitir que las reglas de autorización dependan de predicados, como (conceptualmente) Propietario (Usuario, Recurso) o Bloqueado (Tema)
La consideración (2) es la que más me preocupa. Parece haber un desajuste de impedancia entre mi concepción de los permisos y la concepción REST de las acciones. Por ejemplo, tomar publicaciones (como en un tablero de mensajes). REST dicta la existencia de cuatro operaciones en el recurso Publicar: Crear, Leer, Actualizar y Eliminar. Es simple decir que un usuario debería poder actualizar sus propias publicaciones, pero solo ciertos usuarios (o roles, o grupos) deben poder bloquearlos. La forma tradicional de representar el bloqueo se encuentra dentro del estado de la publicación, pero eso lleva al olor que un usuario en las mismas condiciones puede o no puede actualizar una publicación dependiendo de los valores (completamente válidos) que proporciona. Me parece claro que realmente hay dos acciones diferentes para cambiar el estado del Correo, y calzarlas es simplemente disfrazar una violación de los principios REST.
(Debo tener en cuenta que este problema es bastante distinto del problema de una falla de actualización debido a datos no válidos o inconsistentes ; en principio, una solicitud de bloqueo de un usuario sin privilegios es bastante válida, simplemente no está permitida).
¿No es la descomposición otra palabra para la podredumbre?
Esto puede superarse descomponiendo la publicación: un bloqueo es un sub-recurso de una publicación en particular, y para crear o destruir, uno puede tener permisos separados. Esta solución tiene el anillo de REST, pero trae consigo dificultades tanto teóricas como prácticas. Si factorizo los bloqueos, ¿qué pasa con otros atributos? Supongamos que decido, en un ataque de capricho, que solo a un miembro del Administrador se le debe permitir modificar el título de la publicación. ¡Un simple cambio en la autorización requeriría una reestructuración de la base de datos para acomodarla! Esto no es una gran solución. Para permitir este tipo de flexibilidad bajo una estrategia de descomposición se requeriría que cada atributo sea un recurso. Esto presenta un poco de un dilema. Mi suposición implícita ha sido que un recurso está representado en la base de datos como una tabla. Bajo este supuesto, un recurso para cada atributo significa una tabla para cada atributo. Claramente, esto no es práctico. Sin embargo, para eliminar este supuesto, se presenta un desajuste de impedancia entre tablas y recursos, que podría abrir su propia lata de gusanos. Para utilizar este enfoque se requeriría una consideración mucho más profunda de la que le he dado. Por un lado, los usuarios esperan razonablemente poder editar múltiples atributos a la vez. ¿A dónde va la solicitud? ¿Al recurso más pequeño que contiene todos los atributos? ¿A cada recurso individual en paralelo? ¿A la Luna?
Algunas de estas cosas no son como las otras ...
Supongamos entonces que no descompongo los atributos. La alternativa entonces parece ser definir un conjunto de privilegios para cada recurso. En este punto, sin embargo, se pierde la homogeneidad de REST. Para definir las reglas de acceso para un recurso, el sistema debe tener un conocimiento específico de las capacidades de ese recurso. Además, ahora es imposible propagar genéricamente los permisos a los recursos descendientes, incluso si un recurso hijo tiene un privilegio del mismo nombre, no hay una conexión semántica inherente entre los privilegios. Definir un conjunto de privilegios estándar tipo REST me parece que es el peor de los dos mundos, así que me quedo con una jerarquía de permisos separada para cada tipo de recurso.
Bueno, hicimos la nariz. Y el sombrero. ¡Pero es un recurso!
Una sugerencia que he visto que mitiga algunas de las desventajas del enfoque anterior es definir los permisos como crear / eliminar en recursos y leer / escribir en atributos . Este sistema es un compromiso entre los atributos como recursos y los privilegios por recurso: uno solo se queda con CRUD, pero a los efectos de la autorización, la lectura y la actualización se refieren a atributos, que podrían considerarse como pseudo recursos. Esto proporciona muchos de los beneficios prácticos del enfoque de atributos como recursos, aunque la integridad conceptual está, en cierta medida, comprometida. Los permisos todavía podrían propagarse de un recurso a otro y de un recurso a un pseudo-recurso, pero nunca de un pseudo-recurso. No he explorado completamente las ramificaciones de esta estrategia, pero parece que puede ser prometedor. Se me ocurre que tal sistema funcionaría mejor como parte integral del Modelo. En Rails, por ejemplo, podría ser una actualización de ActiveRecord
. Esto me parece bastante drástico, pero la autorización es una preocupación transversal tan fundamental que puede justificarse.
Ah, y no te olvides del pony.
Todo esto ignora el tema de los permisos predicativos. Obviamente, un usuario debería poder editar sus propias publicaciones, pero las de nadie más. Obviamente, la tabla de permisos escritos por el administrador no debe tener registros separados para cada usuario. Esto no es un requisito poco común, el truco es hacerlo genérico. Creo que toda la funcionalidad que necesito podría obtenerse haciendo que solo las reglas sean predicativas, de modo que la aplicabilidad de la regla se pueda decidir de forma rápida e inmediata. Una regla " allow User write Post where Author(User, Post)
" se traduciría a " for all User, Post such that Author(User, Post), allow User write Post
", y " deny all write Post where Locked(Post)
"para" for all Post such that Locked(Post), deny all write Post
". (Sería grandioso si todos estos predicados pudieran expresarse en términos de un Usuario y un Recurso). Las reglas "finales" conceptualmente resultantes no serían predicativas. Esto plantea la cuestión de cómo implementar dicho sistema. Los predicados deben ser miembros de las clases del Modelo, pero no estoy seguro de cómo uno podría referirse a ellos con gracia en el contexto de las reglas. Hacerlo con seguridad requeriría algún tipo de reflexión. Aquí, nuevamente, tengo la sensación de que esto requeriría una modificación de la implementación del Modelo.
¿Cómo se escribe eso de nuevo?
La pregunta final es cómo representar mejor estas reglas de autorización como datos. Una tabla de base de datos podría hacer el truco, con columnas enumerar para permitir / denegar y C / R / U / D (¿o quizás bits de CRUD? O quizás {C, R, U, D} × {permitir, negar, heredar}?) , y una columna de recursos con una ruta. Quizás, por conveniencia, un bit "hereditario". Estoy en una pérdida en cuanto a predicados. Mesa separada? Ciertamente, un montón de almacenamiento en caché para evitar que sea demasiado lento.
Supongo que esto es mucho pedir. Traté de hacer mi tarea antes de hacer la pregunta, pero en este punto realmente necesito una perspectiva externa. Apreciaría cualquier entrada que cualquiera de ustedes pueda tener sobre el problema.
Como señaló Darrel, REST no es CRUD. Si encuentra que sus recursos identificados son demasiado generales que la interfaz uniforme no proporciona suficiente control, divida su recurso en sub-recursos y use el recurso original como una "colección" de hipervínculos a sus componentes.
Lo siento, no tengo tiempo para hacer justicia a esta pregunta, pero es bueno ver algunas preguntas bien pensadas sobre SO. Aquí hay algunos comentarios:
No caigas en la trampa de mapear los verbos HTTP a CRUD. Sí. GET y DELETE el mapa de forma bastante limpia, pero PUT puede hacer Crear y Actualizar (pero solo reemplazo completo) y POST es un verbo comodín. POST es realmente manejar todo lo que no encaja en GET, PUT y DELETE.
El uso de atributos para representar el estado de un objeto es solo un enfoque para la administración del estado. Supongo que puedes imaginar lo que podría hacer la siguiente solicitud:
POST /LockedPosts?url=/Post/2010
Un sub-recurso también es un enfoque válido para administrar el estado actual de un recurso. No me sentiría obligado a tratar los atributos de "estado" de un recurso y sus atributos de "datos" de manera consistente.
Intentar asignar recursos directamente a las tablas lo limitará seriamente. No olvides que cuando sigues las restricciones REST, de repente estás muy limitado en los verbos que tienes disponibles. Debe ser capaz de compensar eso siendo creativo en los recursos que utiliza. Limitarse a un recurso es igual a una tabla, limitará severamente la funcionalidad de su aplicación final.
Regularmente vemos a los usuarios de Rails, ASP.NET MVC y WCF Rest que publican preguntas aquí en sobre cómo hacer ciertas cosas dentro de las restricciones de REST. El problema a menudo no es una restricción de REST sino en las limitaciones del marco en su soporte para aplicaciones RESTful. Creo que es esencial encontrar primero una solución REST completa para un problema y luego ver si se puede volver a asignar al marco de su elección.
En cuanto a la creación de un modelo de permisos que existe en un grano más fino que el recurso en sí. Recuerde que una de las restricciones REST clave es la hipermedia. Hypermedia se puede usar para más que solo encontrar entidades relacionadas, también se puede usar para representar transiciones de estado válidas / permitidas. Si devuelve una representación que contiene enlaces incrustados, condicionalmente basados en permisos, entonces puede controlar qué acciones pueden ser realizadas por quién. es decir, si un usuario tiene permisos para desbloquear POST 342, puede devolver el siguiente enlace incrustado en la representación:
<Link href="/UnlockedPosts?url=/Post/342" method="POST"/>
Si no tienen ese permiso, entonces no devuelvas el enlace.
Creo que una de sus dificultades aquí es que está tratando de masticar un problema demasiado grande al mismo tiempo. Creo que necesita ver la interfaz RESTful que está intentando exponer al cliente como un problema distinto de cómo va a administrar los permisos y predicados para administrar la autorización en su modelo de dominio.
Me doy cuenta de que no he respondido directamente ninguna de sus preguntas, pero espero que haya proporcionado algunos puntos de vista que puedan ayudar de alguna manera.
Recientemente he descubierto una solución de autenticación que parece abordar la mayoría de mis preocupaciones. Si prefieres esta pregunta, te puede interesar: