$ mkdir ui && cd ui
$ curl https://start.spring.io/starter.tgz -d dependencies=web,security -d name=ui | tar -xzvf -Spring Security ããã³ Angular
ã»ãã¥ã¢ãªã·ã³ã°ã«ããŒãžã¢ããªã±ãŒã·ã§ã³
ãã®ãã¥ãŒããªã¢ã«ã§ã¯ãSpring SecurityãSpring Bootãããã³ Angular ã飿ºããŠãå¿«é©ã§å®å šãªãŠãŒã¶ãŒãšã¯ã¹ããªãšã³ã¹ãæäŸããããã€ãã®åªããæ©èœã瀺ããŸããSpring ãš Angular ã®åå¿è ãã¢ã¯ã»ã¹ã§ããå¿ èŠããããŸãããã©ã¡ãã®å°éå®¶ã«ã圹ç«ã€è©³çްããããããããŸããããã¯å®éã«ã¯ãSpring Security ãš Angular ã«é¢ããäžé£ã®ã»ã¯ã·ã§ã³ã®æåã®ãã®ã§ãããããããã«æ°ããæ©èœã次ã ãšå ¬éãããŠããŸãã2 åç®ä»¥éã®èšäºã§ã¢ããªã±ãŒã·ã§ã³ãæ¹åããŸããããã®åŸã®äž»ãªå€æŽç¹ã¯æ©èœã§ã¯ãªãã¢ãŒããã¯ãã£ã§ãã
Spring ãšã·ã³ã°ã«ããŒãžã¢ããªã±ãŒã·ã§ã³
HTML5ããªãããã©ãŠã¶ãŒããŒã¹ã®æ©èœããã·ã³ã°ã«ããŒãžã¢ããªã±ãŒã·ã§ã³ãã¯ãçŸä»£ã®éçºè ã«ãšã£ãŠéåžžã«äŸ¡å€ã®ããããŒã«ã§ãããæå³ã®ããã€ã³ã¿ã©ã¯ã·ã§ã³ã«ã¯ããã¯ãšã³ããµãŒããŒãå¿ èŠã§ããããã¯ãšã³ããµãŒããŒã¯ãéçãªã³ã³ãã³ãïŒHTMLãCSSãJavaScriptïŒã®æäŸãæã«ã¯ïŒæè¿ã¯ããã§ããªãã§ããïŒåç㪠HTML ã®ã¬ã³ããªã³ã°ããŠãŒã¶ãŒã®èªèšŒãä¿è·ããããªãœãŒã¹ãžã®ã¢ã¯ã»ã¹ã®ç¢ºä¿ãïŒæåŸã«ãªããŸããïŒHTTP ã JSONïŒREST API ãšåŒã°ããããšããããŸãïŒãä»ããŠãã©ãŠã¶ãŒäžã§ JavaScript ãšå¯Ÿè©±ãããªã©ãããã€ãã®ããŒã«ã®ãã¡ã®ã©ããããã¹ãŠãæããããšãã§ããŸãã
Spring ã¯åžžã«ããã¯ãšã³ãæ©èœ (ç¹ã«äŒæ¥ã§) ãæ§ç¯ããããã®äººæ°ã®ããæè¡ã§ãããSpring Boot ã®åºçŸã«ãããç©äºã¯ãã€ãŠãªãã»ã©ç°¡åã«ãªããŸãããSpring BootãAngularãTwitter Bootstrap ã䜿ã£ãŠããŒãããæ°ããã·ã³ã°ã«ããŒãžã¢ããªã±ãŒã·ã§ã³ãæ§ç¯ããæ¹æ³ãèŠãŠã¿ãŸããããç¹å®ã®ã¹ã¿ãã¯ãéžæããç¹å¥ãªçç±ã¯ãããŸããããç¹ã«ãšã³ã¿ãŒãã©ã€ãº Java ã·ã§ããã®ã³ã¢ Spring æ¯æè ã«ã¯éåžžã«äººæ°ããããããåºçºç¹ãšããŠäŸ¡å€ããããŸãã
æ°èŠãããžã§ã¯ãã®äœæ
Spring ãš Angular ãå®å šã«äœ¿ãããªããŠããªã人ã§ããäœãèµ·ãã£ãŠããã®ãã远ãããšãã§ããããã«ããã®ã¢ããªã±ãŒã·ã§ã³ã®äœæãå°ã詳ãã説æããŸãã远ãããããå Žåã¯ãã¢ããªã±ãŒã·ã§ã³ãåäœããŠããæåŸãŸã§ã¹ãããããŠããã¹ãŠãã©ã®ããã«é©åãããã確èªã§ããŸããæ°ãããããžã§ã¯ããäœæããããã®ããŸããŸãªãªãã·ã§ã³ããããŸãã
ãã«ããããããžã§ã¯ãå šäœã®ãœãŒã¹ã³ãŒãã¯ããã® Github (è±èª) ã«ãããããå¿ èŠã«å¿ããŠãããžã§ã¯ããè€è£œããããããçŽæ¥äœæ¥ããããšãã§ããŸããæ¬¡ã«ã次ã®ã»ã¯ã·ã§ã³ã«ãžã£ã³ãããŸãã
Curl ã®äœ¿çš
éå§ããæ°ãããããžã§ã¯ããäœæããæãç°¡åãªæ¹æ³ã¯ãSpring Boot Initializr ã䜿çšããããšã§ããäŸ: UN*X ã®ãããªã·ã¹ãã ã§ curl ã䜿çš:
次ã«ããã®ãããžã§ã¯ãïŒããã©ã«ãã§ã¯éåžžã® Maven Java ãããžã§ã¯ãïŒããæ°ã«å ¥ãã® IDE ã«ã€ã³ããŒãããããã³ãã³ãã©ã€ã³ã§ãã¡ã€ã«ãš "mvn" ãæäœããã ãã§ããæ¬¡ã«ã次ã®ã»ã¯ã·ã§ã³ã«ãžã£ã³ãããŸãã
Spring Boot CLI ã®äœ¿çš
次ã®ããã«ãSpring Boot CLI ã䜿çšããŠåããããžã§ã¯ããäœæã§ããŸãã
$ spring init --dependencies web,security ui/ && cd ui次ã«ã次ã®ã»ã¯ã·ã§ã³ã«ãžã£ã³ãããŸãã
Initializr Web ãµã€ãã®äœ¿çš
å¿ èŠã«å¿ããŠãSpring Boot Initializr ãã .zip ãã¡ã€ã«ãšåãã³ãŒããçŽæ¥ååŸããããšãã§ããŸãããã©ãŠã¶ãŒã§éããäŸåé¢ä¿ "Web" ãšãã»ãã¥ãªãã£ããéžæããŠãããããžã§ã¯ããçæããã¯ãªãã¯ããŸãã.zip ãã¡ã€ã«ã«ã¯ãã«ãŒããã£ã¬ã¯ããªã«æšæºã® Maven ãŸã㯠Gradle ãããžã§ã¯ããå«ãŸããŠãããããå±éããåã«ç©ºã®ãã£ã¬ã¯ããªãäœæããããšããå§ãããŸããæ¬¡ã«ã次ã®ã»ã¯ã·ã§ã³ã«ãžã£ã³ãããŸãã
Eclipse Spring Tool Suite ã®äœ¿çš
Pleiades All in One (JDK, STS, Lombok ä»å±) ãŸã㯠Eclipse Spring Tool Suite (è±èª) ïŒEclipse ãã©ã°ã€ã³ã®ã»ããïŒã§ã¯ãFile->New->Spring Starter Project ã®ãŠã£ã¶ãŒãã䜿çšããŠãããžã§ã¯ããäœæããã³ã€ã³ããŒãããããšãã§ããŸããæ¬¡ã«ã次ã®ã»ã¯ã·ã§ã³ã«ãžã£ã³ãããŸããIntelliJ IDEA ãš NetBeans ã«ã¯åæ§ã®æ©èœããããŸãã
Angular ã¢ããªã远å ãã
AngularïŒãŸãã¯ææ°ã®ããã³ããšã³ããã¬ãŒã ã¯ãŒã¯ïŒã®ã·ã³ã°ã«ããŒãžã¢ããªã±ãŒã·ã§ã³ã®äžæ žã¯ãæè¿ Node.js ãã«ãã«ãªããŸããAngular ã«ã¯ãããããã°ããã»ããã¢ããããããã®ããŒã«ãããã€ãããããã䜿çšã§ããŸãããŸããä»ã® Spring Boot ã¢ããªã±ãŒã·ã§ã³ãšåæ§ã«ãMaven ã§ãã«ããããªãã·ã§ã³ãä¿æã§ããŸããAngular ã¢ããªã®ã»ããã¢ããæ¹æ³ã®è©³çްã¯å¥ã®å Žæ [GitHub] (è±èª) ã§èª¬æãããŠããŸãããŸãã¯ãgithub ãããã®ãã¥ãŒããªã¢ã«ã®ã³ãŒãããã§ãã¯ã¢ãŠãããããšãã§ããŸãã
ã¢ããªã±ãŒã·ã§ã³ã®å®è¡
Angular ã¢ããªã®æºåãæŽããšãã¢ããªã±ãŒã·ã§ã³ã¯ãã©ãŠã¶ãŒã«ããŒãå¯èœã«ãªããŸãïŒãŸã ããŸãæ©èœããŠããŸããïŒãã³ãã³ãã©ã€ã³ã§ãããè¡ãããšãã§ããŸã
$ mvn spring-boot:runhttp://localhost:8080 ã®ãã©ãŠã¶ãŒã«ç§»åããŸããããŒã ããŒãžãããŒããããšããŠãŒã¶ãŒåãšãã¹ã¯ãŒããèŠæ±ãããã©ãŠã¶ãŒãã€ã¢ãã°ã衚瀺ãããŸãïŒãŠãŒã¶ãŒå㯠"user" ã§ããããã¹ã¯ãŒãã¯èµ·åæã«ã³ã³ãœãŒã«ãã°ã«åºåãããŸãïŒãå®éã«ã¯ãŸã ã³ã³ãã³ãããããŸããïŒãŸã㯠ng CLI ã®ããã©ã«ãã®ãããŒããŒããã¥ãŒããªã¢ã«ã³ã³ãã³ãïŒãæ¬è³ªçã«ç©ºçœã®ããŒãžãååŸããå¿
èŠããããŸãã
ãã¹ã¯ãŒãã®ã³ã³ãœãŒã«ãã°ãã¹ã¯ã¬ã€ãã³ã°ããããªãå Žåã¯ãããã "application.properties" ïŒ"src/main/resources" å
ïŒã«è¿œå ããŸã: security.user.password=password ïŒãããŠç¬èªã®ãã¹ã¯ãŒããéžæããŸãïŒãããã¯ã"application.yml" ã䜿çšããŠãµã³ãã«ã³ãŒãã§è¡ããŸããã |
IDE ã§ã¯ãã¢ããªã±ãŒã·ã§ã³ã¯ã©ã¹ã§ main() ã¡ãœãããå®è¡ããã ãã§ãïŒã¯ã©ã¹ã¯ 1 ã€ã ãã§ãäžèšã® "curl" ã³ãã³ãã䜿çšããå Žå㯠UiApplication ãšåŒã°ããŸãïŒã
ã¹ã¿ã³ãã¢ãã³ JAR ãšããŠããã±ãŒãžåããŠå®è¡ããã«ã¯ã次ã®ããã«ããŸãã
$ mvn package
$ java -jar target/*.jarAngular ã¢ããªã±ãŒã·ã§ã³ã®ã«ã¹ã¿ãã€ãº
"app-root" ã³ã³ããŒãã³ãïŒ "src/app/app.component.ts" å ïŒãã«ã¹ã¿ãã€ãºããŸãããã
æå°éã® Angular ã¢ããªã±ãŒã·ã§ã³ã¯æ¬¡ã®ããã«ãªããŸãã
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'Demo';
greeting = {'id': 'XXX', 'content': 'Hello World'};
} ãã® TypeScript ã®ã³ãŒãã®ã»ãšãã©ã¯ãã€ã©ãŒãã¬ãŒãã§ããè峿·±ããã®ã¯ãã¹ãŠ AppComponent ã«ãããããã§ãã»ã¬ã¯ã¿ãŒãïŒHTML èŠçŽ ã®ååïŒãš @Component ã¢ãããŒã·ã§ã³ãä»ããŠã¬ã³ããªã³ã°ãã HTML ã®ã¹ãããããå®çŸ©ããŸããHTML ãã³ãã¬ãŒãïŒ"app.component.html" ïŒãç·šéããå¿
èŠããããŸãã
<div style="text-align:center"class="container">
<h1>
Welcome {{title}}!
</h1>
<div class="container">
<p>Id: <span>{{greeting.id}}</span></p>
<p>Message: <span>{{greeting.content}}!</span></p>
</div>
</div> ãããã®ãã¡ã€ã«ã "src/app" ã«è¿œå ããŠã¢ããªãåæ§ç¯ãããšãå®å
šã§æ©èœããããã«ãªãã"Hello World!" ãšè¡šç€ºãããŸããgreeting ã¯ãhandlebar ãã¬ãŒã¹ãã«ã㌠{{greeting.id}} ããã³ {{greeting.content}} ã䜿çšããŠãHTML ã§ Angular ã«ãã£ãŠã¬ã³ããªã³ã°ãããŸãã
åçã³ã³ãã³ãã®è¿œå
ãããŸã§ã«ãã°ãªãŒãã£ã³ã°ãããŒãã³ãŒããããã¢ããªã±ãŒã·ã§ã³ããããŸããããã¯ãç©äºãã©ã®ããã«çµã¿åããããããåŠç¿ã§ããŸãããå®éã«ã¯ã³ã³ãã³ããããã¯ãšã³ããµãŒããŒããæ¥ãããšãæåŸ
ããŠãããããã°ãªãŒãã£ã³ã°ãååŸããããã«äœ¿çšã§ãã HTTP ãšã³ããã€ã³ããäœæããŸããããã¢ããªã±ãŒã·ã§ã³ã¯ã©ã¹ [GitHub] (è±èª) ïŒ "src/main/java/demo" ïŒã§ã@RestController ã¢ãããŒã·ã§ã³ã远å ããæ°ãã @RequestMapping ãå®çŸ©ããŸãã
@SpringBootApplication
@RestController
public class UiApplication {
@RequestMapping("/resource")
public Map<String,Object> home() {
Map<String,Object> model = new HashMap<String,Object>();
model.put("id", UUID.randomUUID().toString());
model.put("content", "Hello World");
return model;
}
public static void main(String[] args) {
SpringApplication.run(UiApplication.class, args);
}
} æ°ãããããžã§ã¯ãã®äœææ¹æ³ã«ãã£ãŠã¯ãUiApplication ãšåŒã°ããªãå ŽåããããŸãã |
ãã®ã¢ããªã±ãŒã·ã§ã³ãå®è¡ãã"/resource" ãšã³ããã€ã³ãã curl ããããšãããšãããã©ã«ãã§å®å šã§ããããšãããããŸãã
$ curl localhost:8080/resource
{"timestamp":1420442772928,"status":401,"error":"Unauthorized","message":"Full authentication is required to access this resource","path":"/resource"}Angular ããã®åçãªãœãŒã¹ã®ããŒã
ãã©ãŠã¶ãŒã§ãã®ã¡ãã»ãŒãžãååŸããŸããããAppComponent ã倿ŽããŠãXHR ã䜿çšããŠä¿è·ããããªãœãŒã¹ãããŒãããŸãã
import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'Demo';
greeting = {};
constructor(private http: HttpClient) {
http.get('resource').subscribe(data => this.greeting = data);
}
}Angular ã«ãã£ãŠ http ã¢ãžã¥ãŒã«ãä»ããŠæäŸããã http ãµãŒãã¹ (è±èª) ãæ³šå
¥ããããã䜿çšããŠãªãœãŒã¹ãååŸããŸãããAngular ãã¬ã¹ãã³ã¹ãæž¡ããJSON ããã«ããŠã°ãªãŒãã£ã³ã°ã«å²ãåœãŠãŸãã
ã«ã¹ã¿ã ã³ã³ããŒãã³ããžã® http ãµãŒãã¹ã®äŸåæ§æ³šå
¥ãæå¹ã«ããã«ã¯ãã³ã³ããŒãã³ããå«ã AppModule ã§å®£èšããå¿
èŠããããŸãïŒæåã®ãã©ãããšæ¯èŒããŠãimports ã§ã¯ãã 1 è¡ã ãã§ãïŒã
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { HttpClientModule } from '@angular/common/http';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
HttpClientModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }ã¢ããªã±ãŒã·ã§ã³ãå床å®è¡ïŒãŸãã¯ãã©ãŠã¶ãŒã§ããŒã ããŒãžãåèªã¿èŸŒã¿ïŒãããšãåçã¡ãã»ãŒãžãšãã®äžæã® ID ã衚瀺ãããŸãããã®ããããªãœãŒã¹ã¯ä¿è·ãããŠãããçŽæ¥ curl ããããšã¯ã§ããŸãããããã©ãŠã¶ãŒã¯ã³ã³ãã³ãã«ã¢ã¯ã»ã¹ã§ããŸããã100 è¡æªæºã®ã³ãŒãã§å®å šãªåäžããŒãžã®ã¢ããªã±ãŒã·ã§ã³ããããŸãïŒ
| éçãªãœãŒã¹ã倿ŽããåŸããã©ãŠã¶ãŒã«åŒ·å¶çã«ãªããŒããããå¿ èŠãããå ŽåããããŸããChromeïŒããã³ãã©ã°ã€ã³ä»ã FirefoxïŒã§ã¯ããéçºè ããŒã«ãïŒF12ïŒã䜿çšã§ããŸãããããã§ååãããããŸããããŸãã¯ãCTRL + F5 ã䜿çšããå¿ èŠãããå ŽåããããŸãã |
ä»çµã¿ã¯ïŒ
äžéšã®éçºè ããŒã«ã䜿çšãããšããã©ãŠã¶ãŒãšããã¯ãšã³ãéã®çžäºäœçšããã©ãŠã¶ãŒã§ç¢ºèªã§ããŸãïŒéåžžãF12 ã¯ãããéããããã©ã«ãã§ Chrome ã§åäœããFirefox ã§ãã©ã°ã€ã³ãå¿ èŠã«ãªãå ŽåããããŸãïŒãæŠèŠã¯æ¬¡ã®ãšããã§ãã
| åè© | ãã¹ | ã¹ããŒã¿ã¹ | ã¬ã¹ãã³ã¹ |
|---|---|---|---|
GET | / | 401 | èªèšŒã®ããã®ãã©ãŠã¶ãŒããã³ãã |
GET | / | 200 | index.html |
GET | /*.js | 200 | Angular ããã® 3 çªç®ã®ã¢ã»ããã®ããŒã |
GET | /main.bundle.js | 200 | ã¢ããªã±ãŒã·ã§ã³ããžã㯠|
GET | /resource | 200 | JSON ã°ãªãŒãã£ã³ã° |
ãã©ãŠã¶ãŒãããŒã ããŒãžã®èªã¿èŸŒã¿ãåäžã®ã€ã³ã¿ã©ã¯ã·ã§ã³ãšããŠæ±ãããã401 ã衚瀺ãããªãå ŽåããããŸãããŸããCORS [Mozilla] ããŽã·ãšãŒã·ã§ã³ãããããã"/resource" ã«å¯Ÿãã 2 ã€ã®ãªã¯ãšã¹ãã衚瀺ãããå ŽåããããŸãã
ãªã¯ãšã¹ãããã詳现ã«èŠããšããã¹ãŠã®ãªã¯ãšã¹ãã« "Authorization" ããããŒãããããšãããããŸããæ¬¡ã®ãããªãã®ã§ãã
Authorization: Basic dXNlcjpwYXNzd29yZA==ãã©ãŠã¶ãŒã¯ããã¹ãŠã®ãªã¯ãšã¹ãã§ãŠãŒã¶ãŒåãšãã¹ã¯ãŒããéä¿¡ããŠããŸãïŒãã®ãããæ¬çªç°å¢ã§ HTTPS ã®ã¿ã䜿çšããããšãå¿ããªãã§ãã ããïŒãããã«ã€ã㊠"Angular" ã¯ãªããããJavaScript ãã¬ãŒã ã¯ãŒã¯ãŸãã¯éžæããéãã¬ãŒã ã¯ãŒã¯ã§åäœããŸãã
ããã®ã©ããæªããã ãïŒ
äžèŠãããªãè¯ãäœæ¥ãããããã§ããç°¡æœã§å®è£ ããããããã¹ãŠã®ããŒã¿ã¯ç§å¯ã®ãã¹ã¯ãŒãã§ä¿è·ãããŠããŸããããã³ããšã³ããŸãã¯ããã¯ãšã³ãã®ãã¯ãããžãŒã倿ŽããŠãæ©èœããŸããããããããã€ãã®èª²é¡ããããŸãã
åºæ¬èªèšŒã¯ããŠãŒã¶ãŒåãšãã¹ã¯ãŒãã®èªèšŒã«å¶éãããŠããŸãã
èªèšŒ UI ã¯ã©ãã«ã§ããããŸãããäžæ Œå¥œãªãã®ã§ã (ãã©ãŠã¶ãŒãã€ã¢ãã°)ã
ã¯ãã¹ãµã€ããªã¯ãšã¹ããã©ãŒãžã§ãª [Wikipedia] ïŒCSRFïŒããã®ä¿è·ã¯ãããŸããã
CSRF ã¯ãããã¯ãšã³ããªãœãŒã¹ãååŸããã ãã§ããããïŒã€ãŸãããµãŒããŒã®ç¶æ ã倿ŽãããªãããïŒãå®éã®ã¢ããªã±ãŒã·ã§ã³ã§ã¯èª²é¡ã«ãªããŸãããã¢ããªã±ãŒã·ã§ã³ã« POSTãPUTãDELETE ããããšããã«ãåççãªææ°ã®ææ®µã§ã¯ãã¯ãå®å šã§ã¯ãªããªããŸãã
ãã®ã·ãªãŒãºã®æ¬¡ã®ã»ã¯ã·ã§ã³ã§ã¯ããã©ãŒã ããŒã¹èªèšŒã䜿çšããããã«ã¢ããªã±ãŒã·ã§ã³ãç¶æ¿ããŸããããã¯ãHTTP Basic ãããã¯ããã«æè»ã§ãããã©ãŒã ãäœæããããCSRF ä¿è·ãå¿
èŠã«ãªããŸããSpring Security ãš Angular ã®äž¡æ¹ã«ã¯ããããæ¯æŽããããã®ããã«äœ¿ããæ©èœãããã€ããããŸãããã¿ãã¬: HttpSession ã䜿çšããå¿
èŠããããŸãã
ãã°ã€ã³ããŒãž
ãã®ã»ã¯ã·ã§ã³ã§ã¯ããã·ã³ã°ã«ããŒãžã¢ããªã±ãŒã·ã§ã³ãã§ Spring Security ã Angular (è±èª) ãšäœ¿çšããæ¹æ³ã«ã€ããŠåŒãç¶ã説æããŸããããã§ã¯ãAngular ã䜿çšããŠããã©ãŒã ãä»ããŠãŠãŒã¶ãŒãèªèšŒããå®å šãªãªãœãŒã¹ããã§ããã㊠UI ã§ã¬ã³ããªã³ã°ããæ¹æ³ã瀺ããŸããããã¯äžé£ã®ã»ã¯ã·ã§ã³ã® 2 çªç®ã§ãããã¢ããªã±ãŒã·ã§ã³ã®åºæ¬çãªæ§æèŠçŽ ã«è¿œãã€ãããæåã®ã»ã¯ã·ã§ã³ãèªãã§ãŒããããã«ãããããGithub ã®ãœãŒã¹ã³ãŒãã«çŽæ¥ (è±èª) é²ãããšãã§ããŸããæåã®ã»ã¯ã·ã§ã³ã§ã¯ãHTTP åºæ¬èªèšŒã䜿çšããŠããã¯ãšã³ããªãœãŒã¹ãä¿è·ããåçŽãªã¢ããªã±ãŒã·ã§ã³ãæ§ç¯ããŸãããããã§ã¯ããã°ã€ã³ãã©ãŒã ã远å ãããŠãŒã¶ãŒã«èªèšŒãããã©ãããå¶åŸ¡ããæåã®å埩ã§ã®èª²é¡ãä¿®æ£ããŸãïŒäž»ã« CSRF ä¿è·ã®æ¬ åŠïŒã
ãªãã€ã³ããŒ: ãã®ã»ã¯ã·ã§ã³ã§ãµã³ãã«ã¢ããªã±ãŒã·ã§ã³ã䜿çšããŠããå Žåã¯ããã©ãŠã¶ãŒã®ãã£ãã·ã¥ã® Cookie ãš HTTP åºæ¬èªèšŒæ å ±ãå¿ ãã¯ãªã¢ããŠãã ãããChrome ã§ã¯ãåäžãµãŒããŒã§ãããè¡ãæè¯ã®æ¹æ³ã¯ãæ°ããã·ãŒã¯ã¬ãããŠã£ã³ããŠãéãããšã§ãã
ããŒã ããŒãžã«ããã²ãŒã·ã§ã³ã远å
Angular ã¢ããªã±ãŒã·ã§ã³ã®äžæ žã¯ãåºæ¬çãªããŒãžã¬ã€ã¢ãŠãçšã® HTML ãã³ãã¬ãŒãã§ãããã§ã«éåžžã«åºæ¬çãªãã®ããããŸãããããã®ã¢ããªã±ãŒã·ã§ã³ã§ã¯ããã€ãã®ããã²ãŒã·ã§ã³æ©èœïŒãã°ã€ã³ããã°ã¢ãŠããããŒã ïŒãæäŸããå¿
èŠãããããããããïŒsrc/app ã§ïŒå€æŽããŸãããã
<div class="container">
<ul class="nav nav-pills">
<li><a routerLinkActive="active" routerLink="/home">Home</a></li>
<li><a routerLinkActive="active" routerLink="/login">Login</a></li>
<li><a (click)="logout()">Logout</a></li>
</ul>
</div>
<div class="container">
<router-outlet></router-outlet>
</div> ã¡ã€ã³ã³ã³ãã³ã㯠<router-outlet/> ã§ããã°ã€ã³ãªã³ã¯ãšãã°ã¢ãŠããªã³ã¯ã®ããããã²ãŒã·ã§ã³ããŒããããŸãã
<router-outlet/> ã»ã¬ã¯ã¿ãŒã¯ Angular ã«ãã£ãŠæäŸãããã¡ã€ã³ã¢ãžã¥ãŒã«ã®ã³ã³ããŒãã³ãã«æ¥ç¶ããå¿
èŠããããŸããã«ãŒãããšïŒã¡ãã¥ãŒãªã³ã¯ããšïŒã« 1 ã€ã®ã³ã³ããŒãã³ããããã1 ã€ã«ãã£ã€ããŠç¶æ
ãå
±æãããã«ããŒãµãŒãã¹ïŒAppServiceïŒããããŸãããã¹ãŠã®èŠçŽ ããŸãšããã¢ãžã¥ãŒã«ã®å®è£
ã¯æ¬¡ã®ãšããã§ãã
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { RouterModule, Routes } from '@angular/router';
import { AppService } from './app.service';
import { HomeComponent } from './home.component';
import { LoginComponent } from './login.component';
import { AppComponent } from './app.component';
const routes: Routes = [
{ path: '', pathMatch: 'full', redirectTo: 'home'},
{ path: 'home', component: HomeComponent},
{ path: 'login', component: LoginComponent}
];
@NgModule({
declarations: [
AppComponent,
HomeComponent,
LoginComponent
],
imports: [
RouterModule.forRoot(routes),
BrowserModule,
HttpClientModule,
FormsModule
],
providers: [AppService]
bootstrap: [AppComponent]
})
export class AppModule { }"RouterModule" (è±èª) ãšåŒã°ãã Angular ã¢ãžã¥ãŒã«ãžã®äŸåé¢ä¿ã远å ããŸãããããã«ãããéæ³ã® router ã AppComponent ã®ã³ã³ã¹ãã©ã¯ã¿ãŒã«æ³šå
¥ããããšãã§ããŸãããroutes ã¯ãAppModule ã®ã€ã³ããŒãå
ã§äœ¿çšããã"/" ïŒãããŒã ãã³ã³ãããŒã©ãŒïŒããã³ "/login"ïŒããã°ã€ã³ãã³ã³ãããŒã©ãŒïŒãžã®ãªã³ã¯ãèšå®ããŸãã
ãŸããããã« FormsModule ãå¿ã³èŸŒãŸããŸãããããã¯ããŠãŒã¶ãŒããã°ã€ã³ãããšãã«éä¿¡ããããã©ãŒã ã«ããŒã¿ããã€ã³ãããããã«åŸã§å¿
èŠã«ãªãããã§ãã
UI ã³ã³ããŒãã³ãã¯ãã¹ãŠã宣èšãã§ããããµãŒãã¹ã°ã«ãŒã¯ããããã€ããŒãã§ããAppComponent ã¯å®éã«ã¯ããŸãæ©èœããŸãããã¢ããªã®ã«ãŒãã«ä»å±ãã TypeScript ã³ã³ããŒãã³ãã¯æ¬¡ã®ãšããã§ãã
import { Component } from '@angular/core';
import { AppService } from './app.service';
import { HttpClient } from '@angular/common/http';
import { Router } from '@angular/router';
import 'rxjs/add/operator/finally';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
constructor(private app: AppService, private http: HttpClient, private router: Router) {
this.app.authenticate(undefined, undefined);
}
logout() {
this.http.post('logout', {}).finally(() => {
this.app.authenticated = false;
this.router.navigateByUrl('/login');
}).subscribe();
}
}é¡èãªç¹åŸŽ:
ä»åºŠã¯
AppServiceã®äŸåæ§æ³šå ¥ãããã€ããããŸãã³ã³ããŒãã³ãã®ããããã£ãšããŠå ¬éããããã°ã¢ãŠã颿°ããããŸããããã¯åŸã§ãã°ã¢ãŠããªã¯ãšã¹ããããã¯ãšã³ãã«éä¿¡ããããã«äœ¿çšã§ããŸãã
appãµãŒãã¹ã«ãã©ã°ãèšå®ãããŠãŒã¶ãŒããã°ã€ã³ç»é¢ã«éãè¿ããŸãïŒãããfinally()ã³ãŒã«ããã¯ãä»ããŠç¡æ¡ä»¶ã«è¡ããŸãïŒãtemplateUrlã䜿çšããŠããã³ãã¬ãŒã HTML ãå¥ã®ãã¡ã€ã«ã«å€éšåããŸããauthenticate()颿°ã¯ãã³ã³ãããŒã©ãŒãèªã¿èŸŒãŸãããšãã«åŒã³åºããããŠãŒã¶ãŒãå®éã«ãã§ã«èªèšŒãããŠãããã©ããã確èªããŸãïŒããšãã°ããŠãŒã¶ãŒãã»ãã·ã§ã³ã®éäžã§ãã©ãŠã¶ãŒããªãã¬ãã·ã¥ãããã©ããïŒãå®éã®èªèšŒã¯ãµãŒããŒã«ãã£ãŠè¡ãããããããªã¢ãŒãåŒã³åºããè¡ãã«ã¯authenticate()颿°ãå¿ èŠã§ãããã©ãŠã¶ãŒãããã远跡ããããšãä¿¡é Œããããªãããã§ãã
äžèšã§æ³šå
¥ãã app ãµãŒãã¹ã«ã¯ããŠãŒã¶ãŒãçŸåšèªèšŒãããŠãããã©ããã確èªã§ããããŒã«å€ãã©ã°ãšãããã¯ãšã³ããµãŒããŒã§ã®èªèšŒã«äœ¿çšã§ããããŸãã¯åã«ãŠãŒã¶ãŒã®è©³çްãç
§äŒããããã«äœ¿çšã§ãã颿° authenticate() ãå¿
èŠã§ã:
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
@Injectable()
export class AppService {
authenticated = false;
constructor(private http: HttpClient) {
}
authenticate(credentials, callback) {
const headers = new HttpHeaders(credentials ? {
authorization : 'Basic ' + btoa(credentials.username + ':' + credentials.password)
} : {});
this.http.get('user', {headers: headers}).subscribe(response => {
if (response['name']) {
this.authenticated = true;
} else {
this.authenticated = false;
}
return callback && callback();
});
}
}authenticated ãã©ã°ã¯åçŽã§ããauthenticate() 颿°ã¯ãæäŸãããŠããå Žå㯠HTTP åºæ¬èªèšŒè³æ Œæ
å ±ãéä¿¡ããæäŸãããŠããªãå Žåã¯éä¿¡ããŸããããŸãããªãã·ã§ã³ã® callback åŒæ°ããããèªèšŒãæåããå Žåã«ã³ãŒããå®è¡ããããã«äœ¿çšã§ããŸãã
æšæ¶
å€ãããŒã ããŒãžã®æšæ¶ã®å 容ã¯ã"src/app" ã® "app.component.html" ã®ããé£ã«çœ®ãããšãã§ããŸãã
<h1>Greeting</h1>
<div [hidden]="!authenticated()">
<p>The ID is {{greeting.id}}</p>
<p>The content is {{greeting.content}}</p>
</div>
<div [hidden]="authenticated()">
<p>Login to see your greeting</p>
</div> ãŠãŒã¶ãŒã¯ãã°ã€ã³ãããã©ããïŒãã©ãŠã¶ãŒã§ãã¹ãŠå¶åŸ¡ãããåïŒãéžæã§ããããã«ãªã£ããããUI ã§å®å
šãªã³ã³ãã³ããšããã§ãªãã³ã³ãã³ããåºå¥ããå¿
èŠããããŸããïŒãŸã ååšããªãïŒ authenticated() 颿°ãžã®åç
§ã远å ããããšã«ããããããäºæž¬ããŸããã
HomeComponent ã¯ã°ãªãŒãã£ã³ã°ãååŸããå¿
èŠããããAppService ãããã©ã°ããã«ãã authenticated() ãŠãŒãã£ãªãã£æ©èœãæäŸããå¿
èŠããããŸãã
import { Component, OnInit } from '@angular/core';
import { AppService } from './app.service';
import { HttpClient } from '@angular/common/http';
@Component({
templateUrl: './home.component.html'
})
export class HomeComponent {
title = 'Demo';
greeting = {};
constructor(private app: AppService, private http: HttpClient) {
http.get('resource').subscribe(data => this.greeting = data);
}
authenticated() { return this.app.authenticated; }
}ãã°ã€ã³ãã©ãŒã
ãã°ã€ã³ãã©ãŒã ã«ã¯ãç¬èªã®ã³ã³ããŒãã³ããååŸãããŸãã
<div class="alert alert-danger" [hidden]="!error">
There was a problem logging in. Please try again.
</div>
<form role="form" (submit)="login()">
<div class="form-group">
<label for="username">Username:</label> <input type="text"
class="form-control" id="username" name="username" [(ngModel)]="credentials.username"/>
</div>
<div class="form-group">
<label for="password">Password:</label> <input type="password"
class="form-control" id="password" name="password" [(ngModel)]="credentials.password"/>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form> ããã¯éåžžã«æšæºçãªãã°ã€ã³ãã©ãŒã ã§ããŠãŒã¶ãŒåãšãã¹ã¯ãŒãã® 2 ã€ã®å
¥åãšãAngular ã€ãã³ããã³ãã©ãŒ (submit) ãä»ããŠãã©ãŒã ãéä¿¡ããããã®ãã¿ã³ããããŸããform ã¿ã°ã§ã¢ã¯ã·ã§ã³ãå®è¡ããå¿
èŠã¯ãããŸããããã®ãããã¢ã¯ã·ã§ã³ããŸã£ããæ¿å
¥ããªãæ¹ãããã§ããããAngular ã¢ãã«ã« error ãå«ãŸããå Žåã«ã®ã¿è¡šç€ºããããšã©ãŒã¡ãã»ãŒãžããããŸãããã©ãŒã ã³ã³ãããŒã«ã¯ Angular ãã©ãŒã (è±èª) ã® ngModel ã䜿çšã㊠HTML ãš Angular ã³ã³ãããŒã©ãŒéã§ããŒã¿ãæž¡ããŸãããã®å Žåãcredentials ãªããžã§ã¯ãã䜿çšããŠãŠãŒã¶ãŒåãšãã¹ã¯ãŒããä¿æããŸãã
èªèšŒããã»ã¹
远å ãããã°ã€ã³ãã©ãŒã ããµããŒãããã«ã¯ãããã«æ©èœã远å ããå¿
èŠããããŸããã¯ã©ã€ã¢ã³ãåŽã§ã¯ããã㯠LoginComponent ã«å®è£
ããããµãŒããŒã§ã¯ Spring Security æ§æã«ãªããŸãã
ãã°ã€ã³ãã©ãŒã ã®éä¿¡
ãã©ãŒã ãéä¿¡ããã«ã¯ããã§ã« ng-submit ãä»ããŠãã©ãŒã ã§åç
§ãã login() 颿°ãšãng-model ãä»ããŠåç
§ãã credentials ãªããžã§ã¯ããå®çŸ©ããå¿
èŠããããŸããããã°ã€ã³ãã³ã³ããŒãã³ããå
·äœåããŸãã
import { Component, OnInit } from '@angular/core';
import { AppService } from './app.service';
import { HttpClient } from '@angular/common/http';
import { Router } from '@angular/router';
@Component({
templateUrl: './login.component.html'
})
export class LoginComponent {
credentials = {username: '', password: ''};
constructor(private app: AppService, private http: HttpClient, private router: Router) {
}
login() {
this.app.authenticate(this.credentials, () => {
this.router.navigateByUrl('/');
});
return false;
}
}credentials ãªããžã§ã¯ãã®åæåã«å ããŠããã©ãŒã ã§å¿
èŠãª login() ãå®çŸ©ããŸãã
authenticate() ã¯ãçžå¯ŸãªãœãŒã¹ïŒã¢ããªã±ãŒã·ã§ã³ã®ãããã€ã«ãŒãã«å¯ŸããŠïŒ"/user" ã«å¯Ÿã㊠GET ãå®è¡ããŸããlogin() 颿°ããåŒã³åºããããšãBase64 ã§ãšã³ã³ãŒããããã¯ã¬ãã³ã·ã£ã«ãããããŒã«è¿œå ãããããããµãŒããŒäžã§èªèšŒãè¡ããã代ããã« Cookie ãåãå
¥ããããŸããlogin() 颿°ã¯ãèªèšŒã®çµæãååŸãããšãããã«å¿ããŠããŒã«ã« $scope.error ãã©ã°ãèšå®ããŸããããã¯ããã°ã€ã³ãã©ãŒã ã®äžã®ãšã©ãŒã¡ãã»ãŒãžã®è¡šç€ºãå¶åŸ¡ããããã«äœ¿çšãããŸãã
çŸåšèªèšŒãããŠãããŠãŒã¶ãŒ
authenticate() 颿°ãåŠçããã«ã¯ãæ°ãããšã³ããã€ã³ããããã¯ãšã³ãã«è¿œå ããå¿
èŠããããŸãã
@SpringBootApplication
@RestController
public class UiApplication {
@RequestMapping("/user")
public Principal user(Principal user) {
return user;
}
...
} ããã¯ãSpring Security ã¢ããªã±ãŒã·ã§ã³ã§åœ¹ç«ã€ããªãã¯ã§ãã"/user" ãªãœãŒã¹ãå°éå¯èœã§ããå ŽåãçŸåšèªèšŒãããŠãããŠãŒã¶ãŒïŒAuthentication [GitHub] (è±èª) ïŒãè¿ããŸããããã§ãªãå ŽåãSpring Security ã¯ãªã¯ãšã¹ããã€ã³ã¿ãŒã»ããããAuthenticationEntryPoint [GitHub] (è±èª) ãä»ã㊠401 ã¬ã¹ãã³ã¹ãéä¿¡ããŸãã
ãµãŒããŒã§ã®ãã°ã€ã³ãªã¯ãšã¹ãã®åŠç
Spring Security ã䜿çšãããšããã°ã€ã³ãªã¯ãšã¹ããç°¡åã«åŠçã§ããŸããã¡ã€ã³ã¢ããªã±ãŒã·ã§ã³ã¯ã©ã¹ [GitHub] (è±èª) ã«ããã€ãã®æ§æã远å ããã ãã§ãïŒããšãã°ãå éšã¯ã©ã¹ãšããŠïŒã
@SpringBootApplication
@RestController
public class UiApplication {
...
@Configuration
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
protected static class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic()
.and()
.authorizeRequests()
.antMatchers("/index.html", "/", "/home", "/login").permitAll()
.anyRequest().authenticated();
}
}
}ããã¯ãSpring Security ãã«ã¹ã¿ãã€ãºããæšæº Spring Boot ã¢ããªã±ãŒã·ã§ã³ã§ãããéçïŒHTMLïŒãªãœãŒã¹ãžã®å¿åã¢ã¯ã»ã¹ã®ã¿ãèš±å¯ããŸããHTML ãªãœãŒã¹ã¯ãæç¢ºã«ãªãçç±ã®ãããSpring Security ã«ãã£ãŠç¡èŠãããã ãã§ãªããå¿åãŠãŒã¶ãŒãå©çšã§ããå¿ èŠããããŸãã
èŠããŠããå¿
èŠãããæåŸã®ããšã¯ãAngular ãæäŸãã JavaScript ã³ã³ããŒãã³ããã¢ããªã±ãŒã·ã§ã³ã§å¿åã§å©çšã§ããããã«ããããšã§ããäžèšã® HttpSecurity æ§æã§ãããè¡ãããšãã§ããŸãããéçã³ã³ãã³ãã§ãããããåçŽã«ç¡èŠããæ¹ãè¯ãã§ã:
security:
ignored:
- "*.bundle.*"ããã©ã«ãã® HTTP ãªã¯ãšã¹ãããããŒã®è¿œå
ãã®æç¹ã§ã¢ããªãå®è¡ãããšããã©ãŠã¶ãŒãïŒãŠãŒã¶ãŒãšãã¹ã¯ãŒãã®ïŒåºæ¬èªèšŒãã€ã¢ãã°ããããã¢ããããããšãããããŸããããã¯ã"WWW-Authenticate" ããããŒãæã€ /user ããã³ /resource ãžã® XHR ãªã¯ãšã¹ãããã® 401 å¿çã確èªããããã§ãããã®ãããã¢ãããæå¶ããæ¹æ³ã¯ãSpring Security ããæ¥ãããããŒãæå¶ããããšã§ãããŸããå¿çããããŒãæå¶ããæ¹æ³ã¯ãç¹å¥ãªåŸæ¥ã®ãªã¯ãšã¹ãããã㌠"X-Requested-With = XMLHttpRequest" ãéä¿¡ããããšã§ãã以å㯠Angular ã®ããã©ã«ãã§ãããã1.3.0 ã§ã¯åé€ãããŸãã [GitHub] (è±èª) ãAngular XHR ãªã¯ãšã¹ãã§ããã©ã«ãã®ããããŒãèšå®ããæ¹æ³ã¯æ¬¡ã®ãšããã§ãã
æåã«ãAngular HTTP ã¢ãžã¥ãŒã«ã«ãã£ãŠæäŸãããããã©ã«ã RequestOptions ãç¶æ¿ããŸãã
@Injectable()
export class XhrInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler) {
const xhr = req.clone({
headers: req.headers.set('X-Requested-With', 'XMLHttpRequest')
});
return next.handle(xhr);
}
} ããã®æ§æã¯å®åã§ããClass ã® implements ããããã£ã¯ãã®åºæ¬ã¯ã©ã¹ã§ãããã³ã³ã¹ãã©ã¯ã¿ãŒã«å ããŠãæ¬åœã«å¿
èŠãªããšã¯ãAngular ã«ãã£ãŠåžžã«åŒã³åºãããããããŒã远å ããããã«äœ¿çšã§ãã intercept() 颿°ããªãŒããŒã©ã€ãããããšã ãã§ãã
ãã®æ°ãã RequestOptions ãã¡ã¯ããªãã€ã³ã¹ããŒã«ããã«ã¯ãAppModule ã® providers ã§å®£èšããå¿
èŠããããŸãã
@NgModule({
...
providers: [AppService, { provide: HTTP_INTERCEPTORS, useClass: XhrInterceptor, multi: true }],
...
})
export class AppModule { }ãã°ã¢ãŠã
ã¢ããªã±ãŒã·ã§ã³ã¯ã»ãŒé¢æ°ã«çµäºããŠããŸããæåŸã«è¡ãå¿
èŠãããã®ã¯ãããŒã ããŒãžã§ã¹ã±ãããããã°ã¢ãŠãæ©èœãå®è£
ããããšã§ãããŠãŒã¶ãŒãèªèšŒãããŠããå Žåã¯ãããã°ã¢ãŠãããªã³ã¯ã衚瀺ããAppComponent ã® logout() 颿°ã«ããã¯ããŸãã"/logout" ã« HTTP POST ãéä¿¡ããããšãå¿ããªãã§ãã ãããããã¯ããµãŒããŒã«å®è£
ããå¿
èŠããããŸããããã¯ãSpring Security ã«ãã£ãŠãã§ã«è¿œå ãããŠããããç°¡åã§ãïŒã€ãŸãããã®åçŽãªãŠãŒã¹ã±ãŒã¹ã§ã¯äœãããå¿
èŠã¯ãããŸããïŒããã°ã¢ãŠãã®åäœããã现ããå¶åŸ¡ããã«ã¯ãWebSecurityAdapter ã§ HttpSecurity ã³ãŒã«ããã¯ã䜿çšããŠãããšãã°ããã°ã¢ãŠãåŸã«ããžãã¹ããžãã¯ãå®è¡ã§ããŸãã
CSRF ã®ä¿è·
ã¢ããªã±ãŒã·ã§ã³ã¯ã»ãšãã©äœ¿çšããæºåãã§ããŠãããå®éã«å®è¡ãããšããã°ã¢ãŠããªã³ã¯ãé€ããŠããããŸã§ã«äœæãããã¹ãŠãå®éã«æ©èœããããšãããããŸããããã䜿çšããŠã¿ãŠããã©ãŠã¶ãŒã§ã¬ã¹ãã³ã¹ãåç §ããŠãã ããããã®çç±ãããããŸãã
POST /logout HTTP/1.1
...
Content-Type: application/x-www-form-urlencoded
username=user&password=password
HTTP/1.1 403 Forbidden
Set-Cookie: JSESSIONID=3941352C51ABB941781E1DF312DA474E; Path=/; HttpOnly
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
...
{"timestamp":1420467113764,"status":403,"error":"Forbidden","message":"Expected CSRF token not found. Has your session expired?","path":"/login"} ããã¯ãSpring Security ã«çµã¿èŸŒãŸããŠãã CSRF ä¿è·ããè¶³å
ã§èªåèªèº«ãæã€ããšãé²ãããã«éå§ãããããšãæå³ãããããè¯ãããšã§ããå¿
èŠãªã®ã¯ã"X-CSRF" ãšããããããŒã§éä¿¡ãããããŒã¯ã³ã ãã§ããCSRF ããŒã¯ã³ã®å€ã¯ãããŒã ããŒãžãããŒãããæåã®ãªã¯ãšã¹ãããã® HttpRequest 屿§ã§ãµãŒããŒåŽã§å©çšå¯èœã§ãããã¯ã©ã€ã¢ã³ãã«æž¡ãã«ã¯ããµãŒããŒäžã®åç㪠HTML ããŒãžã䜿çšããŠã¬ã³ããªã³ã°ããããã«ã¹ã¿ã ãšã³ããã€ã³ããä»ããŠå
¬éããããCookie ãšããŠéä¿¡ããŸããAngular ã«ã¯ Cookie ã«åºã¥ãã CSRFïŒ "XSRF" ãšåŒã°ããïŒã®ãµããŒããçµã¿èŸŒãŸã (è±èª) ãŠãããããæåŸã®éžæãæé©ã§ãã
ãã®ããããµãŒããŒã«ã¯ãCookie ãéä¿¡ããã«ã¹ã¿ã ãã£ã«ã¿ãŒãå¿
èŠã§ããAngular 㯠Cookie åã "XSRF-TOKEN" ã«ããããšãæãã§ãããSpring Security ã¯ããã©ã«ãã§ããããªã¯ãšã¹ã屿§ãšããŠæäŸããããããªã¯ãšã¹ã屿§ãã Cookie ã«å€ã転éããã ãã§ãã幞ããSpring SecurityïŒ4.1.0 以éïŒã¯ããããæ£ç¢ºã«è¡ãç¹å¥ãª CsrfTokenRepository ãæäŸããŸãã
@Configuration
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
protected static class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
...
.and().csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
}
}ãããã®å€æŽãè¡ããããšãã¯ã©ã€ã¢ã³ãåŽã§äœãããå¿ èŠããªããªãããã°ã€ã³ãã©ãŒã ãæ©èœããããã«ãªããŸãã
ä»çµã¿ã¯ïŒ
äžéšã®éçºè ããŒã«ã䜿çšãããšããã©ãŠã¶ãŒãšããã¯ãšã³ãéã®çžäºäœçšããã©ãŠã¶ãŒã§ç¢ºèªã§ããŸãïŒéåžžãF12 ã¯ãããéããããã©ã«ãã§ Chrome ã§åäœããFirefox ã§ãã©ã°ã€ã³ãå¿ èŠã«ãªãå ŽåããããŸãïŒãæŠèŠã¯æ¬¡ã®ãšããã§ãã
| åè© | ãã¹ | ã¹ããŒã¿ã¹ | ã¬ã¹ãã³ã¹ |
|---|---|---|---|
GET | / | 200 | index.html |
GET | /*.js | 200 | è§åºŠããã®ã¢ã»ãã |
GET | /user | 401 | äžèš±å¯ (ç¡èŠãããŸãã) |
GET | /home | 200 | ããŒã ããŒãž |
GET | /user | 401 | äžèš±å¯ (ç¡èŠãããŸãã) |
GET | /resource | 401 | äžèš±å¯ (ç¡èŠãããŸãã) |
GET | /user | 200 | è³æ Œæ å ±ãéä¿¡ã㊠JSON ãååŸãã |
GET | /resource | 200 | JSON ã°ãªãŒãã£ã³ã° |
äžèšã§ãç¡èŠããšããŒã¯ãããŠããã¬ã¹ãã³ã¹ã¯ãXHR åŒã³åºãã§ Angular ã«ãã£ãŠåä¿¡ããã HTML ã¬ã¹ãã³ã¹ã§ããããã®ããŒã¿ãåŠçããŠããªããããHTML ã¯ããã¢ã«ãããããããŸãã"/user" ãªãœãŒã¹ã®å ŽåãèªèšŒããããŠãŒã¶ãŒãæ¢ããŸãããæåã®åŒã³åºãã«ã¯ååšããªãããããã®ã¬ã¹ãã³ã¹ã¯ãããããããŸãã
ãªã¯ãšã¹ããããèŠããšããã¹ãŠã®ãªã¯ãšã¹ãã« Cookie ãå«ãŸããŠããããšãããããŸããã¯ãªãŒã³ãªãã©ãŠã¶ãŒïŒããšãã° Chrome ã®ã·ãŒã¯ã¬ããã¢ãŒãïŒã§èµ·åããå Žåãæåã®ãªã¯ãšã¹ãã§ã¯ãµãŒããŒã«éä¿¡ããã Cookie ã¯ãããŸãããããµãŒããŒã¯ "JSESSIONID"ïŒéåžžã® HttpSessionïŒããã³ "X- XSRF-TOKENãïŒäžèšã§èšå®ãã CRSF CookieïŒãåŸç¶ã®ãªã¯ãšã¹ãã«ã¯ãã¹ãŠãããã® Cookie ãããããããã¯éèŠã§ããã¢ããªã±ãŒã·ã§ã³ã¯ãããã® Cookie ãªãã§ã¯æ©èœãããããã€ãã®æ¬åœã«åºæ¬çãªã»ãã¥ãªãã£æ©èœïŒèªèšŒããã³ CSRF ä¿è·ïŒãæäŸããŠããŸããCookie ã®å€ã¯ããŠãŒã¶ãŒãïŒPOST åŸã«ïŒèªèšŒãããšå€æŽãããŸããããã¯ãå¥ã®éèŠãªã»ãã¥ãªãã£æ©èœã§ãïŒã»ãã·ã§ã³åºå®æ»æ [Wikipedia] ã鲿¢ããŸãïŒã
| ã¢ããªã±ãŒã·ã§ã³ããããŒããããããŒãžã«ãªãå Žåã§ããã©ãŠã¶ãŒãèªåçã«éä¿¡ããããããµãŒããŒã«éãè¿ããã Cookie ã«äŸåãã CSRF ä¿è·ã«ã¯äžååã§ãïŒã¯ãã¹ãµã€ãã¹ã¯ãªããã£ã³ã°æ»æãå¥å XSS [Wikipedia] ïŒãããããŒã¯èªåçã«éä¿¡ãããªãããããªãªãžã³ã¯å¶åŸ¡ãããŸãããã®ã¢ããªã±ãŒã·ã§ã³ã§ã¯ãCSRF ããŒã¯ã³ã Cookie ãšããŠã¯ã©ã€ã¢ã³ãã«éä¿¡ãããããããã©ãŠã¶ãŒã«ãã£ãŠèªåçã«è¿éãããŸãããä¿è·ãæäŸããã®ã¯ããããŒã§ãã |
ãã«ããç§ã®ã¢ããªã±ãŒã·ã§ã³ã¯ã©ã®ããã«æ¡åŒµãããŸããïŒ
ãããã... 1 ããŒãžã®ã¢ããªã±ãŒã·ã§ã³ã§ã»ãã·ã§ã³ã¹ããŒãã䜿çšããã®ã¯éåžžã«æªãããšã§ã¯ãããŸããã ? ããã®è³ªåã«å¯Ÿããçãã¯ããã»ãšãã©ã䜿çšããŸãããªããªããèªèšŒãš CSRF ä¿è·ã®ããã«ã»ãã·ã§ã³ã䜿çšããããšã¯ãééããªãè¯ãããšã§ãããã®ç¶æ ã¯ã©ããã«ä¿åããå¿ èŠããããã»ãã·ã§ã³ããåãåºãå Žåã¯ãå¥ã®å Žæã«çœ®ãããµãŒããŒãšã¯ã©ã€ã¢ã³ãã®äž¡æ¹ã§æåã§ç®¡çããå¿ èŠããããŸããããã¯åã«ã³ãŒããå¢ããã ãã§ãããã¡ã³ããã³ã¹ãå¢ããã ãã§ãããå šäœãšããŠå®å šã«è»èŒªãåçºæããããšã«ãªããŸãã
ãããããããã ⊠ã¢ããªã±ãŒã·ã§ã³ãä»ã©ã®ããã«æ°Žå¹³ã«ã¹ã±ãŒãªã³ã°ããã®ã§ããïŒãããã¯äžèšã§å°ããŠãããæ¬åœãã®è³ªåã§ããããã»ãã·ã§ã³ç¶æ ãæªããã¹ããŒãã¬ã¹ã§ãªããã°ãªããŸãããã«ççž®ãããåŸåããããŸãããããã¯ã«ãªããªãã§ãã ãããããã§éèŠãªã®ã¯ãã»ãã¥ãªãã£ãã¹ããŒããã«ã§ããããšã§ããå®å šã§ã¹ããŒãã¬ã¹ãªã¢ããªã±ãŒã·ã§ã³ãæã€ããšã¯ã§ããŸãããç¶æ ãã©ãã«ä¿åããŸããïŒ ããã§ãã¹ãŠã§ããRob Winch (è±èª) 㯠Spring Exchange 2014 (è±èª) ã§éåžžã«æçã§ã€ã³ãµã€ãã«å¯ãã è¬æŒãè¡ããç¶æ ã®å¿ èŠæ§ïŒããã³ãã®æ®éæ§: TCP ãš SSL ã¯ã¹ããŒããã«ã§ãããããã·ã¹ãã ã¯ãããç¥ã£ãŠãããã©ããã«é¢ä¿ãªãã¹ããŒããã«ã§ãïŒããã®ãããã¯ãããã«è©³ãã調ã¹ãŸãã
è¯ããã¥ãŒã¹ã¯ãéžæè¢ãããããšã§ããæãç°¡åãªéžæã¯ãã»ãã·ã§ã³ããŒã¿ãã¡ã¢ãªã«ä¿åããããŒããã©ã³ãµãŒã®ã¹ãã£ãããŒã»ãã·ã§ã³ã«äŸåããŠãåãã»ãã·ã§ã³ããã®ãªã¯ãšã¹ããåã JVM ã«ã«ãŒãã£ã³ã°ããããšã§ãïŒãã¹ãŠãµããŒãããŠããŸãïŒãããã§ååã«çè§£ã§ããéåžžã«å€ãã®ãŠãŒã¹ã±ãŒã¹ã§æ©èœããŸãããã 1 ã€ã®éžæè¢ã¯ãã¢ããªã±ãŒã·ã§ã³ã®ã€ã³ã¹ã¿ã³ã¹éã§ã»ãã·ã§ã³ããŒã¿ãå ±æããããšã§ãã峿 Œã§ã»ãã¥ãªãã£ããŒã¿ã®ã¿ãä¿åããŠããéããããŒã¿ã¯å°ããã倿Žã¯ãã£ãã«è¡ãããŸããïŒãŠãŒã¶ãŒããã°ã€ã³ãŸãã¯ãã°ã¢ãŠããããšãããŸãã¯ã»ãã·ã§ã³ãã¿ã€ã ã¢ãŠããããšãã®ã¿ïŒãã€ã³ãã©ã¹ãã©ã¯ãã£ã«å€§ããªåé¡ã¯ãããŸãããSpring Session [GitHub] (è±èª) ã䜿çšããã®ãç°¡åã§ãããã®ã·ãªãŒãºã®æ¬¡ã®ã»ã¯ã·ã§ã³ã§ Spring Session ã䜿çšãããããããã§èšå®ããæ¹æ³ã«ã€ããŠè©³ãã説æããå¿ èŠã¯ãããŸããããæåéãæ°è¡ã®ã³ãŒããš Redis ãµãŒããŒã§ãããéåžžã«é«éã§ãã
| å ±æã»ãã·ã§ã³ç¶æ ãã»ããã¢ãããããã 1 ã€ã®ç°¡åãªæ¹æ³ã¯ãã¢ããªã±ãŒã·ã§ã³ã WAR ãã¡ã€ã«ãšã㊠Cloud Foundry Pivotal Web ãµãŒãã¹ (è±èª) ã«ãããã€ããããã Redis ãµãŒãã¹ã«ãã€ã³ãããããšã§ãã |
ããããã«ã¹ã¿ã ããŒã¯ã³ã®å®è£ ïŒã¹ããŒãã¬ã¹ãå€èгïŒã¯ã©ãã§ããïŒ
ãããæåŸã®ã»ã¯ã·ã§ã³ãžã®ã¬ã¹ãã³ã¹ã§ãã£ãå ŽåããããããäžåºŠåç
§ããŠãã ãããããŒã¯ã³ãã©ããã«ä¿åããå Žåã¯ããããã¹ããŒãã¬ã¹ã§ã¯ãããŸããããä¿åããŠããªãå ŽåïŒããšãã°ãJWT ãšã³ã³ãŒãããŒã¯ã³ã䜿çšããŠããå ŽåïŒãCSRF ä¿è·ãã©ã®ããã«æäŸããŸããïŒ ããã¯éèŠã§ããçµéšåïŒRob Winch ã«èµ·å ïŒã¯æ¬¡ã®ãšããã§ããã¢ããªã±ãŒã·ã§ã³ãŸã㯠API ããã©ãŠã¶ãŒããã¢ã¯ã»ã¹ãããå ŽåãCSRF ä¿è·ãå¿
èŠã§ããã»ãã·ã§ã³ãªãã§ãããã§ããªããšããããšã§ã¯ãªããèªåã§ãã¹ãŠã®ã³ãŒããæžããªããã°ãªããªããšããããšã§ãããããŠããã§ã«å®è£
ãããŠãããHttpSession ã®äžã§å®å
šã«ããŸãæ©èœãããããäœããã€ã³ãã«ãªãã§ããã䜿çšããæåãã仿§ã«çŒãä»ããŠããã³ã³ãããŒã®ïŒ ïŒ CSRF ãå¿
èŠãšãããå®å
šã«ãã¹ããŒãã¬ã¹ãïŒéã»ãã·ã§ã³ããŒã¹ïŒã®ããŒã¯ã³å®è£
ãæã£ãŠãããšå€æãããšããŠãããããæ¶è²»ããŠäœ¿çšããããã«ã¯ã©ã€ã¢ã³ãã«è¿œå ã®ã³ãŒããèšè¿°ããå¿
èŠããããŸããããã©ãŠã¶ãŒãšãµãŒããŒã®ç¬èªã®çµã¿èŸŒã¿æ©èœ: ãã©ãŠã¶ãŒã¯åžžã« Cookie ãéä¿¡ãããµãŒããŒã¯åžžã«ã»ãã·ã§ã³ãä¿æããŸãïŒã¹ã€ããããªãã«ããªãéãïŒããã®ã³ãŒãã¯ããžãã¹ããžãã¯ã§ã¯ãªããåçãäžãããã®ã§ã¯ãªããåãªããªãŒããŒãããã§ãããããããã«æªãããšã«ãéãããããŸãã
çµè«
çŸåšã®ã¢ããªã±ãŒã·ã§ã³ã¯ããŠãŒã¶ãŒãã©ã€ãç°å¢ã®ãå®éã®ãã¢ããªã±ãŒã·ã§ã³ã«æåŸ
ãããã®ã«è¿ããã®ã§ãããããããããã®ã¢ãŒããã¯ãã£ãåããããæ©èœè±å¯ãªã¢ããªã±ãŒã·ã§ã³ïŒéçãªåäžãµãŒããŒã³ã³ãã³ããš JSON ãªãœãŒã¹ïŒãHttpSession ã䜿çšããŠã»ãã¥ãªãã£ããŒã¿ãä¿åããã¯ã©ã€ã¢ã³ããéä¿¡ãã Cookie ãèæ
®ããŠäœ¿çšããããšãé Œãã«ããŠããŸãããç¬èªã®ããžãã¹ãã¡ã€ã³ã«éäžã§ãããããããã«æºè¶³ããŠããŸããæ¬¡ã®ã»ã¯ã·ã§ã³ã§ã¯ãã¢ãŒããã¯ãã£ãåå¥ã®èªèšŒããã³ UI ãµãŒããŒã«å ããŠãJSON çšã®ã¹ã¿ã³ãã¢ãã³ãªãœãŒã¹ãµãŒããŒã«æ¡åŒµããŸããããã¯æããã«ãè€æ°ã®ãªãœãŒã¹ãµãŒããŒã«ç°¡åã«äžè¬åã§ããŸãããŸããSpring Session ãã¹ã¿ãã¯ã«å°å
¥ããããã䜿çšããŠèªèšŒããŒã¿ãå
±æããæ¹æ³ã瀺ããŸãã
ãªãœãŒã¹ãµãŒããŒ
ãã®ã»ã¯ã·ã§ã³ã§ã¯ããã·ã³ã°ã«ããŒãžã¢ããªã±ãŒã·ã§ã³ãã§ Spring Security ã Angular (è±èª) ãšäœ¿çšããæ¹æ³ã«ã€ããŠåŒãç¶ã説æããŸããããã§ã¯ãã¢ããªã±ãŒã·ã§ã³ã®åçã³ã³ãã³ããšããŠäœ¿çšããŠããããããã€ããªãœãŒã¹ãããŸãä¿è·ãããŠããªããªãœãŒã¹ãšããŠå¥ã®ãµãŒããŒã«åå²ããæ¬¡ã« Opaque ããŒã¯ã³ã§ä¿è·ããŸããããã¯äžé£ã®ã»ã¯ã·ã§ã³ã® 3 çªç®ã§ãããã¢ããªã±ãŒã·ã§ã³ã®åºæ¬çãªæ§æèŠçŽ ã«è¿œãã€ãããæåã®ã»ã¯ã·ã§ã³ãèªãã§ãŒããããã«ãããããGithub ã®ãœãŒã¹ã³ãŒãã«çŽæ¥é²ãããšãã§ããŸãã2 ã€ã®éšå: ãªãœãŒã¹ãä¿è·ãããŠããªã [GitHub] (è±èª) éšåãšãããŒã¯ã³ã«ãã£ãŠä¿è·ãããŠããéšå [GitHub] (è±èª) ã
| ãã®ã»ã¯ã·ã§ã³ã§ãµã³ãã«ã¢ããªã±ãŒã·ã§ã³ã䜿çšããŠããå Žåã¯ããã©ãŠã¶ãŒã®ãã£ãã·ã¥ã® Cookie ãš HTTP åºæ¬èªèšŒæ å ±ãå¿ ãã¯ãªã¢ããŠãã ãããChrome ã§ã¯ãåäžãµãŒããŒã§ãããè¡ãæè¯ã®æ¹æ³ã¯ãæ°ããã·ãŒã¯ã¬ãããŠã£ã³ããŠãéãããšã§ãã |
å¥ã®ãªãœãŒã¹ãµãŒããŒ
ã¯ã©ã€ã¢ã³ãåŽã®å€æŽ
ã¯ã©ã€ã¢ã³ãåŽã§ã¯ããªãœãŒã¹ãå¥ã®ããã¯ãšã³ãã«ç§»åããããã«è¡ãããšã¯ããŸããããŸãããæåŸã®ã»ã¯ã·ã§ã³ [GitHub] (è±èª) ã®ãããŒã ãã³ã³ããŒãã³ãã¯æ¬¡ã®ãšããã§ãã
@Component({
templateUrl: './home.component.html'
})
export class HomeComponent {
title = 'Demo';
greeting = {};
constructor(private app: AppService, private http: HttpClient) {
http.get('resource').subscribe(data => this.greeting = data);
}
authenticated() { return this.app.authenticated; }
}ããã«å¿ èŠãªã®ã¯ãURL ã倿Žããããšã ãã§ããäŸ: localhost ã§æ°ãããªãœãŒã¹ãå®è¡ããå Žåãæ¬¡ã®ããã«ãªããŸãã
http.get('http://localhost:9000').subscribe(data => this.greeting = data);ãµãŒããŒåŽã®å€æŽ
UI ãµãŒã㌠[GitHub] (è±èª) ã®å€æŽã¯ç°¡åã§ããã°ãªãŒãã£ã³ã°ãªãœãŒã¹ã® @RequestMapping ãåé€ããå¿
èŠããããŸãïŒ"/resource" ã§ããïŒã次ã«ãæ°ãããªãœãŒã¹ãµãŒããŒãäœæããå¿
èŠããããŸããããã¯ãSpring Boot Initializr ã䜿çšããŠæåã®ã»ã¯ã·ã§ã³ã§è¡ã£ãããã«å®è¡ã§ããŸããããšãã°ãUN*X ã©ã€ã¯ãªã·ã¹ãã ã§ curl ã䜿çšãã:
$ mkdir resource && cd resource
$ curl https://start.spring.io/starter.tgz -d dependencies=web -d name=resource | tar -xzvf -ãã®åŸããã®ãããžã§ã¯ãïŒããã©ã«ãã§ã¯éåžžã® Maven Java ãããžã§ã¯ãïŒããæ°ã«å ¥ãã® IDE ã«ã€ã³ããŒãããããã³ãã³ãã©ã€ã³ã§ãã¡ã€ã«ãš "mvn" ãæäœããã ãã§ãã
ã¡ã€ã³ã¢ããªã±ãŒã·ã§ã³ã¯ã©ã¹ [GitHub] (è±èª) ã« @RequestMapping ã远å ããå€ã UI [GitHub] (è±èª) ããå®è£
ãã³ããŒããã ãã§ã:
@SpringBootApplication
@RestController
class ResourceApplication {
@RequestMapping("/")
public Message home() {
return new Message("Hello World");
}
public static void main(String[] args) {
SpringApplication.run(ResourceApplication.class, args);
}
}
class Message {
private String id = UUID.randomUUID().toString();
private String content;
public Message(String content) {
this.content = content;
}
// ... getters and setters and default constructor
}ãããå®äºãããšãã¢ããªã±ãŒã·ã§ã³ã¯ãã©ãŠã¶ãŒã«ããŒãå¯èœã«ãªããŸããã³ãã³ãã©ã€ã³ã§ãããè¡ãããšãã§ããŸã
$ mvn spring-boot:run -Dserver.port=9000http://localhost:9000 ã®ãã©ãŠã¶ãŒã«ç§»åãããšãæšæ¶ä»ãã® JSON ã衚瀺ãããŸããapplication.properties ïŒ "src/main/resources" å
ïŒã§ããŒãã®å€æŽããã€ã¯ã§ããŸãã
server.port: 9000ãã©ãŠã¶ãŒã® UIïŒããŒã 8080ïŒãããã®ãªãœãŒã¹ãããŒãããããšãããšããã©ãŠã¶ãŒã XHR ãªã¯ãšã¹ããèš±å¯ããªããããæ©èœããªãããšãããããŸãã
CORS ããŽã·ãšãŒã·ã§ã³
ãã©ãŠã¶ãŒã¯ãã¯ãã¹ãªãªãžã³ãªãœãŒã¹å ±æ [Mozilla] ãããã³ã«ã«åŸã£ãŠãªãœãŒã¹ãµãŒããŒãžã®ã¢ã¯ã»ã¹ãèš±å¯ãããŠãããã©ããã確èªããããã«ããªãœãŒã¹ãµãŒããŒãšããŽã·ãšãŒãããããšããŸããAngular ã®è²¬ä»»ã§ã¯ãªããããCookie å¥çŽãšåæ§ã«ããã©ãŠã¶ãŒå ã®ãã¹ãŠã® JavaScript ã§ãã®ããã«æ©èœããŸãã2 ã€ã®ãµãŒããŒã¯å ±éã®çºä¿¡å ã宣èšããŠããªãããããã©ãŠã¶ãŒã¯ãªã¯ãšã¹ãã®éä¿¡ãæåŠããUI ã¯å£ããŠããŸãã
ãããä¿®æ£ããã«ã¯ããããªãã©ã€ããOPTIONS ãªã¯ãšã¹ããšãåŒã³åºãå ã®èš±å¯ãããåäœããªã¹ãããããã€ãã®ããããŒãå«ã CORS ãããã³ã«ããµããŒãããå¿ èŠããããŸããSpring 4.2 ã«ã¯ãããã®çްãã CORS ãµããŒã (è±èª) ãããã€ããããŸãããã®ãããã³ã³ãããŒã©ãŒãããã³ã°ã«ã¢ãããŒã·ã§ã³ã远å ããã ãã§ããäŸ:
@RequestMapping("/")
@CrossOrigin(origins="*", maxAge=3600)
public Message home() {
return new Message("Hello World");
}origins=* ãç°¡åã«äœ¿çšããã®ã¯ç°¡åã§æ±ããŠãããåäœããŸãããå®å
šã§ã¯ãªããæ±ºããŠæšå¥šãããŸããã |
ãªãœãŒã¹ãµãŒããŒã®ã»ãã¥ãªãã£ä¿è·
ãããïŒ æ°ããã¢ãŒããã¯ãã£ãŒã§åäœããã¢ããªã±ãŒã·ã§ã³ããããŸããå¯äžã®åé¡ã¯ããªãœãŒã¹ãµãŒããŒã«ã»ãã¥ãªãã£ããªãããšã§ãã
Spring Security ã®è¿œå
UI ãµãŒããŒã®ããã«ããã£ã«ã¿ãŒã¬ã€ã€ãŒãšããŠãªãœãŒã¹ãµãŒããŒã«ã»ãã¥ãªãã£ã远å ããæ¹æ³ã確èªã§ããŸããæåã®ã¹ãããã¯æ¬åœã«ç°¡åã§ã: Spring Security ã Maven POM ã®ã¯ã©ã¹ãã¹ã«è¿œå ããã ãã§ã:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
...
</dependencies>ãªãœãŒã¹ãµãŒããŒãåèµ·åããŸããå®å šã§ã:
$ curl -v localhost:9000
< HTTP/1.1 302 Found
< Location: http://localhost:9000/login
...curl 㯠Angular ã¯ã©ã€ã¢ã³ããšåãããããŒãéä¿¡ããŠããªããããïŒãã¯ã€ãã©ãã«ïŒãã°ã€ã³ããŒãžãžã®ãªãã€ã¬ã¯ããååŸããŠããŸããããé¡äŒŒããããããŒãéä¿¡ããããã«ã³ãã³ãã倿ŽããŸãã
$ curl -v -H "Accept: application/json" \
-H "X-Requested-With: XMLHttpRequest" localhost:9000
< HTTP/1.1 401 Unauthorized
...ã¯ã©ã€ã¢ã³ãã«ãã¹ãŠã®ãªã¯ãšã¹ãã§è³æ Œæ å ±ãéä¿¡ããããã«æããã ãã§ãã
ããŒã¯ã³èªèšŒ
ã€ã³ã¿ãŒãããããã³äººã
ã® Spring ããã¯ãšã³ããããžã§ã¯ãã«ã¯ãã«ã¹ã¿ã ããŒã¯ã³ããŒã¹ã®èªèšŒãœãªã¥ãŒã·ã§ã³ãæ£ãã°ã£ãŠããŸããSpring Security ã¯ãããªãèªèº«ã§å§ããããã®æäœéã® Filter å®è£
ãæäŸããŸãïŒããšãã°ãAbstractPreAuthenticatedProcessingFilter [GitHub] (è±èª) ããã³ TokenService [GitHub] (è±èª) ãåç
§ïŒããã ããSpring Security ã«ã¯æ£èŠã®å®è£
ã¯ãããŸããããã®çç±ã® 1 ã€ã¯ãããããããã«ç°¡åãªæ¹æ³ãããããã§ãã
ãã®ã·ãªãŒãºã®ããŒã II ãããSpring Security ã¯ããã©ã«ãã§ HttpSession ã䜿çšããŠèªèšŒããŒã¿ãä¿ç®¡ããããšãæãåºããŠãã ããããã ããã»ãã·ã§ã³ãšçŽæ¥å¯Ÿè©±ããããšã¯ãããŸããããã®éã«ã¯ã¹ãã¬ãŒãžããã¯ãšã³ãã倿Žããããã«äœ¿çšã§ããæœè±¡åã¬ã€ã€ãŒïŒSecurityContextRepository [GitHub] (è±èª) ïŒããããŸãããªãœãŒã¹ãµãŒããŒã§ãUI ã«ãã£ãŠèªèšŒãæ€èšŒãããã¹ãã¢ããã®ãªããžããªã«ãã€ã³ãã§ããå Žåã2 ã€ã®ãµãŒããŒéã§èªèšŒãå
±æããæ¹æ³ããããŸããUI ãµãŒããŒã«ã¯ãã§ã«ãã®ãããªã¹ãã¢ïŒHttpSessionïŒããããŸãããã®ããããã®ã¹ãã¢ãé
åžããŠãªãœãŒã¹ãµãŒããŒã«éãããšãã§ããå Žåãã»ãšãã©ã®ãœãªã¥ãŒã·ã§ã³ããããŸãã
Spring Session
ãœãªã¥ãŒã·ã§ã³ã®ãã®éšå㯠Spring Session [GitHub] (è±èª) ã§éåžžã«ç°¡åã§ããå¿
èŠãªã®ã¯ãå
±æããŒã¿ã¹ãã¢ïŒRedis ãš JDBC ã¯ããã«ãµããŒããããŠããŸãïŒãããã³ Filter ãã»ããã¢ããããããã®ãµãŒããŒã®æ§æè¡ã§ãã
UI ã¢ããªã±ãŒã·ã§ã³ã§ã¯ãPOM [GitHub] (è±èª) ã«ããã€ãã®äŸåé¢ä¿ã远å ããå¿ èŠããããŸãã
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>Spring Boot ãš Spring Session ã¯é£æºã㊠Redis ã«æ¥ç¶ããã»ãã·ã§ã³ããŒã¿ãäžå çã«ä¿åããŸãã
ãã® 1 è¡ã®ã³ãŒããš localhost ã§å®è¡ãããŠãã Redis ãµãŒããŒã䜿çšããŠãUI ã¢ããªã±ãŒã·ã§ã³ãå®è¡ããæå¹ãªãŠãŒã¶ãŒè³æ Œæ å ±ã§ãã°ã€ã³ãããšãã»ãã·ã§ã³ããŒã¿ïŒèªèšŒïŒã redis ã«ä¿åãããŸãã
ããŒã«ã«ã§å®è¡ããŠãã redis ãµãŒããŒããªãå Žåã¯ãDocker (è±èª) ã§ç°¡åã«èµ·åã§ããŸãïŒWindows ãŸã㯠MacOS ã§ã¯ VM ãå¿
èŠã§ãïŒãGithub ã®ãœãŒã¹ã³ãŒã [GitHub] (è±èª) ã«ã¯ docker-compose.yml (è±èª) ãã¡ã€ã«ããããdocker-compose up ã䜿çšããŠã³ãã³ãã©ã€ã³ã§ç°¡åã«å®è¡ã§ããŸããVM ã§ãããè¡ããšãRedis ãµãŒããŒã¯ localhost ãšã¯ç°ãªããã¹ãã§å®è¡ããããããlocalhost ã«ãã³ããªã³ã°ããããapplication.properties ã®æ£ãã spring.redis.host ãæãããã«ã¢ããªãæ§æããå¿
èŠããããŸãã |
UI ããã«ã¹ã¿ã ããŒã¯ã³ãéä¿¡ãã
äžè¶³ããŠããå¯äžã®éšåã¯ãã¹ãã¢å
ã®ããŒã¿ãžã®ããŒã®è»¢éã¡ã«ããºã ã§ããããŒã¯ HttpSession ID ã§ãããããUI ã¯ã©ã€ã¢ã³ãã§ãã®ããŒãååŸã§ããå ŽåããªãœãŒã¹ãµãŒããŒã«ã«ã¹ã¿ã ããããŒãšããŠéä¿¡ã§ããŸãããã®ããããããŒã ãã³ã³ãããŒã©ãŒã¯ãã°ãªãŒãã£ã³ã°ãªãœãŒã¹ã® HTTP ãªã¯ãšã¹ãã®äžéšãšããŠããããŒãéä¿¡ããããã«å€æŽããå¿
èŠããããŸããäŸïŒ
constructor(private app: AppService, private http: HttpClient) {
http.get('token').subscribe(data => {
const token = data['token'];
http.get('http://localhost:9000', {headers : new HttpHeaders().set('X-Auth-Token', token)})
.subscribe(response => this.greeting = response);
}, () => {});
}ïŒãããšã¬ã¬ã³ããªãœãªã¥ãŒã·ã§ã³ã¯ãå¿
èŠã«å¿ããŠããŒã¯ã³ãååŸããRequestOptionsService ã䜿çšããŠããªãœãŒã¹ãµãŒããŒãžã®ãã¹ãŠã®ãªã¯ãšã¹ãã«ããããŒã远å ããããšã§ãïŒ
"http://localhost:9000" ã«çŽæ¥ç§»åãã代ããã«ã"/token" ã® UI ãµãŒããŒäžã®æ°ããã«ã¹ã¿ã ãšã³ããã€ã³ããžã®åŒã³åºãã®æåã³ãŒã«ããã¯ã§ãã®åŒã³åºããã©ããããŸããããã®å®è£ ã¯ç°¡åã§ãã
@SpringBootApplication
@RestController
public class UiApplication {
public static void main(String[] args) {
SpringApplication.run(UiApplication.class, args);
}
...
@RequestMapping("/token")
public Map<String,String> token(HttpSession session) {
return Collections.singletonMap("token", session.getId());
}
}ããã§ãUI ã¢ããªã±ãŒã·ã§ã³ã®æºåãæŽããããã¯ãšã³ããžã®ãã¹ãŠã®åŒã³åºãã«å¯Ÿã㊠"X-Auth-Token" ãšããããããŒã«ã»ãã·ã§ã³ ID ãå«ãŸããŸãã
ãªãœãŒã¹ãµãŒããŒã§ã®èªèšŒ
ãªãœãŒã¹ãµãŒããŒãã«ã¹ã¿ã ããããŒãåãå ¥ããããšãã§ããããã«ããªãœãŒã¹ãµãŒããŒã«å°ããªå€æŽã 1 ã€ãããŸããCORS æ§æã§ã¯ããã®ããããŒããªã¢ãŒãã¯ã©ã€ã¢ã³ãããã®èš±å¯ãããããããŒãšããŠæå®ããå¿ èŠããããŸãã
@RequestMapping("/")
@CrossOrigin(origins = "*", maxAge = 3600,
allowedHeaders={"x-auth-token", "x-requested-with", "x-xsrf-token"})
public Message home() {
return new Message("Hello World");
}ãã©ãŠã¶ãŒããã®ããªãã©ã€ããã§ãã¯ã¯ Spring MVC ã«ãã£ãŠåŠçãããããã«ãªããŸããããSpring Security ã«ééãèš±å¯ããããšãäŒããå¿ èŠããããŸãã
public class ResourceApplication extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().authorizeRequests()
.anyRequest().authenticated();
}
... ãã¹ãŠã®ãªãœãŒã¹ã« permitAll() ã¢ã¯ã»ã¹ããå¿
èŠã¯ãããŸããããŸãããªã¯ãšã¹ããããªãã©ã€ãã§ããããšãèªèããŠããªããããæ©å¯ããŒã¿ã誀ã£ãŠéä¿¡ãããã³ãã©ãŒãååšããå ŽåããããŸããcors() æ§æãŠãŒãã£ãªãã£ã¯ããã£ã«ã¿ãŒå±€ã§ãã¹ãŠã®ããªãã©ã€ããªã¯ãšã¹ããåŠçããããšã«ãããããã軜æžããŸãã |
ããšã¯ããªãœãŒã¹ãµãŒããŒã§ã«ã¹ã¿ã ããŒã¯ã³ãååŸããããã䜿çšããŠãŠãŒã¶ãŒãèªèšŒããã ãã§ããå¿
èŠãªã®ã¯ãSpring Security ã«ã»ãã·ã§ã³ãªããžããªã®å Žæãšãåä¿¡ãªã¯ãšã¹ãã§ããŒã¯ã³ïŒã»ãã·ã§ã³ IDïŒãæ¢ãå ŽæãäŒããã ããªã®ã§ãããã¯éåžžã«ç°¡åã§ããæåã« Spring Session ãš Redis ã®äŸåé¢ä¿ã远å ããå¿
èŠããããæ¬¡ã« Filter ãã»ããã¢ããã§ããŸãã
@SpringBootApplication
@RestController
class ResourceApplication {
...
@Bean
HeaderHttpSessionStrategy sessionStrategy() {
return new HeaderHttpSessionStrategy();
}
} äœæããããã® Filter ã¯ãUI ãµãŒããŒã«ãããã®ã®ãã©ãŒã€ã¡ãŒãžã§ãããããRedis ãã»ãã·ã§ã³ã¹ãã¢ãšããŠç¢ºç«ããŸããå¯äžã®éãã¯ãããã©ã«ãïŒ "JSESSIONID" ãšããååã® CookieïŒã§ã¯ãªããããããŒïŒããã©ã«ãã§ã¯ "X-Auth-Token" ïŒã調ã¹ãã«ã¹ã¿ã HttpSessionStrategy ã䜿çšããããšã§ãããŸãããã©ãŠã¶ãŒãèªèšŒãããŠããªãã¯ã©ã€ã¢ã³ãã§ãã€ã¢ãã°ããããã¢ããããªãããã«ããå¿
èŠããããŸããã¢ããªã¯å®å
šã§ãããããã©ã«ãã§ WWW-Authenticate: Basic ã§ 401 ãéä¿¡ããããããã©ãŠã¶ãŒã¯ãŠãŒã¶ãŒåãšãã¹ã¯ãŒãã®ãã€ã¢ãã°ã§å¿çããŸãããããå®çŸããæ¹æ³ã¯è€æ°ãããŸããããã§ã« Angular ã "X-Requested-With" ããããŒãéä¿¡ããããã«ããŠãããããSpring Security ã¯ããã©ã«ãã§ãããåŠçããŸãã
ãªãœãŒã¹ãµãŒããŒã«ã¯ãæ°ããèªèšŒã¹ããŒã ã§åäœããããã«ãæåŸã« 1 ã€ã®å€æŽããããŸããSpring Boot ã®ããã©ã«ãã»ãã¥ãªãã£ã¯ã¹ããŒãã¬ã¹ã§ãããã»ãã·ã§ã³ã«èªèšŒãä¿åããããã«ãapplication.yml ïŒãŸã㯠application.propertiesïŒã§æç€ºçã«ããå¿
èŠããããŸãã
security:
sessions: NEVERããã¯ãSpring Security ã«å¯ŸããŠãã»ãã·ã§ã³ãäœæããããšã¯ãããŸããããã»ãã·ã§ã³ãååšããå Žåã¯ã»ãã·ã§ã³ã䜿çšããããšèšããŸãïŒUI ã§ã®èªèšŒã®ããã«ãã§ã«ååšããŸãïŒã
ãªãœãŒã¹ãµãŒããŒãåèµ·åããæ°ãããã©ãŠã¶ãŒãŠã£ã³ããŠã§ UI ãéããŸãã
ãªããã¹ãŠã Cookie ã§æ©èœããªãã®ã§ããïŒ
ã«ã¹ã¿ã ããããŒã䜿çšããã¯ã©ã€ã¢ã³ãã«ããããŒãå ¥åããã³ãŒããèšè¿°ããå¿ èŠããããŸãããããã¯ããã»ã©è€éã§ã¯ãããŸããããå¯èœãªéã Cookie ãšã»ãã·ã§ã³ã䜿çšãããšããããŒã II ã®ã¢ããã€ã¹ãšççŸããããã§ããããããªããšãäžå¿ èŠãªè€éãã远å ããããšããè°è«ããããŸãããã確ãã«ãçŸåšã®å®è£ ã¯ãããŸã§èŠãŠããäžã§æãè€éã§ã: ãœãªã¥ãŒã·ã§ã³ã®æè¡çãªéšåã¯ãããžãã¹ããžãã¯ïŒæããã«å°ããïŒãã¯ããã«äžåã£ãŠããŸããããã¯ééããªãå ¬æ£ãªæ¹å€ã§ãïŒãããŠããã®ã·ãªãŒãºã®æ¬¡ã®ã»ã¯ã·ã§ã³ã§èª¬æããäºå®ã§ãïŒãããã¹ãŠã®ç®çã§ Cookie ãšã»ãã·ã§ã³ã䜿çšããã»ã©åçŽã§ã¯ãªãçç±ãç°¡åã«èŠãŠã¿ãŸãããã
å°ãªããšããŸã ã»ãã·ã§ã³ã䜿çšããŠããŸãããããã¯çã«ããªã£ãŠããŸãããªããªã Spring Security ãš Servlet ã³ã³ãããŒã¯ç§ãã¡ã®åªåãªãã«ãããè¡ãæ¹æ³ãç¥ã£ãŠããããã§ããããããCookie ã䜿çšããŠèªèšŒããŒã¯ã³ã転éãç¶ããããšãã§ããªãã£ãã§ããããïŒ ããã¯çŽ æŽãããã£ãã§ããããããããæ©èœããªãçç±ãããããã©ãŠã¶ãŒãç§ãã¡ãèªå¯ããªããšããããšã§ããJavaScript ã¯ã©ã€ã¢ã³ããããã©ãŠã¶ãŒã® Cookie ã¹ãã¢ãããã£ãŠã¿ãããšãã§ããŸãããããã€ãã®å¶éããããååãªçç±ããããŸããç¹ã«ããµãŒããŒãã "HttpOnly" ãšããŠéä¿¡ããã Cookie ã«ã¯ã¢ã¯ã»ã¹ã§ããŸããïŒã»ãã·ã§ã³ Cookie ã®å Žåãããã©ã«ãã§è¡šç€ºãããŸãïŒããŸããéä¿¡ãªã¯ãšã¹ãã« Cookie ãèšå®ã§ããªãããã"SESSION" CookieïŒSpring Session ã®ããã©ã«ã Cookie åïŒãèšå®ã§ããªãã£ããããã«ã¹ã¿ã ã® "X-Session" ããããŒã䜿çšããå¿ èŠããããŸããããããã®å¶éã¯äž¡æ¹ãšããŠãŒã¶ãŒèªèº«ã®ä¿è·ã®ããã®ãã®ã§ãããããæªæã®ããã¹ã¯ãªããã¯é©åãªèªå¯ãªãã«ãªãœãŒã¹ã«ã¢ã¯ã»ã¹ã§ããŸããã
TL; DR UI ãšãªãœãŒã¹ãµãŒããŒã¯å ±éã®èµ·æºãæããªããããCookie ãå ±æã§ããŸããïŒSpring Session ã䜿çšããŠåŒ·å¶çã«ã»ãã·ã§ã³ãå ±æã§ããŸãïŒã
çµè«
ãã®ã·ãªãŒãºã®ããŒã II ã®ã¢ããªã±ãŒã·ã§ã³ã®æ©èœãè€è£œããŸããããªã¢ãŒãããã¯ãšã³ãããååŸããæšæ¶ãå«ãããŒã ããŒãžã§ãããã²ãŒã·ã§ã³ããŒã«ãã°ã€ã³ãšãã°ã¢ãŠãã®ãªã³ã¯ããããŸããéãã¯ãã°ãªãŒãã£ã³ã°ã UI ãµãŒããŒã«çµã¿èŸŒãŸããŠããããã¯ãªããã¹ã¿ã³ãã¢ãã³ã®ãªãœãŒã¹ãµãŒããŒããéä¿¡ãããããšã§ããããã«ãããå®è£ ãå€§å¹ ã«è€éã«ãªããŸãããã幞ããªããšã«ãã»ãšãã©æ§æããŒã¹ã® (å®éã«ã¯ 100% 宣èšå) ãœãªã¥ãŒã·ã§ã³ã䜿çšã§ããŸããæ°ããã³ãŒãããã¹ãŠã©ã€ãã©ãª (Spring æ§æãš Angular ã«ã¹ã¿ã ãã£ã¬ã¯ãã£ã) ã«æœåºããããšã§ããœãªã¥ãŒã·ã§ã³ 100% ã宣èšåã«ããããšãã§ããŸãããã®è峿·±ãã¿ã¹ã¯ã¯ã次㮠2 åã®èšäºä»¥éã«å»¶æããŸããæ¬¡ã®ã»ã¯ã·ã§ã³ã§ã¯ãçŸåšã®å®è£ ã®è€éãããã¹ãŠè»œæžããããã®ãå¥ã®æ¬åœã«åªããæ¹æ³ãšããŠãAPI ã²ãŒããŠã§ã€ãã¿ãŒã³ (ã¯ã©ã€ã¢ã³ãã¯ãã¹ãŠã®ãªã¯ãšã¹ãã 1 ã€ã®å Žæã«éä¿¡ãããã®å Žæã§èªèšŒåŠç) ãèŠãŠãããŸãã
| ããã§ã¯ãSpring Session ã䜿çšããŠãè«ççã«åãã¢ããªã±ãŒã·ã§ã³ã§ã¯ãªã 2 ã€ã®ãµãŒããŒéã§ã»ãã·ã§ã³ãå ±æããŸãããããã¯å·§åŠãªããªãã¯ã§ããããéåžžã®ãJEE 忣ã»ãã·ã§ã³ã§ã¯äžå¯èœã§ãã |
API Gateway
ãã®ã»ã¯ã·ã§ã³ã§ã¯ããã·ã³ã°ã«ããŒãžã¢ããªã±ãŒã·ã§ã³ãã§ Spring Security ã Angular (è±èª) ãšäœ¿çšããæ¹æ³ã«ã€ããŠåŒãç¶ã説æããŸããããã§ã¯ãSpring Cloud ã䜿çšããŠèªèšŒãšããã¯ãšã³ããªãœãŒã¹ãžã®ã¢ã¯ã»ã¹ãå¶åŸ¡ãã API ã²ãŒããŠã§ã€ãæ§ç¯ããæ¹æ³ã瀺ããŸããããã¯äžé£ã®ã»ã¯ã·ã§ã³ã® 4 çªç®ã§ãããã¢ããªã±ãŒã·ã§ã³ã®åºæ¬çãªæ§æèŠçŽ ã«è¿œãã€ãããæåã®ã»ã¯ã·ã§ã³ãèªãã§ãŒãããæ§ç¯ããããGithub ã®ãœãŒã¹ã³ãŒãã«çŽæ¥ (è±èª) é²ãããšãã§ããŸããåã®ã»ã¯ã·ã§ã³ã§ã¯ãSpring Session [GitHub] (è±èª) ã䜿çšããŠããã¯ãšã³ããªãœãŒã¹ãèªèšŒããåçŽãªåæ£ã¢ããªã±ãŒã·ã§ã³ãæ§ç¯ããŸãããããã§ã¯ãUI ãµãŒããŒãããã¯ãšã³ããªãœãŒã¹ãµãŒããŒãžã®ãªããŒã¹ãããã·ã«ããæåŸã®å®è£ ïŒã«ã¹ã¿ã ããŒã¯ã³èªèšŒã«ãã£ãŠå°å ¥ãããæè¡çãªè€éãïŒã®èª²é¡ãä¿®æ£ãããã©ãŠã¶ãŒã¯ã©ã€ã¢ã³ãããã®ã¢ã¯ã»ã¹ãå¶åŸ¡ããããã®å€ãã®æ°ãããªãã·ã§ã³ãæäŸããŸãã
ãªãã€ã³ããŒ: ãã®ã»ã¯ã·ã§ã³ã§ãµã³ãã«ã¢ããªã±ãŒã·ã§ã³ã䜿çšããŠããå Žåã¯ããã©ãŠã¶ãŒã®ãã£ãã·ã¥ã® Cookie ãš HTTP åºæ¬èªèšŒæ å ±ãå¿ ãã¯ãªã¢ããŠãã ãããChrome ã§ã¯ãåäžãµãŒããŒã§ãããè¡ãæè¯ã®æ¹æ³ã¯ãæ°ããã·ãŒã¯ã¬ãããŠã£ã³ããŠãéãããšã§ãã
API Gateway ã®äœæ
API Gateway ã¯ãããã³ããšã³ãã¯ã©ã€ã¢ã³ãã®åäžã®ãšã³ããªãã€ã³ãïŒããã³å¶åŸ¡ïŒã§ããããã©ãŠã¶ãŒããŒã¹ïŒãã®ã»ã¯ã·ã§ã³ã®äŸã®ãããªïŒãŸãã¯ã¢ãã€ã«ã®å ŽåããããŸããã¯ã©ã€ã¢ã³ã㯠1 ã€ã®ãµãŒããŒã® URL ãç¥ãã ãã§ãããããã¯ãšã³ãã倿Žããã«èªç±ã«ãªãã¡ã¯ã¿ãªã³ã°ã§ããŸããããã¯å€§ããªå©ç¹ã§ããéäžåãšå¶åŸ¡ã«é¢ããŠãã¬ãŒãå¶éãèªèšŒãç£æ»ããã°èšé²ãªã©ã®å©ç¹ããããŸããSpring Cloud ã䜿çšãããšãåçŽãªãªããŒã¹ãããã·ã®å®è£ ã¯éåžžã«ç°¡åã§ãã
ã³ãŒããé å®ããŠããå ŽåãæåŸã®ã»ã¯ã·ã§ã³ã®æåŸã®ã¢ããªã±ãŒã·ã§ã³å®è£ ãå°ãè€éã§ããããšããããããããããç¹°ãè¿ãã®ã«æé©ãªå Žæã§ã¯ãããŸããããã ããããç°¡åã«éå§ã§ããäžéç¹ããããããã¯ãšã³ããªãœãŒã¹ã¯ãŸã Spring Security ã§ä¿è·ãããŠããŸããã§ããããã®ãœãŒã¹ã³ãŒã㯠Github (è±èª) ã®å¥åã®ãããžã§ã¯ããªã®ã§ãããããå§ããŸããUI ãµãŒããŒãšãªãœãŒã¹ãµãŒããŒãããããäºãã«éä¿¡ããŠããŸãããªãœãŒã¹ãµãŒããŒã«ã¯ Spring Security ããŸã ãªããããæåã«ã·ã¹ãã ãåäœãããŠããããã®ã¬ã€ã€ãŒã远å ã§ããŸãã
1 è¡ã®å®£èšçãªããŒã¹ãããã·
ããã API Gateway ã«å€æããã«ã¯ãUI ãµãŒããŒã« 1 ã€ã®å°ããªèª¿æŽãå¿
èŠã§ããSpring æ§æã®ã©ããã§ãããšãã°ã¡ã€ã³ïŒã®ã¿ïŒã¢ããªã±ãŒã·ã§ã³ã¯ã©ã¹ [GitHub] (è±èª) ã« @EnableZuulProxy ã¢ãããŒã·ã§ã³ã远å ããå¿
èŠããããŸãã
@SpringBootApplication
@RestController
@EnableZuulProxy
public class UiApplication {
...
}ãŸããå€éšæ§æãã¡ã€ã«ã§ãUI ãµãŒããŒã®ããŒã«ã«ãªãœãŒã¹ãå€éšæ§æ [GitHub] (è±èª) ïŒ"application.yml" ïŒã®ãªã¢ãŒããªãœãŒã¹ã«ãããããå¿ èŠããããŸãã
security:
...
zuul:
routes:
resource:
path: /resource/**
url: http://localhost:9000ããã¯ãããã®ãµãŒããŒã®ãã¿ãŒã³ /resource/** ã®ãã¹ãããªã¢ãŒããµãŒããŒã® localhost:9000 ã«ããåããã¹ã«ãããããããšããæå³ã§ããã·ã³ãã«ã§ãã广çã§ã (YAML ãå«ã㊠6 è¡ã§ãããå¿ ãããå¿ èŠãªããã§ã¯ãããŸãã)ã
ãã®äœæ¥ãè¡ãããã«å¿ èŠãªã®ã¯ãã¯ã©ã¹ãã¹äžã®é©åãªãã®ã ãã§ãããã®ããã«ãMaven POM ã«ããã€ãã®æ°ããè¡ããããŸãã
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.SR4</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>
...
</dependencies> "spring-cloud-starter-zuul" ã®äœ¿çšã«æ³šæããŠãã ããããã㯠Spring Boot ã®ãã®ãšåãã¹ã¿ãŒã¿ãŒ POM ã§ããããã® Zuul ãããã·ã«å¿
èŠãªäŸåé¢ä¿ã管çããŸãããŸããæšç§»çãªäŸåé¢ä¿ã®ãã¹ãŠã®ããŒãžã§ã³ãæ£ããããšã«äŸåã§ããããã«ããããã«ã<dependencyManagement> ã䜿çšããŠããŸãã
ã¯ã©ã€ã¢ã³ãã§ãããã·ã䜿çšãã
ãããã®å€æŽãé©åã«é©çšãããŠããŠããã¢ããªã±ãŒã·ã§ã³ã¯åŒãç¶ãæ©èœããŸãããã¯ã©ã€ã¢ã³ãã倿ŽãããŸã§ãå®éã«ã¯æ°ãããããã·ã䜿çšããŠããŸããã幞ããªããšã«ããã¯ç°¡åã§ããæåŸã®ã»ã¯ã·ã§ã³ã§ ãåäžããããããã©ããµã³ãã«ã«è¡ã£ã倿Žãå ã«æ»ãå¿ èŠããããŸãã
constructor(private app: AppService, private http: HttpClient) {
http.get('resource').subscribe(data => this.greeting = data);
}ãµãŒããŒãèµ·åãããšããã¹ãŠãæ©èœãããªã¯ãšã¹ã㯠UIïŒAPI GatewayïŒãä»ããŠãªãœãŒã¹ãµãŒããŒã«ãããã·ãããŸãã
ãããªãç°¡çŽ å
ããã«è¯ã: ãªãœãŒã¹ãµãŒããŒã« CORS ãã£ã«ã¿ãŒã¯ããå¿ èŠãããŸãããããã«ãããäžç·ã«æããŸããããæè¡çã«æäœæ¥ã§çŠç¹ãåãããªããã°ãªããªãããšã¯ç¹ã«å±éºã§ããïŒç¹ã«ã»ãã¥ãªãã£ã«é¢ããå ŽåïŒã幞ããªããšã«ããã¯åé·ã§ããããããããæšãŠãŠãå€ç ãã«æ»ãããšãã§ããŸãïŒ
ãªãœãŒã¹ãµãŒããŒã®ã»ãã¥ãªãã£ä¿è·
äžéç¶æ ã§ã¯ããªãœãŒã¹ãµãŒããŒã«ã»ãã¥ãªãã£ãèšå®ãããŠããªãããšãèŠããŠãããããããŸããã
äœè«: ãããã¯ãŒã¯ã¢ãŒããã¯ãã£ãã¢ããªã±ãŒã·ã§ã³ã¢ãŒããã¯ãã£ãåæ ããŠããå ŽåããœãããŠã§ã¢ã»ãã¥ãªãã£ã®æ¬ åŠã¯åé¡ã«ãªããªãå ŽåããããŸãïŒãªãœãŒã¹ãµãŒããŒã« UI ãµãŒããŒä»¥å€ã®äººãç©ççã«ã¢ã¯ã»ã¹ã§ããªãããã«ããããšãã§ããŸãïŒããã®ç°¡åãªãã¢ã³ã¹ãã¬ãŒã·ã§ã³ãšããŠããªãœãŒã¹ãµãŒããŒã«ããŒã«ã«ãã¹ãã§ã®ã¿ã¢ã¯ã»ã¹ã§ããããã«ããŸããããããªãœãŒã¹ãµãŒããŒã®
application.propertiesã«è¿œå ããã ãã§ãã
server.address: 127.0.0.1ãããŒãç°¡åã§ããïŒ ããŒã¿ã»ã³ã¿ãŒã§ã®ã¿è¡šç€ºããããããã¯ãŒã¯ã¢ãã¬ã¹ã䜿çšããŠããã¹ãŠã®ãªãœãŒã¹ãµãŒããŒãšãã¹ãŠã®ãŠãŒã¶ãŒãã¹ã¯ãããã§æ©èœããã»ãã¥ãªãã£ãœãªã¥ãŒã·ã§ã³ãå®çŸããŸãã
ãœãããŠã§ã¢ã¬ãã«ã§ã»ãã¥ãªãã£ãå¿ èŠã§ãããšå€æãããšä»®å®ããŸãïŒå€ãã®çç±ã«ãããããªãã®å¯èœæ§ããããŸãïŒãåé¡ã«ãªãããšã¯ãããŸãããäŸåé¢ä¿ãšã㊠Spring Security ãïŒãªãœãŒã¹ãµãŒã㌠POM [GitHub] (è±èª) ã«ïŒè¿œå ããã ãã§ããã:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>ã»ãã¥ã¢ãªãªãœãŒã¹ãµãŒããŒãååŸããã«ã¯ããã§ååã§ãããããŒã III ã«ã¯ãªãã£ãã®ãšåãçç±ã§ããŸã åäœããŠããã¢ããªã±ãŒã·ã§ã³ã¯ååŸã§ããŸããã2 ã€ã®ãµãŒããŒéã§å ±æèªèšŒç¶æ ã¯ãããŸããã
èªèšŒç¶æ ã®å ±æ
èªèšŒïŒããã³ CSRFïŒç¶æ ãå ±æããããã«ãååãšåãã¡ã«ããºã ãã€ãŸã Spring Session [GitHub] (è±èª) ã䜿çšã§ããŸãã以åã®ããã«ãäž¡æ¹ã®ãµãŒããŒã«äŸåé¢ä¿ã远å ããŸãã
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-redis</artifactId>
</dependency> ãã ããåã Filter 宣èšãäž¡æ¹ã«è¿œå ã§ãããããä»åã¯æ§æãã¯ããã«ç°¡åã«ãªããŸããæåã« UI ãµãŒããŒããã¹ãŠã®ããããŒã転éããããšãæç€ºçã«å®£èšããŸãïŒã€ãŸãããæ©å¯ãã¯ãããŸããïŒã
zuul:
routes:
resource:
sensitive-headers:ãã®åŸããªãœãŒã¹ãµãŒããŒã«ç§»åã§ããŸãã2 ã€ã®å°ããªå€æŽãå¿ èŠã§ãã1 ã€ã¯ããªãœãŒã¹ãµãŒããŒã§ HTTP Basic ãæç€ºçã«ç¡å¹ã«ããããšã§ãïŒãã©ãŠã¶ãŒãèªèšŒãã€ã¢ãã°ããããã¢ããããã®ãé²ãããïŒã
@SpringBootApplication
@RestController
class ResourceApplication extends WebSecurityConfigurerAdapter {
...
@Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic().disable();
http.authorizeRequests().anyRequest().authenticated();
}
}äœè«: èªèšŒãã€ã¢ãã°ãé²ãå¥ã®æ¹æ³ã¯ãHTTP Basic ãä¿æãããŸãŸã401 ãã£ã¬ã³ãžã "Basic" 以å€ã«å€æŽããããšã§ãã
HttpSecurityæ§æã³ãŒã«ããã¯ã®AuthenticationEntryPointã® 1 è¡ã®å®è£ ã§ãããè¡ãããšãã§ããŸãã
ãã 1 ã€ã¯ãapplication.properties ã§éã¹ããŒãã¬ã¹ã»ãã·ã§ã³äœæããªã·ãŒãæç€ºçã«èŠæ±ããããšã§ãã
security.sessions: NEVERredis ãããã¯ã°ã©ãŠã³ãã§å®è¡ãããŠããéãïŒèµ·åããå Žå㯠docker-compose.yml [GitHub] (è±èª) ã䜿çšããŠãã ããïŒãã·ã¹ãã ã¯åäœããŸããhttp://localhost:8080 ã§ UI ã®ããŒã ããŒãžãããŒãããŠãã°ã€ã³ãããšãããã¯ãšã³ãããã®ã¡ãã»ãŒãžãããŒã ããŒãžã«è¡šç€ºãããŸãã
ä»çµã¿ã¯ïŒ
ããã¯ã°ã©ãŠã³ãã§äœãèµ·ãã£ãŠããŸããïŒ æåã«ãUI ãµãŒããŒïŒããã³ API GatewayïŒã§ HTTP ãªã¯ãšã¹ãã確èªã§ããŸãã
| åè© | ãã¹ | ã¹ããŒã¿ã¹ | ã¬ã¹ãã³ã¹ |
|---|---|---|---|
GET | / | 200 | index.html |
GET | /*.js | 200 | è§åºŠããã®ã¢ã»ãã |
GET | /user | 401 | äžèš±å¯ (ç¡èŠãããŸãã) |
GET | /resource | 401 | ãªãœãŒã¹ãžã®èªèšŒãããŠããªãã¢ã¯ã»ã¹ |
GET | /user | 200 | JSON èªèšŒæžã¿ãŠãŒã¶ãŒ |
GET | /resource | 200 | ïŒãããã·ïŒJSON ã°ãªãŒãã£ã³ã° |
ããã¯ãSpring Session ã䜿çšããŠãããããCookie åããããã«ç°ãªãïŒ "JSESSIONID" ã§ã¯ãªã "SESSION" ïŒãšããç¹ãé€ããŠãããŒã II ã®æåŸã®ã·ãŒã±ã³ã¹ãšåãã§ãããã ããã¢ãŒããã¯ãã£ã¯ç°ãªãã"/resource" ãžã®æåŸã®ãªã¯ãšã¹ãã¯ããªãœãŒã¹ãµãŒããŒã«ãããã·ãããŠãããããç¹å¥ã§ãã
UI ãµãŒããŒã® "/trace" ãšã³ããã€ã³ãïŒSpring Cloud ã®äŸåé¢ä¿ã§è¿œå ãã Spring Boot Actuator ããïŒã確èªããããšã§ããªããŒã¹ãããã·ã®åäœã確èªã§ããŸããæ°ãããã©ãŠã¶ãŒã§ http://localhost:8080/trace ã«ç§»åããŸãïŒãã©ãŠã¶ãŒçšã® JSON ãã©ã°ã€ã³ããŸã å ¥æããŠããªãå Žåã¯ããã©ãŠã¶ãŒãèªã¿ãããããŸãïŒãHTTP BasicïŒãã©ãŠã¶ãŒãããã¢ããïŒã§èªèšŒããå¿ èŠããããŸããããã°ã€ã³ãã©ãŒã ãšåãè³æ Œæ å ±ãæå¹ã§ããéå§æãŸãã¯éå§è¿ãã«ã次ã®ãããªãªã¯ãšã¹ãã®ãã¢ã衚瀺ãããŸãã
| èªèšŒã®ã¯ãã¹ãªãŒããŒã®å¯èœæ§ããªãããã«å¥ã®ãã©ãŠã¶ãŒã䜿çšããŠã¿ãŠãã ããïŒããšãã°ãUI ã®ãã¹ãã« Chrome ã䜿çšããªãå Žå㯠Firefox ã䜿çšããŸãïŒ- ã¢ããªã®åäœã忢ããŸããããå«ãŸããŠããå Žåã¯ãã¬ãŒã¹ãèªã¿ã«ããããŸããåããã©ãŠã¶ãŒããã®èªèšŒã®æ··åã |
{
"timestamp": 1420558194546,
"info": {
"method": "GET",
"path": "/",
"query": ""
"remote": true,
"proxy": "resource",
"headers": {
"request": {
"accept": "application/json, text/plain, */*",
"x-xsrf-token": "542c7005-309c-4f50-8a1d-d6c74afe8260",
"cookie": "SESSION=c18846b5-f805-4679-9820-cd13bd83be67; XSRF-TOKEN=542c7005-309c-4f50-8a1d-d6c74afe8260",
"x-forwarded-prefix": "/resource",
"x-forwarded-host": "localhost:8080"
},
"response": {
"Content-Type": "application/json;charset=UTF-8",
"status": "200"
}
},
}
},
{
"timestamp": 1420558200232,
"info": {
"method": "GET",
"path": "/resource/",
"headers": {
"request": {
"host": "localhost:8080",
"accept": "application/json, text/plain, */*",
"x-xsrf-token": "542c7005-309c-4f50-8a1d-d6c74afe8260",
"cookie": "SESSION=c18846b5-f805-4679-9820-cd13bd83be67; XSRF-TOKEN=542c7005-309c-4f50-8a1d-d6c74afe8260"
},
"response": {
"Content-Type": "application/json;charset=UTF-8",
"status": "200"
}
}
}
},2 çªç®ã®ãšã³ããªã¯ "/resource" ã®ã¯ã©ã€ã¢ã³ãããã²ãŒããŠã§ã€ãžã®ãªã¯ãšã¹ãã§ãããCookieïŒãã©ãŠã¶ãŒã«ãã£ãŠè¿œå ãããïŒãš CSRF ããããŒïŒããŒã II ã«åŸã£ãŠ Angular ã«ãã£ãŠè¿œå ãããïŒã確èªã§ããŸããæåã®ãšã³ããªã«ã¯ remote: true ããããããã¯ãªãœãŒã¹ãµãŒããŒãžã®åŒã³åºãããã¬ãŒã¹ããŠããããšãæå³ããŸããããã URI ãã¹ "/" ã«éä¿¡ãããããšãããããŸãããŸããïŒéèŠãªããšã«ïŒCookie ãš CSRF ããããŒãéä¿¡ãããŠããããšãããããŸããSpring Session ããªããšããããã®ããããŒã¯ãªãœãŒã¹ãµãŒããŒã«ãšã£ãŠæå³ããããŸããããã»ããã¢ããæ¹æ³ã§ã¯ããããã®ããããŒã䜿çšããŠãèªèšŒãš CSRF ããŒã¯ã³ããŒã¿ã䜿çšããŠã»ãã·ã§ã³ãåæ§æã§ããŸãããªã¯ãšã¹ãã¯èš±å¯ãããããžãã¹ãè¡ã£ãŠããŸãïŒ
çµè«
ãã®ã»ã¯ã·ã§ã³ã§ã¯ããªãå€ãã®ããšã説æããŸãããã2 å°ã®ãµãŒããŒã«æå°éã®ãã€ã©ãŒãã¬ãŒãã³ãŒãããããã©ã¡ããå®å šã§ããããŠãŒã¶ãŒãšã¯ã¹ããªãšã³ã¹ãæãªãããªããæ¬åœã«çŽ æŽãããå Žæã«å°éããŸãããããã ãã API Gateway ãã¿ãŒã³ã䜿çšããçç±ã«ãªããŸãããå®éã«ã¯ã䜿çšãããå¯èœæ§ã®ãã衚é¢ã®ã»ãã®äžéšã«éããŸããïŒNetflix ã¯å€ãã®ã [GitHub] (è±èª) ãšã«äœ¿çšããŠããŸãïŒãSpring Cloud ãèªãã§ãããå€ãã®æ©èœãã²ãŒããŠã§ã€ã«ç°¡åã«è¿œå ããæ¹æ³ã確èªããŠãã ããããã®ã·ãªãŒãºã®æ¬¡ã®ã»ã¯ã·ã§ã³ã§ã¯ãèªèšŒã®è²¬ä»»ãå¥ã®ãµãŒããŒã«æœåºããããšã§ãã¢ããªã±ãŒã·ã§ã³ã¢ãŒããã¯ãã£ãå°ãæ¡åŒµããŸãïŒã·ã³ã°ã«ãµã€ã³ãªã³ãã¿ãŒã³ïŒã
OAuth2 ã䜿çšããã·ã³ã°ã«ãµã€ã³ãªã³
ãã®ã»ã¯ã·ã§ã³ã§ã¯ããã·ã³ã°ã«ããŒãžã¢ããªã±ãŒã·ã§ã³ãã§ Spring Security ã Angular (è±èª) ãšäœ¿çšããæ¹æ³ã«ã€ããŠåŒãç¶ã説æããŸããããã§ã¯ãSpring Security OAuth (è±èª) ãš Spring Cloud ã䜿çšã㊠API Gateway ãç¶æ¿ããã·ã³ã°ã«ãµã€ã³ãªã³ãš OAuth2 ããŒã¯ã³èªèšŒãããã¯ãšã³ããªãœãŒã¹ã«å®è¡ããæ¹æ³ã瀺ããŸããããã¯äžé£ã®ã»ã¯ã·ã§ã³ã® 5 çªç®ã§ãããã¢ããªã±ãŒã·ã§ã³ã®åºæ¬çãªæ§æèŠçŽ ã«è¿œãã€ãããæåã®ã»ã¯ã·ã§ã³ãèªãã§ãŒããããã«ãããããGithub ã®ãœãŒã¹ã³ãŒãã«çŽæ¥ (è±èª) é²ãããšãã§ããŸããæåŸã®ã»ã¯ã·ã§ã³ã§ã¯ãSpring Session [GitHub] (è±èª) ã䜿çšããŠããã¯ãšã³ããªãœãŒã¹ãèªèšŒããSpring Cloud ã䜿çšã㊠UI ãµãŒããŒã«çµã¿èŸŒã¿ API ã²ãŒããŠã§ã€ãå®è£ ããå°ããªåæ£ã¢ããªã±ãŒã·ã§ã³ãæ§ç¯ããŸããããã®ã»ã¯ã·ã§ã³ã§ã¯ãèªèšŒãµãŒããŒãžã®å€ãã®ã·ã³ã°ã«ãµã€ã³ãªã³ã¢ããªã±ãŒã·ã§ã³ã®æåã® UI ãµãŒããŒãäœæããããã«ãèªèšŒã®è²¬ä»»ãå¥ã®ãµãŒããŒã«æœåºããŸããããã¯ãäŒæ¥ããã³ãœãŒã·ã£ã«ã¹ã¿ãŒãã¢ããã®äž¡æ¹ã§ãæè¿ã®å€ãã®ã¢ããªã±ãŒã·ã§ã³ã§äžè¬çãªãã¿ãŒã³ã§ããOAuth2 ãµãŒããŒããªãŒã»ã³ãã£ã±ãŒã¿ãŒãšããŠäœ¿çšãããããOAuth2 ãµãŒããŒã䜿çšããŠããã¯ãšã³ããªãœãŒã¹ãµãŒããŒã®ããŒã¯ã³ãä»äžããããšãã§ããŸããSpring Cloud ã¯ã¢ã¯ã»ã¹ããŒã¯ã³ãããã¯ãšã³ãã«èªåçã«äžç¶ããUI ãµãŒããŒãšãªãœãŒã¹ãµãŒããŒã®äž¡æ¹ã®å®è£ ãããã«ç°¡çŽ åã§ããããã«ããŸãã
ãªãã€ã³ããŒ: ãã®ã»ã¯ã·ã§ã³ã§ãµã³ãã«ã¢ããªã±ãŒã·ã§ã³ã䜿çšããŠããå Žåã¯ããã©ãŠã¶ãŒã®ãã£ãã·ã¥ã® Cookie ãš HTTP åºæ¬èªèšŒæ å ±ãå¿ ãã¯ãªã¢ããŠãã ãããChrome ã§ã¯ãåäžãµãŒããŒã§ãããè¡ãæè¯ã®æ¹æ³ã¯ãæ°ããã·ãŒã¯ã¬ãããŠã£ã³ããŠãéãããšã§ãã
OAuth2 èªå¯ãµãŒããŒã®äœæ
æåã®ã¹ãããã¯ãèªèšŒãšããŒã¯ã³ç®¡çãåŠçããæ°ãããµãŒããŒãäœæããããšã§ããããŒã I ã®æé ã«åŸã£ãŠãSpring Boot Initializr ããå§ããããšãã§ããŸããäŸ: UN*X ã®ãããªã·ã¹ãã ã§ curl ã䜿çš:
$ curl https://start.spring.io/starter.tgz -d dependencies=web,security -d name=authserver | tar -xzvf -ãã®åŸããã®ãããžã§ã¯ãïŒããã©ã«ãã§ã¯éåžžã® Maven Java ãããžã§ã¯ãïŒããæ°ã«å ¥ãã® IDE ã«ã€ã³ããŒãããããã³ãã³ãã©ã€ã³ã§ãã¡ã€ã«ãš "mvn" ãæäœããã ãã§ãã
OAuth2 äŸåé¢ä¿ã®è¿œå
Spring OAuth (è±èª) äŸåé¢ä¿ã远å ããå¿ èŠããããããPOM [GitHub] (è±èª) ã«ä»¥äžã远å ããŸãã
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
</dependency>èªå¯ãµãŒããŒã®å®è£ ã¯éåžžã«ç°¡åã§ããæå°ããŒãžã§ã³ã¯æ¬¡ã®ããã«ãªããŸãã
@SpringBootApplication
@EnableAuthorizationServer
public class AuthserverApplication extends WebMvcConfigurerAdapter {
public static void main(String[] args) {
SpringApplication.run(AuthserverApplication.class, args);
}
}ïŒ@EnableAuthorizationServer ã远å ããåŸïŒããš 1 ã€ã ãè¡ãå¿
èŠããããŸãã
---
...
security.oauth2.client.clientId: acme
security.oauth2.client.clientSecret: acmesecret
security.oauth2.client.authorized-grant-types: authorization_code,refresh_token,password
security.oauth2.client.scope: openid
---ããã«ãããã¯ã©ã€ã¢ã³ã "acme" ãã·ãŒã¯ã¬ãããš "authorization_code" ãå«ãããã€ãã®èªå¯ãããèªå¯åã§ç»é²ãããŸãã
次ã«ããã¹ãçšã®äºæž¬å¯èœãªãã¹ã¯ãŒãã䜿çšããŠãããŒã 9999 ã§å®è¡ããŸãã
server.port=9999
security.user.password=password
server.contextPath=/uaa
...ãŸããããã©ã«ãïŒ"/"ïŒã䜿çšããªãããã«ã³ã³ããã¹ããã¹ãèšå®ããŸããããããªããšãããŒã«ã«ãã¹ãäžã®ä»ã®ãµãŒããŒã® Cookie ãééã£ããµãŒããŒã«éä¿¡ãããå¯èœæ§ããããŸãããµãŒããŒãå®è¡ãããšããµãŒããŒãæ©èœããŠããããšã確èªã§ããŸãã
$ mvn spring-boot:run ãŸãã¯ãIDE ã§ main() ã¡ãœãããéå§ããŸãã
èªå¯ãµãŒããŒã®ãã¹ã
ãµãŒããŒã¯ Spring Boot ã®ããã©ã«ãã®ã»ãã¥ãªãã£èšå®ã䜿çšããŠãããããããŒã I ã®ãµãŒããŒãšåæ§ã«ãHTTP Basic èªèšŒã«ãã£ãŠä¿è·ãããŸããèªå¯ã³ãŒãããŒã¯ã³ã®ä»äž [IETF] (è±èª) ãéå§ããã«ã¯ãèªå¯ãšã³ããã€ã³ãã«ã¢ã¯ã»ã¹ããŸããhttp://localhost:9999/uaa/oauth/authorize?response_type=code&client_id=acme&redirect_uri=http://example.com ã§èªèšŒããããšãèªèšŒã³ãŒããæ·»ä»ããã example.com ãžã®ãªãã€ã¬ã¯ããååŸãããŸããäŸ: http://example.com/?code=jYWioI (è±èª)
| ãã®ãµã³ãã«ã¢ããªã±ãŒã·ã§ã³ã®ç®çã®ããã«ããªãã€ã¬ã¯ããç»é²ãããŠããªãã¯ã©ã€ã¢ã³ã "acme" ãäœæããŸãããããã«ãããexample.com ãžã®ãªãã€ã¬ã¯ããååŸã§ããŸããæ¬çªã¢ããªã±ãŒã·ã§ã³ã§ã¯ãåžžã«ãªãã€ã¬ã¯ããç»é²ããïŒããã³ HTTPS ã䜿çšããïŒå¿ èŠããããŸãã |
ããŒã¯ã³ãšã³ããã€ã³ãã® "acme" ã¯ã©ã€ã¢ã³ãè³æ Œæ å ±ã䜿çšããŠãã³ãŒããã¢ã¯ã»ã¹ããŒã¯ã³ã«äº€æã§ããŸãã
$ curl acme:acmesecret@localhost:9999/uaa/oauth/token \
-d grant_type=authorization_code -d client_id=acme \
-d redirect_uri=http://example.com -d code=jYWioI
{"access_token":"2219199c-966e-4466-8b7e-12bb9038c9bb","token_type":"bearer","refresh_token":"d193caf4-5643-4988-9a4a-1c03c9d657aa","expires_in":43199,"scope":"openid"}ã¢ã¯ã»ã¹ããŒã¯ã³ã¯ UUIDïŒ"2219199c ⊠"ïŒã§ããããµãŒããŒå ã®ã¡ã¢ãªå ããŒã¯ã³ã¹ãã¢ã«ãã£ãŠããã¯ã¢ãããããŸãããŸããçŸåšã®ããŒã¯ã³ãæéåãã«ãªã£ããšãã«æ°ããã¢ã¯ã»ã¹ããŒã¯ã³ãååŸããããã«äœ¿çšã§ãããªãã¬ãã·ã¥ããŒã¯ã³ãååŸããŸããã
| "acme" ã¯ã©ã€ã¢ã³ãã« "password" ä»äžãèªå¯ãããããèªèšŒã³ãŒãã®ä»£ããã« curl ãšãŠãŒã¶ãŒè³æ Œæ å ±ã䜿çšããŠãããŒã¯ã³ãšã³ããã€ã³ãããããŒã¯ã³ãçŽæ¥ååŸããããšãã§ããŸããããã¯ãã©ãŠã¶ãŒããŒã¹ã®ã¯ã©ã€ã¢ã³ãã«ã¯é©ããŠããŸãããããã¹ãã«ã¯åœ¹ç«ã¡ãŸãã |
äžèšã®ãªã³ã¯ããã©ããšãSpring OAuth ãæäŸãããã¯ã€ãã©ãã« UI ã衚瀺ãããŸãããŸããããã䜿çšããŸããåŸããæ»ã£ãŠãããŒã II ã®èªå·±å®çµåãµãŒããŒã®å Žåãšåãããã«åŒ·åããããšãã§ããŸãã
ãªãœãŒã¹ãµãŒããŒã®å€æŽ
ããŒã IV ããç¶ãããšããªãœãŒã¹ãµãŒããŒã¯èªèšŒã« Spring Session [GitHub] (è±èª) ã䜿çšããŠããããããããåãåºã㊠Spring OAuth ã«çœ®ãæããããšãã§ããŸããSpring Session ãš Redis ã®äŸåé¢ä¿ãåé€ããå¿ èŠããããããããã眮ãæããŸãã
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-redis</artifactId>
</dependency>ãããšãšãã«:
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
</dependency> 次ã«ãã»ãã·ã§ã³ Filter ãã¡ã€ã³ã¢ããªã±ãŒã·ã§ã³ã¯ã©ã¹ [GitHub] (è±èª) ããåé€ãã䟿å©ãª @EnableResourceServer ã¢ãããŒã·ã§ã³ïŒSpring Security OAuth2 ããïŒã«çœ®ãæããŸãã
@SpringBootApplication
@RestController
@EnableResourceServer
class ResourceApplication {
@RequestMapping("/")
public Message home() {
return new Message("Hello World");
}
public static void main(String[] args) {
SpringApplication.run(ResourceApplication.class, args);
}
}ãã® 1 ã€ã®å€æŽã§ãã¢ããªã¯ HTTP Basic ã§ã¯ãªãã¢ã¯ã»ã¹ããŒã¯ã³ãèŠæ±ããæºåãã§ããŠããŸãããå®éã«ããã»ã¹ãå®äºããã«ã¯ãæ§æã倿Žããå¿ èŠããããŸãããªãœãŒã¹ãµãŒããŒãäžããããããŒã¯ã³ããã³ãŒãããŠãŠãŒã¶ãŒãèªèšŒã§ããããã«ãå°éã®å€éšæ§æïŒ"application.properties" ïŒã远å ããŸãã
...
security.oauth2.resource.userInfoUri: http://localhost:9999/uaa/user ããã¯ãããŒã¯ã³ã䜿çšã㊠"/user" ãšã³ããã€ã³ãã«ã¢ã¯ã»ã¹ããããã䜿çšããŠèªèšŒæ
å ±ãååŸã§ããããšããµãŒããŒã«éç¥ããŸãïŒFacebook API ã® "/me" ãšã³ããã€ã³ã (è±èª) ã«å°ã䌌ãŠããŸãïŒãäºå®äžãSpring OAuth2 ã® ResourceServerTokenServices ã€ã³ã¿ãŒãã§ãŒã¹ã§è¡šãããããã«ããªãœãŒã¹ãµãŒããŒãããŒã¯ã³ããã³ãŒãããæ¹æ³ãæäŸããŸãã
ã¢ããªã±ãŒã·ã§ã³ãå®è¡ããã³ãã³ãã©ã€ã³ã¯ã©ã€ã¢ã³ãã§ããŒã ããŒãžã«ã¢ã¯ã»ã¹ããŸãã
$ curl -v localhost:9000
> GET / HTTP/1.1
> User-Agent: curl/7.35.0
> Host: localhost:9000
> Accept: */*
>
< HTTP/1.1 401 Unauthorized
...
< WWW-Authenticate: Bearer realm="null", error="unauthorized", error_description="An Authentication object was not found in the SecurityContext"
< Content-Type: application/json;charset=UTF-8
{"error":"unauthorized","error_description":"An Authentication object was not found in the SecurityContext"}ãŸãããã¢ã©ãŒããŒã¯ã³ãå¿ èŠã§ããããšã瀺ã "WWW-Authenticate" ããããŒãæã€ 401 ã衚瀺ãããŸãã
userInfoUri ã¯ãããŒã¯ã³ããã³ãŒãããæ¹æ³ã§ãªãœãŒã¹ãµãŒããŒãæ¥ç¶ããå¯äžã®æ¹æ³ã§ã¯ãããŸãããå®éãããã¯æãäžè¬çãªåæ¯ã®ãããªãã®ã§ïŒä»æ§ã®äžéšã§ã¯ãããŸããïŒãOAuth2 ãããã€ããŒïŒFacebookãCloud FoundryãGithub ãªã©ïŒããéåžžã«é »ç¹ã«å©çšã§ããä»ã®éžæè¢ãå©çšã§ããŸããããšãã°ãããŒã¯ã³èªäœã§ãŠãŒã¶ãŒèªèšŒããšã³ã³ãŒãããïŒäŸ: JWT (è±èª) ã䜿çšïŒããå
±æããã¯ãšã³ãã¹ãã¢ã䜿çšããŸããCloudFoundry ã«ã¯ /token_info ãšã³ããã€ã³ããããããŠãŒã¶ãŒæ
å ±ãšã³ããã€ã³ããããè©³çŽ°ãªæ
å ±ãæäŸããŸãããããå®å
šãªèªèšŒãå¿
èŠã§ããããŸããŸãªãªãã·ã§ã³ãïŒåœç¶ïŒããŸããŸãªå©ç¹ãšãã¬ãŒããªããæäŸããŸããããããã®è©³çްãªèª¬æã¯ãã®ã»ã¯ã·ã§ã³ã®ç¯å²å€ã§ãã |
ãŠãŒã¶ãŒãšã³ããã€ã³ãã®å®è£
èªå¯ãµãŒããŒã§ã¯ããã®ãšã³ããã€ã³ããç°¡åã«è¿œå ã§ããŸã
@SpringBootApplication
@RestController
@EnableAuthorizationServer
@EnableResourceServer
public class AuthserverApplication {
@RequestMapping("/user")
public Principal user(Principal user) {
return user;
}
...
} ããŒã II ã® UI ãµãŒããŒãšåã @RequestMapping ã远å ããSpring OAuth ããã® @EnableResourceServer ã¢ãããŒã·ã§ã³ã远å ããŸãããããã¯ãããã©ã«ãã§ "/oauth/*" ãšã³ããã€ã³ããé€ãèªå¯ãµãŒããŒå
ã®ãã¹ãŠãä¿è·ããŸãã
ãšã³ããã€ã³ããé 眮ããããèªå¯ãµãŒããŒã«ãã£ãŠäœæããããã¢ã©ãŒããŒã¯ã³ãåãå ¥ããããã«ãªã£ãããããããšã°ãªãŒãã£ã³ã°ãªãœãŒã¹ããã¹ãã§ããŸãã
$ TOKEN=2219199c-966e-4466-8b7e-12bb9038c9bb
$ curl -H "Authorization: Bearer $TOKEN" localhost:9000
{"id":"03af8be3-2fc3-4d75-acf7-c484d9cf32b1","content":"Hello World"}
$ curl -H "Authorization: Bearer $TOKEN" localhost:9999/uaa/user
{"details":...,"principal":{"username":"user",...},"name":"user"}ïŒèªåã§èªèšŒãµãŒããŒããååŸããã¢ã¯ã»ã¹ããŒã¯ã³ã®å€ãä»£å ¥ããŠããããèªåã§åäœãããŸãïŒã
UI ãµãŒããŒ
å®äºããå¿
èŠããããã®ã¢ããªã±ãŒã·ã§ã³ã®æåŸã®éšåã¯ãèªèšŒéšåãæœåºããèªå¯ãµãŒããŒã«å§è²ãã UI ãµãŒããŒã§ãããã®ããããªãœãŒã¹ãµãŒããŒãšåæ§ã«ããŸã Spring Session ãš Redis ã®äŸåé¢ä¿ãåé€ããSpring OAuth2 ã«çœ®ãæããå¿
èŠããããŸããUI 局㧠Zuul ã䜿çšããŠãããããå®éã«ã¯ spring-security-oauth2 ã§ã¯ãªã spring-cloud-starter-oauth2 ãçŽæ¥äœ¿çšããŸãïŒããã«ããããããã·ãä»ããŠããŒã¯ã³ãäžç¶ããããã®èªåæ§æãèšå®ãããŸãïŒã
ãããå®äºããããã»ãã·ã§ã³ãã£ã«ã¿ãŒãš "/user" ãšã³ããã€ã³ããåé€ããïŒ@EnableOAuth2Sso ã¢ãããŒã·ã§ã³ã䜿çšããŠïŒèªå¯ãµãŒããŒã«ãªãã€ã¬ã¯ãããããã«ã¢ããªã±ãŒã·ã§ã³ãèšå®ã§ããŸãã
@SpringBootApplication
@EnableZuulProxy
@EnableOAuth2Sso
public class UiApplication {
public static void main(String[] args) {
SpringApplication.run(UiApplication.class, args);
}
...
}UI ãµãŒããŒã @EnableZuulProxy ã®ãããã§ API ã²ãŒããŠã§ã€ãšããŠæ©èœããYAML ã§ã«ãŒããããã³ã°ã宣èšã§ããããšãããŒã IV ããæãåºããŠãã ããã"/user" ãšã³ããã€ã³ããèªèšŒãµãŒããŒã«ãããã·ã§ããŸãã
zuul:
routes:
resource:
path: /resource/**
url: http://localhost:9000
user:
path: /user/**
url: http://localhost:9999/uaa/user æåŸã«ã@EnableOAuth2Sso ã«ãã£ãŠèšå®ããã SSO ãã£ã«ã¿ãŒãã§ãŒã³ã®ããã©ã«ãã倿Žããããã«äœ¿çšããããããã¢ããªã±ãŒã·ã§ã³ã WebSecurityConfigurerAdapter ã«å€æŽããå¿
èŠããããŸãã
@SpringBootApplication
@EnableZuulProxy
@EnableOAuth2Sso
public class UiApplication extends WebSecurityConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http
.logout().logoutSuccessUrl("/").and()
.authorizeRequests().antMatchers("/index.html", "/app.html", "/")
.permitAll().anyRequest().authenticated().and()
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
}
} äž»ãªå€æŽç¹ïŒåºæ¬ã¯ã©ã¹åãé€ãïŒã¯ããããã£ãŒãç¬èªã®ã¡ãœããã«å
¥ãããšã§ãããformLogin() ã¯ããå¿
èŠãããŸãããæç€ºç㪠logout() æ§æã¯ãä¿è·ãããŠããªãæå URL ãæç€ºçã«è¿œå ããããã/logout ãžã® XHR ãªã¯ãšã¹ãã¯æ£åžžã«æ»ããŸãã
@EnableOAuth2Sso ã¢ãããŒã·ã§ã³ã«ã¯ãé©åãªèªå¯ãµãŒããŒãšéä¿¡ããŠèªèšŒã§ããããã«ããããã®ããã€ãã®å¿
é ã®å€éšæ§æããããã£ããããŸããapplication.yml ã§ãããå¿
èŠã§ã:
security:
...
oauth2:
client:
accessTokenUri: http://localhost:9999/uaa/oauth/token
userAuthorizationUri: http://localhost:9999/uaa/oauth/authorize
clientId: acme
clientSecret: acmesecret
resource:
userInfoUri: http://localhost:9999/uaa/user ãã®å€§éšåã¯ãOAuth2 ã¯ã©ã€ã¢ã³ãïŒ "acme" ïŒãšèªå¯ãµãŒããŒã®å Žæã«é¢ãããã®ã§ãããŠãŒã¶ãŒã UI ã¢ããªèªäœã§èªèšŒã§ããããã«ãuserInfoUri ããããŸãïŒãªãœãŒã¹ãµãŒããŒã®å Žåãšåæ§ïŒã
UI ã¢ããªã±ãŒã·ã§ã³ã§æéåãã®ã¢ã¯ã»ã¹ããŒã¯ã³ãèªåçã«ãªãã¬ãã·ã¥ã§ããããã«ããã«ã¯ãäžç¶ãè¡ã Zuul ãã£ã«ã¿ãŒã« OAuth2RestOperations ãæ¿å
¥ããå¿
èŠããããŸãããããè¡ãã«ã¯ããã®åã® Bean ãäœæããã ãã§ãïŒè©³çްã«ã€ããŠã¯ OAuth2TokenRelayFilter ã確èªããŠãã ããïŒã |
@Bean
protected OAuth2RestTemplate OAuth2RestTemplate(
OAuth2ProtectedResourceDetails resource, OAuth2ClientContext context) {
return new OAuth2RestTemplate(resource, context);
}ã¯ã©ã€ã¢ã³ã
ããã³ããšã³ãã® UI ã¢ããªã±ãŒã·ã§ã³ã«ã¯ãèªå¯ãµãŒããŒãžã®ãªãã€ã¬ã¯ããããªã¬ãŒããããã«ãŸã è¡ãå¿ èŠããã調æŽãããã€ããããŸãããã®ç°¡åãªãã¢ã§ã¯ãAngular ã¢ããªãå¿ èŠæäœéã®èŠçŽ ã«ãŸãšããŠãäœãèµ·ãã£ãŠããããããæç¢ºã«ç¢ºèªã§ããŸãããã®ãããä»ã®ãšããããã©ãŒã ãã«ãŒãã®äœ¿çšãæ§ããåäžã® Angular ã³ã³ããŒãã³ãã«æ»ããŸãã
import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import 'rxjs/add/operator/finally';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'Demo';
authenticated = false;
greeting = {};
constructor(private http: HttpClient) {
this.authenticate();
}
authenticate() {
this.http.get('user').subscribe(response => {
if (response['name']) {
this.authenticated = true;
this.http.get('resource').subscribe(data => this.greeting = data);
} else {
this.authenticated = false;
}
}, () => { this.authenticated = false; });
}
logout() {
this.http.post('logout', {}).finally(() => {
this.authenticated = false;
}).subscribe();
}
}AppComponent ã¯ãã¹ãŠãåŠçãããŠãŒã¶ãŒã®è©³çްãšãæåããå Žåã¯æšæ¶ãååŸããŸãããŸããlogout æ©èœãæäŸããŸãã
次ã«ããã®æ°ããã³ã³ããŒãã³ãã®ãã³ãã¬ãŒããäœæããå¿ èŠããããŸãã
app.component.html
<div class="container">
<ul class="nav nav-pills">
<li><a>Home</a></li>
<li><a href="login">Login</a></li>
<li><a (click)="logout()">Logout</a></li>
</ul>
</div>
<div class="container">
<h1>Greeting</h1>
<div [hidden]="!authenticated">
<p>The ID is {{greeting.id}}</p>
<p>The content is {{greeting.content}}</p>
</div>
<div [hidden]="authenticated">
<p>Login to see your greeting</p>
</div> ãããããŒã ããŒãžã« <app-root/> ãšããŠå«ããŸãã
ããã°ã€ã³ãã®ããã²ãŒã·ã§ã³ãªã³ã¯ã¯ãhref ïŒAngular ã«ãŒãã§ã¯ãªãïŒãšã®éåžžã®ãªã³ã¯ã§ããããšã«æ³šæããŠãã ããããããéä¿¡ããã "/login" ãšã³ããã€ã³ã㯠Spring Security ã«ãã£ãŠåŠçããããŠãŒã¶ãŒãèªèšŒãããŠããªãå Žåã¯ãèªèšŒãµãŒããŒã«ãªãã€ã¬ã¯ããããŸãã
ä»çµã¿ã¯ïŒ
ãã¹ãŠã®ãµãŒããŒãäžç·ã«å®è¡ããhttp://localhost:8080 ã®ãã©ãŠã¶ãŒã§ UI ã«ã¢ã¯ã»ã¹ããŸããããã°ã€ã³ããªã³ã¯ãã¯ãªãã¯ãããšãOAuth2 ãããã§ãããããã°ãªãŒãã£ã³ã°ã䜿çšã㊠UI ã®ããŒã ããŒãžã«ãªãã€ã¬ã¯ããããåã«ãèªèšŒãµãŒããŒïŒHTTP Basic ãããã¢ããïŒã«ãªãã€ã¬ã¯ããããããŒã¯ã³ä»äžïŒãã¯ã€ãã©ãã« HTMLïŒãèªå¯ãããŸããUI ãèªèšŒããã®ãšåãããŒã¯ã³ã䜿çšãããªãœãŒã¹ãµãŒããŒã
äžéšã®éçºè ããŒã«ã䜿çšãããšããã©ãŠã¶ãŒãšããã¯ãšã³ãéã®çžäºäœçšããã©ãŠã¶ãŒã§ç¢ºèªã§ããŸãïŒéåžžãF12 ã¯ãããéããããã©ã«ãã§ Chrome ã§åäœããFirefox ã§ãã©ã°ã€ã³ãå¿ èŠã«ãªãå ŽåããããŸãïŒãæŠèŠã¯æ¬¡ã®ãšããã§ãã
| åè© | ãã¹ | ã¹ããŒã¿ã¹ | ã¬ã¹ãã³ã¹ |
|---|---|---|---|
GET | / | 200 | index.html |
GET | /*.js | 200 | è§åºŠããã®ã¢ã»ãã |
GET | /user | 302 | ãã°ã€ã³ããŒãžã«ãªãã€ã¬ã¯ã |
GET | /login | 302 | èªèšŒãµãŒããŒã«ãªãã€ã¬ã¯ããã |
GET | ïŒuaaïŒ/oauth/authorize | 401 | ïŒç¡èŠïŒ |
GET | /login | 302 | èªèšŒãµãŒããŒã«ãªãã€ã¬ã¯ããã |
GET | ïŒuaaïŒ/oauth/authorize | 200 | HTTP åºæ¬èªèšŒã¯ããã§çºçããŸã |
POST | ïŒuaaïŒ/oauth/authorize | 302 | ãŠãŒã¶ãŒã¯èš±å¯ãæ¿èªãã/login ã«ãªãã€ã¬ã¯ãããŸã |
GET | /login | 302 | ããŒã ããŒãžã«ãªãã€ã¬ã¯ã |
GET | /user | 200 | ïŒãããã·ïŒJSON èªèšŒæžã¿ãŠãŒã¶ãŒ |
GET | /app.html | 200 | ããŒã ããŒãžã® HTML ããŒã·ã£ã« |
GET | /resource | 200 | ïŒãããã·ïŒJSON ã°ãªãŒãã£ã³ã° |
ïŒuaaïŒã§å§ãŸããªã¯ãšã¹ãã¯ãèªå¯ãµãŒããŒã«å¯Ÿãããã®ã§ãããç¡èŠããšããŒã¯ãããã¬ã¹ãã³ã¹ã¯ãXHR åŒã³åºãã§ Angular ã«ãã£ãŠåä¿¡ãããã¬ã¹ãã³ã¹ã§ããããã®ããŒã¿ãåŠçããŠããªããããããã¢ã«ãããããããŸãã"/user" ãªãœãŒã¹ã®å ŽåãèªèšŒããããŠãŒã¶ãŒãæ¢ããŸãããæåã®åŒã³åºãã«ã¯ååšããªãããããã®ã¬ã¹ãã³ã¹ã¯ãããããããŸãã
UI ã® "/trace" ãšã³ããã€ã³ãïŒäžã«ã¹ã¯ããŒã«ïŒã«ã"/user" ããã³ "/resource" ãžã®ãããã·ãããããã¯ãšã³ããªã¯ãšã¹ãã衚瀺ãããŸããèªèšŒã«ã¯ãCookie ã®ä»£ããã« remote:true ãšãã¢ã©ãŒããŒã¯ã³ã䜿çšãããŸãïŒããŒã IV ã®å Žåãšåæ§ïŒãSpring Cloud Security ããããåŠçããŠãããŸããã@EnableOAuth2Sso ãš @EnableZuulProxy ãããããšãèªèããããšã§ãïŒããã©ã«ãã§ïŒããŒã¯ã³ããããã·ãããããã¯ãšã³ãã«äžç¶ãããããšãããããŸããã
| åã®ã»ã¯ã·ã§ã³ãšåæ§ã«ãèªèšŒã®ã¯ãã¹ãªãŒããŒã®å¯èœæ§ããªãããã«ã"/trace" ã«å¥ã®ãã©ãŠã¶ãŒã䜿çšããŠã¿ãŠãã ããïŒããšãã°ãUI ã®ãã¹ãã« Chrome ã䜿çšããå Žå㯠Firefox ã䜿çšããŠãã ããïŒã |
ãã°ã¢ãŠããšã¯ã¹ããªãšã³ã¹
ããã°ã¢ãŠãããªã³ã¯ãã¯ãªãã¯ãããšããŠãŒã¶ãŒã UI ãµãŒããŒã§èªèšŒãããªããªããããããŒã ããŒãžã倿ŽãããïŒã°ãªãŒãã£ã³ã°ã衚瀺ãããªããªãïŒããšãããããŸããããã°ã€ã³ããã¯ãªãã¯ããŸãããå®éã«ã¯èªèšŒãµãŒããŒã§èªèšŒãšèªå¯ã®ãµã€ã¯ã«ã«æ»ãå¿ èŠã¯ãããŸããïŒãã°ã¢ãŠãããŠããªãããïŒããããæãŸãããŠãŒã¶ãŒãšã¯ã¹ããªãšã³ã¹ã§ãããã©ããã«ã€ããŠæèŠã¯åãããŸãããæªåé«ãããªãããŒãªåé¡ã§ãïŒã·ã³ã°ã«ãµã€ã³ã¢ãŠã: Science Direct ã®èšäº (è±èª) ããã³ Shibboleth ã®ããã¥ã¡ã³ã (è±èª) ïŒãçæ³çãªãŠãŒã¶ãŒãšã¯ã¹ããªãšã³ã¹ã¯æè¡çã«å®çŸå¯èœã§ã¯ãªããããããŸããããŸãããŠãŒã¶ãŒãæãããšãæ¬åœã«æãã§ããããšãçããªããã°ãªããªãããšããããŸãããããã°ã¢ãŠããããŠãã°ã¢ãŠãããŠã»ããããšããã®ã¯ç°¡åã§ããããäœãããã°ã¢ãŠãããŸããïŒ ãã® SSO ãµãŒããŒã«ãã£ãŠå¶åŸ¡ãããŠãããã¹ãŠã®ã·ã¹ãã ãããã°ã¢ãŠãããŸããããããšãããªãã ãã®ã·ã¹ãã ãããã°ã¢ãŠãããŸããïŒã ããã°ã¢ãŠãããªã³ã¯ãã¯ãªãã¯ããŸãããïŒãèå³ãããå Žåã¯ããã®ãã¥ãŒããªã¢ã«ã®åŸã®ã»ã¯ã·ã§ã³ã§è©³çްã«èª¬æãããŠããŸãã
çµè«
ããã§ãSpring Security ããã³ Angular ã¹ã¿ãã¯ã®æµ ããã¢ãŒã¯ã»ãŒçµäºã§ããçŸåšãUI/API ã²ãŒããŠã§ã€ããªãœãŒã¹ãµãŒããŒãèªå¯ãµãŒã㌠/ ããŒã¯ã³ä»äžè ã® 3 ã€ã®åå¥ã®ã³ã³ããŒãã³ãã§æç¢ºãªè²¬ä»»ãæã€åªããã¢ãŒããã¯ãã£ããããŸãããã¹ãŠã®ã¬ã€ã€ãŒã®éããžãã¹ã³ãŒãã®éãæå°éã«ãªããããå€ãã®ããžãã¹ããžãã¯ã䜿çšããŠå®è£ ãç¶æ¿ããã³æ¹åããå Žæãç°¡åã«ç¢ºèªã§ããŸããæ¬¡ã®ã¹ãããã¯ãèªèšŒãµãŒããŒã® UI ãæŽçããJavaScript ã¯ã©ã€ã¢ã³ãã§ã®ãã¹ããå«ãããã€ãã®ãã¹ãã远å ããããšã§ãããã 1 ã€ã®è峿·±ãã¿ã¹ã¯ã¯ããã¹ãŠã®ãã€ã©ãŒãã¬ãŒãã³ãŒããæœåºããSpring Security ãš Spring Session ã®èªåæ§æãããã³ Angular ããŒã¹ã®ããã²ãŒã·ã§ã³ã³ã³ãããŒã©ãŒçšã®ããã€ãã® webjar ãªãœãŒã¹ãå«ãã©ã€ãã©ãªïŒ "spring-security-angular" ãªã©ïŒã«é 眮ããããšã§ãããã®ã·ãªãŒãºã®ã»ã¯ã·ã§ã³ãèªãã ããAngular ãŸã㯠Spring Security ã®å éšåäœãåŠã³ãããšæã£ãŠãã人ã¯ãããããã£ããããã§ãããããããããã©ã®ããã«ããŸã飿ºããå°ãã®æ§æãã©ã®ããã«é·ããªãããç¥ãããå Žåã¯æ¹æ³ãããŸãããã°ãè¯ãçµéšãããã§ããããSpring Cloud ã¯æ°ããããããã®ãµã³ãã«ã¯äœææã«ã¹ãããã·ã§ãããå¿ èŠã§ãããããªãªãŒã¹åè£ããããGA ãªãªãŒã¹ãéããªããªãªãŒã¹ããããããããããã§ãã¯ããŠãGithub (è±èª) ãŸã㯠gitter.im (è±èª) ãä»ã [GitHub] (è±èª) ãŠãã£ãŒãããã¯ãéä¿¡ããŠãã ããã
ã·ãªãŒãºã®æ¬¡ã®ã»ã¯ã·ã§ã³ã§ã¯ãã¢ã¯ã»ã¹æ±ºå®ïŒèªèšŒä»¥å€ïŒã«ã€ããŠèª¬æããåããããã·ã®èåŸã§è€æ°ã® UI ã¢ããªã±ãŒã·ã§ã³ã䜿çšããŸãã
è£éº: èªå¯ãµãŒããŒã®ããŒãã¹ãã©ãã UI ããã³ JWT ããŒã¯ã³
ãã®ã¢ããªã±ãŒã·ã§ã³ã®å¥ã®ããŒãžã§ã³ã¯ãGithub ã®ãœãŒã¹ã³ãŒãã«ãããŸããGithub ã®ãœãŒã¹ã³ãŒãã«ã¯ãããŒã II (è±èª) ã®ãã°ã€ã³ããŒãžãšåãããã«ãããããªãã°ã€ã³ããŒãžãšãŠãŒã¶ãŒæ¿èªããŒãžãå®è£ ãããŠããŸãããŸããJWT (è±èª) ã䜿çšããŠããŒã¯ã³ããšã³ã³ãŒãããããã"/user" ãšã³ããã€ã³ãã䜿çšãã代ããã«ããªãœãŒã¹ãµãŒããŒã¯ããŒã¯ã³èªäœããååãªæ å ±ããã«ããŠåçŽãªèªèšŒãè¡ãããšãã§ããŸãããã©ãŠã¶ãŒã¯ã©ã€ã¢ã³ãã¯åŒãç¶ã UI ãµãŒããŒãä»ããŠãããã·ãããŠããã䜿çšããããããŠãŒã¶ãŒãèªèšŒãããŠãããã©ããã倿ã§ããŸãïŒå®éã®ã¢ããªã±ãŒã·ã§ã³ã§ã®ãªãœãŒã¹ãµãŒããŒãžã®åŒã³åºãã®å¯èœæ§ãšæ¯èŒããŠãããã»ã©é »ç¹ã«è¡ãå¿ èŠã¯ãããŸããïŒïŒã
è€æ°ã® UI ã¢ããªã±ãŒã·ã§ã³ãšã²ãŒããŠã§ã€
ãã®ã»ã¯ã·ã§ã³ã§ã¯ããã·ã³ã°ã«ããŒãžã¢ããªã±ãŒã·ã§ã³ãã§ Spring Security ã Angular (è±èª) ãšäœ¿çšããæ¹æ³ã«ã€ããŠåŒãç¶ã説æããŸããããã§ã¯ãSpring Session (è±èª) ãš Spring Cloud ã䜿çšããŠãããŒã II ãš IV ã§æ§ç¯ããã·ã¹ãã ã®æ©èœãçµã¿åãããå®éã«ã¯ãŸã£ããç°ãªã責任ãæã€ 3 ã€ã®ã·ã³ã°ã«ããŒãžã¢ããªã±ãŒã·ã§ã³ãæ§ç¯ããæ¹æ³ã瀺ããŸããç®çã¯ãAPI ãªãœãŒã¹ã ãã§ãªããããã¯ãšã³ããµãŒããŒãã UI ãããŒãããããã«äœ¿çšãããïŒããŒã IV ã®ãããªïŒã²ãŒããŠã§ã€ãæ§ç¯ããããšã§ããã²ãŒããŠã§ã€ã䜿çšããŠèªèšŒãããã¯ãšã³ãã«æž¡ãããšã«ãããããŒã II ã®ããŒã¯ã³ã®åé¡ãåçŽåããŸããæ¬¡ã«ãã·ã¹ãã ãç¶æ¿ããŠãã²ãŒããŠã§ã€ã§ ID ãšèªèšŒãå¶åŸ¡ããªãããããã¯ãšã³ãã§ããŒã«ã«ã§è©³çްãªã¢ã¯ã»ã¹æ±ºå®ãè¡ãæ¹æ³ã瀺ããŸããããã¯ãäžè¬ã«åæ£ã·ã¹ãã ãæ§ç¯ããããã®éåžžã«åŒ·åãªã¢ãã«ã§ãããæ§ç¯ããã³ãŒãã«æ©èœãå°å ¥ããéã«æ€èšã§ããå€ãã®å©ç¹ããããŸãã
ãªãã€ã³ããŒ: ãã®ã»ã¯ã·ã§ã³ã§ãµã³ãã«ã¢ããªã±ãŒã·ã§ã³ã䜿çšããŠããå Žåã¯ããã©ãŠã¶ãŒã®ãã£ãã·ã¥ã® Cookie ãš HTTP åºæ¬èªèšŒæ å ±ãå¿ ãã¯ãªã¢ããŠãã ãããChrome ã§ãããè¡ãæè¯ã®æ¹æ³ã¯ãæ°ããã·ãŒã¯ã¬ãããŠã£ã³ããŠãéãããšã§ãã
ã¿ãŒã²ããã¢ãŒããã¯ãã£
以äžã¯ãæåã«æ§ç¯ããåºæ¬ã·ã¹ãã ã®å³ã§ãã

ãã®ã·ãªãŒãºã®ä»ã®ãµã³ãã«ã¢ããªã±ãŒã·ã§ã³ãšåæ§ã«ãUIïŒHTML ããã³ JavaScriptïŒãšãªãœãŒã¹ãµãŒããŒããããŸããã»ã¯ã·ã§ã³ IV ã®ãµã³ãã«ãšåæ§ã«ãã²ãŒããŠã§ã€ããããŸãããããã§ã¯ UI ã®äžéšã§ã¯ãªããå¥åã®ãã®ã§ããUI ã¯äºå®äžããã¯ãšã³ãã®äžéšã«ãªããæ©èœãåæ§æããã³åå®è£ ããããã®éžæè¢ãããã«åºãããŸãããŸãããããã説æããä»ã®å©ç¹ããããããŸãã
ãã©ãŠã¶ãŒã¯ãã¹ãŠã®ããã«ã²ãŒããŠã§ã€ã«ã¢ã¯ã»ã¹ããããã¯ãšã³ãã®ã¢ãŒããã¯ãã£ãç¥ãå¿ èŠã¯ãããŸããïŒåºæ¬çã«ãããã¯ãšã³ããããããšãç¥ããŸããïŒããã®ã²ãŒããŠã§ã€ã§ãã©ãŠã¶ãŒãè¡ãããšã® 1 ã€ã«èªèšŒããããŸããã»ã¯ã·ã§ã³ II ã®ããã«ãŠãŒã¶ãŒåãšãã¹ã¯ãŒããéä¿¡ãã代ããã« Cookie ãååŸããŸããåŸç¶ã®ãªã¯ãšã¹ãã§ã¯ãCookie ãèªåçã«æç€ºãããã²ãŒããŠã§ã€ã¯ãããããã¯ãšã³ãã«æž¡ããŸããCookie ã®åãæž¡ããæå¹ã«ããããã«ãã¯ã©ã€ã¢ã³ãã§ã³ãŒããèšè¿°ããå¿ èŠã¯ãããŸãããããã¯ãšã³ã㯠Cookie ã䜿çšããŠèªèšŒãããã¹ãŠã®ã³ã³ããŒãã³ããã»ãã·ã§ã³ãå ±æããããããŠãŒã¶ãŒã«é¢ããåãæ å ±ãå ±æããŸãããããšæ¯èŒããŠãã²ãŒããŠã§ã€ã§ Cookie ãã¢ã¯ã»ã¹ããŒã¯ã³ã«å€æããå¿ èŠããããã¢ã¯ã»ã¹ããŒã¯ã³ã¯ãã¹ãŠã®ããã¯ãšã³ãã³ã³ããŒãã³ãã§åå¥ã«ãã³ãŒãããå¿ èŠãããã»ã¯ã·ã§ã³ V ãšæ¯èŒããŠãã ããã
ã»ã¯ã·ã§ã³ IV ãšåæ§ã«ãã²ãŒããŠã§ã€ã¯ã¯ã©ã€ã¢ã³ããšãµãŒããŒéã®å¯Ÿè©±ãç°¡çŽ åããã»ãã¥ãªãã£ãåŠçããããã®å°ããæç¢ºã«å®çŸ©ããã衚é¢ãæäŸããŸããäŸ: ã¯ãã¹ãªãªãžã³ãªãœãŒã¹å ±æ [Mozilla] ã«ã€ããŠå¿é ããå¿ èŠã¯ãããŸãããããã¯ééããç¯ãããããããæè¿ãããŸãã
ãã«ããããããžã§ã¯ãå šäœã®ãœãŒã¹ã³ãŒãã¯ããã® Github (è±èª) ã«ãããããå¿ èŠã«å¿ããŠãããžã§ã¯ããè€è£œããããããçŽæ¥äœæ¥ããããšãã§ããŸãããã®ã·ã¹ãã ã®çµäºç¶æ ã«ã¯è¿œå ã®ã³ã³ããŒãã³ãïŒ"double-admin"ïŒããããããä»ã¯ç¡èŠããŠãã ããã
ããã¯ãšã³ãã®æ§ç¯
ãã®ã¢ãŒããã¯ãã£ã§ã¯ãããã¯ãšã³ãã¯ã»ã¯ã·ã§ã³ III ã§æ§ç¯ãã "spring-session" [GitHub] (è±èª) ãµã³ãã«ã«éåžžã«äŒŒãŠããŸãããå®éã«ã¯ãã°ã€ã³ããŒãžã¯å¿ èŠãããŸãããããã§å¿ èŠãªãã®ã«å°éããæãç°¡åãªæ¹æ³ã¯ãããããã»ã¯ã·ã§ã³ III ããããªãœãŒã¹ããµãŒããŒãã³ããŒããã»ã¯ã·ã§ã³ I ã®ãåºæ¬ã [GitHub] (è±èª) ãµã³ãã«ãã UI ãååŸããããšã§ãããåºæ¬ãUI ããããã§å¿ èŠãª UI ã«å°éããã«ã¯ãããã€ãã®äŸåé¢ä¿ã远å ããã ãã§ãïŒã»ã¯ã·ã§ã³ III ã§ Spring Session [GitHub] (è±èª) ãæåã«äœ¿çšãããšãã®ããã«ïŒã
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-redis</artifactId>
</dependency>ãã㯠UI ã«ãªã£ãŠããããã"/resource" ãšã³ããã€ã³ãã¯å¿ èŠãããŸããããããè¡ããšãéåžžã«åçŽãª Angular ã¢ããªã±ãŒã·ã§ã³ïŒãåºæ¬ããµã³ãã«ãšåãïŒãäœæããããã®åäœã®ãã¹ããšæšè«ãå€§å¹ ã«ç°¡çŽ åãããŸãã
æåŸã«ããã®ãµãŒããŒãããã¯ãšã³ããšããŠå®è¡ããããããïŒapplication.properties ã§ïŒãªãã¹ã³ããããã©ã«ã以å€ã®ããŒããæå®ããŸãã
server.port: 8081
security.sessions: NEVER ãããã³ã³ãã³ã application.properties å
šäœã§ããå Žåãã¢ããªã±ãŒã·ã§ã³ã¯å®å
šã§ããã"user" ãšåŒã°ãããŠãŒã¶ãŒãã©ã³ãã ãªãã¹ã¯ãŒãã§ã¢ã¯ã»ã¹ã§ããŸãããèµ·åæã«ã³ã³ãœãŒã«ã«ïŒãã°ã¬ãã« INFO ã§ïŒåºåãããŸãã"security.sessions" èšå®ã¯ãSpring Security ãèªèšŒããŒã¯ã³ãšã㊠Cookie ãåãå
¥ããããCookie ããã§ã«ååšããªãéã Cookie ãäœæããªãããšãæå³ããŸãã
ãªãœãŒã¹ãµãŒããŒ
ãªãœãŒã¹ãµãŒããŒã¯ãæ¢åã®ãµã³ãã«ã® 1 ã€ããç°¡åã«çæã§ããŸããããã¯ãã»ã¯ã·ã§ã³ III ã® "spring-session" ãªãœãŒã¹ãµãŒããŒãšåãã§ãã忣ã»ãã·ã§ã³ããŒã¿ãååŸããããã® "/resource" ãšã³ããã€ã³ã Spring Session ã ãã§ãããã®ãµãŒããŒã«ãªãã¹ã³ããããã©ã«ã以å€ã®ããŒããæãããã»ãã·ã§ã³ã§èªèšŒãæ€çŽ¢ã§ããããã«ãããããããããå¿
èŠã§ãïŒapplication.properties ã§ïŒã
server.port: 9000
security.sessions: NEVERãã®ãã¥ãŒããªã¢ã«ã®æ°ããæ©èœã§ããã¡ãã»ãŒãžãªãœãŒã¹ãžã®å€æŽã POST ããäºå®ã§ããããã¯ãããã¯ãšã³ãã§ CSRF ä¿è·ãå¿ èŠã«ãªãããšãæå³ããSpring Security ã Angular ãšããŸãåäœãããããã«éåžžã®ããªãã¯ãè¡ãå¿ èŠããããŸãã
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
}宿ãããµã³ãã«ã¯ããã¡ãã® github (è±èª) ã«ãããŸãã
ã²ãŒããŠã§ã€
ã²ãŒããŠã§ã€ã®åæå®è£
ïŒããããåäœããå¯èœæ§ã®ããæãåçŽãªãã®ïŒã§ã¯ã空㮠Spring Boot Web ã¢ããªã±ãŒã·ã§ã³ãååŸã㊠@EnableZuulProxy ã¢ãããŒã·ã§ã³ã远å ããã ãã§ããã»ã¯ã·ã§ã³ I ã§èŠãããã«ããããè¡ãã«ã¯ããã€ãã®æ¹æ³ãããããã® 1 ã€ã¯ Spring Initializr ã䜿çšããŠã¹ã±ã«ãã³ãããžã§ã¯ããçæããããšã§ããããã«ç°¡åãªã®ã¯ãSpring Cloud Initializr (è±èª) ã䜿çšããããšã§ãããããã¯åãããšã§ãããSpring Cloud (è±èª) ã¢ããªã±ãŒã·ã§ã³çšã§ããã»ã¯ã·ã§ã³ I ãšåãäžé£ã®ã³ãã³ãã©ã€ã³æäœã䜿çšããŸãã
$ mkdir gateway && cd gateway
$ curl https://cloud-start.spring.io/starter.tgz -d style=web \
-d style=security -d style=cloud-zuul -d name=gateway \
-d style=redis | tar -xzvf -次ã«ããã®ãããžã§ã¯ãïŒããã©ã«ãã§ã¯éåžžã® Maven Java ãããžã§ã¯ãïŒããæ°ã«å ¥ãã® IDE ã«ã€ã³ããŒãããããã³ãã³ãã©ã€ã³ã§ãã¡ã€ã«ãš "mvn" ãæäœããã ãã§ããããããè¡ãããå Žå㯠github (è±èª) ã«ããŒãžã§ã³ããããŸããããŸã å¿ èŠã®ãªãè¿œå æ©èœãããã€ããããŸãã
空çœã® Initializr ã¢ããªã±ãŒã·ã§ã³ããå§ããŠãSpring Session äŸåé¢ä¿ã远å ããŸãïŒäžèšã® UI ã®ããã«ïŒãã²ãŒããŠã§ã€ãå®è¡ããæºåã¯ã§ããŠããŸãããããã¯ãšã³ããµãŒãã¹ã«ã€ããŠã¯ãŸã ç¥ããªããããapplication.yml ã§ã»ããã¢ããããŠã¿ãŸãããïŒäžèšã® curl ãè¡ã£ãå Žå㯠application.properties ããååã倿ŽããŸãïŒã
zuul:
sensitive-headers:
routes:
ui:
url: http://localhost:8081
resource:
url: http://localhost:9000
security:
user:
password:
password
sessions: ALWAYS ãããã·ã«ã¯ 2 ã€ã®ã«ãŒãããããã©ã¡ãã sensitive-headers ããããã£ã䜿çšã㊠Cookie ãããŠã³ã¹ããªãŒã ã§æž¡ããŸããã©ã¡ãã UI ãšãªãœãŒã¹ãµãŒããŒã« 1 ã€ãã€ãããããã©ã«ãã®ãã¹ã¯ãŒããšã»ãã·ã§ã³æ°žç¶æ§æŠç¥ãèšå®ããŠããŸãïŒSpring Security ã«åžžã«ã»ãã·ã§ã³ãäœæããããã«äŒããŸãïŒèªèšŒïŒãèªèšŒãšã»ãã·ã§ã³ãã²ãŒããŠã§ã€ã§ç®¡çããå¿
èŠãããããããã®æåŸã®ãããã¯éèŠã§ãã
皌å
çŸåšã3 ã€ã®ããŒãã§å®è¡ããã 3 ã€ã®ã³ã³ããŒãã³ãããããŸãããã©ãŠã¶ãŒã http://localhost:8080/ui/ ã«åãããšãHTTP Basic ãã£ã¬ã³ãžãååŸããå¿ èŠããããããŠãŒã¶ãŒ / ãã¹ã¯ãŒããïŒã²ãŒããŠã§ã€ã®è³æ Œæ å ±ïŒãšããŠèªèšŒã§ããŸãããããè¡ããšãããã¯ãšã³ãçµç±ã§ UI ã«æšæ¶ã衚瀺ãããŸãããããã·ãä»ããŠãªãœãŒã¹ãµãŒããŒã«åŒã³åºããŸãã
äžéšã®éçºè ããŒã«ã䜿çšãããšããã©ãŠã¶ãŒãšããã¯ãšã³ãéã®çžäºäœçšããã©ãŠã¶ãŒã§ç¢ºèªã§ããŸãïŒéåžžãF12 ã¯ãããéããããã©ã«ãã§ Chrome ã§åäœããFirefox ã§ãã©ã°ã€ã³ãå¿ èŠã«ãªãå ŽåããããŸãïŒãæŠèŠã¯æ¬¡ã®ãšããã§ãã
| åè© | ãã¹ | ã¹ããŒã¿ã¹ | ã¬ã¹ãã³ã¹ |
|---|---|---|---|
GET | /ui/ | 401 | èªèšŒã®ããã®ãã©ãŠã¶ãŒããã³ãã |
GET | /ui/ | 200 | index.html |
GET | /ui/*.js | 200 | Angular ã¢ã»ãã |
GET | /ui/js/hello.js | 200 | ã¢ããªã±ãŒã·ã§ã³ããžã㯠|
GET | /ui/user | 200 | èªèšŒ |
GET | /resource/ | 200 | JSON ã°ãªãŒãã£ã³ã° |
ãã©ãŠã¶ãŒã¯ããŒã ããŒãžã®èªã¿èŸŒã¿ãåäžã®å¯Ÿè©±ãšããŠæ±ãããã401 ã衚瀺ãããªãå ŽåããããŸãããã¹ãŠã®ãªã¯ãšã¹ãã¯ãããã·ãããŸãïŒç®¡ççšã®ã¢ã¯ãã¥ãšãŒã¿ãŒãšã³ããã€ã³ããè¶ ããŠãã²ãŒããŠã§ã€ã«ã¯ãŸã ã³ã³ãã³ãããããŸããïŒã
ããŸããããŸããïŒ 2 ã€ã®ããã¯ãšã³ããµãŒããŒãããããã® 1 ã€ã¯ UI ã§ããããããããç¬ç«ããæ©èœãæã¡ãç¬ç«ããŠãã¹ãããããšãã§ãããããã¯å¶åŸ¡ããèªèšŒãæ§æããã»ãã¥ã¢ã²ãŒããŠã§ã€ãšäžç·ã«æ¥ç¶ãããŸãããã©ãŠã¶ãŒãããã¯ãšã³ãã«ã¢ã¯ã»ã¹ã§ããªãå Žåã¯éèŠã§ã¯ãããŸããïŒå®éãç©ççãªã»ãã¥ãªãã£ãããã«å¶åŸ¡ã§ãããããããããå©ç¹ã§ãïŒã
ãã°ã€ã³ãã©ãŒã ã®è¿œå
ã»ã¯ã·ã§ã³ I ã®ãåºæ¬ããµã³ãã«ã®ããã«ããã°ã€ã³ãã©ãŒã ãã²ãŒããŠã§ã€ã«è¿œå ã§ããŸããã»ã¯ã·ã§ã³ II ããã³ãŒããã³ããŒããŸãããããè¡ããšããã²ãŒããŠã§ã€ã«ããã€ãã®åºæ¬çãªããã²ãŒã·ã§ã³èŠçŽ ã远å ããããšãã§ããããããŠãŒã¶ãŒã¯ãããã·ã® UI ããã¯ãšã³ããžã®ãã¹ãç¥ãå¿
èŠããããŸãããããã§ã¯ãæåã«éçã¢ã»ããããåäžãUI ããã²ãŒããŠã§ã€ã«ã³ããŒããã¡ãã»ãŒãžã¬ã³ããªã³ã°ãåé€ããŠããã°ã€ã³ãã©ãŒã ãããŒã ããŒãžïŒ<app/> ã®ã©ããïŒã«æ¿å
¥ããŸãã
<div class="container" [hidden]="authenticated">
<form role="form" (submit)="login()">
<div class="form-group">
<label for="username">Username:</label> <input type="text"
class="form-control" id="username" name="username"
[(ngModel)]="credentials.username" />
</div>
<div class="form-group">
<label for="password">Password:</label> <input type="password"
class="form-control" id="password" name="password"
[(ngModel)]="credentials.password" />
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>ã¡ãã»ãŒãžã®ã¬ã³ããªã³ã°ã®ä»£ããã«ãçŽ æµãªå€§ããªããã²ãŒã·ã§ã³ãã¿ã³ããããŸãã
<div class="container" [hidden]="!authenticated">
<a class="btn btn-primary" href="/ui/">Go To User Interface</a>
</div>github ã§ãµã³ãã«ãèŠãŠããå Žåãããã°ã¢ãŠãããã¿ã³ã®ããæå°éã®ããã²ãŒã·ã§ã³ããŒããããŸããã¹ã¯ãªãŒã³ã·ã§ããã®ãã°ã€ã³ãã©ãŒã ã¯æ¬¡ã®ãšããã§ãã

ãã°ã€ã³ãã©ãŒã ããµããŒãããã«ã¯ã<form/> ã§å®£èšãã login() 颿°ãå®è£
ããã³ã³ããŒãã³ããå«ã TypeScript ãå¿
èŠã§ãããŸããauthenticated ãã©ã°ãèšå®ããŠããŠãŒã¶ãŒãèªèšŒãããŠãããã©ããã«å¿ããŠããŒã ããŒãžã®ã¬ã³ããªã³ã°ãç°ãªãããã«ããå¿
èŠããããŸããäŸïŒ
include::src/app/app.component.tslogin() 颿°ã®å®è£
ã¯ã»ã¯ã·ã§ã³ II ã®å®è£
ã«äŒŒãŠããŸãã
ãã®åçŽãªã¢ããªã±ãŒã·ã§ã³ã«ã¯ã³ã³ããŒãã³ãã 1 ã€ãããªããããself ã䜿çšã㊠authenticated ãã©ã°ãæ ŒçŽã§ããŸãã
ãã®åŒ·åãããã²ãŒããŠã§ã€ãå®è¡ããå ŽåãUI ã® URL ãèŠããå¿ èŠã¯ãªããããŒã ããŒãžãèªã¿èŸŒãã§ãªã³ã¯ããã©ãããšãã§ããŸããèªèšŒæžã¿ãŠãŒã¶ãŒã®ããŒã ããŒãžã¯æ¬¡ã®ãšããã§ãã

ããã¯ãšã³ãã§ã®ãã现ããã¢ã¯ã»ã¹æ±ºå®
ãããŸã§ã®ã¢ããªã±ãŒã·ã§ã³ã¯ã颿°ã«ã¯ã»ã¯ã·ã§ã³ III ãŸãã¯ã»ã¯ã·ã§ã³ IV ã®ãã®ãšéåžžã«ãã䌌ãŠããŸãããå°çšã®ã²ãŒããŠã§ã€ã远å ãããŠããŸããäœåãªã¬ã€ã€ãŒã®å©ç¹ã¯ãŸã æããã§ã¯ãªããããããŸããããã·ã¹ãã ãå°ãæ¡åŒµããããšã§åŒ·èª¿ã§ããŸãããŠãŒã¶ãŒãã¡ã€ã³ UI ã§ã³ã³ãã³ããã管çãããããã«ããã®ã²ãŒããŠã§ã€ã䜿çšããŠå¥ã®ããã¯ãšã³ã UI ãå ¬éãããã®æ©èœãžã®ã¢ã¯ã»ã¹ãç¹å¥ãªããŒã«ãæã€ãŠãŒã¶ãŒã«å¶éãããšããŸãããããã·ã®èåŸã«ã管çãã¢ããªã±ãŒã·ã§ã³ã远å ãããšãã·ã¹ãã ã¯æ¬¡ã®ããã«ãªããŸãã

application.yml ã®ã²ãŒããŠã§ã€ã«ã¯ãæ°ããã³ã³ããŒãã³ãïŒAdminïŒãšæ°ããã«ãŒãããããŸãã
zuul:
sensitive-headers:
routes:
ui:
url: http://localhost:8081
admin:
url: http://localhost:8082
resource:
url: http://localhost:9000 "USER" ããŒã«ã®ãŠãŒã¶ãŒãæ¢åã® UI ã䜿çšã§ããããšã¯ã管çã¢ããªã±ãŒã·ã§ã³ã«ã¢ã¯ã»ã¹ããããã« "ADMIN" ããŒã«ãå¿
èŠã§ãããšããäºå®ãšåæ§ã«ãã²ãŒããŠã§ã€ããã¯ã¹ïŒç·è²ã®æåïŒã®äžã®ãããã¯å³ã«ç€ºãããŠããŸãã"ADMIN" ããŒã«ã®ã¢ã¯ã»ã¹æ±ºå®ã¯ãã²ãŒããŠã§ã€ã§é©çšã§ããŸãããã®å ŽåãWebSecurityConfigurerAdapter ã«è¡šç€ºããããã管çã¢ããªã±ãŒã·ã§ã³èªäœã«é©çšã§ããŸãïŒãããè¡ãæ¹æ³ã«ã€ããŠã¯åŸè¿°ããŸãïŒã
æåã«ãæ°ãã Spring Boot ã¢ããªã±ãŒã·ã§ã³ãäœæããããUI ãã³ããŒããŠç·šéããŸããUI ã¢ããªã§å€æŽããå¿ èŠã¯ãããŸããããæåã«ååã倿Žããå¿ èŠããããŸãã宿ããã¢ããªã¯ããã® Github (è±èª) ã«ãããŸãã
管çã¢ããªã±ãŒã·ã§ã³å ã§ã"READER" ããŒã«ãš "WRITER" ããŒã«ãåºå¥ããç£æ»è ã§ãããŠãŒã¶ãŒã«ãã¡ã€ã³ç®¡çè ãŠãŒã¶ãŒã«ãã倿Žã®è¡šç€ºãèš±å¯ã§ããããã«ãããšããŸããããã¯ãã现ããã¢ã¯ã»ã¹æ±ºå®ã§ãããã«ãŒã«ã¯ããã¯ãšã³ãã¢ããªã±ãŒã·ã§ã³ã§ã®ã¿ç¥ãããç¥ãããã¹ãã§ããã²ãŒããŠã§ã€ã§ã¯ããŠãŒã¶ãŒã¢ã«ãŠã³ãã«å¿ èŠãªããŒã«ãããããšã確èªããã ãã§ããããã®æ å ±ã¯å©çšå¯èœã§ãããã²ãŒããŠã§ã€ã¯ãããè§£éããæ¹æ³ãç¥ãå¿ èŠã¯ãããŸãããã²ãŒããŠã§ã€ã§ã¯ããŠãŒã¶ãŒã¢ã«ãŠã³ããäœæããŠããµã³ãã«ã¢ããªã±ãŒã·ã§ã³ãèªå·±å®çµåã«ä¿ã¡ãŸãã
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
public void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user").password("password").roles("USER")
.and()
.withUser("admin").password("admin").roles("USER", "ADMIN", "READER", "WRITER")
.and()
.withUser("audit").password("audit").roles("USER", "ADMIN", "READER");
}
}"admin" ãŠãŒã¶ãŒã¯ 3 ã€ã®æ°ããããŒã«ïŒ "ADMIN"ã"READER"ã"WRITER" ïŒã§æ¡åŒµããã"ADMIN" ã¢ã¯ã»ã¹æš©ãæã€ "audit" ãŠãŒã¶ãŒã远å ãããŸãããã"WRITER" ã¢ã¯ã»ã¹æš©ã¯ãããŸããã
| æ¬çªã·ã¹ãã ã§ã¯ããŠãŒã¶ãŒã¢ã«ãŠã³ãããŒã¿ã¯ãSpring æ§æã§ããŒãã³ãŒããããŠããªãããã¯ãšã³ãããŒã¿ããŒã¹ïŒã»ãšãã©ã®å Žåããã£ã¬ã¯ããªãµãŒãã¹ïŒã§ç®¡çãããŸãããã®ãããªããŒã¿ããŒã¹ã«æ¥ç¶ãããµã³ãã«ã¢ããªã±ãŒã·ã§ã³ã¯ãSpring Security ãµã³ãã« [GitHub] (è±èª) ãªã©ã®ã€ã³ã¿ãŒãããã§ç°¡åã«èŠã€ããããšãã§ããŸãã |
ã¢ã¯ã»ã¹ã®æ±ºå®ã¯ã管çã¢ããªã±ãŒã·ã§ã³ã§è¡ããŸãã"ADMIN" ããŒã«ïŒãã®ããã¯ãšã³ãã«ã°ããŒãã«ã«å¿ èŠïŒã«ã€ããŠã¯ãSpring Security ã§ãããè¡ããŸãã
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
...
.authorizeRequests()
.antMatchers("/index.html", "/").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
...
}
}"READER" ããŒã«ãš "WRITER" ããŒã«ã®å Žåãã¢ããªã±ãŒã·ã§ã³èªäœãåå²ãããŸããã¢ããªã±ãŒã·ã§ã³ã¯ JavaScript ã«å®è£ ãããŠãããããããã§ã¢ã¯ã»ã¹ã決å®ããå¿ èŠããããŸãããããè¡ã 1 ã€ã®æ¹æ³ã¯ãèšç®ããããã¥ãŒãã«ãŒã¿ãŒãä»ããŠåã蟌ãŸããããŒã ããŒãžãæã€ããšã§ãã
<div class="container">
<h1>Admin</h1>
<router-outlet></router-outlet>
</div>ã«ãŒãã¯ãã³ã³ããŒãã³ããããŒãããããšãã«èšç®ãããŸãã
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
user: {};
constructor(private app: AppService, private http: HttpClient, private router: Router) {
app.authenticate(response => {
this.user = response;
this.message();
});
}
logout() {
this.http.post('logout', {}).subscribe(function() {
this.app.authenticated = false;
this.router.navigateByUrl('/login');
});
}
message() {
if (!this.app.authenticated) {
this.router.navigate(['/unauthenticated']);
} else {
if (this.app.writer) {
this.router.navigate(['/write']);
} else {
this.router.navigate(['/read']);
}
}
}
...
}ã¢ããªã±ãŒã·ã§ã³ãæåã«è¡ãããšã¯ããŠãŒã¶ãŒãèªèšŒãããŠãããã©ããã確èªãããŠãŒã¶ãŒããŒã¿ãèŠãŠã«ãŒããèšç®ããããšã§ããã«ãŒãã¯ã¡ã€ã³ã¢ãžã¥ãŒã«ã§å®£èšãããŸãã
const routes: Routes = [
{ path: '', pathMatch: 'full', redirectTo: 'read'},
{ path: 'read', component: ReadComponent},
{ path: 'write', component: WriteComponent},
{ path: 'unauthenticated', component: UnauthenticatedComponent},
{ path: 'changes', component: ChangesComponent}
]; ãããã®åã³ã³ããŒãã³ãïŒåã«ãŒãã« 1 ã€ïŒã¯åå¥ã«å®è£
ããå¿
èŠããããŸããäŸãšã㊠ReadComponent ãæ¬¡ã«ç€ºããŸãã
import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Component({
templateUrl: './read.component.html'
})
export class ReadComponent {
greeting = {};
constructor(private http: HttpClient) {
http.get('/resource').subscribe(data => this.greeting = data);
}
}<h1>Greeting</h1>
<div>
<p>The ID is {{greeting.id}}</p>
<p>The content is {{greeting.content}}</p>
</div>WriteComponent ãåæ§ã§ãããããã¯ãšã³ãã§ã¡ãã»ãŒãžã倿Žãã圢åŒããããŸãã
import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Component({
templateUrl: './write.component.html'
})
export class WriteComponent {
greeting = {};
constructor(private http: HttpClient) {
this.http.get('/resource').subscribe(data => this.greeting = data);
}
update() {
this.http.post('/resource', {content: this.greeting['content']}).subscribe(response => {
this.greeting = response;
});
}
}<form (submit)="update()">
<p>The ID is {{greeting.id}}</p>
<div class="form-group">
<label for="username">Content:</label> <input type="text"
class="form-control" id="content" name="content" [(ngModel)]="greeting.content"/>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>AppService ã¯ãã«ãŒããèšç®ããããã®ããŒã¿ãæäŸããå¿
èŠããããããauthenticate() 颿°ã§ã¯æ¬¡ã®ããã«è¡šç€ºãããŸãã
http.get('/user').subscribe(function(response) {
var user = response.json();
if (user.name) {
self.authenticated = true;
self.writer = user.roles && user.roles.indexOf("ROLE_WRITER")>0;
} else {
self.authenticated = false;
self.writer = false;
}
callback && callback(response);
}) ããã¯ãšã³ãã§ãã®æ©èœããµããŒãããã«ã¯ã/user ãšã³ããã€ã³ããå¿
èŠã§ããã¡ã€ã³ã¢ããªã±ãŒã·ã§ã³ã¯ã©ã¹ã§:
@SpringBootApplication
@RestController
public class AdminApplication {
@RequestMapping("/user")
public Map<String, Object> user(Principal user) {
Map<String, Object> map = new LinkedHashMap<String, Object>();
map.put("name", user.getName());
map.put("roles", AuthorityUtils.authorityListToSet(((Authentication) user)
.getAuthorities()));
return map;
}
public static void main(String[] args) {
SpringApplication.run(AdminApplication.class, args);
}
}| ããŒã«åã¯ã"ROLE_" ãã¬ãã£ãã¯ã¹ãä»ãã "/user" ãšã³ããã€ã³ãããè¿ããããããä»ã®çš®é¡ã®æš©éãšåºå¥ã§ããŸãïŒãã㯠Spring Security ã®ãã®ã§ãïŒã"ROLE_" ãã¬ãã£ãã¯ã¹ã¯ JavaScript ã§å¿ èŠã§ããããããŒã«ããæäœã®çŠç¹ã§ããããšãã¡ãœããåããæãã㪠Spring Security æ§æã§ã¯å¿ èŠãããŸããã |
管ç UI ããµããŒãããããã®ã²ãŒããŠã§ã€ã®å€æŽ
ã²ãŒããŠã§ã€ã§ãããŒã«ã䜿çšããŠã¢ã¯ã»ã¹ã決å®ããããïŒç®¡ç UI ãžã®ãªã³ã¯ãæ¡ä»¶ä»ãã§è¡šç€ºã§ããããã«ããããïŒãã²ãŒããŠã§ã€ã® "/user" ãšã³ããã€ã³ãã«ããããŒã«ãã远å ããå¿
èŠããããŸãããããæŽã£ãããJavaScript ã远å ããŠãçŸåšã®ãŠãŒã¶ãŒã "ADMIN" ã§ããããšã瀺ããã©ã°ãèšå®ã§ããŸããauthenticated() 颿°ã®å Žå:
this.http.get('user', {headers: headers}).subscribe(data => {
this.authenticated = data && data['name'];
this.user = this.authenticated ? data['name'] : '';
this.admin = this.authenticated && data['roles'] && data['roles'].indexOf('ROLE_ADMIN') > -1;
}); ãŸãããŠãŒã¶ãŒããã°ã¢ãŠããããšãã« admin ãã©ã°ã false ã«ãªã»ããããå¿
èŠããããŸãã
this.logout = function() {
http.post('logout', {}).subscribe(function() {
self.authenticated = false;
self.admin = false;
});
}ãã®åŸãHTML ã§æ¡ä»¶ä»ãã§æ°ãããªã³ã¯ã衚瀺ã§ããŸãã
<div class="container" [hidden]="!authenticated">
<a class="btn btn-primary" href="/ui/">Go To User Interface</a>
</div>
<br />
<div class="container" [hidden]="!authenticated || !admin">
<a class="btn btn-primary" href="/admin/">Go To Admin Interface</a>
</div>ãã¹ãŠã®ã¢ããªãå®è¡ããhttp://localhost:8080 ã«ç§»åããŠçµæã確èªããŸãããã¹ãŠãæ£åžžã«æ©èœããçŸåšèªèšŒãããŠãããŠãŒã¶ãŒã«å¿ã㊠UI ã倿ŽãããŸãã
ã©ãããŠããã«ïŒ
ããã§ã2 ã€ã®ç¬ç«ãããŠãŒã¶ãŒã€ã³ã¿ãŒãã§ãŒã¹ãšããã¯ãšã³ããªãœãŒã¹ãµãŒããŒãåããããŠããªå°ããªã·ã¹ãã ãã§ããŸããããããã¯ãã¹ãŠãã²ãŒããŠã§ã€ã§åãèªèšŒã«ãã£ãŠä¿è·ãããŠããŸããã²ãŒããŠã§ã€ããã€ã¯ããããã·ãšããŠæ©èœãããšããäºå®ã«ãããããã¯ãšã³ãã»ãã¥ãªãã£ã®é¢å¿äºã®å®è£ ãéåžžã«ç°¡åã«ãªããããžãã¹äžã®é¢å¿äºã«èªç±ã«éäžã§ããŸããSpring Session ã®äœ¿çšã«ãããïŒåã³ïŒèšå€§ãªéã®æéãšæœåšçãªãšã©ãŒãåé¿ãããŸããã
匷åãªæ©èœã¯ãããã¯ãšã³ããä»»æã®çš®é¡ã®èªèšŒãç¬èªã«æã€ããšãã§ããããšã§ãïŒããšãã°ãç©çã¢ãã¬ã¹ãšäžé£ã®ããŒã«ã«èªèšŒæ å ±ãããã£ãŠããå Žåã¯ãUI ã«çŽæ¥ã¢ã¯ã»ã¹ã§ããŸãïŒãã²ãŒããŠã§ã€ã¯ããŠãŒã¶ãŒãèªèšŒããããã¯ãšã³ãã®ã¢ã¯ã»ã¹ã«ãŒã«ãæºããã¡ã¿ããŒã¿ããŠãŒã¶ãŒã«å²ãåœãŠãããšãã§ããéããå®å šã«ç¡é¢ä¿ãªäžé£ã®å¶çŽã課ããŸããããã¯ãããã¯ãšã³ãã³ã³ããŒãã³ããåå¥ã«éçºããã³ãã¹ãã§ããåªããèšèšã§ããå¿ èŠã«å¿ããŠãã²ãŒããŠã§ã€ã§ã®èªèšŒã®ããã«å€éš OAuth2 ãµãŒããŒïŒã»ã¯ã·ã§ã³ V ãªã©ããŸãã¯ãŸã£ããç°ãªããã®ïŒã«æ»ãããšãã§ããããã¯ãšã³ãã«è§Šããå¿ èŠã¯ãããŸããã
ãã®ã¢ãŒããã¯ãã£ã®ããŒãã¹æ©èœïŒèªèšŒãå¶åŸ¡ããåäžã®ã²ãŒããŠã§ã€ãããã³ãã¹ãŠã®ã³ã³ããŒãã³ãã«ãããå ±æã»ãã·ã§ã³ããŒã¯ã³ïŒã¯ãã»ã¯ã·ã§ã³ V ã§å®è£ ãå°é£ã§ãããšå€æããæ©èœã§ãããã·ã³ã°ã«ãã°ã¢ãŠãããããªãŒã§æäŸãããããšã§ããããæ£ç¢ºã«ã¯ãã·ã³ã°ã«ãã°ã¢ãŠãã®ãŠãŒã¶ãŒãšã¯ã¹ããªãšã³ã¹ã«å¯Ÿããç¹å®ã®ã¢ãããŒãã宿ããã·ã¹ãã ã§èªåçã«äœ¿çšå¯èœã«ãªããŸãããŠãŒã¶ãŒã UIïŒã²ãŒããŠã§ã€ãUI ããã¯ãšã³ãã管çããã¯ãšã³ãïŒãããã°ã¢ãŠããããšããã¹ãŠã®ãã°ã¢ãŠããã®ä»ãåã ã® UI ãããã°ã¢ãŠããæ©èœãåãæ¹æ³ã§å®è£ ãããšä»®å®ããŸãïŒã»ãã·ã§ã³ãç¡å¹ã«ããŸãïŒã
Angular ã¢ããªã±ãŒã·ã§ã³ã®ãã¹ã
ãã®ã»ã¯ã·ã§ã³ã§ã¯ããã·ã³ã°ã«ããŒãžã¢ããªã±ãŒã·ã§ã³ãã§ Spring Security ã Angular (è±èª) ãšäœ¿çšããæ¹æ³ã«ã€ããŠåŒãç¶ã説æããŸããããã§ã¯ãAngular ãã¹ããã¬ãŒã ã¯ãŒã¯ã䜿çšããŠãã¯ã©ã€ã¢ã³ãåŽã³ãŒãã®åäœãã¹ããäœæããã³å®è¡ããæ¹æ³ã瀺ããŸããã¢ããªã±ãŒã·ã§ã³ã®åºæ¬çãªæ§æèŠçŽ ã«è¿œãã€ãããæåã®ã»ã¯ã·ã§ã³ãèªãã§ãŒããããã«ãããããGithub ã®ãœãŒã¹ã³ãŒãã«çŽæ¥ (è±èª) é²ãããšãã§ããŸãïŒããŒã I ãšåããœãŒã¹ã³ãŒãã§ããããã¹ãã远å ãããŠããŸãïŒããã®ã»ã¯ã·ã§ã³ã«ã¯ãå®éã«ã¯ Spring ãŸã㯠Spring Security ã䜿çšããã³ãŒãã¯ã»ãšãã©ãããŸããããéåžžã® Angular ã³ãã¥ããã£ãªãœãŒã¹ã§ã¯ç°¡åã«èŠã€ããããªãæ¹æ³ã§ã¯ã©ã€ã¢ã³ãåŽã®ãã¹ããæ±ããŸããSpring ãŠãŒã¶ãŒã
ãªãã€ã³ããŒ: ãã®ã»ã¯ã·ã§ã³ã§ãµã³ãã«ã¢ããªã±ãŒã·ã§ã³ã䜿çšããŠããå Žåã¯ããã©ãŠã¶ãŒã®ãã£ãã·ã¥ã® Cookie ãš HTTP åºæ¬èªèšŒæ å ±ãå¿ ãã¯ãªã¢ããŠãã ãããChrome ã§ã¯ãåäžãµãŒããŒã§ãããè¡ãæè¯ã®æ¹æ³ã¯ãæ°ããã·ãŒã¯ã¬ãããŠã£ã³ããŠãéãããšã§ãã
仿§ãæžã
ãåºæ¬ãã¢ããªã±ãŒã·ã§ã³ã®ãã¢ããªãã³ã³ããŒãã³ãã¯éåžžã«ã·ã³ãã«ãªã®ã§ã培åºçã«ãã¹ãããã®ã«ããã»ã©æéã¯ããããŸãããã³ãŒãã®ãªãã€ã³ããŒã¯æ¬¡ã®ãšããã§ãã
include::basic/src/app/app.component.ts çŽé¢ããäž»ãªèª²é¡ã¯ããã¹ãã§ http ãªããžã§ã¯ããæäŸããããšã§ãããã®ãããã³ã³ããŒãã³ãã§ã® http ãªããžã§ã¯ãã®äœ¿çšã¡ãœããã«ã€ããŠã¢ãµãŒã·ã§ã³ãäœæã§ããŸããå®éããã®èª²é¡ã«çŽé¢ããåã§ãã£ãŠããã³ã³ããŒãã³ãã€ã³ã¹ã¿ã³ã¹ãäœæã§ããå¿
èŠããããŸãããã®ãããããŒãæã«äœãèµ·ãããããã¹ãã§ããŸãã以äžã«ãã®ã¡ãœããã瀺ããŸãã
ng new ããäœæãããã¢ããªã® Angular ãã«ãã«ã¯ããã§ã«ä»æ§ãšãããå®è¡ããããã®æ§æããããŸããçæããã仿§ã¯ "src/app" ã«ãããæ¬¡ã®ããã«å§ãŸããŸãã
import { TestBed, async } from '@angular/core/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [],
declarations: [
AppComponent
]
}).compileComponents();
}));
it('should create the app', async(() => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app).toBeTruthy();
}));
...
}ãã®éåžžã«åºæ¬çãªãã¹ãã¹ã€ãŒãã«ã¯ã次ã®éèŠãªèŠçŽ ããããŸãã
颿°ã䜿çšããŠããã¹ã察象ã®
describe()ïŒãã®å Žå㯠"AppComponent" ïŒããã®é¢æ°å ã§ãAngular ã³ã³ããŒãã³ããããŒããã
beforeEach()ã³ãŒã«ããã¯ãæäŸããŸããæ¯ãèãã¯
it()ã®åŒã³åºããéããŠè¡šçŸãããŸããããã§ã¯ãæåŸ ãäœã§ããããèšèã§è¡šæããã¢ãµãŒã·ã§ã³ãè¡ã颿°ãæäŸããŸãããã¹ãç°å¢ã¯ãä»ã®äœããçºçããåã«åæåãããŸããããã¯ãã»ãšãã©ã® Angular ã¢ããªã®å®åã§ãã
ããã§ã®ãã¹ã颿°ã¯éåžžã«åçŽãªãããå®éã«ã¯ã³ã³ããŒãã³ããååšããããšãã¢ãµãŒãããã ããªã®ã§ãããã倱æãããšãã¹ãã¯å€±æããŸãã
åäœãã¹ãã®æ¹å: HTTP ããã¯ãšã³ãã®ã¢ãã¯
仿§ã補åã°ã¬ãŒãã«æ¹åããã«ã¯ãã³ã³ãããŒã©ãŒã®ããŒãæã«äœãèµ·ãããã«ã€ããŠå®éã«ã¢ãµãŒãããå¿
èŠããããŸããhttp.get() ãåŒã³åºããããåäœãã¹ãã®ããã ãã«ã¢ããªã±ãŒã·ã§ã³å
šäœãå®è¡ããå¿
èŠããªãããã«ããã®åŒã³åºããã¢ãã¯ããå¿
èŠããããŸãããã®ããã«ã¯ãAngular HttpClientTestingModule ã䜿çšããŸãã
Unresolved directive in testing.adoc - include::basic/src/app/app.component.spec[indent=0]ããã§ã®æ°ããéšåã¯æ¬¡ã®ãšããã§ãã
beforeEach()ã®TestBedã®ã€ã³ããŒããšããŠã®HttpClientTestingModuleã®å®£èšããã¹ã颿°ã§ã¯ãã³ã³ããŒãã³ããäœæããåã«ããã¯ãšã³ãã«æåŸ å€ãèšå®ãã"resource/" ãžã®åŒã³åºããæåŸ ããããã«äŒããã¬ã¹ãã³ã¹ãã©ãããããäŒããŸãã
仿§ã®å®è¡
ããã¹ããå®è¡ãããã³ãŒãã¯ããããžã§ã¯ãã®ã»ããã¢ããæã«äœæããã䟿å©ãªã¹ã¯ãªããã䜿çšã㊠./ng test ïŒãŸã㯠./ng buildïŒãå®è¡ã§ããŸãããã㯠Maven ã©ã€ããµã€ã¯ã«ã®äžéšãšããŠãå®è¡ãããããã./mvnw install ã¯ãã¹ããå®è¡ããè¯ãæ¹æ³ã§ããããŸããCI ãã«ãã§äœãèµ·ãããã§ãã
ãšã³ãããŒãšã³ãã®ãã¹ã
Angular ã«ã¯ããã©ãŠã¶ãŒãšçæããã JavaScript ã䜿çšããããšã³ãããŒãšã³ããã¹ããçšã«ã»ããã¢ãããããæšæºãã«ãããããŸãããããã¯ãæäžäœã® e2e ãã£ã¬ã¯ããªã«ã仿§ããšããŠæžã蟌ãŸããŸãããã®ãã¥ãŒããªã¢ã«ã®ãã¹ãŠã®ãµã³ãã«ã«ã¯ãMaven ã©ã€ããµã€ã¯ã«ã§å®è¡ãããéåžžã«åçŽãªãšã³ãããŒãšã³ããã¹ããå«ãŸããŠããŸãïŒãããã£ãŠã"ui" ã¢ããªã§ mvn install ãå®è¡ãããšããã©ãŠã¶ãŒãŠã£ã³ããŠããããã¢ãã衚瀺ãããŸãïŒã
çµè«
Javascript ã®åäœãã¹ããå®è¡ã§ããããšã¯ãææ°ã® Web ã¢ããªã±ãŒã·ã§ã³ã§ã¯éèŠã§ããããã®ã·ãªãŒãºã§ã¯ãããŸã§ç¡èŠïŒãŸãã¯åé¿ïŒãããããã¯ã§ããä»åã®èšäºã§ã¯ããã¹ããèšè¿°ããæ¹æ³ãéçºæã«ãã¹ããå®è¡ããæ¹æ³ãããã«éèŠãªããšãšããŠãç¶ç¶çã€ã³ãã°ã¬ãŒã·ã§ã³èšå®ã®åºæ¬çãªèŠçŽ ã玹ä»ããŸãããåã£ãã¢ãããŒãã¯ãã¹ãŠã®äººã«é©ããŠããããã§ã¯ãªããããå¥ã®æ¹æ³ã§ããã®ãæ°ã«ããªãã§ãã ãããããã§è¡ã£ãæ¹æ³ã¯ãããããåŸæ¥ã® Java ãšã³ã¿ãŒãã©ã€ãºéçºè ã«ãšã£ãŠå¿«é©ã§ãããæ¢åã®ããŒã«ãããã»ã¹ãšããŸãçµ±åã§ããããããã®ã«ããŽãªã«ãããªããåºçºç¹ãšããŠåœ¹ç«ã€ãšæããŸããAngular ããã³ Jasmine ã䜿çšãããã¹ãã®ãã®ä»ã®äŸã¯ãã€ã³ã¿ãŒãããäžã®å€ãã®å Žæã§èŠã€ããããšãã§ããŸãããæåã®ã³ãŒã«ãã€ã³ãã¯ãã®ã·ãªãŒãºã®ãåäžããµã³ãã« [GitHub] (è±èª) ã§ããå¯èœæ§ããããŸãããã®ãã¥ãŒããªã¢ã«ã®ãåºæ¬ããµã³ãã«çšã«èšè¿°ããå¿ èŠãããã³ãŒãã
OAuth2 ã¯ã©ã€ã¢ã³ãã¢ããªã±ãŒã·ã§ã³ããã®ãã°ã¢ãŠã
ãã®ã»ã¯ã·ã§ã³ã§ã¯ããã·ã³ã°ã«ããŒãžã¢ããªã±ãŒã·ã§ã³ãã§ Spring Security ã Angular (è±èª) ãšäœ¿çšããæ¹æ³ã«ã€ããŠåŒãç¶ã説æããŸããããã§ã¯ãOAuth2 ãµã³ãã«ãååŸããå¥ã®ãã°ã¢ãŠããšã¯ã¹ããªãšã³ã¹ã远å ããæ¹æ³ã瀺ããŸããOAuth2 ã·ã³ã°ã«ãµã€ã³ãªã³ãå®è£ ããå€ãã®äººã ã¯ãããããã«ããã°ã¢ãŠãããæ¹æ³ã解決ããããã®ããºã«ãããããšã«æ°ä»ããŠããŸããïŒ ãããããºã«ã§ããçç±ã¯ããããè¡ãããã®åäžã®æ£ããæ¹æ³ããªãããšã§ãããéžæãã解決çã¯ãæ¢ããŠãããŠãŒã¶ãŒãšã¯ã¹ããªãšã³ã¹ãšãåŒãåãããè€éãã®éã«ãã£ãŠæ±ºå®ãããŸããè€éãã®çç±ã¯ãã·ã¹ãã å ã«æœåšçã«è€æ°ã®ãã©ãŠã¶ãŒã»ãã·ã§ã³ãããããã¹ãŠãç°ãªãããã¯ãšã³ããµãŒããŒã§ãããšããäºå®ã«èµ·å ããããããŠãŒã¶ãŒããã®ãã¡ã® 1 ã€ãããã°ã¢ãŠããããšãä»ã®ãŠãŒã¶ãŒã¯ã©ããªãã§ããããïŒ ããã¯ãã¥ãŒããªã¢ã«ã® 9 çªç®ã®ã»ã¯ã·ã§ã³ã§ãããã¢ããªã±ãŒã·ã§ã³ã®åºæ¬çãªæ§æèŠçŽ ã«è¿œãã€ãããæåã®ã»ã¯ã·ã§ã³ãèªãã§ãŒããããã«ãããããGithub ã®ãœãŒã¹ã³ãŒãã«çŽæ¥ (è±èª) é²ãããšãã§ããŸãã
ãã°ã¢ãŠããã¿ãŒã³
ãã®ãã¥ãŒããªã¢ã«ã® oauth2 ãµã³ãã«ã®ãã°ã¢ãŠãã«é¢ãããŠãŒã¶ãŒãšã¯ã¹ããªãšã³ã¹ã¯ãUI ã¢ããªããã¯ãã°ã¢ãŠãããŸãããèªèšŒãµãŒããŒããã¯ãã°ã¢ãŠãããªããããUI ã¢ããªã«å床ãã°ã€ã³ãããšãã«ãèªèšŒãµãŒããŒã¯è³æ Œæ
å ±ãååºŠèŠæ±ããŸãããèªèšŒãµãŒããŒãå€éšã®å Žåãããã¯å®å
šã«äºæãããŠãããæ£åžžã§ãããæãŸãããã®ã§ããGoogle ããã®ä»ã®å€éšèªèšŒãµãŒããŒãããã€ããŒã¯ãä¿¡é Œã§ããªãã¢ããªã±ãŒã·ã§ã³ãããµãŒããŒãããã°ã¢ãŠãããããšãæãã§ããŸããããèš±å¯ããŸãããããããèªèšŒãµãŒããŒãå®éã«èªèšŒãµãŒããŒã§ããå Žåãããã¯æé«ã®ãŠãŒã¶ãŒãšã¯ã¹ããªãšã³ã¹ã§ã¯ãããŸãããUI ãšåãã·ã¹ãã ã®äžéšã§ãã
倧ãŸãã«èšãã°ãOAuth2 ã¯ã©ã€ã¢ã³ããšããŠèªèšŒããã UI ã¢ããªãããã°ã¢ãŠãããããã® 3 ã€ã®ãã¿ãŒã³ããããŸãã
å€éšèªèšŒãµãŒã㌠(EAããªãªãžãã«ã®ãµã³ãã«)ããŠãŒã¶ãŒã¯èªèšŒãµãŒããŒããµãŒãããŒãã£ãšããŠèªèããŸã (ããšãã°ãèªèšŒã« Facebook ãŸã㯠Google ã䜿çšããŸã)ãã¢ããªã»ãã·ã§ã³ã®çµäºæã«èªèšŒãµãŒããŒãããã°ã¢ãŠãããããããŸããããã¹ãŠã®è£å©éã®æ¿èªãå¿ èŠã§ãããã®ãã¥ãŒããªã¢ã«ã®
oauth2(ããã³oauth2-vanilla) ãµã³ãã«ã¯ããã®ãã¿ãŒã³ãå®è£ ããŠããŸããã²ãŒããŠã§ã€ããã³å éšèªèšŒãµãŒããŒïŒGIAïŒããã°ã¢ãŠãããå¿ èŠãããã®ã¯ 2 ã€ã®ã¢ããªã®ã¿ã§ããããããã¯ãŠãŒã¶ãŒãèªèããåãã·ã¹ãã ã®äžéšã§ããéåžžããã¹ãŠã®æš©éä»äžãèªåæ¿èªããå¿ èŠããããŸãã
ã·ã³ã°ã«ãã°ã¢ãŠãïŒSLïŒã1 ã€ã®èªèšŒãµãŒããŒãšè€æ°ã® UI ã¢ããªã¯ãã¹ãŠç¬èªã®èªèšŒãåããŠããããŠãŒã¶ãŒã 1 ã€ãããã°ã¢ãŠããããšããã¹ãŠã®ã¢ããªãããã«è¿œåŸããå¿ èŠããããŸãããããã¯ãŒã¯ããŒãã£ã·ã§ã³ãšãµãŒããŒã®é害ã®ããã«ãåçŽãªå®è£ ã§å€±æããå¯èœæ§ãé«ã - åºæ¬çã«ã¯ãã°ããŒãã«ã«äžè²«ããã¹ãã¬ãŒãžãå¿ èŠã§ãã
å€éšèªèšŒãµãŒããŒã䜿çšããŠããå Žåã§ããèªèšŒãå¶åŸ¡ããã¢ã¯ã»ã¹å¶åŸ¡ã®å
éšã¬ã€ã€ãŒïŒèªèšŒãµãŒããŒããµããŒãããŠããªãã¹ã³ãŒããããŒã«ãªã©ïŒã远å ãããå ŽåããããŸããæ¬¡ã«ãèªèšŒã« EA ã䜿çšããããšããå§ãããŸãããå¿
èŠãªè¿œå ã®è©³çްãããŒã¯ã³ã«è¿œå ã§ããå
éšèªèšŒãµãŒããŒãçšæããŸãããã®ä»ã® OAuth2 ãã¥ãŒããªã¢ã« [GitHub] (è±èª) ã® auth-server ãµã³ãã«ã¯ãéåžžã«ç°¡åãªæ¹æ³ã§ãããè¡ãæ¹æ³ã瀺ããŠããŸãããã®åŸãå
éšèªèšŒãµãŒããŒãå«ãã·ã¹ãã ã« GIA ãŸã㯠SL ãã¿ãŒã³ãé©çšã§ããŸãã
EA ãå¿ èŠãªãå Žåã®ãªãã·ã§ã³ã¯æ¬¡ã®ãšããã§ãã
ãã©ãŠã¶ãŒã¯ã©ã€ã¢ã³ãã® authserver ããã³ UI ã¢ããªãããã°ã¢ãŠãããŸããã·ã³ãã«ãªã¢ãããŒãã§ãããããã€ãã®æ³šææ·±ã CRSF ããã³ CORS æ§æã§åäœããŸããSL ãªã
ããŒã¯ã³ãå©çšå¯èœã«ãªã次第ãauthserver ãããã°ã¢ãŠãããŸããauthserver ã®ã»ãã·ã§ã³ Cookie ããªããããããŒã¯ã³ãååŸãã UI ã§ã®å®è£ ã¯å°é£ã§ããSpring OAuth [GitHub] (è±èª) ã«ã¯ãè峿·±ãã¢ãããŒããç€ºãæ©èœãªã¯ãšã¹ã [GitHub] (è±èª) ããããŸããèªèšŒã³ãŒããçæããããšããã«ãèªèšŒãµãŒããŒã®ã»ãã·ã§ã³ãç¡å¹ã«ããŸããGithub ã®èª²é¡ã«ã¯ãã»ãã·ã§ã³ã®ç¡å¹åãå®è£ ããåŽé¢ãå«ãŸããŠããŸããã
HandlerInterceptorãšããŠè¡ãæ¹ãç°¡åã§ããSL ãªãUI ãšåãã²ãŒããŠã§ã€ãä»ããŠèªèšŒãµãŒããŒããããã·ãã1 ã€ã® Cookie ã§ã·ã¹ãã å šäœã®ç¶æ ã管çããã®ã«ååã§ããããšãæã¿ãŸããå ±æã»ãã·ã§ã³ãååšããªãéãæ©èœããŸãããå ±æã»ãã·ã§ã³ã¯ããªããžã§ã¯ããããçšåºŠç¡å¹ã«ããŸãïŒãã以å€ã®å Žåãauthserver ã®ã»ãã·ã§ã³ã¹ãã¬ãŒãžã¯ãããŸããïŒãã»ãã·ã§ã³ããã¹ãŠã®ã¢ããªéã§å ±æãããå Žåã«ã®ã¿ SLã
ã²ãŒããŠã§ã€ã® Cookie ãªã¬ãŒãèªèšŒã®çã®ãœãŒã¹ãšããŠã²ãŒããŠã§ã€ã䜿çšããŠããŸããèªèšŒãµãŒããŒã¯ããã©ãŠã¶ãŒã§ã¯ãªã Cookie ã管çãããããå¿ èŠãªãã¹ãŠã®ç¶æ ãä¿æããŠããŸãããã©ãŠã¶ãŒã«è€æ°ã®ãµãŒããŒããã® Cookie ãå«ãŸããããšã¯ãããŸãããSL ãªã
ããŒã¯ã³ãã°ããŒãã«èªèšŒãšããŠäœ¿çšãããŠãŒã¶ãŒã UI ã¢ããªãããã°ã¢ãŠããããšãã«ããŒã¯ã³ãç¡å¹ã«ããŸããæ¬ ç¹: ã¯ã©ã€ã¢ã³ãã¢ããªã«ãã£ãŠããŒã¯ã³ãç¡å¹ã«ããå¿ èŠããããŸãããããã¯å®éã«ã¯æå³ããããã®ã§ã¯ãããŸãããSL ã¯å¯èœã§ãããéåžžã®å¶çŽãé©çšãããŸãã
authserver ã§ïŒãŠãŒã¶ãŒããŒã¯ã³ã«å ããŠïŒã°ããŒãã«ã»ãã·ã§ã³ããŒã¯ã³ãäœæããã³ç®¡çããŸãããã㯠OpenId æ¥ç¶ (è±èª) ãæ¡çšããã¢ãããŒãã§ãããSL ã«ããã€ãã®ãªãã·ã§ã³ãæäŸããŸãããããã€ãã®è¿œå ã®æ©æ¢°ãå¿ èŠã«ãªããŸããéåžžã®åæ£ã·ã¹ãã ã®å¶éãã圱é¿ãåãããªãã·ã§ã³ã¯ãããŸããããããã¯ãŒã¯ãšã¢ããªã±ãŒã·ã§ã³ããŒããå®å®ããŠããªãå Žåãå¿ èŠã«å¿ããŠãã¹ãŠã®åå è éã§ãã°ã¢ãŠãã·ã°ãã«ãå ±æããããšããä¿èšŒã¯ãããŸããããã°ã¢ãŠã仿§ã¯ãã¹ãŠãã©ãã圢åŒã®ãŸãŸã§ããã仿§ãžã®ãªã³ã¯ã¯æ¬¡ã®ãšããã§ã: ã»ãã·ã§ã³ç®¡ç (è±èª) ãããã³ããã£ã³ãã«ãã°ã¢ãŠã (è±èª) ãããã¯ãã£ã³ãã«ãã°ã¢ãŠã (è±èª) ã
SL ãå°é£ãŸãã¯äžå¯èœãªå Žåããã¹ãŠã® UI ãåäžã®ã²ãŒããŠã§ã€ã®èåŸã«é 眮ããæ¹ãããå Žåãããããšã«æ³šæããŠãã ãããæ¬¡ã«ãããç°¡å㪠GIA ã䜿çšããŠãäžåç£å šäœããã®ãã°ã¢ãŠããå¶åŸ¡ã§ããŸãã
GIA ãã¿ãŒã³ã«ããŸãåœãŠã¯ãŸãæãç°¡å㪠2 ã€ã®ãªãã·ã§ã³ã¯ããã¥ãŒããªã¢ã«ãµã³ãã«ã§æ¬¡ã®ããã«å®è£
ã§ããŸãïŒoauth2 ãµã³ãã«ãååŸããŠãããããäœæ¥ããŸãïŒã
ãã©ãŠã¶ãŒããã®äž¡æ¹ã®ãµãŒããŒã®ãã°ã¢ãŠã
UI ã¢ããªããã°ã¢ãŠãããããšããã« authserver ãããã°ã¢ãŠãããã³ãŒãããã©ãŠã¶ãŒã¯ã©ã€ã¢ã³ãã«è¿œå ããã®ã¯éåžžã«ç°¡åã§ããäŸ:
logout() {
this.http.post('logout', {}).finally(() => {
self.authenticated = false;
this.http.post('http://localhost:9999/uaa/logout', {}, {withCredentials:true})
.subscribe(() => {
console.log('Logged out');
});
}).subscribe();
}; ãã®ãµã³ãã«ã§ã¯ãauthserver ãã°ã¢ãŠããšã³ããã€ã³ã URL ã JavaScript ã«ããŒãã³ãŒãããŸããããå¿
èŠã«å¿ããŠå€éšåããã®ã¯ç°¡åã§ããã»ãã·ã§ã³ Cookie ãäžç·ã«éä¿¡ãããããauthserver ã«çŽæ¥ POST ããå¿
èŠããããŸããXHR ãªã¯ãšã¹ãã¯ãwithCredentials:true ãæç¢ºã«ãªã¯ãšã¹ãããå Žåã«ã®ã¿ãCookie ãæ·»ä»ããããã©ãŠã¶ãŒããéä¿¡ãããŸãã
éã«ããªã¯ãšã¹ãã¯å¥ã®ãã¡ã€ã³ããéä¿¡ãããããããµãŒããŒã§ã¯ CORS æ§æãå¿
èŠã§ããäŸ: WebSecurityConfigurerAdapter
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.requestMatchers().antMatchers("/login", "/logout", "/oauth/authorize", "/oauth/confirm_access")
.and()
.cors().configurationSource(configurationSource())
...
}
private CorsConfigurationSource configurationSource() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOrigin("*");
config.setAllowCredentials(true);
config.addAllowedHeader("X-Requested-With");
config.addAllowedHeader("Content-Type");
config.addAllowedMethod(HttpMethod.POST);
source.registerCorsConfiguration("/logout", config);
return source;
}"/logout" ãšã³ããã€ã³ãã«ã¯ç¹å¥ãªåŠçãæœãããŠããŸããã©ã®çºä¿¡å ããã§ãåŒã³åºãããšãã§ããè³æ Œæ å ±ïŒCookie ãªã©ïŒã®éä¿¡ãæç€ºçã«èš±å¯ããŸããèš±å¯ãããããããŒã¯ãAngular ããµã³ãã«ã¢ããªã§éä¿¡ããããããŒã®ã¿ã§ãã
Angular ã¯ã¯ãã¹ãã¡ã€ã³ãªã¯ãšã¹ãã§ X-XSRF-TOKEN ããããŒãéä¿¡ããªããããCORS èšå®ã«å ããŠããã°ã¢ãŠããšã³ããã€ã³ãã® CSRF ãç¡å¹ã«ããå¿
èŠããããŸããauthserver ã¯ãããŸã§ CSRF èšå®ãå¿
èŠãšããŸããã§ãããããã°ã¢ãŠããšã³ããã€ã³ãã®ç¡èŠãç°¡åã«è¿œå ã§ããŸãã
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.ignoringAntMatchers("/logout/**")
...
}| CSRF ä¿è·ãããããããããšã¯å®éã«ã¯ãå§ãã§ããŸãããããã®å¶éããããŠãŒã¹ã±ãŒã¹ã§ã¯èš±å®¹ããæºåãã§ããŠãããããããŸããã |
UI ã¢ããªã¯ã©ã€ã¢ã³ããš authserver ã® 2 ã€ã®ç°¡åãªå€æŽã«ãããUI ã¢ããªãããã°ã¢ãŠããããšãå床ãã°ã€ã³ãããšãåžžã«ãã¹ã¯ãŒãã®å ¥åãæ±ããããããšãããããŸãã
ãã 1 ã€ã®äŸ¿å©ãªå€æŽç¹ã¯ãOAuth2 ã¯ã©ã€ã¢ã³ããèªåæ¿èªããããã«èšå®ããããšã§ããããã«ããããŠãŒã¶ãŒã¯ããŒã¯ã³ã®ä»äžãæ¿èªããå¿
èŠããªããªããŸããããã¯ããŠãŒã¶ãŒãå¥ã®ã·ã¹ãã ãšããŠèªèããªãå
éšèªèšŒãµãŒããŒã§ã¯äžè¬çã§ããAuthorizationServerConfigurerAdapter ã§ã¯ãã¯ã©ã€ã¢ã³ãã®åæåæã«ãã©ã°ãå¿
èŠã§ãã
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory().withClient("acme")
...
.autoApprove(true);
}èªèšŒãµãŒããŒã®ã»ãã·ã§ã³ãç¡å¹å
ãã°ã¢ãŠããšã³ããã€ã³ãã§ CSRF ä¿è·ãæŸæ£ããããªãå Žåã¯ãä»ã®ç°¡åãªã¢ãããŒãã詊ãããšãã§ããŸããããã¯ãããŒã¯ã³ãèš±å¯ããããšããã«ïŒå®éã¯èªèšŒã³ãŒããšãªããšããã«ãèªèšŒãµãŒããŒã®ãŠãŒã¶ãŒã»ãã·ã§ã³ãç¡å¹ã«ããçæãããŸãïŒããããéåžžã«ç°¡åã«å®è£
ã§ããŸããoauth2 ãµã³ãã«ããå§ããŠãHandlerInterceptor ã OAuth2 ãšã³ããã€ã³ãã«è¿œå ããã ãã§ãã
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
...
endpoints.addInterceptor(new HandlerInterceptorAdapter() {
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
if (modelAndView != null
&& modelAndView.getView() instanceof RedirectView) {
RedirectView redirect = (RedirectView) modelAndView.getView();
String url = redirect.getUrl();
if (url.contains("code=") || url.contains("error=")) {
HttpSession session = request.getSession(false);
if (session != null) {
session.invalidate();
}
}
}
}
});
} ãã®ã€ã³ã¿ãŒã»ãã¿ãŒã¯ RedirectView ãæ¢ããŸããããã¯ããŠãŒã¶ãŒãã¯ã©ã€ã¢ã³ãã¢ããªã«ãªãã€ã¬ã¯ããããŠããããšã瀺ãã·ã°ãã«ã§ãããå Žæã«èªèšŒã³ãŒããŸãã¯ãšã©ãŒãå«ãŸããŠãããã©ããã確èªããŸããæé»çãªèš±å¯ã䜿çšããŠããå Žåã¯ã"token =" ã远å ã§ããŸãã
ãã®ç°¡åãªå€æŽã«ãããèªèšŒãããšããã«ãauthserver ã®ã»ãã·ã§ã³ã¯ãã§ã«åæ¢ããŠãããããã¯ã©ã€ã¢ã³ãããã»ãã·ã§ã³ã詊è¡ããã³ç®¡çããå¿
èŠã¯ãããŸãããUI ã¢ããªãããã°ã¢ãŠãããŠããå床ãã°ã€ã³ãããšãèªèšŒãµãŒããŒã¯ãŠãŒã¶ãŒãèªèãããè³æ Œæ
å ±ã®å
¥åãæ±ããŸãããã®ãã¿ãŒã³ã¯ããã®ãã¥ãŒããªã¢ã«ã®ãœãŒã¹ã³ãŒã [GitHub] (è±èª) ã® oauth2-logout ãµã³ãã«ã«ãã£ãŠå®è£
ããããã®ã§ãããã®ã¢ãããŒãã®æ¬ ç¹ã¯ãæ¬åœã®ã·ã³ã°ã«ãµã€ã³ãªã³ããã¯ããªãããšã§ããã·ã¹ãã ã®äžéšã§ããä»ã®ã¢ããªã¯ãauthserver ã»ãã·ã§ã³ã忢ããŠããããšãçºèŠããå床èªèšŒãèŠæ±ããå¿
èŠããããŸããè€æ°ã®ã¢ããªãããå Žåã®åªãããŠãŒã¶ãŒãšã¯ã¹ããªãšã³ã¹ã
çµè«
ãã®ã»ã¯ã·ã§ã³ã§ã¯ãOAuth2 ã¯ã©ã€ã¢ã³ãã¢ããªã±ãŒã·ã§ã³ãããã°ã¢ãŠãããããã®ããã€ãã®ç°ãªããã¿ãŒã³ãå®è£ ããæ¹æ³ãèŠãŠããŸããïŒéå§ç¹ãšããŠãã¥ãŒããªã¢ã«ã®ã»ã¯ã·ã§ã³ V ã®ã¢ããªã±ãŒã·ã§ã³ã䜿çšïŒãä»ã®ãã¿ãŒã³ã®ãªãã·ã§ã³ã«ã€ããŠã説æããŸããããããã®ãªãã·ã§ã³ã¯ãã¹ãŠãç¶²çŸ ããŠããããã§ã¯ãããŸãããããã¬ãŒããªãã®é©åãªã¢ã€ãã¢ãšããŠãŒã¹ã±ãŒã¹ã«æé©ãªãœãªã¥ãŒã·ã§ã³ãæ€èšããããã®ããã€ãã®ããŒã«ãæäŸããå¿ èŠããããŸãããã®ã»ã¯ã·ã§ã³ã«ã¯ JavaScript ã®è¡ã 2ã3 è¡ãããªããAngular ã«åºæã®ãã®ã§ã¯ãªãã£ãããïŒXHR ãªã¯ãšã¹ãã«ãã©ã°ã远å ããŸãïŒããã®ã¬ã€ãã®ãµã³ãã«ã¢ããªã®çãç¯å²ãè¶ ããŠãã¹ãŠã®ã¬ãã¹ã³ãšãã¿ãŒã³ãé©çšã§ããŸããç¹°ãè¿ãçºçããããŒãã¯ãè€æ°ã® UI ã¢ããªããããåäžã®èªèšŒãµãŒããŒã«äœããã®æ¬ é¥ãããåŸåãããã·ã³ã°ã«ãã°ã¢ãŠãïŒSLïŒãžã®ãã¹ãŠã®ã¢ãããŒãã§ã: ã§ããããšã¯ããŠãŒã¶ãŒã®äžå¿«æãæå°éã«æããã¢ãããŒããéžæããããšã§ããå éš authserver ãšå€ãã®ã³ã³ããŒãã³ãã§æ§æãããã·ã¹ãã ãããå Žåãåäžã®ã·ã¹ãã ã®ããã«ãŠãŒã¶ãŒã«æããå¯äžã®ã¢ãŒããã¯ãã£ã¯ããã¹ãŠã®ãŠãŒã¶ãŒã€ã³ã¿ã©ã¯ã·ã§ã³ã®ã²ãŒããŠã§ã€ã§ããå¯èœæ§ããããŸãã
æ°ããã¬ã€ããäœæããããæ¢åã®ã¬ã€ãã«è²¢ç®ãããã§ããïŒ æçš¿ã¬ã€ãã©ã€ã³ãåç §ããŠãã ãã [GitHub] (è±èª) ã
| ãã¹ãŠã®ã¬ã€ãã¯ãã³ãŒãçšã® ASLv2 ã©ã€ã»ã³ã¹ãããã³ããã¥ã¡ã³ãçšã®åž°å±ãNoDerivatives ã¯ãªãšã€ãã£ãã³ã¢ã³ãºã©ã€ã»ã³ã¹ (è±èª) ã§ãªãªãŒã¹ãããŠããŸãã |