Angular-Auth Guard阻止授权用户通过直接URL访问身份验证页面

时间:2019-06-21 03:14:08

标签: angular typescript firebase

 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:用户未登录

没问题。 Auth Guard保护仪表板免受未经身份验证的用户的侵害,并将其重定向到身份验证页面(即登录页面)。

情况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,后卫不会阻止他访问它)。如果约翰已经通过身份验证,我该如何保护我的警卫以防止他访问身份验证页面?

2 个答案:

答案 0 :(得分:1)

您应该这样更改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” URL时,您的AuthGuard.user.subscribe [即执行this.user.subscribe(时,构造函数IsLoggedGuard's canActivate()中的变量可能尚未发出任何值。 AuthService.isLoggedIn()可能返回false,因为订阅回调可能未执行(填充了UserDetails)。

让我知道它是否可以解决您的问题。

可能有更好的方法来实现AuthService以及Guards来使用AuthService。让我知道您是否想要更好的代码。

编辑-编写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$;
    }
}

呈现如下的userDetails:

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

注意事项-在此更新的代码中,我们正在订阅任何可观察对象。注意组件模板中的async,这用于订阅/取消订阅已使用的可观察对象。

希望它会给您一个方向/想法。让我们尽可能地“主动”,而不是“即时” ..:)

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

答案 1 :(得分:0)

如果用户已经登录,我的登录和注册页面将无法加载,因此在这种情况下,我将检查两个组件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 }
];

如果您有许多不需要身份验证的路由,那么请代替上面的操作为其创建另一个保护(与为经过身份验证的路由创建的保护方法不同),并在此进行检查,以确保用户登录后是否重定向到经过身份验证的路由的默认路由。 / p>