【问题标题】:Angular - Auth Guard Prevent Authorized Users From Accessing Authentication Pages Via Direct URLAngular - Auth Guard 防止授权用户通过直接 URL 访问身份验证页面
【发布时间】:2019-11-03 21:20:17
【问题描述】:
 src
  |_auth/
    |_authentication/
    |_auth-service.ts
    |_auth-guard.ts
    |_is-logged-guard.ts
  |_dashboard/

auth-guard-service.ts

export class AuthService {
  public user: Observable<firebase.User>;
  public userDetails: firebase.User = null;
  public userProfileRef: firebase.database.Reference;
  userData: any[] = [];
  constructor(private _firebaseAuth: AngularFireAuth, private router: Router) {
    this.user = _firebaseAuth.authState;
    this.userProfileRef = firebase.database().ref('/userProfile');
        this.user.subscribe(
      (user) => {
        if (user) {
          this.userDetails = user;
        } else {
          this.userDetails = null;
        }
      }
    );
  }

  isLoggedIn() {
    if (this.userDetails == null) {
      return false;
    } else {
      return true;
    }
  }

  doSignOut() {
    this._firebaseAuth.auth.signOut()
      .then((res) => this.router.navigate(['/auth/login']));
  }
}


auth-guard.ts

@Injectable()
export class AuthGuard implements CanActivate {

  constructor(private auth: AuthService, private router: Router) { }

  canActivate() {
    return this.auth.user.take(1).map(authState => !!authState).do(authenticated => { new Promise<boolean>( (resolve, reject) => {
      if (!authenticated) {
        this.router.navigate(['auth/sigin']);
        return resolve(false);
      } else {
        return resolve(true);
      }
  }

}

is-logged-guard.ts - 我知道这是问题所在。我将如何解决它?

@Injectable()
export class IsLoggedGuard implements CanActivate {

  constructor(private auth: AuthService, private router: Router) { }

  canActivate() {
    return !this.auth.isLoggedIn();
  }

}

app-routing.module.ts


const routes: Routes = [
  { path: 'dashboard',
    canActivate: [AuthGuard],
    loadChildren: './dashboard/dashboard.module#DashboardModule'
  },
  {
    path: 'auth',
    component: NbAuthComponent,
    canActivate: [IsLoggedGuard],
    children: [
      {
        path: '',
        component: SignInComponent,
      },
      {
        path: 'SignIn',
        component: SignInComponent,
      },
      {
        path: 'SignUp',
        component: SignUpComponent,
      },
    ],
  },
  { path: '', redirectTo: 'dashboard', pathMatch: 'full' },
  { path: '**', redirectTo: 'dashboard' },
];

const config: ExtraOptions = {
  useHash: true,
};

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

案例 1:用户未登录

没问题。身份验证保护保护仪表板免受未经身份验证的用户的攻击,并将他们重定向到身份验证页面(即登录页面)。

案例2:用户已经登录#

没问题。如果经过身份验证的用户通过 localhost:4200 或 localhost:4200/#/dashboard 或 localhost:4200/#/ 或 localhost:4200/#/RANDOM_INVALID_URL 访问仪表板,则一切正常。守卫还将阻止已经在仪表板内的经过身份验证的用户访问身份验证页面。

案例 3:用户已经登录

问题。如果经过身份验证的用户通过 localhost:4200/#/auth 或 localhost:4200/#/auth/signin 访问仪表板,则守卫将无法保护并将用户重定向到仪表板主页。 (即 John 已经登录并打开一个新的 Chrome 选项卡,并输入 localhost:4200/#/auth 守卫不会阻止他访问它)。如果 John 已经通过身份验证,我该如何修复我的守卫以阻止他访问身份验证页面?

【问题讨论】:

    标签: angular typescript firebase


    【解决方案1】:

    如果用户已经登录,我的登录和注册页面不应加载,因此在这种情况下,我将检查两个组件的 ngOnInit 方法,如下所示。

    ngOnInit() {
        if(localStorage.getItem('isLoggedIn')){
            this.router.navigate(['/']);
            return;
        }
    }
    

    这是我的路线配置

    const routes: Routes = [
        { path: '', loadChildren: './modules/dashboard/dashboard.module#DashboardModule', canActivate: [AuthGuard] },
        { path: 'auth', loadChildren: './modules/auth/auth.module#AuthModule' },
        { path: '**', component: NotFoundComponent }
    ];
    

    如果您有许多不需要身份验证的路由,则不要为它创建另一个防护(与为经过身份验证的路由创建的不同)并检查用户是否登录而不是重定向到经过身份验证的路由的默认路由。

    【讨论】:

      【解决方案2】:

      你应该像这样改变你的 IsLoggedGuard:

      @Injectable()
      export class IsLoggedGuard implements CanActivate {
      
        constructor(private auth: AuthService, private router: Router) { }
      
        canActivate() {
          return this.auth.user
                          .take(1)
                          .map(authState => {
                             if (authState) {
                                //user is already loggedin
                                //route the user to Dashboard page
                                //Or a page where you want the app to naviagte
                                this.router.navigate("dashboard route");
                                //dont show the Login page
                                return false;
                             } else {
                               //user is not loggedin
                               return true;
                             }
                          });
        }
      
      }
      

      您看到了这个问题,因为当您在浏览器中输入“localhost:4200/#/auth”网址时,您的 AuthGuard.user.subscribe [即在 IsLoggedGuard's canActivate() 执行时,构造函数中的 this.user.subscribe(] 可能还没有发出任何值 [即AuthService.isLoggedIn() 可能会返回 false,因为订阅回调可能尚未执行(它会填充 userDetails)]。

      如果它解决了你的问题,请告诉我。

      可能有更好的方法来实现您的 AuthService 以及使用 AuthService 的 Guards。如果您想要更好的代码,请告诉我。

      编辑 - 编写 AuthService 的另一种方法

      让我们像这样更改 AuthService:

      export class AuthService {
      
          //NOTE: I AM JUST SHOWING TWO THINGS - isUserLoggedIn AND userDetails
          //FROM THIS CODE YOU WILL GET AN IDEA HOW TO WRITE OTHER PROPERTIES WHICH ARE RELEVANT FOR YOUR APP
      
          //This will be used as a source for various observables
          private _authState$: Observable<any>;
      
          //Have an observable which will tell if user is loggedin or not
          isUserLoggedIn$: Observable<boolean>;
          userDetails$: Observable<firebase.User>;
      
          public userProfileRef: firebase.database.Reference;
      
          constructor(private _firebaseAuth: AngularFireAuth, private router: Router) {            
            this.userProfileRef = firebase.database().ref('/userProfile');
            this.setupObserables();
          }
      
          setupObserables() {
      
              // this observable will broadcast the emited values to multiple subscribers [or composed/dependent observables]
              this._authState$ = this._firebaseAuth.authState
                                              .publishReplay(1)
                                              .refCount();
      
              // lets componse/derive different observables required by the consumer of this service
      
              // This observable's emitted value will tell if user is logged in or not
              this.isUserLoggedIn$ = this._authState$
                                         .map(user => {
                                              return user ? true : false;
                                          });
      
              // This observable's emited value will return the user's detail [NOTE If user is not logged in then the emitted value will be NULL
              // i.e. userDetail is NULL; Your consumer of this observable should decide what to do with NULL/NOT NULL Value]        
              this.userDetails$ = this._authState$
                                      .map(user => user);
          }    
      
          doSignOut() {
            this._firebaseAuth.auth.signOut()
              .then((res) => this.router.navigate(['/auth/login']));
          }
        }
      

      现在让我们在 IsLoggedGuard 中使用更新的 AuthService:

          @Injectable()
          export class IsLoggedGuard implements CanActivate {
      
            constructor(private auth: AuthService, private router: Router) { }
      
            canActivate() {
              return this.auth.isUserLoggedIn$
                              .take(1)
                              .map(isLoggedIn => {
                                 if (isLoggedIn) {
                                    //user is already loggedin
                                    //route the user to Dashboard page
                                    //Or a page where you want the app to naviagte
                                    this.router.navigate("dashboard route");
                                    //dont show the Login page
                                    return false;
                                 } else {
                                   //user is not loggedin
                                   return true;
                                 }
                              });
            }
      
          }
      

      现在让我们在 AuthGuard 中使用更新的 AuthService:

          @Injectable()
          export class AuthGuard implements CanActivate {
      
          constructor(private auth: AuthService, private router: Router) { }
      
          canActivate() {
              return this.auth.isUserLoggedIn$
                         .take(1)
                         .map(isLoggedIn => {
                          if (!isLoggedIn) {
                             //user isNOT loggedin
                             //route the user to login page
                             this.router.navigate(['auth/sigin']);
                             //dont show the next route
                             //lets fail the guard
                             return false;
                          } else {
                            //user is loggedin; pass the guard i.e. show the next route associated with this guard
                            return true;
                          }
                       });
              }
      
          }
      

      现在假设某个组件​​(假设组件名称为UserComponent)您想要显示登录用户的详细信息:

      ....component decorator...
      export class UserComponent implements OnInit {
      
          userDetails$: Observable<User>;
          constructor(private _authService: AuthService) {
              this.userDetails$ = this._authService.userDetails$;
          }
      }
      

      像这样渲染用户详细信息:

      <div *ngIf="(userDetails$ | async) as userDetails">
          <!-- Render your user details here -->
          <!-- If userDetails is NULL then nothing will be rendered -->
      </div>
      

      注意事项 - 在此更新后的代码中,我们在 NOWHERE 中订阅任何 observables。注意组件模板中的async,它负责订阅/取消订阅已使用的 observable。

      希望它能给你一个方向/想法。让我们尽可能地“反应”而不是“命令式”..:)

      注意:我已经在 rxjs6 中测试了等效代码。看起来您正在使用 rxjs5,所以我已经根据 rxjs5 调整了发布的代码。希望它会起作用。

      【讨论】:

      • 这太棒了。非常感谢您教我正确的做法。如果你能教我更好的 AuthService 方法。
      • 非常感谢您的宝贵时间。谢谢!
      猜你喜欢
      • 2014-04-27
      • 2021-02-22
      • 2021-03-07
      • 1970-01-01
      • 2020-10-29
      • 2016-11-22
      • 2016-08-09
      • 2021-07-31
      • 1970-01-01
      相关资源
      最近更新 更多