# 对接后台 本节我们尝试完成个人中心后台的对接。 ## 接口信息 我们为大家提供了如下接口来获取当前登录用户的基本信息: ```bash GET /teacher/me ``` #### 参数 Parameters | type | name | Description | Schema | | ---------- | ------------------------------- | ------------ | ---------------- | | **Header** | **认证x-auth-token** *requried* | 获取登录信息 | 当前登录教师实体 | #### 返回值 Responses | HTTP Code | Description | Schema | | --------- | ------------------------------------------------------ | ------ | | **200** | 认证x-auth-token合法 | Ok | | **401** | 未携带x-auth-token信息,未携带的x-auth-token信息不正确 | 未授权 | 接口中提到了x-auth-token信息,我们暂时先不考虑它,看看按以前请求思想会发生什么。 ## HttpClient ```typescript +++ b/first-app/src/app/personal-center/personal-center.component.ts @@ -1,5 +1,6 @@ import {Component, OnInit} from '@angular/core'; import {Teacher} from '../entity/teacher'; +import {HttpClient} from '@angular/common/http'; @Component({ selector: 'app-personal-center', @@ -9,18 +10,14 @@ import {Teacher} from '../entity/teacher'; export class PersonalCenterComponent implements OnInit { me = {} as Teacher; - constructor() { + constructor(private httpClient: HttpClient) { } ngOnInit(): void { - this.me = new Teacher( - 1, - 'zhangsan@yunzhi.club', - '张三', - 'password', - null as unknown as boolean, - 'zhangsan' - ); + const url = 'http://angular.api.codedemo.club:81/teacher/me'; + this.httpClient.get<Teacher>(url) + .subscribe(teacher => this.me = teacher, + error => console.log('请求发生错误', error)); } } ``` 单元测试中的动态测试模块: ```typescript +++ b/first-app/src/app/personal-center/personal-center.component.spec.ts @@ -2,6 +2,7 @@ import {ComponentFixture, TestBed} from '@angular/core/testing'; import {PersonalCenterComponent} from './personal-center.component'; import {SexPipe} from './sex.pipe'; +import {HttpClientModule} from '@angular/common/http'; describe('PersonalCenterComponent', () => { let component: PersonalCenterComponent; @@ -9,7 +10,8 @@ describe('PersonalCenterComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [PersonalCenterComponent, SexPipe] + declarations: [PersonalCenterComponent, SexPipe], + imports: [HttpClientModule] }) .compileComponents(); }); @@ -20,7 +22,7 @@ describe('PersonalCenterComponent', () => { fixture.detectChanges(); }); - it('should create', () => { + fit('should create', () => { expect(component).toBeTruthy(); fixture.autoDetectChanges(); }); ``` 测试结果: ![image-20210308104228124](https://img.kancloud.cn/67/17/6717ade960183f748ad9d1bc5d2d68a8_2546x366.png) ## 登录 与前面章节中对教师的操作不同,后台获取当前登录教师的接口必须在**用户登录成功**后,才能够获取到。也就是说只有登录的用户才有资格访问该接口。 那么如果在请求该接口前先完成用户的登录,是否可以请求成功呢?心动不如行动,我们参考登录组件简单写下代码: ```typescript +++ b/first-app/src/app/personal-center/personal-center.component.ts @@ -1,5 +1,6 @@ import {Component, OnInit} from '@angular/core'; import {Teacher} from '../entity/teacher'; +import {HttpClient, HttpHeaders} from '@angular/common/http'; @Component({ selector: 'app-personal-center', @@ -9,18 +10,29 @@ import {Teacher} from '../entity/teacher'; export class PersonalCenterComponent implements OnInit { me = {} as Teacher; - constructor() { + constructor(private httpClient: HttpClient) { } ngOnInit(): void { - this.me = new Teacher( - 1, - 'zhangsan@yunzhi.club', - '张三', - 'password', - null as unknown as boolean, - 'zhangsan' - ); - } + const authString = 'zhangsan:codedemo.club'; + const authToken = btoa(authString); + let httpHeaders = new HttpHeaders(); + httpHeaders = httpHeaders.append('Authorization', 'Basic ' + authToken); + this.httpClient + .get<Teacher>( + 'http://angular.api.codedemo.club:81/teacher/login', + {headers: httpHeaders}) + .subscribe(() => { + console.log('登录成功,接着尝试获取当前登录用户'); + const url = 'http://angular.api.codedemo.club:81/teacher/me'; + this.httpClient.get<Teacher>(url) + .subscribe(teacher => { + console.log('请求当前登录用户成功'); + this.me = teacher; + }, + error => console.log('请求当前登录用户发生错误', error)); + }, + error => console.log('发生错误, 登录失败', error)); + } } ``` ![image-20210308105049039](https://img.kancloud.cn/a1/29/a12921dccbfb432925175c5f467f9fdc_1422x208.png) > 注意:此时的用户名可能已经不是`zhangsan`,请参考登录一节查看当前可用的用户名。 可见:即使是在请求当前用户前发起登录请求,在登录成功的情况下仍然没有被后台认为是**已认证用户**。 ## 再学COOKIE 而值得一提的是:在前后台统一的项目中,先完成用户登录再进行请求,是可以请求成功的。为什么会发生这种情况呢? 在前后台地址相同的情况下,浏览器会自动处理用于认为的cookie信息。包括:将后台返回的cookie信息缓存到浏览器中,以及再次请求中自动在header中携带当前已缓存的cookie信息。 比如我们在浏览器中直接访问 [http://angular.api.codedemo.club:81](http://angular.api.codedemo.club:81),则会在请求头中发现如下信息(如果没有,就再次访问一次): ![image-20210306145611485](https://img.kancloud.cn/e2/c0/e2c0d5cc50c206edce54639ac0b1e671_1850x840.png) 该信息则由浏览器自动处理。 但如果我们在当前项目中向样的地址发起请求,则会发现请求头中并没有携带cookie信息,反而增加了Origin信息: ![image-20210306150116177](https://img.kancloud.cn/19/ec/19ecb19a274e7a7caaf454f5fa44e1ac_1904x828.png) 实际上,即使是我们使用类似于登录时的代码,手动的在header中添加cookie信息,浏览器在发起请求时也会将此信息删除。 ```typescript +++ b/first-app/src/app/personal-center/personal-center.component.ts @@ -18,6 +18,7 @@ export class PersonalCenterComponent implements OnInit { const authToken = btoa(authString); let httpHeaders = new HttpHeaders(); httpHeaders = httpHeaders.append('Authorization', 'Basic ' + authToken); + httpHeaders = httpHeaders.append('Cookie', 'test cookie'); this.httpClient .get<Teacher>( ``` ![image-20210308110606538](https://img.kancloud.cn/15/54/1554e51e62a8726310d99dde0dd3fffe_1344x334.png) 这是浏览器出于保护普通的用户,在安全方面做的考虑。 浏览器将这种在`http://localhost:4200`下请求`http://angular.api.codedemo.club:81`的请求称为**跨域**请求。也就是说,浏览器将`http://localhost:4200`与`http://angular.api.codedemo.club:81`认为是两个**域**。浏览器进行跨域判断时,将对以下三个因素进行判断: 1. 协议是否相同。比如在`http://www.codedemo.club`与`https://www.codedemo.club`的协议一个是`http`,一个是`https`,则认为协议不同。将被识别为跨域。 2. 域名是否相同。比如https://www.baidu.com与http://www.google.com,由于域名分别是`www.baidu.com`及`www.google.com`,所以认为域名不同。将被识别为跨域。 3. 端口号是否相同。浏览器发起请求时,会默认省略http协议的80端口,以及https协议的443端口。所以访问:http://www.baidu.com,则相当于访问:http://www.baidu.com:80;访问https://www.baidu.com,则相当于访问:https://www.baidu.com:443。除此以外,我们还可以为其指定特定的端口,比如我们的后台地址为:`http://angular.api.codedemo.club:81`。该地址与`http://angular.api.codedemo.club`,一个端口为81,另一个为默认的80,不同。将被识别为跨域。 浏览器在发起请求时,如果发现请求的地址与当前访问的地址在协议、域名以及端口号上有任意一点不同的时候,则将此次请求识别为跨域请求。 跨域更像是电影中的谍战片,浏览器发现有人向外部传递信息时,自动的过滤掉被其认为是敏感信息的Cookie,以达到身份认证信息不被坏人获取的目的(其实目的不是这样的,但这不重要,只要知道Cookie是敏感信息,浏览器过滤掉了就可以)。 浏览器过滤掉了原来自动携带的用于认证的Cookie,这使得在跨域请求中,必须采取其的认证方式。 ## 域 什么是域呢?如果你对网络知识感兴趣,或是尝试建立过个人站点,那么对它肯定不陌生。在建立个人站点时,重要的一环就是购买域名,比如`codedemo.club`就是一个域名。 域为domain的译文,原意为`范围、领土`,简单来说就是用它来表示一定的范围。在现实生活中,我们接触了很多域。比如国家是一个域、省是一个域、市县等都是一个域。有了域,我们就能快速的根据域来找现实生活中的事物通讯,比如由于河北工业大学座落天津市这个域,所以我们在与河北工业大学通讯时,可以先指定其域名**天津**,可见域就是指范围。当前我们的现实世界里有个顶级的大域是宇宙,然后第二个域是地球,接下来的域有国家、省、市等。 在计算机的网络世界里同样也是如此,网络里也有根域名、二级域名、三级域名: ![image-20210306151140032](https://img.kancloud.cn/50/c5/50c5536787d130115b5c121895661690_1442x514.png) 每个域名下都可以有很多台计算机,每台计算机在当前域下都有一个唯一的名字,我们把域名下计算机的名字称作主机名。在进行http请求时,实际上是域中的某个计算机发起请求,所以在请求时要指名这个主机名。 ### 域名 知道了什么是域,域名就简单了。这就像是张三是张三的名字,李四是李四的名字,河北工业大学是河北工业大学的名字一样。为了区分不同的域,就需要对不同的域起名字。所以域名就是域的名字。 ## CORS 所以浏览器认为的跨域的请求,实际上是考虑了域、协议以及端口号。即使在同一个域下,如果协议或是端口号不同,也么被浏览器认为是跨域访问。我们把域、协议以及端口号也称为**同源**的三要素,即如果两个请求的域、协议以及端口号均相同时,则认为两个请求是同源的,而非同源则被认为是跨域的。所以,实际上**跨域**这个专有名词是不确切、不足以正确的表达其中的含义的,正确的专有名词应该是**跨源**,全称为**跨源资源共享**,对应英文原文为:Cross-Origin Resource Sharing,称写为`CORS`。 而在前后台分离的项目中,前台与后台的分离架构导致了前后台必然不同源,而不同源时浏览器必然不携带cookie信息,也就无法实现**自动认证**。所以在CORS中,我们一般使用其它的认证方法。在下个小节中,我们将对其中的一种进行介绍。 | 名称 | 地址 | | | ---------------------- | ------------------------------------------------------------ | ---- | | 跨域资源共享 CORS 详解 | [https://www.ruanyifeng.com/blog/2016/04/cors.html](https://www.ruanyifeng.com/blog/2016/04/cors.html) | | | 跨源资源共享(CORS) | [https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CORS](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CORS) | | | 域名 | [https://zh.wikipedia.org/wiki/%E5%9F%9F%E5%90%8D](https://zh.wikipedia.org/wiki/%E5%9F%9F%E5%90%8D) | | | 本节源码 | [https://github.com/mengyunzhi/angular11-guild/archive/step4.3.zip](https://github.com/mengyunzhi/angular11-guild/archive/step4.3.zip) | |