简体   繁体   English

Angular14 中基于角色的菜单项和路由?

[英]Role Based Menu Item and routing in Angular14?

I have an Angular(angular 14) App.我有一个 Angular(angular 14) 应用程序。 below is my Header with Register, Login, Add New Employee, List of employees, update employee profile, employee profile, working hours, logout menu.下面是我的标题,其中包含注册、登录、添加新员工、员工列表、更新员工资料、员工资料、工作时间、注销菜单。

在此处输入图像描述

If the user logs into my site and user have an Admin role, the user should see below menu items in the header.如果用户登录到我的站点并且用户具有管理员角色,则用户应该在标题中看到以下菜单项。

  1. Login登录
  2. Register登记
  3. Add New Employees,添加新员工,
  4. update employee profile更新员工资料
  5. List of employees,员工名单,
  6. logout登出

When a user logs into my site with the employee role, the user should see below menu items in the header.当用户以员工角色登录我的站点时,用户应该在标题中看到以下菜单项。

  1. Login登录
  2. employee profile,员工档案,
  3. working hours,工作时间,
  4. logout登出

When the user logs into my site with the support role, the user should see below menu items in the header.当用户以支持角色登录我的站点时,用户应该在标题中看到以下菜单项。

  1. Login登录
  2. List of employee员工名单
  3. logout登出

How can I do a role-based menu?我怎样才能做一个基于角色的菜单? Could someone help me with this?有人可以帮我吗?

My app.component.html我的 app.component.html

<div>
  <nav class="navbar navbar-expand navbar-dark bg-dark">
    <a href="#" class="navbar-brand">Dashboard</a>
    <div class="navbar-nav mr-auto">
      <li class="nav-item">
        <a routerLink="register" class="nav-link">Register</a>
      </li>
      <li class="nav-item">
        <a routerLink="login" class="nav-link">Login</a>
      </li>
      <li class="nav-item">
        <a routerLink="" class="nav-link">Add New Employee</a>
      </li>
      <li class="nav-item">
        <a routerLink="" class="nav-link">List of employee</a>
      </li>
      <li class="nav-item">
        <a routerLink="" class="nav-link">Update employee profile</a>
      </li>
      <li class="nav-item">
        <a routerLink="" class="nav-link">employee profile</a>
      </li>
      <li class="nav-item">
        <a routerLink="" class="nav-link">working hours</a>
      </li>
      <li class="nav-item">
        <a routerLink="" class="nav-link">Logout</a>
      </li>
    </div>
  </nav>

  <div class="container mt-3">
    <router-outlet></router-outlet>
  </div>
</div>

app.component.ts应用程序组件.ts

在此处输入图像描述

Folder structure文件夹结构

在此处输入图像描述

Below is my login-user.component.ts下面是我的 login-user.component.ts

import { Component } from '@angular/core';
import { User } from 'src/app/models/user.model';
import { UserService } from 'src/app/services/user.service';

@Component({
  selector: 'app-login-user',
  templateUrl: './login-user.component.html',
  styleUrls: ['./login-user.component.css']
})
export class LoginUserComponent {

  user: User = {
    username: '',
    password:''
  };
  submitted = false;

  constructor(private userService: UserService) { }

  loginUser(): void {
    const data = {
      username: this.user.username,
      password:this.user.password
    };

    this.userService.login(data)
      .subscribe({
        next: (res) => {
          console.log(res);
          this.submitted = true;
        },
        error: (e) => console.error(e)
      });
  }

  newUser(): void {
    this.submitted = false;
    this.user = {
      username: '',
      password:''
    };
  }

}

enter code here

my user.service.ts我的 user.service.ts

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { User } from '../models/user.model';

const baseUrl = 'http://localhost:8080/api/user';

@Injectable({
  providedIn: 'root'
})
export class UserService {

  constructor(private http: HttpClient) { }

  signup(data: any): Observable<any> {
    return this.http.post(baseUrl+"/signup", data);
  }

  login(data: any): Observable<any> {
    return this.http.post(baseUrl+"/login", data);
  }

  getAll(): Observable<User[]> {
    return this.http.get<User[]>(baseUrl);
  }

  get(id: any): Observable<User> {
    return this.http.get<User>(`${baseUrl}/${id}`);
  }

  create(data: any): Observable<any> {
    return this.http.post(baseUrl, data);
  }

  update(id: any, data: any): Observable<any> {
    return this.http.put(`${baseUrl}/${id}`, data);
  }

  delete(id: any): Observable<any> {
    return this.http.delete(`${baseUrl}/${id}`);
  }

  deleteAll(): Observable<any> {
    return this.http.delete(baseUrl);
  }

  findByTitle(name: any): Observable<User[]> {
    return this.http.get<User[]>(`${baseUrl}?name=${name}`);
  }
}

My app-routing.module.ts我的 app-routing.module.ts

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { RegisterUserComponent } from './components/user/register_user/register-user.component';
import { LoginUserComponent } from './components/user/login_user/login-user.component';
import { AddEmployeeComponent } from './components/employee/add-employee/add-employee.component';
import { UpdateEmployeeComponent } from './components/employee/update-employee/update-employee.component';
import { EmployeeListComponent } from './components/employee/list-employee/employee-list.component';
import { EmployeeDetailsComponent } from './components/employee/employee-profile/employee-profile.component';
import { EmployeeWorkingHoursComponent } from './components/employee/employee-workingHours/employee-workingHours.component';


import { HomeComponent } from './components/home/home.component';

const routes: Routes = [
  { path: '', redirectTo: 'home', pathMatch: 'full' },
  { path: 'home', component: HomeComponent },
  { path: 'register', component: RegisterUserComponent },
  { path: 'login', component: LoginUserComponent },
  { path: 'addEmployee', component: AddCameraComponent },
  { path: 'updateEmployee/:id', component: UpdateEmployeeComponent },
  { path: 'listAllEmployee', component: EmployeeListComponent },
  { path: 'employeeDetails/:id', component:EmployeeDetailsComponent},
  { path: 'employeeWorkingHours/:id', component:EmployeeWorkingHoursComponent}
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

I have checked below but I'm not sure how to do this also new to angular14,please advise here我已经在下面检查过,但我不确定如何做到这一点也是 angular14 的新手,请在这里提出建议

Angular Role Based Menu And Page Routing 基于 Angular 角色的菜单和页面路由

It's not an ideal way to show the menus from Client side.这不是从客户端显示菜单的理想方式。 You should get the menu from server side.您应该从服务器端获取菜单。

If you forced to do this then maintain and array and iterate it by checking the roles如果您被迫这样做,则通过检查角色来维护和排列并迭代它

So do a role based menu, you can create a component for each role then used nested ifs to direct those users to that particular component based on the role the user has when logging.基于角色的菜单也是如此,您可以为每个角色创建一个组件,然后使用嵌套的 ifs 根据用户在登录时所拥有的角色将这些用户定向到该特定组件。 Also, you will need to make use of role guards.此外,您将需要使用角色守卫。 See code below.请参阅下面的代码。

 if (this.currentUserDetails.roles == "ADMIN") {
      this.router.navigate(["/admin",])
      this.toastr.success('You Have Successfully Logged In', username);
    }
    else

      if (this.currentUserDetails.roles == "EMPLOYEE") {
        this.router.navigate(["/employee",])
        this.toastr.success('You Have Successfully Logged In', username);
      }
      else

        if (this.currentUserDetails.roles == "SUPPORT") {
          this.router.navigate(["/support",])
          this.toastr.success('You Have Successfully Logged In', username);
        }
  },

I am using JWT in to get where the role is stored for each user.我正在使用 JWT 获取每个用户的角色存储位置。 so this is my role guard.所以这是我的角色后卫。 you can use it as a guide你可以用它作为指南

checkUserLogin(route: ActivatedRouteSnapshot, url: any): boolean {
if (this.tokenStorageService.isUserLoggedIn()) {
  const userRole = this.tokenStorageService.getRole();
  if (route.data['role'] && route.data['role'].indexOf(userRole) === -1) {
    this.router.navigate(['/home']);
    window.alert("You don't have permission to view this page")
    return false;
  }
  return true;
}
return true;

} }

The next thing you do is go in your app-routing.module.ts file and add the following to each of your components that should have the role needed to access that component接下来你要做的是进入你的 app-routing.module.ts 文件并将以下内容添加到你的每个组件中,这些组件应该具有访问该组件所需的角色

canActivate: [RoleGuard],
    data: {
      role: 'ADMIN'
    },

You will then need to create a enum for the above code and import it in the app-routing.module.ts file然后,您需要为上述代码创建一个枚举并将其导入到 app-routing.module.ts 文件中

export enum Role {
ADMIN = 'ADMIN',
EMPLOYEE = 'EMPLOYEE',
SUPPORT = 'SUPPORT',
}

You could put fields in the user table in your DB with the various permissions (or the various roles), so when the user logs in it returns them to you, after which you can check with if on each menu item whether the currently logged-in user has permission to see that item and then set up guards to protect your routes for non-logged in users with the correct permissions.您可以使用各种权限(或各种角色)将字段放入数据库中的用户表中,这样当用户登录时,它会将它们返回给您,之后您可以检查是否在每个菜单项上是否当前登录 - in 用户有权查看该项目,然后设置警卫以保护具有正确权限的未登录用户的路线。

In case of using different levels of security, what I recommend is that you use GUARDS and make a routing based on it如果使用不同级别的安全性,我建议您使用 GUARDS 并基于它进行路由

interface CanActivate { canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean |接口 CanActivate { canActivate(路线:ActivatedRouteSnapshot,状态:RouterStateSnapshot):Observable <boolean | UrlTree> |网址树> | Promise<boolean |承诺<布尔值 | UrlTree> |网址树> | boolean |布尔 | UrlTree }网址树 }

Well, nice question.嗯,很好的问题。 Authorization setup always requires lots of work on different layers of application and is not a trivial issue.授权设置总是需要在应用程序的不同层上做大量工作,这不是一个小问题。

As a simplest solution for an angular app (or any other SPA technology), you could do following:作为角度应用程序(或任何其他 SPA 技术)的最简单解决方案,您可以执行以下操作:

1 Create some service, which performs actual validation of credentials user enters on UI (you already did that). 1 创建一些服务,它执行用户在 UI 上输入的凭据的实际验证(您已经这样做了)。 For my POC I've created one:对于我的 POC,我创建了一个:

import { Observable, of } from 'rxjs';

import { Injectable } from '@angular/core';

@Injectable({
    providedIn: 'root',
})
export class UserService {
    quickLogin(roles: UserRole[]): Observable<User> {
        // should be a back-end call here, for testing purposes stubbed that part
        return of({
            name: 'test_user___' + roles.join(','),
            roles: [...roles],
        });
    }
}

export interface User {
    name: string;
    roles: UserRole[];
}

export type UserRole = 'support' | 'admin' | 'employee';

2 Implement some front-end storage for current user data (user properties / permissions / tokes / etc...) 2 为当前用户数据(用户属性/权限/令牌/等...)实现一些前端存储

import { User, UserRole } from './user.service';

import { Injectable } from '@angular/core';

@Injectable({
    providedIn: 'root',
})
export class AuthStorageService {
    current: User | null = null;

    constructor() {
        const authPersisted = localStorage.getItem('auth');
        if (authPersisted) {
            this.current = JSON.parse(authPersisted);
        }
    }

    login(user: User) {
        localStorage.setItem('auth', JSON.stringify(user));
        this.current = user;
    }

    logout() {
        localStorage.removeItem('auth');
        this.current = null;
    }

    hasRole(role: UserRole) {
        return this.current && this.current.roles.indexOf(role) !== -1;
    }
}

3 Implement some front-end component,usually it contains a form which, when submitted, deals with back-end using previously implemented service and persists user data in storage we've created 3 实现一些前端组件,通常它包含一个表单,当提交时,使用先前实现的服务处理后端并将用户数据持久保存在我们创建的存储中

import { UserRole, UserService } from 'src/app/providers/user.service';

import { AuthStorageService } from 'src/app/providers/auth-storage.service';
import { Component } from '@angular/core';

@Component({
    selector: 'app-quick-login',
    templateUrl: './quick-login.component.html',
    styleUrls: ['./quick-login.component.scss'],
})
export class QuickLoginComponent {
    constructor(
        public auth: AuthStorageService,
        private userService: UserService
    ) {}

    signIn(roles: UserRole[]) {
        this.userService
            .quickLogin(roles)
            .subscribe((user) => this.auth.login(user));
    }

    signOut() {
        this.auth.logout();
    }
}

<span *ngIf="auth.current">{{ auth.current.name }}</span>
<button (click)="signIn(['support'])">sign-in support</button>
<button (click)="signIn(['admin'])">sign-in admin</button>
<button (click)="signIn(['employee'])">sign-in employee</button>
<button (click)="signOut()">sign out</button>

4 Now, whenever-wherever needed, we could check current user information using auth-storage service, for example: 4 现在,无论何时何地,我们都可以使用auth-storage 服务查看当前用户信息,例如:

import { AuthStorageService } from './providers/auth-storage.service';
import { Component } from '@angular/core';

@Component({
    selector: 'app-root',
    template: ` <header>
            <nav>
                <a routerLink="/">home</a>
                <a routerLink="/login">login</a>
                <a
                    routerLink="/employee"
                    *ngIf="auth.hasRole('support') || auth.hasRole('admin')"
                >
                    employee
                </a>
                <a
                    routerLink="/hours"
                    *ngIf="auth.hasRole('employee') || auth.hasRole('admin')"
                >
                    hours
                </a>
            </nav>
            <app-quick-login></app-quick-login>
        </header>
        <main><router-outlet></router-outlet></main>`,
})
export class AppComponent {
    constructor(public auth: AuthStorageService) {}
}

Of course, that's just a very very "lightweight" implementation, notready for production:( It just displays how to log in user, how to persist user data and how to apply this data in other parts of application当然,这只是一个非常非常“轻量级”的实现,还没有准备好用于生产:(它只是展示了如何登录用户,如何持久化用户数据以及如何在应用程序的其他部分应用这些数据

What else missing here?这里还缺少什么?

1 Definitely your angular routes have to be protected using some route guards, auto-redirecting user to login page (or somewhere else) when user has no permissions to access current url 1 绝对必须使用一些路由保护来保护您的角度路由,当用户无权访问当前 url 时,自动将用户重定向到登录页面(或其他地方)

https://angular.io/api/router/CanActivate https://angular.io/api/router/CanActivate

2 Nice-to-have thing is http interceptor which, whenever 401 http response arrives, acts in the same manner as route guard + usually displays some "unauthorized toast message" 2 值得拥有的东西是 http 拦截器,每当 401 http 响应到达时,它的行为方式与路由守卫相同 + 通常显示一些“未经授权的消息”

https://angular.io/api/common/http/HttpInterceptor https://angular.io/api/common/http/HttpInterceptor

3 code responsible for checking permissions in angular templates better to incapsulate in, fe, some structural directive, so it could look like following: 3 负责检查角度模板中的权限的代码更好地封装在 fe 一些结构指令中,因此它看起来如下所示:

import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';

import { AuthStorageService } from '../providers/auth-storage.service';
import { Subscription } from 'rxjs';
import { UserRole } from '../providers/user.service';

// https://angular.io/guide/structural-directives
@Directive({
    selector: '[requireAuth]',
})
export class RequireAuthDirective {
    private hasView = false;
    private authWatcher?: Subscription;

    private _rolesRequired: UserRole[] = [];

    @Input() set requireAuth(roles: UserRole[]) {
        this._rolesRequired = roles;
        this.checkConditions();
    }

    constructor(
        private auth: AuthStorageService,
        private templateRef: TemplateRef<any>,
        private viewContainer: ViewContainerRef
    ) {}

    ngOnInit() {
        this.authWatcher = this.auth.changed.subscribe(() =>
            this.checkConditions()
        );
    }

    ngOnDestroy() {
        this?.authWatcher?.unsubscribe();
    }

    private checkConditions() {
        const authConditionsMet =
            !!this.auth.current &&
            !!this._rolesRequired.find((r) => this.auth.hasRole(r));

        if (authConditionsMet && !this.hasView) {
            this.viewContainer.createEmbeddedView(this.templateRef);
            this.hasView = true;
        } else if (!authConditionsMet && this.hasView) {
            this.viewContainer.clear();
            this.hasView = false;
        }
    }
}

and it's usage:它的用法:

<a routerLink="/employee" *requireAuth="['support', 'admin']">
    employee
</a>
<a routerLink="/hours" *requireAuth="['employee', 'admin']">
    hours
</a>

Then you do not need to inject auth-storage service application-wide to apply some visibiltiy conditions in component templates然后你不需要在整个应用程序范围内注入 auth-storage 服务来在组件模板中应用一些可见性条件

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM