用Angular 4和Flask进行用户验证【翻译】

java技术文章

2018-12-06

19

0

内容列表
 

在这篇文章中,我们将示范怎样通过Angular 4和Flask设置token基础认证。
主要依赖:
1、Angular v4.2.4(通过Angular CLI v1.3.2)
2、Flask v0.12
3、Python v3.6.2
 

认证流程

这里是所有用户认证过程:
1、客户端登录并且将凭证发送到服务端。
2、如果凭证正确,服务生成一个token并且响应到客户端。
3、客户端接收并且存储token进本地缓存。
4、在后面的请求客户端带着请求参数头信息发送token到服务端。
 

项目安装:

开始安装Angular CLI:

$ npm install -g @angular/cli@1.3.2
生成新的Angular 4样板项目:
$ ng new angular4-auth
在安装依赖之后启动APP
$ cd angular4-auth
$ ng serve
它将花一到两分钟去编译和构建你的应用程序,一旦完成,输入  http://localhost:4200确信app已经启动和运行。
 
用编辑工具打开你的项目,然后扫视一下你的代码:
 
├── e2e
│   ├── app.e2e-spec.ts
│   ├── app.po.ts
│   └── tsconfig.e2e.json
├── karma.conf.js
├── package.json
├── protractor.conf.js
├── src
│   ├── app
│   │   ├── app.component.css
│   │   ├── app.component.html
│   │   ├── app.component.spec.ts
│   │   ├── app.component.ts
│   │   └── app.module.ts
│   ├── assets
│   ├── environments
│   │   ├── environment.prod.ts
│   │   └── environment.ts
│   ├── favicon.ico
│   ├── index.html
│   ├── main.ts
│   ├── polyfills.ts
│   ├── styles.css
│   ├── test.ts
│   ├── tsconfig.app.json
│   ├── tsconfig.spec.json
│   └── typings.d.ts
├── tsconfig.json
└── tslint.json
总之,客户端代码存放在src目录下,且Angular app 它自己能在app文件夹下发现它。
 
注意app.module.ts中的AppModule 。这个通常是Angular app的引导程序。@NgModule注解获取源数据让Angular知道怎么样去运行APP。
在这边文章中我们创建的每一件事都将添加到这个项目。
 
在移动以前你确信你已经理解这个app的结构。
你注意到了CLI初始化一个新的GIT repo?这个部分是可选的,但是创建一个新的Github库和远程更新是一个好的主意:
$ git remote set-url origin <newurl>
现在,开始装载一个新的组件。。。
 

认证组件:

首先,使用CLI去生成一个新的登录组件:
$ ng generate component components/login
这个安装了一个组件文件和文件夹,甚至写入到app.module.ts。接下来,按如下来修改
import { Component } from '@angular/core';

@Component({
  selector: 'login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.css']
})

export class LoginComponent {
  test: string = 'just a test';
}
如果你之前没有使用 TypeScript,这段代码看起来对你非常不相关。TypeScript 静态类型的javascript超集,编译成 vanilla JavaScript,它事实上是构建Angular 4 apps的程序语言。
 
在Angular4,我们通过@Component注解封装的一个配置对象到一个组件。我们能通过导入我们需要的类在包之间共享代码;并且在这种情况下,我们从@angular/core包中导入组件。LoginComponent 类是一个controller组件,并且对于其他导入的类我们使用export操作使它可用。
 
添加如下HTML到 login.component.html
<h1>Login</h1>

<p>{{test}}</p>
接下来,在app.module.ts文件中通过  RouterModule配置路由
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';

import { AppComponent } from './app.component';
import { LoginComponent } from './components/login/login.component';

@NgModule({
  declarations: [
    AppComponent,
    LoginComponent,
  ],
  imports: [
    BrowserModule,
    RouterModule.forRoot([
      { path: 'login', component: LoginComponent }
    ])
  ],
  providers: [],
  bootstrap: [AppComponent]
})

export class AppModule { }
最后在app.component.html文件中用<router-outlet>标签替代所有的HTML启用路由:
<router-outlet></router-outlet>
在你的终端运行ng服务,如果你已经启动,在导航栏中输入 http://localhost:4200/login.如果一切正常,你应该看到一个 test 文本。
 
Bootstrap Style
快速添加一些样式,更新index.html,添加到  Bootstrap并且在一个容器中添加<app-root></app-root>标签。
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Angular4Auth</title>
  <base href="/">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
  <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
</head>
<body>
  <div class="container">
    <app-root></app-root>
  </div>
</body>
</html>
一旦你保存,你就会看到app自动重载。

 

认证服务

接下来,我们创建一个全局服务去进行用户登录,用户注销,用户签名:
$ ng generate service services/auth
编辑auth.service.ts写入如下代码:
import { Injectable } from '@angular/core';

@Injectable()
export class AuthService {
  test(): string {
    return 'working';
  }
}
记住在Angular 1中怎样提供工作的?它们是一个存储单态的全局对象。当一个供应者数据发生改变,注入该提供者的任何对象都将接收更新。在angular 4,供应者保持它们特有的行为并且他们使用@Injectable注解定义。
 

明智的检查

在添加任何有意义的事物到AuthService之前。让我们确信该服务自己已经正确装载,这样做,在 login.component.ts中注入服务并且调用test()方法:
import { Component, OnInit } from '@angular/core';
import { AuthService } from '../../services/auth.service';

@Component({
  selector: 'login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.css']
})

export class LoginComponent implements OnInit {
  test: string = 'just a test';
  constructor(private auth: AuthService) {}
  ngOnInit(): void {
    console.log(this.auth.test());
  }
}
我们介绍一些新概念和关键字。constructor()功能是一个指定的方法,我们通常用它来实例化一个类。constructor()的作用是我们可以传入需要的参数到类中,包括我们想注入的任何供应者(i.e., AuthService)。在TypeScript中,我们能用private关键字对外隐藏内部变量。在构造函数中通过一个私有变量快速的在类中定义属性,并且赋值给这个参数值。注意的是在它赋值给构造函数的时候这个对象如何访问身份验证变量。
 
我们实现OnInit 接口确保我们明确的定义了一个ngOnInit()功能。实现OnInit 确保我们的组件在第一次探测到改变之后被调用。当这个组件首先被初始化时该功能将被调用,使它成为依赖其他angular类配置数据的理想场所。
 
不像组件,被自动添加,服务不得不手动的导入并且在@NgModule配置。因此,为了让它工作,你将不得不在app.module.ts中导入AuthService 和添加它到providers:
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';

import { AppComponent } from './app.component';
import { LoginComponent } from './components/login/login.component';
import { AuthService } from './services/auth.service';

@NgModule({
  declarations: [
    AppComponent,
    LoginComponent,
  ],
  imports: [
    BrowserModule,
    RouterModule.forRoot([
      { path: 'login', component: LoginComponent }
    ])
  ],
  providers: [AuthService],
  bootstrap: [AppComponent]
})

export class AppModule { }
运行服务并且导航中输入 http://localhost:4200/login。你应该在JavaScript控制台看到工作日志输出。
 

用户登录

手工登录一个用户,按照如下更新AuthService :
import { Injectable } from '@angular/core';
import { Headers, Http } from '@angular/http';
import 'rxjs/add/operator/toPromise';

@Injectable()
export class AuthService {
  private BASE_URL: string = 'http://localhost:5000/auth';
  private headers: Headers = new Headers({'Content-Type': 'application/json'});
  constructor(private http: Http) {}
  login(user): Promise<any> {
    let url: string = `${this.BASE_URL}/login`;
    return this.http.post(url, user, {headers: this.headers}).toPromise();
  }
}
我们使用一些内置Angular帮助类,Headers和Http,控制我们的ajax调用服务。
 
同时,更新app.module.ts导入HttpModule。
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { HttpModule } from '@angular/http';

import { AppComponent } from './app.component';
import { LoginComponent } from './components/login/login.component';
import { AuthService } from './services/auth.service';

@NgModule({
  declarations: [
    AppComponent,
    LoginComponent,
  ],
  imports: [
    BrowserModule,
    HttpModule,
    RouterModule.forRoot([
      { path: 'login', component: LoginComponent }
    ])
  ],
  providers: [AuthService],
  bootstrap: [AppComponent]
})

export class AppModule { }
这里,我们使用Http服务去发送一个ajax请求/user/login终端。它将返回一个响应的对象。
 

用户注册

让我们回到前面并且添加了一些注册用户的功能,类似用户登录。更新src/app/services/auth.service.ts,需要注意的是register 方法:
import { Injectable } from '@angular/core';
import { Headers, Http } from '@angular/http';
import 'rxjs/add/operator/toPromise';

@Injectable()
export class AuthService {
  private BASE_URL: string = 'http://localhost:5000/auth';
  private headers: Headers = new Headers({'Content-Type': 'application/json'});
  constructor(private http: Http) {}
  login(user): Promise<any> {
    let url: string = `${this.BASE_URL}/login`;
    return this.http.post(url, user, {headers: this.headers}).toPromise();
  }
  register(user): Promise<any> {
    let url: string = `${this.BASE_URL}/register`;
    return this.http.post(url, user, {headers: this.headers}).toPromise();
  }
}
现在,为了测试我们需要启动后端。。。
 

服务端启动

对于服务端,我们将使用前面博客完成的项目, Token-Based Authentication With Flask.你可以从  flask-jwt-auth库中查看代码。
 
在一个新的终端中克隆项目结构:
$ git clone https://github.com/realpython/flask-jwt-auth
README 中的指示安装项目,在移动之前确信能通过测试。一旦完成,用python的manage.py启动服务端,将监听5000端口。
 

明智检查

为了测试,从服务端更新LoginComponent的login和register方法:
import { Component, OnInit } from '@angular/core';
import { AuthService } from '../../services/auth.service';

@Component({
  selector: 'login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {
  test: string = 'just a test';
  constructor(private auth: AuthService) {}
  ngOnInit(): void {
    let sampleUser: any = {
      email: 'michael@realpython.com' as string,
      password: 'michael' as string
    };
    this.auth.register(sampleUser)
    .then((user) => {
      console.log(user.json());
    })
    .catch((err) => {
      console.log(err);
    });
    this.auth.login(sampleUser).then((user) => {
      console.log(user.json());
    })
    .catch((err) => {
      console.log(err);
    });
  }
}
在浏览器中刷新 http://localhost:4200/login ,在用户登录后,你应该在javascript控制台中看到带有token的成功输出:
{
  "auth_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1M…jozfQ.bPNQb3C98yyNe0LDyl1Bfkp0Btn15QyMxZnBoE9RQMI",
  "message": "Successfully logged in.",
  "status": "success"
}

认证登录:

更新 login.component.html:
<div class="row">
  <div class="col-md-4">
    <h1>Login</h1>
    <hr><br>
    <form (ngSubmit)="onLogin()" novalidate>
     <div class="form-group">
       <label for="email">Email</label>
       <input type="text" class="form-control" id="email" placeholder="enter email" [(ngModel)]="user.email" name="email" required>
     </div>
     <div class="form-group">
       <label for="password">Password</label>
       <input type="password" class="form-control" id="password" placeholder="enter password" [(ngModel)]="user.password" name="password" required>
     </div>
     <button type="submit" class="btn btn-default">Submit</button>
    </form>
  </div>
</div>
注意表单。我们使用ngModel标注每一个表单inputs在controller中获取这些值,同时,当表单提交,ngSubmit指示控制事件去调用onLogin()方法。
 
现在,让我们更新组件代码,在 onLogin()添加如下代码:
import { Component } from '@angular/core';
import { AuthService } from '../../services/auth.service';
import { User } from '../../models/user';

@Component({
  selector: 'login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.css']
})
export class LoginComponent {
  user: User = new User();
  constructor(private auth: AuthService) {}
  onLogin(): void {
    this.auth.login(this.user)
    .then((user) => {
      console.log(user.json());
    })
    .catch((err) => {
      console.log(err);
    });
  }
}
如果你有一个Angular web服务端运行,你应该在浏览器中看到一个错误提示不能找到模块../../models/user。在我们的代码工作之前,我们需要创建一个用户模块:
$ ng generate class models/user
更新src/app/models/user.ts:
export class User {
  constructor(email?: string, password?: string) {}
}
我们的user模块有两个配置文件,email和password. ?问好符号是一个特殊的操作符,它表明用一个可选的email和password值去初始化用户。这个在Python中等效如下类:
class User(object):
    def __init__(self, email=None, password=None):
        self.email = email
        self.password = password
不要忘了使用新对象更新auth.service.ts。
import { Injectable } from '@angular/core';
import { Headers, Http } from '@angular/http';
import { User } from '../models/user';
import 'rxjs/add/operator/toPromise';

@Injectable()
export class AuthService {
  private BASE_URL: string = 'http://localhost:5000/auth';
  private headers: Headers = new Headers({'Content-Type': 'application/json'});
  constructor(private http: Http) {}
  login(user: User): Promise<any> {
    let url: string = `${this.BASE_URL}/login`;
    return this.http.post(url, user, {headers: this.headers}).toPromise();
  }
  register(user: User): Promise<any> {
    let url: string = `${this.BASE_URL}/register`;
    return this.http.post(url, user, {headers: this.headers}).toPromise();
  }
}
最后一件事,我们需要在app.module.ts 导入FormsModule。
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { HttpModule } from '@angular/http';
import { FormsModule } from '@angular/forms';

import { AppComponent } from './app.component';
import { LoginComponent } from './components/login/login.component';
import { AuthService } from './services/auth.service';

@NgModule({
  declarations: [
    AppComponent,
    LoginComponent,
  ],
  imports: [
    BrowserModule,
    HttpModule,
    FormsModule,
    RouterModule.forRoot([
      { path: 'login', component: LoginComponent }
    ])
  ],
  providers: [AuthService],
  bootstrap: [AppComponent]
})

export class AppModule { }
因此,当一个表单提交,我们捕获email和password传输他们到服务端的login()方法.
 
用下面字符串测试:
 
再者,你应该在javascript控制台看到一个带有token的成功输出。

 

认证注册

仅仅像登录功能。我们需要添加一个组件注册一个用户。开始生成一个新的注册组件:
ng generate component components/register
更新src/app/components/register/register.component.html:
<div class="row">
  <div class="col-md-4">
    <h1>Register</h1>
    <hr><br>
    <form (ngSubmit)="onRegister()" novalidate>
     <div class="form-group">
       <label for="email">Email</label>
       <input type="text" class="form-control" id="email" placeholder="enter email" [(ngModel)]="user.email" name="email" required>
     </div>
     <div class="form-group">
       <label for="password">Password</label>
       <input type="password" class="form-control" id="password" placeholder="enter password" [(ngModel)]="user.password" name="password" required>
     </div>
     <button type="submit" class="btn btn-default">Submit</button>
    </form>
  </div>
</div>
接着,如下更新:src/app/components/register/register.component.ts
import { Component } from '@angular/core';
import { AuthService } from '../../services/auth.service';
import { User } from '../../models/user';

@Component({
  selector: 'register',
  templateUrl: './register.component.html',
  styleUrls: ['./register.component.css']
})
export class RegisterComponent {
  user: User = new User();
  constructor(private auth: AuthService) {}
  onRegister(): void {
    this.auth.register(this.user)
    .then((user) => {
      console.log(user.json());
    })
    .catch((err) => {
      console.log(err);
    });
  }
}
添加一个新的路由控制到app.module.ts文件:
RouterModule.forRoot([
  { path: 'login', component: LoginComponent },
  { path: 'register', component: RegisterComponent }
])
通过注册一个新的用户测试输出!
 

本地缓存

接下来,添加一个token到本地缓存以实现持久性,在src/app/components/login/login.component.ts中通过使用localStorage.setItem('token', user.data.token);取代console.log(user.json());
onLogin(): void {
  this.auth.login(this.user)
  .then((user) => {
    localStorage.setItem('token', user.json().auth_token);
  })
  .catch((err) => {
    console.log(err);
  });
}
在src/app/components/register/register.component.ts中同样的做法:
onRegister(): void {
  this.auth.register(this.user)
  .then((user) => {
    localStorage.setItem('token', user.json().auth_token);
  })
  .catch((err) => {
    console.log(err);
  });
}
只要token出现,用户被认为是登录的,并且当一个用户发起一个ajax请求,token也能被使用。
 
测试结果输出。确信再你登录后token存储在本地缓存中。
 

用户状态

为了测试登录持久化,我们能添加一个新的视图证明用户已经登录并且token有效。
 
添加如下方法到AuthService:
ensureAuthenticated(token): Promise<any> {
  let url: string = `${this.BASE_URL}/status`;
  let headers: Headers = new Headers({
    'Content-Type': 'application/json',
    Authorization: `Bearer ${token}`
  });
  return this.http.get(url, {headers: headers}).toPromise();
}
注意授权:'Bearer ' + token.这个调用了一个 Bearer schema,随请求一起发送。在服务端,我们简单检查Authorization头信息和检查token是否有效。你能在服务端找到这段代码?
 
接着,生成一个新的状态组件:
$ ng generate component components/status
创建一个html模板,src/app/components/status/status.component.html:
<div class="row">
  <div class="col-md-4">
    <h1>User Status</h1>
    <hr><br>
    <p>Logged In? {{isLoggedIn}}</p>
  </div>
</div>
并且改变src/app/components/status/status.component.ts中的代码:
import { Component, OnInit } from '@angular/core';
import { AuthService } from '../../services/auth.service';

@Component({
  selector: 'status',
  templateUrl: './status.component.html',
  styleUrls: ['./status.component.css']
})
export class StatusComponent implements OnInit {
  isLoggedIn: boolean = false;
  constructor(private auth: AuthService) {}
  ngOnInit(): void {
    const token = localStorage.getItem('token');
    if (token) {
      this.auth.ensureAuthenticated(token)
      .then((user) => {
        console.log(user.json());
        if (user.json().status === 'success') {
          this.isLoggedIn = true;
        }
      })
      .catch((err) => {
        console.log(err);
      });
    }
  }
}
最后添加一个新的路由控制到app.module.ts中:
RouterModule.forRoot([
  { path: 'login', component: LoginComponent },
  { path: 'register', component: RegisterComponent },
  { path: 'status', component: StatusComponent }
])
准备测试?登录并且在地址栏中输入 http://localhost:4200/status.如果有个token在缓存中,你应该看见如下:
{
  "message": "Signature expired. Please log in again.",
  "status": "fail"
}
为什么呢?如果你在服务端深度挖掘,你将发现在项目/server/models.py中token仅仅存在5分钟:
def encode_auth_token(self, user_id):
    """
    Generates the Auth Token
    :return: string
    """
    try:
        payload = {
            'exp': datetime.datetime.utcnow() + datetime.timedelta(days=0, seconds=5),
            'iat': datetime.datetime.utcnow(),
            'sub': user_id
        }
        return jwt.encode(
            payload,
            app.config.get('SECRET_KEY'),
            algorithm='HS256'
        )
    except Exception as e:
        return e
更新这个为1天:
'exp': datetime.datetime.utcnow() + datetime.timedelta(days=1, seconds=0)
接着再次测试.你应该看到如下:
{
  "data": {
    "admin": false,
    "email": "michael@realpython.com",
    "registered_on": "Sun, 13 Aug 2017 17:21:52 GMT",
    "user_id": 4
  },
  "status": "success"
}
最后,在用户成功注册或者登陆后跳转到状态页面。更新代码如下:
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { AuthService } from '../../services/auth.service';
import { User } from '../../models/user';

@Component({
  selector: 'login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.css']
})
export class LoginComponent {
  user: User = new User();
  constructor(private router: Router, private auth: AuthService) {}
  onLogin(): void {
    this.auth.login(this.user)
    .then((user) => {
      localStorage.setItem('token', user.json().auth_token);
      this.router.navigateByUrl('/status');
    })
    .catch((err) => {
      console.log(err);
    });
  }
}
接着更新src/app/components/register/register.component.ts:
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { AuthService } from '../../services/auth.service';
import { User } from '../../models/user';

@Component({
  selector: 'register',
  templateUrl: './register.component.html',
  styleUrls: ['./register.component.css']
})
export class RegisterComponent {
  user: User = new User();
  constructor(private router: Router, private auth: AuthService) {}
  onRegister(): void {
    this.auth.register(this.user)
    .then((user) => {
      localStorage.setItem('token', user.json().auth_token);
      this.router.navigateByUrl('/status');
    })
    .catch((err) => {
      console.log(err);
    });
  }
}
测试并输出!
 

路由限制

现在,所有的路由已经打开;因此,不管用户是否已经登录,他们能访问每一个路由,在用户未登录时,某一个些路由应该被限制。或者在用户登录后,那些路由应该被限制:
1、/- 不限制
2、/login- 登录时限制
3、/register-登录限制
4、/status-未登录限制
 
为了达到这些效果,选择添加EnsureAuthenticated或者LoginRedirect其中一个路由,决定你想指导用户跳转到status视图或者login视图。
 
开始创建两个新services:
$ ng generate service services/ensure-authenticated
$ ng generate service services/login-redirect
在文件中ensure-authenticated.service.ts取代如下代码:
import { Injectable } from '@angular/core';
import { CanActivate, Router } from '@angular/router';
import { AuthService } from './auth.service';

@Injectable()
export class EnsureAuthenticated implements CanActivate {
  constructor(private auth: AuthService, private router: Router) {}
  canActivate(): boolean {
    if (localStorage.getItem('token')) {
      return true;
    }
    else {
      this.router.navigateByUrl('/login');
      return false;
    }
  }
}
并且在文件中login-redirect.service.ts替换如下代码:
import { Injectable } from '@angular/core';
import { CanActivate, Router } from '@angular/router';
import { AuthService } from './auth.service';

@Injectable()
export class LoginRedirect implements CanActivate {
  constructor(private auth: AuthService, private router: Router) {}
  canActivate(): boolean {
    if (localStorage.getItem('token')) {
      this.router.navigateByUrl('/status');
      return false;
    }
    else {
      return true;
    }
  }
}
最后,更新app.module.ts文件导入和配置新services:
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { HttpModule } from '@angular/http';
import { FormsModule } from '@angular/forms';

import { AppComponent } from './app.component';
import { LoginComponent } from './components/login/login.component';
import { AuthService } from './services/auth.service';
import { RegisterComponent } from './components/register/register.component';
import { StatusComponent } from './components/status/status.component';
import { EnsureAuthenticated } from './services/ensure-authenticated.service';
import { LoginRedirect } from './services/login-redirect.service';

@NgModule({
  declarations: [
    AppComponent,
    LoginComponent,
    RegisterComponent,
    StatusComponent,
  ],
  imports: [
    BrowserModule,
    HttpModule,
    FormsModule,
    RouterModule.forRoot([
      {
        path: 'login',
        component: LoginComponent,
        canActivate: [LoginRedirect]
      },
      {
        path: 'register',
        component: RegisterComponent,
        canActivate: [LoginRedirect]
      },
      {
        path: 'status',
        component: StatusComponent,
        canActivate:
        [EnsureAuthenticated]
      }
    ])
  ],
  providers: [
    AuthService,
    EnsureAuthenticated,
    LoginRedirect
  ],
  bootstrap: [AppComponent]
})

export class AppModule { }
注意,我们怎样添加我们的services到一个新的路由配置项canActivate.路由系统在canActivate 数组中使用services决定是否部署请求url路径。如果路由已经LoginRedirect并且用户已经登录,接着它们将被导航到一个状态视图。如果它们访问一个需要认证的URL,EnsureAuthenticated service跳转用户到登录界面。
 
最后进行测试。

发表评论

全部评论:0条

鸿福951

努力打造一个好用的webui

热评文章

推荐文章