如何在权限下适应UX / UI

许多项目都有认证过程(某种程度上)。 在所有已知技术中都写有许多最佳实践。 等


但是用户登录了吗? 毕竟,他不能做任何事情。 如何确定他能看到什么,什么看不到。 它有权按哪些按钮,可以更改创建或删除的按钮。


在本文中,我想考虑一种解决Web应用程序上这些问题的方法。


图片


首先,真正/有效的授权只能在服务器上进行。 在前端,我们只能改善用户界面(嗯,俄语很强大,但并不总是...),这也是UX之外的用户体验。 我们可以隐藏用户无权单击的按钮,或者阻止用户访问该页面,或者显示一条消息,表明他无权执行此操作。


随之而来的问题是,如何尽可能正确地做到这一点。


让我们从定义问题开始。


我们创建了Todo App,它具有不同类型的用户:


USER-可以查看所有任务并进行更改(设置和删除V),但不能删除或创建,也看不到统计信息。


管理 -可以查看所有任务并创建新任务,但看不到统计信息。


SUPER_ADMIN-可以查看所有任务,创建和删除新任务,还可以查看统计信息。


查看任务创建任务选中/取消选中任务(更新)删除任务查看统计
用户名VXVXX
管理员VVVXX
SUPER_ADMINVVVVV

在这种情况下,我们可以轻松地胜任这些角色。 但是,情况可能会发生很大变化。 假设有一个用户必须具有与ADMIN相同的权限以及删除任务。 或者仅是具有查看统计信息功能的USER


一个简单的解决方案是创建新角色。
但是在拥有大量用户的大型系统上,我们很快就迷失了许多角色...在这里,我们回顾了“用户权限” 权限 。 为了简化管理,我们可以创建具有多个权限的组并将其附加到用户。 总是有机会向特定用户添加特定权限。


在许多出色的服务中都可以找到类似的解决方案。 AWS,Google Cloud,SalesForce等
同样,类似的解决方案已经在许多框架中实现,例如Django(python)。


我想举一个Angular应用程序的实现示例。 (以相同的ToDo应用为例)。


首先,您需要确定所有可能的权限。


分为功能


  1. 我们有任务和统计数据。
  2. 我们确定它们各自可能采取的措施。
    • 任务:创建,读取,更新,删除
    • 统计信息:已读(在我们的示例中,仅用于查看)
  3. 创建角色和权限的地图(MAP)

export const permissionsMap = { todos:{ create:'*', read:'*', update:'*', delete:'*' }, stats:'*' } 

我认为这是最好的选择,但是在大多数情况下,服务器将返回以下内容:


 export permissions = [ 'todos_create', 'todos_read', 'todos_update', 'todos_delete', 'stas' ] 

可读性差,但也很好。


如果用户拥有所有可能的权限,这就是我们的应用程序的外观。


图片


让我们从USER开始,它的权限如下所示:


 export const USERpermissionsMap = { todos:{ read:'*', update:'*', } } 

1)USER不能查看统计信息,即原则上不能进入统计信息页面。


在这种情况下,Angular拥有Guard,它们在Routes级别注册( 文档 )。


在我们的例子中,它看起来像这样:


 const routes: Routes = [ // {...Other routes}, { path: 'stats', component: TodoStatisticsComponent, canActivate: [ PermissionsGuardService ], data: { permission: 'stats' }, } ]; 

您应该注意数据中的对象。


peremissions ='stats',这是用户访问该页面所必须具有的权限。


根据所需的data.permissions和PermissionsGuardService服务器提供给我们的权限,它将决定是否让USER转到“ / stats”页面。


 @Injectable({ providedIn: 'root' }) export class PermissionsGuardService implements CanActivate { constructor( private store: Store<any>) { } canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | boolean { // Required permission to continue navigation const required = route.data.permission; // User permissions that we got from server const userPerms = this.getPermissions(); // verification const isPermitted = checkPermissions(required, userPerms); if (!isPermitted) { alert('ROUTE GUARD SAYS: \n You don\'t have permissions to see this page'); } return isPermitted; } getPermissions() { return localStorage.get('userPermissions') } } 

此函数包含决策逻辑-USER(userPerms)的所有权限中是否存在不可替代的权限(必需)。


 export function checkPermissions(required: string, userPerms) { // 1) Separate feature and action const [feature, action] = required.split('_'); // 2) Check if user have any type of access to the feature if (!userPerms.hasOwnProperty(feature)) { return false; } // 3) Check if user have permission for required action if (!userPerms[feature].hasOwnProperty(action)) { return false; } return true; } 

图片


等等。 我们的用户无法访问统计信息页面。 但是,他仍然可以创建任务并将其删除。


为了使USER无法删除任务,从任务行中删除红色(X)就足够了。
为此,我们将使用“ 结构指令”


 <!-- Similar to other Structural Directives in angular (*ngIF, *ngFor..) we have '*' in directive name Input for directive is a required permission to see this btn. --> <button *appPermissions="'todos_delete'" class="delete-button" (click)="removeSingleTodo()"> X </button> 

 @Directive({ selector: '[appPermissions]' }) export class PermissionsDirective { private _required: string; private _viewRef: EmbeddedViewRef<any> | null = null; private _templateRef: TemplateRef<any> | null = null; @Input() set appPermissions(permission: string) { this._required = permission; this._viewRef = null; this.init(); } constructor(private templateRef: TemplateRef<any>, private viewContainerRef: ViewContainerRef) { this._templateRef = templateRef; } init() { const isPermitted = checkPermissions(this._required, this.getPermissions()); if (isPermitted) { this._viewRef = this.viewContainerRef.createEmbeddedView(this.templateRef); } else { console.log('PERMISSIONS DIRECTIVE says \n You don\'t have permissions to see it'); } } getPermissions() { localStorage.get('userPermissions') } } 

图片


现在,USER没有看到DELETE按钮,但是仍然可以添加新任务。


删除输入字段-将破坏我们所有的应用程序。 在这种情况下,正确的解决方案是disable输入字段。


我们将使用管道。


 <!-- as a value `permissions` pipe will get required permissions `permissions` pipe return true or false, that's why we have !('todos_create' | permissions) to set disable=true if pipe returns false --> <input class="centered-block" [disabled]="!('todos_create' | permissions)" placeholder="What needs to be done?" autofocus/> 

 @Pipe({ name: 'permissions' }) export class PermissionsPipe implements PipeTransform { constructor(private store: Store<any>) { } transform(required: any, args?: any): any { const isPermitted = checkPermissions(required, this.getPermissions()); if (isPermitted) { return true; } else { console.log('[PERMISSIONS PIPE] You don\'t have permissions'); return false; } } getPermissions() { return localStorage.get('userPermissions') } } 

现在,用户只能看到任务并对其进行更改(V / X)。 但是,我们还有一个名为“清除完成”的按钮。


假设我们具有以下产品经理要求:


  1. “清除完成”按钮应该对所有人和所有人都是可见的。
  2. 它也应该是可点击的。
  3. 如果没有相应权限的USER按下按钮,则会出现一条消息。

施工指令对我们以及管道都没有帮助。
在组件函数中编写权限检查也不是很方便。


我们所需要做的就是在单击和绑定函数执行之间执行权限检查。


我认为值得使用装饰器。


 export function Permissions(required) { return (classProto, propertyKey, descriptor) => { const originalFunction = descriptor.value; descriptor.value = function (...args: any[]) { const userPerms = localStorage.get('userPermissions') const isPermitted = checkPermissions(required, userPerms); if (isPermitted) { originalFunction.apply(this, args); } else { alert('you have no permissions \n [PERMISSIONS DECORATOR]'); } }; return descriptor; }; } 

 @Component({ selector: 'app-actions', templateUrl: './actions.component.html', styleUrls: ['./actions.component.css'] }) export class ActionsComponent implements OnInit { @Output() deleteCompleted = new EventEmitter(); constructor() { } @Permissions('todos_delete') public deleteCompleted() { this.deleteCompleted.emit(); } } 

演讲者值得单独发表一篇文章。


我们的最终结果:


图片


结果:


这种方法使我们能够根据用户所拥有的权限轻松而动态地调整UX。


最重要的是,为此,我们不需要将服务推入所有组件中,也不需要使用“ * ngIf”模板。


整个申请已经完成


我很乐意发表评论。

Source: https://habr.com/ru/post/zh-CN439100/


All Articles