Ionic v3 使用 Token Based Authentication 实践Login页面

Ionic v3 使用 Token Based Authentication 实践Login页面


此篇文章是参照下面两篇文章改写的:

  • Angular 5 Login and Logout with Web API Using Token Based Authentication
  • Use a promise in Angular HttpClient Interceptor (stackoverflow)

前端程序:

auth.ts (Ionic Providers 或叫Service也行)

import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Injectable } from '@angular/core';

@Injectable()
export class AuthProvider {
  readonly rootUrl = 'http://localhost:57086';
  constructor(public http: HttpClient) {}

  login(userName: string, password: string) {
    var data = "username=" + userName + "&password=" + password + "&grant_type=password";
    // Header指定'No-Auth': 'True' ,当拦截器(HttpInterceptor)看到这个,就跳过不会在Header动手脚了
    var reqHeader = new HttpHeaders({ 
    	'Content-Type': 'application/x-www-urlencoded', 'No-Auth': 'True' 
    });
    return this.http.post(this.rootUrl + '/token', data, { headers: reqHeader });
  }

  //Header没有加上'No-Auth': 'True',Request送出前,拦截器(HttpInterceptor)会自动在Header加上access_token
  getEmail() {
    return this.http.get(this.rootUrl + '/api/account/GetUser');
  }
}

auth.interceptor.ts (HTTP拦截器,在Request送出前拦截下来,然后任你处置...)

HttpInterceptort参考

import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from "@angular/common/http";
import { Observable } from "rxjs/Observable";
import { Injectable } from "@angular/core";
import { Storage } from '@ionic/storage';

import { fromPromise } from 'rxjs/observable/fromPromise';
import { mergeMap } from 'rxjs/operators/mergeMap';

//处理Request header
@Injectable()
export class AuthInterceptor implements HttpInterceptor {

  constructor(public storage: Storage) { }

	//取得token
  getToken(): Promise {
    return this.storage.get('userToken');
  }

  intercept(req: HttpRequest, next: HttpHandler): Observable> {
  	//若headers有'No-Auth'='True',Request就原封不动送出
    if (req.headers.get('No-Auth') == "True")
      return next.handle(req.clone());

	//在Request header加上token
    return fromPromise(this.getToken()).pipe(
      mergeMap(token => {
        // Use the token in the request
        req = req.clone({
          headers: req.headers.set("Authorization", "Bearer " + token)
        });

        // Handle the request
        return next.handle(req);
      }));
  }
}

app.module.ts


  providers: [
    ...
    { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true},
    ...
  ]

login.html (Ionic Login页面)


  
    登入
  



  
    
      

账号密码错误

login.ts

import { Component } from '@angular/core';
import { HttpErrorResponse } from '@angular/common/http';
import { NavController, NavParams } from 'ionic-angular';
import { HomePage } from '../home/home';
import { AuthProvider } from '../../providers/auth/auth';
import { Storage } from '@ionic/storage';

@Component({
  selector: 'page-login',
  templateUrl: 'login.html',
})
export class LoginPage {
  isLoginError = false;

  constructor(public navCtrl: NavController, public navParams: NavParams,
    private auth: AuthProvider, public storage: Storage) {
  }

  ionViewDidLoad() {}

  OnSubmit(userId, password) {
    // 登入
    this.auth.login(userId, password).subscribe((data: any) => {
      this.storage.set('userToken', data.access_token);
      this.navCtrl.setRoot(HomePage);
    },
      (err: HttpErrorResponse) => {
        //console.log(err);
        this.isLoginError = true;
      });
  }

  hideError() {
    this.isLoginError = false;
  }
}

=========================================================

后端 Web API

Web.config (设定CROS)

  
    
      
        
        
      
    
    ...
   

Global.asax (设定CROS,跳过OPTIONS请求)

        protected void Application_BeginRequest()
        {
            if (Request.Headers.AllKeys.Contains("Origin", StringComparer.OrdinalIgnoreCase) &&
                Request.HttpMethod == "OPTIONS")
            {
                Response.End();
            }
        }

使用NuGet安装下面套件  

  • Microsoft.Owin.Host.SystemWeb

在项目下新增Startup.cs (OWIN Startup class)

    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            OAuthAuthorizationServerOptions option = new OAuthAuthorizationServerOptions
            {
            	//指定路径以验证使用者,若验证通过,回传 access_token
                TokenEndpointPath = new PathString("/token"),
                //指定用来验证使用者的Provider(自己写)
                Provider = new ApplicationOAuthProvider(),
                //指定Token过期时间
                AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
                AllowInsecureHttp = true
            };
            app.UseOAuthAuthorizationServer(option);
            app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
        }
    }

ApplicationOAuthProvider.cs (验证使用者的Provider,最上面的参考范例是用ASP.NET Identity去验证使用者,这边的写法改成自己写个类去判断账号密码是否正确,然后回传自订使用者的Model)

    public class ApplicationOAuthProvider : OAuthAuthorizationServerProvider
    {
        public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
        {
            context.Validated();
        }

        public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
        {
            AccountManager manager = new AccountManager();
            //验证账号密码,若成功就回传UserModel
            UserModel user = await manager.FindUserAsync(context.UserName, context.Password);
            if (user != null)
            {
            	//使用ClaimsIdentity保存User的数据(用什么字段名称,存几个字段,视需求自订)
                var identity = new ClaimsIdentity(context.Options.AuthenticationType);
                identity.AddClaim(new Claim("Username", user.UserName));
                identity.AddClaim(new Claim("Email", user.Email));
                identity.AddClaim(new Claim("FirstName", user.FirstName));
                identity.AddClaim(new Claim("LastName", user.LastName));
                identity.AddClaim(new Claim("LoggedOn", DateTime.Now.ToString()));
                context.Validated(identity);
            }
            else
                return;
        }
    }

AccountController.cs (Web API Controller)

    public class AccountController : ApiController
    {
        [Authorize]
        [ActionName("GetEmail")]
        public string GetEmail()
        {
        	//从ClaimsIdentity取回先前存的使用者数据
            var identityClaims = (ClaimsIdentity)User.Identity;
            IEnumerable claims = identityClaims.Claims;
            return identityClaims.FindFirst("Email").Value;
        }
    }

WebApiConfig.cs (若调用网址要用Action Name,可加上下面这段)

        public static void Register(HttpConfiguration config)
        {
            ...

            // Controllers with Actions
            // To handle routes like `/api/Account/UserProfile`
            config.Routes.MapHttpRoute(
                name: "ControllerAndAction",
                routeTemplate: "api/{controller}/{action}"
            );
        }