概要
簡単なツール作成を通してAngularの使い方を勉強をしています。
その過程で、APIと連携させようとすると、下記のようなCORSのエラーが発生したので
私が行った対応を記載します。
クロスオリジン要求をブロックしました: 同一生成元ポリシーにより、http://*****/api/company/get_detail.html にあるリモートリソースの読み込みは拒否されます (理由: CORS ヘッダー ‘Access-Control-Allow-Origin’ が足りない)。
前提
- 作成しているツールは、
http://localhost:4200
で動作している - API(http://*****/api/company/get_detail.html)へのアクセスに成功すると、会社情報がJSON形式で返ってくる
- APIはJSONPに対応している
原因
-
http://localhost:4200
で動作しているツールから、別ドメインで動いているAPIにアクセスしようとしたため、
同一生成元ポリシーに反しており、リクエストが制限された。 -
http://localhost:4200
からのクロスドメインアクセスがサーバ側で許可されていなかった。
- エラーが発生していた時のコード
export class CompanyService <T> {
constructor(private http: Http) {}
getCompanyDetail() : Promise<T> {
let sendUrl = 'http://*****/api/company/get_detail.html';
let headers = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers: headers });
return this.http.post(sendUrl , {}, options).map(res => res.json()).toPromise();
}
}
私が行った対応
対応1 JSONPを使ってみる
AngularにJsonpクラスがあったので、使い方を知るためにJSONPでアクセスするようにしてみました。
export class CompanyService <T> {
constructor(private jsonp: Jsonp) {}
getCompanyDetail() : Promise<T> {
// コールバック関数は固定で「JSONP_CALLBACK」を指定
let sendUrl = 'http://*****/api/company/get_detail.html?callback=JSONP_CALLBACK';
return this.jsonp.get(sendUrl).map(res => res.json()).toPromise();
}
}
export class CompanyComponent implements OnInit {
constructor(private companyService : CompanyService<{}>) {}
ngOnInit() {
this.getCompany();
}
getCompany() {
this.companyService.getCompanyDetail()
.then(res => console.log(res))
.catch(err => console.log(err));
}
}
JSONPを使うことで、クロスドメインの制約を受けずに手軽に実装できますが、
セキュリティなどの問題もあるので、JSONPを使う場合は一般に公開しても問題ないAPIへのアクセスにのみ使用した方が良さそうです。
対応2 CORSを利用してみる
クロスドメインアクセスを許可する設定をサーバ側に追加します。
ツール側は、Httpクラスを使うように修正。(JSONPに変更する前の状態に戻しました。)
export class CompanyService <T> {
constructor(private http: Http) {}
getCompanyDetail() : Promise<T> {
let sendUrl = 'http://*****/api/company/get_detail.html';
let headers = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers: headers });
return this.http.post(sendUrl , {}, options).map(res => res.json()).toPromise();
}
}
(1) Access-Control-Allow-Originを追加
<Location /api/company>
<IfModule mod_headers.c>
# アクセスを許可するURLを指定
Header set Access-Control-Allow-Origin "http://localhost:4200"
</IfModule>
</Location>
この状態でAPIへアクセスしてみると、今度は下記のようなエラーが発生しました。
クロスオリジン要求をブロックしました: 同一生成元ポリシーにより、http://*****/api/company/get_detail.html にあるリモートリソースの読み込みは拒否されます (理由: CORS プリフライトチャンネルからの CORS ヘッダー ‘Access-Control-Allow-Headers’ にトークン ‘content-type’ が足りない)。
Access-Control-Allow-Headersの設定が必要なようです。
(2) Access-Control-Allow-Headersを追加
<Location /api/company>
<IfModule mod_headers.c>
# アクセスを許可するURLを指定
Header set Access-Control-Allow-Origin "http://localhost:4200"
# 許可するリクエストヘッダの種類を指定
Header set Access-Control-Allow-Headers "Content-Type"
</IfModule>
</Location>
この設定を追加すると、APIへアクセス出来るようになりました。
ついでなので、許可するHTTPメソッドも追加してみました。
(3) Access-Control-Allow-Methodsを追加
<Location /api/company>
<IfModule mod_headers.c>
# アクセスを許可するURLを指定
Header set Access-Control-Allow-Origin "http://localhost:4200"
# 許可するリクエストヘッダの種類を指定
Header set Access-Control-Allow-Headers "Content-Type"
# 許可するメソッドを指定
Header set Access-Control-Allow-Methods "GET"
</IfModule>
</Location>
POST
でアクセスすると、何らかのエラーが出る事を期待していたのですが、
正常にアクセスできてしまいました。設定は有効になっているようなのですが・・・
そもそも、Access-Control-Allow-Methods
を設定していなくてもアクセス出来ていたので、
POST、GET、プリフライトリクエストで使用されるOPTIONS
は常に許可されているのか、
どこか設定が間違っていたのか、分からず・・・
許可する HTTP メソッドの種類を指定する
には、常に許可すると記載されているので、POSTやGETは指定しなくても許可されているのかな。
PUT
でアクセスするようにツールを修正。
export class CompanyService <T> {
constructor(private http: Http) {}
getCompanyDetail() : Promise<T> {
let sendUrl = 'http://*****/api/company/get_detail.html';
let headers = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers: headers });
return this.http.put(url, {}, options).map(res => res.json()).toPromise();
}
}
APIへアクセスすると、PUT
は許可していないので、期待していたエラーがでました。
クロスオリジン要求をブロックしました: 同一生成元ポリシーにより、http://*****/api/company/get_detail.html にあるリモートリソースの読み込みは拒否されます (理由: CORS ヘッダー ‘Access-Control-Allow-Methods’ にメソッドが見つからない)。
それにしても、Angular難しいです・・・。慣れるまで、まだまだ時間が掛かりそうです。