许多项目都有认证过程(某种程度上)。 在所有已知技术中都写有许多最佳实践。 等
但是用户登录了吗? 毕竟,他不能做任何事情。 如何确定他能看到什么,什么看不到。 它有权按哪些按钮,可以更改创建或删除的按钮。
在本文中,我想考虑一种解决Web应用程序上这些问题的方法。

首先,真正/有效的授权只能在服务器上进行。 在前端,我们只能改善用户界面(嗯,俄语很强大,但并不总是...),这也是UX之外的用户体验。 我们可以隐藏用户无权单击的按钮,或者阻止用户访问该页面,或者显示一条消息,表明他无权执行此操作。
随之而来的问题是,如何尽可能正确地做到这一点。
让我们从定义问题开始。
我们创建了Todo App,它具有不同类型的用户:
USER-可以查看所有任务并进行更改(设置和删除V),但不能删除或创建,也看不到统计信息。
管理 -可以查看所有任务并创建新任务,但看不到统计信息。
SUPER_ADMIN-可以查看所有任务,创建和删除新任务,还可以查看统计信息。
| 查看任务 | 创建任务 | 选中/取消选中任务(更新) | 删除任务 | 查看统计 |
---|
用户名 | V | X | V | X | X |
管理员 | V | V | V | X | X |
SUPER_ADMIN | V | V | V | V | V |
在这种情况下,我们可以轻松地胜任这些角色。 但是,情况可能会发生很大变化。 假设有一个用户必须具有与ADMIN相同的权限以及删除任务。 或者仅是具有查看统计信息功能的USER 。
一个简单的解决方案是创建新角色。
但是在拥有大量用户的大型系统上,我们很快就迷失了许多角色...在这里,我们回顾了“用户权限” 权限 。 为了简化管理,我们可以创建具有多个权限的组并将其附加到用户。 总是有机会向特定用户添加特定权限。
在许多出色的服务中都可以找到类似的解决方案。 AWS,Google Cloud,SalesForce等
同样,类似的解决方案已经在许多框架中实现,例如Django(python)。
我想举一个Angular应用程序的实现示例。 (以相同的ToDo应用为例)。
首先,您需要确定所有可能的权限。
分为功能
- 我们有任务和统计数据。
- 我们确定它们各自可能采取的措施。
- 任务:创建,读取,更新,删除
- 统计信息:已读(在我们的示例中,仅用于查看)
- 创建角色和权限的地图(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 = [
您应该注意数据中的对象。
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 {
此函数包含决策逻辑-USER(userPerms)的所有权限中是否存在不可替代的权限(必需)。
export function checkPermissions(required: string, userPerms) {

等等。 我们的用户无法访问统计信息页面。 但是,他仍然可以创建任务并将其删除。
为了使USER无法删除任务,从任务行中删除红色(X)就足够了。
为此,我们将使用“ 结构指令” 。
<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)。 但是,我们还有一个名为“清除完成”的按钮。
假设我们具有以下产品经理要求:
- “清除完成”按钮应该对所有人和所有人都是可见的。
- 它也应该是可点击的。
- 如果没有相应权限的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”模板。
整个申请已经完成 。
我很乐意发表评论。