Mã hóa html góc

Trong hướng dẫn này, chúng tôi trình bày một số tính năng hay của Spring Security, Spring Boot và Angular hoạt động cùng nhau để mang lại trải nghiệm người dùng thú vị và an toàn. Người mới bắt đầu sử dụng Spring và Angular có thể truy cập được, nhưng cũng có rất nhiều chi tiết sẽ hữu ích cho các chuyên gia trong cả hai lĩnh vực này. Đây thực sự là phần đầu tiên trong một loạt các phần về Spring Security và Angular, với các tính năng mới được trình bày liên tiếp trong mỗi phần. Chúng tôi sẽ cải thiện ứng dụng trong các phần sau và các phần tiếp theo, nhưng những thay đổi chính sau phần này là về kiến ​​trúc hơn là chức năng

Show

Mùa xuân và Ứng dụng Trang đơn

HTML5, các tính năng phong phú dựa trên trình duyệt và "ứng dụng một trang" là những công cụ cực kỳ có giá trị đối với các nhà phát triển hiện đại, nhưng bất kỳ tương tác có ý nghĩa nào cũng sẽ liên quan đến máy chủ phụ trợ, cũng như nội dung tĩnh (HTML, CSS và JavaScript) mà chúng tôi sẽ hướng tới . Máy chủ phụ trợ có thể đóng bất kỳ hoặc tất cả các vai trò. cung cấp nội dung tĩnh, đôi khi (nhưng không thường xuyên như hiện nay) hiển thị HTML động, xác thực người dùng, đảm bảo quyền truy cập vào các tài nguyên được bảo vệ và (cuối cùng nhưng không kém phần quan trọng) tương tác với JavaScript trong trình duyệt thông qua HTTP và JSON (đôi khi được gọi là REST

Spring luôn là một công nghệ phổ biến để xây dựng các tính năng phụ trợ (đặc biệt là trong doanh nghiệp) và với sự ra đời của Spring Boot, mọi thứ chưa bao giờ dễ dàng hơn thế. Chúng ta hãy xem cách xây dựng một ứng dụng trang đơn mới từ con số không bằng cách sử dụng Spring Boot, Angular và Twitter Bootstrap. Không có lý do cụ thể nào để chọn ngăn xếp cụ thể đó, nhưng nó khá phổ biến, đặc biệt là với khu vực bầu cử Spring cốt lõi trong các cửa hàng Java dành cho doanh nghiệp, vì vậy đây là một điểm khởi đầu đáng giá

Tạo một dự án mới

Chúng tôi sẽ từng bước tạo ứng dụng này một cách chi tiết, để bất kỳ ai không hoàn toàn thành thạo với Spring và Angular đều có thể theo dõi những gì đang xảy ra. Nếu bạn muốn đi sâu vào cuộc rượt đuổi, bạn có thể xem ứng dụng đang hoạt động ở đâu và xem tất cả chúng ăn khớp với nhau như thế nào. Có nhiều tùy chọn khác nhau để tạo một dự án mới

Mã nguồn cho dự án hoàn chỉnh mà chúng ta sẽ xây dựng có trong Github tại đây, vì vậy bạn chỉ cần sao chép dự án và làm việc trực tiếp từ đó nếu muốn. Sau đó nhảy đến

Sử dụng Curl

Cách dễ nhất để tạo một dự án mới để bắt đầu là thông qua Spring Boot Initializr. e. g. sử dụng curl trên hệ thống giống UN*X

$ mkdir ui && cd ui
$ curl https://start.spring.io/starter.tgz -d dependencies=web,security -d name=ui | tar -xzvf -

Sau đó, bạn có thể nhập dự án đó (theo mặc định là dự án Maven Java bình thường) vào IDE yêu thích của mình hoặc chỉ làm việc với các tệp và "mvn" trên dòng lệnh. Sau đó nhảy đến

Sử dụng Spring Boot CLI

You can create the same project using the , like this

$ spring init --dependencies web,security ui/ && cd ui

Then jump to the

Using the Initializr Website

If you prefer you can also get the same code directly as a . zip file from the Spring Boot Initializr. Just open it up in your browser and select dependencies "Web" and "Security", then click on "Generate Project". Các. zip chứa một dự án Maven hoặc Gradle tiêu chuẩn trong thư mục gốc, vì vậy bạn có thể muốn tạo một thư mục trống trước khi giải nén nó. Sau đó nhảy đến

Sử dụng bộ công cụ mùa xuân

Trong Spring Tool Suite (một bộ các plugin Eclipse), bạn cũng có thể tạo và nhập dự án bằng trình hướng dẫn tại

$ spring init --dependencies web,security ui/ && cd ui
07. Sau đó nhảy đến. IntelliJ IDEA và NetBeans có các tính năng tương tự

Thêm một ứng dụng góc cạnh

Cốt lõi của một ứng dụng trang đơn trong Angular (hoặc bất kỳ khung giao diện người dùng hiện đại nào) ngày nay sẽ là một Nút. xây dựng js. Angular có một số công cụ để thiết lập điều này một cách nhanh chóng, vì vậy hãy sử dụng những công cụ đó và cũng giữ tùy chọn xây dựng với Maven, giống như bất kỳ ứng dụng Spring Boot nào khác. Chi tiết về cách thiết lập ứng dụng Angular được trình bày ở nơi khác hoặc bạn chỉ có thể kiểm tra mã cho hướng dẫn này từ github

Chạy ứng dụng

Khi ứng dụng Angular được khởi chạy, ứng dụng của bạn sẽ có thể tải được trong trình duyệt (mặc dù nó chưa làm được gì nhiều). Trên dòng lệnh bạn có thể làm điều này

$ mvn spring-boot:run

và truy cập trình duyệt tại http. //máy chủ cục bộ. 8080. Khi bạn tải trang chủ, bạn sẽ nhận được hộp thoại trình duyệt yêu cầu tên người dùng và mật khẩu (tên người dùng là "người dùng" và mật khẩu được in trong nhật ký bảng điều khiển khi khởi động). Thực tế là chưa có nội dung nào (hoặc có thể là nội dung hướng dẫn "anh hùng" mặc định từ

$ spring init --dependencies web,security ui/ && cd ui
08 CLI), vì vậy về cơ bản bạn sẽ nhận được một trang trống

Nếu bạn không thích cạo nhật ký giao diện điều khiển để tìm mật khẩu, chỉ cần thêm mật khẩu này vào "ứng dụng. thuộc tính" (trong "src/main/resource").

$ spring init --dependencies web,security ui/ && cd ui
09 (và chọn mật khẩu của riêng bạn). We did this in the sample code using "application. yml"

Trong IDE, chỉ cần chạy phương thức

$ spring init --dependencies web,security ui/ && cd ui
10 trong lớp ứng dụng (chỉ có một lớp và nó được gọi là
$ spring init --dependencies web,security ui/ && cd ui
11 nếu bạn đã sử dụng lệnh "curl" ở trên)

Để đóng gói và chạy như một JAR độc lập, bạn có thể làm điều này

________số 8_______

Tùy chỉnh ứng dụng góc

Hãy tùy chỉnh thành phần "app-root" (trong "src/app/app. thành phần. ts")

Một ứng dụng Angular tối thiểu trông như thế này

ứng dụng. thành phần. ts

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'};
}

Hầu hết mã trong TypeScript này là bản nồi hơi. Tất cả nội dung thú vị sẽ có trong

$ spring init --dependencies web,security ui/ && cd ui
12 nơi chúng tôi xác định "bộ chọn" (tên của phần tử HTML) và một đoạn mã HTML để hiển thị thông qua chú thích
$ spring init --dependencies web,security ui/ && cd ui
13. Chúng tôi cũng cần chỉnh sửa mẫu HTML ("ứng dụng. thành phần. html")

ứng dụng. thành phần. html

Welcome {{title}}!

Id: {{greeting.id}}

Message: {{greeting.content}}!

Nếu bạn đã thêm các tệp đó trong "src/app" và xây dựng lại ứng dụng của mình thì giờ đây, ứng dụng đó sẽ an toàn và hoạt động hiệu quả, đồng thời nó sẽ thông báo "Xin chào thế giới. ".

$ spring init --dependencies web,security ui/ && cd ui
14 được kết xuất bởi Angular trong HTML bằng cách sử dụng trình giữ chỗ trên thanh điều khiển,
$ spring init --dependencies web,security ui/ && cd ui
15 và
$ spring init --dependencies web,security ui/ && cd ui
16

Thêm nội dung động

Cho đến nay, chúng tôi có một ứng dụng với lời chào được mã hóa cứng. Điều đó hữu ích cho việc tìm hiểu cách mọi thứ khớp với nhau, nhưng thực sự chúng tôi mong đợi nội dung đến từ máy chủ phụ trợ, vì vậy, hãy tạo một điểm cuối HTTP mà chúng tôi có thể sử dụng để nhận lời chào. Trong lớp ứng dụng của bạn (trong "src/main/java/demo"), hãy thêm chú thích

$ spring init --dependencies web,security ui/ && cd ui
17 và xác định một
$ spring init --dependencies web,security ui/ && cd ui
18 mới

UiỨng dụng. java

@SpringBootApplication
@RestController
public class UiApplication {

  @RequestMapping("/resource")
  public Map home() {
    Map model = new HashMap();
    model.put("id", UUID.randomUUID().toString());
    model.put("content", "Hello World");
    return model;
  }

  public static void main(String[] args) {
    SpringApplication.run(UiApplication.class, args);
  }

}

Tùy thuộc vào cách bạn tạo dự án mới của mình, nó có thể không được gọi là

$ spring init --dependencies web,security ui/ && cd ui
11

Chạy ứng dụng đó và cố gắng cuộn tròn điểm cuối "/resource" và bạn sẽ thấy rằng nó được bảo mật theo mặc định

$ curl localhost:8080/resource
{"timestamp":1420442772928,"status":401,"error":"Unauthorized","message":"Full authentication is required to access this resource","path":"/resource"}

Đang tải tài nguyên động từ góc

Vì vậy, hãy lấy tin nhắn đó trong trình duyệt. Sửa đổi

$ spring init --dependencies web,security ui/ && cd ui
12 để tải tài nguyên được bảo vệ bằng XHR

ứng dụng. thành phần. ts

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);
  }
}

Chúng tôi đã thêm một dịch vụ

$ spring init --dependencies web,security ui/ && cd ui
21, được cung cấp bởi Angular thông qua mô-đun
$ spring init --dependencies web,security ui/ && cd ui
21 và sử dụng nó để NHẬN tài nguyên của chúng tôi. Angular chuyển cho chúng tôi phản hồi và chúng tôi lấy JSON ra và gán nó cho lời chào

Để cho phép đưa dịch vụ phụ thuộc của dịch vụ

$ spring init --dependencies web,security ui/ && cd ui
21 vào thành phần tùy chỉnh của chúng tôi, chúng tôi cần khai báo dịch vụ đó trong
$ spring init --dependencies web,security ui/ && cd ui
24 bao gồm thành phần đó (nó chỉ là một dòng nữa trong
$ spring init --dependencies web,security ui/ && cd ui
25 so với bản nháp ban đầu)

ứng dụng. mô-đun. ts

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 { }

Chạy lại ứng dụng (hoặc chỉ cần tải lại trang chủ trong trình duyệt) và bạn sẽ thấy thông báo động với ID duy nhất của nó. Vì vậy, mặc dù tài nguyên được bảo vệ và bạn không thể cuộn trực tiếp tài nguyên đó nhưng trình duyệt vẫn có thể truy cập nội dung. Chúng tôi có một ứng dụng trang đơn an toàn trong chưa đầy một trăm dòng mã

Bạn có thể cần buộc trình duyệt của mình tải lại tài nguyên tĩnh sau khi bạn thay đổi chúng. Trong Chrome (và Firefox có plugin), bạn có thể sử dụng "công cụ dành cho nhà phát triển" (F12) và điều đó có thể là đủ. Hoặc bạn có thể phải sử dụng CTRL+F5

Làm thế nào nó hoạt động?

Có thể nhìn thấy các tương tác giữa trình duyệt và chương trình phụ trợ trong trình duyệt của bạn nếu bạn sử dụng một số công cụ dành cho nhà phát triển (thường là F12 mở ra, hoạt động trong Chrome theo mặc định, có thể yêu cầu plugin trong Firefox). Đây là một bản tóm tắt

Động từĐường dẫnTrạng tháiPhản hồi

ĐƯỢC

/

401

Trình duyệt nhắc xác thực

ĐƯỢC

/

200

mục lục. html

ĐƯỢC

/*. js

200

Tải nội dung thứ ba từ góc

ĐƯỢC

/chủ yếu. bó. js

200

logic ứng dụng

ĐƯỢC

/nguồn

200

Lời chào JSON

Bạn có thể không thấy 401 vì trình duyệt coi tải trang chủ là một tương tác đơn lẻ và bạn có thể thấy 2 yêu cầu cho "/resource" vì có thương lượng CORS

Xem xét kỹ hơn các yêu cầu và bạn sẽ thấy rằng tất cả chúng đều có tiêu đề "Ủy quyền", đại loại như thế này

$ spring init --dependencies web,security ui/ && cd ui
0

Trình duyệt đang gửi tên người dùng và mật khẩu với mọi yêu cầu (vì vậy hãy nhớ chỉ sử dụng HTTPS trong sản xuất). Không có gì là "Góc cạnh" về điều đó, vì vậy nó hoạt động với khung JavaScript hoặc khung lựa chọn không phải của bạn

Có gì sai với điều đó?

Nhìn bề ngoài, có vẻ như chúng tôi đã làm khá tốt, nó ngắn gọn, dễ triển khai, tất cả dữ liệu của chúng tôi được bảo mật bằng mật khẩu bí mật và nó vẫn hoạt động nếu chúng tôi thay đổi công nghệ giao diện người dùng hoặc công nghệ phụ trợ. Nhưng có một số vấn đề

  • Xác thực cơ bản bị hạn chế đối với xác thực tên người dùng và mật khẩu

  • Giao diện người dùng xác thực phổ biến nhưng xấu (hộp thoại trình duyệt)

  • Không có sự bảo vệ khỏi giả mạo yêu cầu trang web chéo (CSRF)

CSRF không thực sự là một vấn đề với ứng dụng của chúng tôi vì nó chỉ cần NHẬN các tài nguyên phụ trợ (tôi. e. không có trạng thái nào bị thay đổi trong máy chủ). Ngay khi bạn có POST, PUT hoặc DELETE trong ứng dụng của mình, đơn giản là nó không còn an toàn nữa bằng bất kỳ biện pháp hiện đại hợp lý nào

Trong phần này, chúng tôi sẽ mở rộng ứng dụng để sử dụng xác thực dựa trên biểu mẫu, linh hoạt hơn nhiều so với HTTP Basic. Khi chúng tôi có một biểu mẫu, chúng tôi sẽ cần bảo vệ CSRF và cả Spring Security và Angular đều có một số tính năng tuyệt vời để hỗ trợ việc này. spoiler. chúng ta sẽ cần sử dụng

$ spring init --dependencies web,security ui/ && cd ui
26

Thanks. Tôi xin cảm ơn tất cả những người đã giúp tôi phát triển loạt bài này, đặc biệt là Rob Winch và Thorsten Spaeth vì đã xem xét cẩn thận văn bản và mã nguồn, cũng như đã dạy cho tôi một số thủ thuật mà tôi thậm chí còn không biết về những phần mà tôi nghĩ

Trang đăng nhập

Trong phần này, chúng ta tiếp tục về cách sử dụng Spring Security với Angular trong một "ứng dụng trang đơn". Ở đây chúng tôi trình bày cách sử dụng Angular để xác thực người dùng thông qua biểu mẫu và tìm nạp tài nguyên an toàn để hiển thị trong giao diện người dùng. Đây là phần thứ hai trong một loạt các phần và bạn có thể bắt kịp các khối xây dựng cơ bản của ứng dụng hoặc xây dựng ứng dụng từ đầu bằng cách đọc hoặc bạn có thể truy cập thẳng vào mã nguồn trong Github. Trong phần đầu tiên, chúng tôi đã xây dựng một ứng dụng đơn giản sử dụng xác thực HTTP Basic để bảo vệ tài nguyên phụ trợ. Trong phần này, chúng tôi thêm một biểu mẫu đăng nhập, cung cấp cho người dùng một số quyền kiểm soát xem có xác thực hay không và khắc phục sự cố với lần lặp đầu tiên (chủ yếu là thiếu bảo vệ CSRF)

Lời nhắc nhở. nếu bạn đang làm việc qua phần này với ứng dụng mẫu, hãy đảm bảo xóa bộ nhớ cache của trình duyệt khỏi cookie và thông tin xác thực HTTP Basic. Trong Chrome, cách tốt nhất để làm điều đó cho một máy chủ là mở một cửa sổ ẩn danh mới

Thêm Điều hướng vào Trang chủ

Cốt lõi của ứng dụng Angular là một mẫu HTML cho bố cục trang cơ bản. Chúng tôi đã có một ứng dụng thực sự cơ bản, nhưng đối với ứng dụng này, chúng tôi cần cung cấp một số tính năng điều hướng (đăng nhập, đăng xuất, trang chủ), vì vậy hãy sửa đổi nó (trong

$ spring init --dependencies web,security ui/ && cd ui
27)

ứng dụng. thành phần. html

$ spring init --dependencies web,security ui/ && cd ui
1

Nội dung chính là một

$ spring init --dependencies web,security ui/ && cd ui
28 và có một thanh điều hướng với các liên kết đăng nhập và đăng xuất

Bộ chọn

$ spring init --dependencies web,security ui/ && cd ui
28 được cung cấp bởi Angular và nó cần được kết nối với một thành phần trong mô-đun chính. Sẽ có một thành phần trên mỗi tuyến đường (mỗi liên kết menu) và một dịch vụ trợ giúp để dán chúng lại với nhau và chia sẻ một số trạng thái (
$ spring init --dependencies web,security ui/ && cd ui
30). Đây là cách triển khai mô-đun kéo tất cả các phần lại với nhau

ứng dụng. mô-đun. ts

$ spring init --dependencies web,security ui/ && cd ui
2

Chúng tôi đã thêm một phụ thuộc vào một mô-đun Góc có tên là "RouterModule" và điều này cho phép chúng tôi thêm một phép thuật

$ spring init --dependencies web,security ui/ && cd ui
31 vào hàm tạo của
$ spring init --dependencies web,security ui/ && cd ui
12.
$ spring init --dependencies web,security ui/ && cd ui
33 được sử dụng bên trong quá trình nhập của
$ spring init --dependencies web,security ui/ && cd ui
24 để thiết lập liên kết đến "/" (bộ điều khiển "nhà") và "/login" (bộ điều khiển "đăng nhập")

Chúng tôi cũng lén đưa

$ spring init --dependencies web,security ui/ && cd ui
35 vào đó, vì nó sẽ cần sau này để liên kết dữ liệu với một biểu mẫu mà chúng tôi muốn gửi khi người dùng đăng nhập

Các thành phần giao diện người dùng đều là "khai báo" và keo dịch vụ là "nhà cung cấp".

$ spring init --dependencies web,security ui/ && cd ui
12 thực sự không làm được gì nhiều. Thành phần TypeScript đi kèm với ứng dụng gốc có tại đây

ứng dụng. thành phần. ts

$ spring init --dependencies web,security ui/ && cd ui
3

Tính năng nổi bật

  • Có thêm một số mũi tiêm phụ thuộc, lần này là của

    $ spring init --dependencies web,security ui/ && cd ui
    30

  • Có một chức năng đăng xuất được hiển thị dưới dạng thuộc tính của thành phần, mà chúng ta có thể sử dụng sau này để gửi yêu cầu đăng xuất đến phần phụ trợ. Nó đặt một cờ trong dịch vụ

    $ spring init --dependencies web,security ui/ && cd ui
    38 và gửi người dùng trở lại màn hình đăng nhập (và nó thực hiện điều này vô điều kiện thông qua một cuộc gọi lại
    $ spring init --dependencies web,security ui/ && cd ui
    39)

  • Chúng tôi đang sử dụng

    $ spring init --dependencies web,security ui/ && cd ui
    40 để ngoại hóa HTML mẫu thành một tệp riêng biệt

  • Hàm

    $ spring init --dependencies web,security ui/ && cd ui
    41 được gọi khi bộ điều khiển được tải để xem liệu người dùng có thực sự đã được xác thực chưa (e. g. nếu anh ấy đã làm mới trình duyệt vào giữa phiên). Chúng tôi cần hàm
    $ spring init --dependencies web,security ui/ && cd ui
    41 để thực hiện cuộc gọi từ xa vì xác thực thực tế được thực hiện bởi máy chủ và chúng tôi không muốn tin tưởng vào trình duyệt để theo dõi nó

Dịch vụ

$ spring init --dependencies web,security ui/ && cd ui
38 mà chúng tôi đã thêm ở trên cần một cờ boolean để chúng tôi có thể biết liệu người dùng hiện có được xác thực hay không và một hàm
$ spring init --dependencies web,security ui/ && cd ui
41 có thể được sử dụng để xác thực với máy chủ phụ trợ hoặc chỉ để truy vấn nó để biết thông tin chi tiết về người dùng

ứng dụng. Dịch vụ. ts

$ spring init --dependencies web,security ui/ && cd ui
4

Cờ

$ spring init --dependencies web,security ui/ && cd ui
45 rất đơn giản. Hàm
$ spring init --dependencies web,security ui/ && cd ui
41 gửi thông tin xác thực HTTP Basic nếu chúng được cung cấp và nếu không thì không. Nó cũng có một đối số
$ spring init --dependencies web,security ui/ && cd ui
47 tùy chọn mà chúng ta có thể sử dụng để thực thi một số mã nếu xác thực thành công

lời chào

Nội dung lời chào từ trang chủ cũ có thể vào ngay bên cạnh "ứng dụng. thành phần. html" trong "src/ứng dụng"

Trang Chủ. thành phần. html

$ spring init --dependencies web,security ui/ && cd ui
5

Vì người dùng hiện có quyền lựa chọn đăng nhập hay không (trước khi tất cả được kiểm soát bởi trình duyệt), chúng tôi cần phân biệt trong giao diện người dùng giữa nội dung an toàn và nội dung không an toàn. Chúng tôi đã dự đoán điều này bằng cách thêm các tham chiếu đến hàm

$ spring init --dependencies web,security ui/ && cd ui
48 (chưa tồn tại)

$ spring init --dependencies web,security ui/ && cd ui
49 phải tìm nạp lời chào, đồng thời cung cấp chức năng tiện ích
$ spring init --dependencies web,security ui/ && cd ui
48 để kéo cờ ra khỏi
$ spring init --dependencies web,security ui/ && cd ui
30

Trang Chủ. thành phần. ts

$ spring init --dependencies web,security ui/ && cd ui
6

Biểu mẫu đăng nhập

Biểu mẫu đăng nhập cũng có thành phần riêng

đăng nhập. thành phần. html

$ spring init --dependencies web,security ui/ && cd ui
7

Đây là một biểu mẫu đăng nhập rất chuẩn, với 2 đầu vào cho tên người dùng và mật khẩu và một nút để gửi biểu mẫu thông qua trình xử lý sự kiện Angular

$ spring init --dependencies web,security ui/ && cd ui
52. Bạn không cần một hành động trên thẻ biểu mẫu, vì vậy tốt hơn hết là không nên đặt một hành động nào trong đó. Ngoài ra còn có một thông báo lỗi, chỉ hiển thị nếu mô hình góc có chứa
$ spring init --dependencies web,security ui/ && cd ui
53. Các điều khiển biểu mẫu sử dụng
$ spring init --dependencies web,security ui/ && cd ui
54 từ Biểu mẫu góc để truyền dữ liệu giữa HTML và bộ điều khiển Góc và trong trường hợp này, chúng tôi đang sử dụng một đối tượng
$ spring init --dependencies web,security ui/ && cd ui
55 để giữ tên người dùng và mật khẩu

Quá trình xác thực

Để hỗ trợ biểu mẫu đăng nhập mà chúng tôi vừa thêm, chúng tôi cần thêm một số tính năng khác. On the client side these will be implemented in the

$ spring init --dependencies web,security ui/ && cd ui
56, and on the server it will be Spring Security configuration

Gửi biểu mẫu đăng nhập

Để gửi biểu mẫu, chúng tôi cần xác định hàm

$ spring init --dependencies web,security ui/ && cd ui
57 mà chúng tôi đã tham chiếu đã có trong biểu mẫu qua
$ spring init --dependencies web,security ui/ && cd ui
58 và đối tượng
$ spring init --dependencies web,security ui/ && cd ui
55 mà chúng tôi đã tham chiếu qua
$ spring init --dependencies web,security ui/ && cd ui
60. Hãy bổ sung thành phần "đăng nhập"

đăng nhập. thành phần. ts

$ spring init --dependencies web,security ui/ && cd ui
8

Ngoài việc khởi tạo đối tượng

$ spring init --dependencies web,security ui/ && cd ui
55, nó xác định
$ spring init --dependencies web,security ui/ && cd ui
57 mà chúng ta cần trong biểu mẫu

$ spring init --dependencies web,security ui/ && cd ui
41 tạo GET cho một tài nguyên tương đối (liên quan đến gốc triển khai của ứng dụng của bạn) "/user". Khi được gọi từ hàm
$ spring init --dependencies web,security ui/ && cd ui
57, nó sẽ thêm thông tin đăng nhập được mã hóa Base64 vào các tiêu đề để trên máy chủ, nó thực hiện xác thực và chấp nhận lại một cookie. Hàm
$ spring init --dependencies web,security ui/ && cd ui
57 cũng đặt cờ
$ spring init --dependencies web,security ui/ && cd ui
66 cục bộ tương ứng khi chúng tôi nhận được kết quả xác thực, được sử dụng để kiểm soát việc hiển thị thông báo lỗi phía trên biểu mẫu đăng nhập

Người dùng được xác thực hiện tại

Để phục vụ hàm

$ spring init --dependencies web,security ui/ && cd ui
41, chúng ta cần thêm một điểm cuối mới vào phần phụ trợ

UiỨng dụng. java

$ spring init --dependencies web,security ui/ && cd ui
9

Đây là một thủ thuật hữu ích trong ứng dụng Spring Security. Nếu tài nguyên "/user" có thể truy cập được thì nó sẽ trả về người dùng được xác thực hiện tại (một

$ spring init --dependencies web,security ui/ && cd ui
68) và nếu không thì Spring Security sẽ chặn yêu cầu và gửi phản hồi 401 thông qua một
$ spring init --dependencies web,security ui/ && cd ui
69

Xử lý yêu cầu đăng nhập trên máy chủ

Spring Security giúp dễ dàng xử lý yêu cầu đăng nhập. Chúng ta chỉ cần thêm một số cấu hình vào lớp ứng dụng chính của mình (e. g. như một lớp bên trong)

UiỨng dụng. java

$ mvn spring-boot:run
0

Đây là ứng dụng Spring Boot tiêu chuẩn với tùy chỉnh Spring Security, chỉ cho phép truy cập ẩn danh vào tài nguyên tĩnh (HTML). Tài nguyên HTML cần phải có sẵn cho người dùng ẩn danh, không chỉ bị Spring Security bỏ qua, vì những lý do sẽ trở nên rõ ràng

Điều cuối cùng chúng ta cần nhớ là làm cho các thành phần JavaScript do Angular cung cấp ẩn danh cho ứng dụng. Chúng ta có thể làm điều đó trong cấu hình

$ spring init --dependencies web,security ui/ && cd ui
70 ở trên, nhưng vì nó là nội dung tĩnh nên tốt hơn hết là bỏ qua nó

ứng dụng. yml

$ mvn spring-boot:run
1

Thêm tiêu đề yêu cầu HTTP mặc định

Nếu bạn chạy ứng dụng vào thời điểm này, bạn sẽ thấy rằng trình duyệt bật lên hộp thoại Xác thực cơ bản (đối với người dùng và mật khẩu). Nó làm điều này bởi vì nó thấy phản hồi 401 từ các yêu cầu XHR tới

$ spring init --dependencies web,security ui/ && cd ui
71 và
$ spring init --dependencies web,security ui/ && cd ui
72 với tiêu đề "WWW-Authenticate". Cách để chặn cửa sổ bật lên này là chặn tiêu đề đến từ Bảo mật mùa xuân. Và cách để chặn tiêu đề phản hồi là gửi một tiêu đề yêu cầu thông thường, đặc biệt "X-Requested-With=XMLHttpRequest". Nó từng là mặc định trong Angular nhưng họ đã gỡ bỏ nó trong 1. 3. 0. Vì vậy, đây là cách đặt tiêu đề mặc định trong yêu cầu XHR góc

Trước tiên, hãy mở rộng mặc định

$ spring init --dependencies web,security ui/ && cd ui
73 được cung cấp bởi mô-đun HTTP góc

ứng dụng. mô-đun. ts

$ mvn spring-boot:run
2

Cú pháp ở đây là soạn sẵn. Thuộc tính

$ spring init --dependencies web,security ui/ && cd ui
74 của
$ spring init --dependencies web,security ui/ && cd ui
75 là lớp cơ sở của nó và ngoài hàm tạo, tất cả những gì chúng ta thực sự cần làm là ghi đè hàm
$ spring init --dependencies web,security ui/ && cd ui
76 luôn được gọi bởi Angular và có thể được sử dụng để thêm các tiêu đề bổ sung

Để cài đặt nhà máy

$ spring init --dependencies web,security ui/ && cd ui
73 mới này, chúng tôi cần khai báo nó trong
$ spring init --dependencies web,security ui/ && cd ui
78 của
$ spring init --dependencies web,security ui/ && cd ui
24

ứng dụng. mô-đun. ts

$ mvn spring-boot:run
3

Đăng xuất

Ứng dụng gần như đã hoàn thành chức năng. Điều cuối cùng chúng ta cần làm là triển khai tính năng đăng xuất mà chúng ta đã phác thảo trong trang chủ. Nếu người dùng được xác thực thì chúng tôi sẽ hiển thị liên kết "đăng xuất" và nối liên kết đó với hàm

$ spring init --dependencies web,security ui/ && cd ui
80 trong
$ spring init --dependencies web,security ui/ && cd ui
12. Hãy nhớ rằng, nó sẽ gửi HTTP POST tới "/logout" mà bây giờ chúng ta cần triển khai trên máy chủ. Điều này rất đơn giản vì nó đã được Spring Security thêm vào cho chúng ta (i. e. chúng tôi không cần phải làm bất cứ điều gì cho trường hợp sử dụng đơn giản này). Để kiểm soát nhiều hơn hành vi đăng xuất, bạn có thể sử dụng lệnh gọi lại
$ spring init --dependencies web,security ui/ && cd ui
70 trong
$ spring init --dependencies web,security ui/ && cd ui
83 của mình để, chẳng hạn như thực thi một số logic nghiệp vụ sau khi đăng xuất

Bảo vệ CSRF

Ứng dụng gần như đã sẵn sàng để sử dụng và trên thực tế, nếu bạn chạy nó, bạn sẽ thấy rằng mọi thứ chúng tôi đã xây dựng cho đến nay thực sự hoạt động ngoại trừ liên kết đăng xuất. Hãy thử sử dụng nó và xem các phản hồi trong trình duyệt và bạn sẽ thấy tại sao

$ mvn spring-boot:run
4

Điều đó tốt vì điều đó có nghĩa là tính năng bảo vệ CSRF tích hợp của Spring Security đã khởi động để ngăn chúng tôi tự bắn vào chân mình. Tất cả những gì nó muốn là một mã thông báo được gửi tới nó trong tiêu đề có tên "X-CSRF". Giá trị của mã thông báo CSRF đã có sẵn phía máy chủ trong các thuộc tính

$ spring init --dependencies web,security ui/ && cd ui
84 từ yêu cầu ban đầu đã tải trang chủ. Để đưa nó đến ứng dụng khách, chúng tôi có thể kết xuất nó bằng trang HTML động trên máy chủ hoặc hiển thị nó qua điểm cuối tùy chỉnh hoặc nếu không, chúng tôi có thể gửi nó dưới dạng cookie. The last choice is the best because Angular has (which it calls "XSRF") based on cookies

Vì vậy, trên máy chủ, chúng tôi cần một bộ lọc tùy chỉnh sẽ gửi cookie. Angular muốn tên cookie là "XSRF-TOKEN" và Spring Security mặc định cung cấp nó dưới dạng thuộc tính yêu cầu, vì vậy chúng ta chỉ cần chuyển giá trị từ thuộc tính yêu cầu sang cookie. May mắn thay, Spring Security (kể từ 4. 1. 0) cung cấp một

$ spring init --dependencies web,security ui/ && cd ui
85 đặc biệt thực hiện chính xác điều này

UiỨng dụng. java

$ mvn spring-boot:run
5

Với những thay đổi đó, chúng tôi không cần phải làm bất cứ điều gì ở phía máy khách và biểu mẫu đăng nhập hiện đang hoạt động

Làm thế nào nó hoạt động?

Có thể nhìn thấy các tương tác giữa trình duyệt và chương trình phụ trợ trong trình duyệt của bạn nếu bạn sử dụng một số công cụ dành cho nhà phát triển (thường là F12 mở ra, hoạt động trong Chrome theo mặc định, có thể yêu cầu plugin trong Firefox). Đây là một bản tóm tắt

Động từĐường dẫnTrạng tháiPhản hồi

ĐƯỢC

/

200

mục lục. html

ĐƯỢC

/*. js

200

Tài sản từ góc

ĐƯỢC

/người sử dụng

401

Trái phép (bỏ qua)

ĐƯỢC

/Trang Chủ

200

trang chủ

ĐƯỢC

/người sử dụng

401

Trái phép (bỏ qua)

ĐƯỢC

/nguồn

401

Trái phép (bỏ qua)

ĐƯỢC

/người sử dụng

200

Gửi thông tin đăng nhập và nhận JSON

ĐƯỢC

/nguồn

200

Lời chào JSON

Các phản hồi được đánh dấu "bỏ qua" ở trên là các phản hồi HTML mà Angular nhận được trong lệnh gọi XHR và vì chúng tôi không xử lý dữ liệu đó nên HTML bị loại bỏ trên sàn. Chúng tôi tìm kiếm một người dùng được xác thực trong trường hợp tài nguyên "/user", nhưng vì nó không có trong cuộc gọi đầu tiên nên phản hồi đó bị hủy

Xem xét kỹ hơn các yêu cầu và bạn sẽ thấy rằng tất cả chúng đều có cookie. Nếu bạn bắt đầu với một trình duyệt sạch (e. g. ẩn danh trong Chrome), yêu cầu đầu tiên không có cookie nào được chuyển đến máy chủ, nhưng máy chủ sẽ gửi lại "Set-Cookie" cho "JSESSIONID" (_______26 thông thường) và "X-XSRF-TOKEN" (cookie CRSF mà . Tất cả các yêu cầu tiếp theo đều có những cookie đó và chúng rất quan trọng. ứng dụng không hoạt động nếu không có chúng và chúng đang cung cấp một số tính năng bảo mật thực sự cơ bản (xác thực và bảo vệ CSRF). Các giá trị của cookie thay đổi khi người dùng xác thực (sau POST) và đây là một tính năng bảo mật quan trọng khác (ngăn chặn các cuộc tấn công cố định phiên)

Việc bảo vệ CSRF dựa vào cookie được gửi trở lại máy chủ là không đủ vì trình duyệt sẽ tự động gửi nó ngay cả khi bạn không ở trong trang được tải từ ứng dụng của mình (một cuộc tấn công Cross Site Scripting, còn được gọi là XSS). Tiêu đề không được gửi tự động, vì vậy nguồn gốc được kiểm soát. Bạn có thể thấy rằng trong ứng dụng của chúng tôi, mã thông báo CSRF được gửi tới ứng dụng khách dưới dạng cookie, vì vậy chúng tôi sẽ thấy nó được trình duyệt tự động gửi lại, nhưng chính tiêu đề cung cấp sự bảo vệ

Trợ giúp, Ứng dụng của tôi sẽ mở rộng như thế nào?

"Nhưng đợi đã..." bạn đang nói, "việc sử dụng trạng thái phiên trong ứng dụng một trang có thực sự tệ không?" . Trạng thái đó phải được lưu trữ ở đâu đó và nếu bạn lấy nó ra khỏi phiên, bạn sẽ phải đặt nó ở một nơi khác và tự quản lý nó theo cách thủ công, trên cả máy chủ và máy khách. Đó chỉ là nhiều mã hơn và có thể là bảo trì nhiều hơn, và nói chung là phát minh lại một bánh xe hoàn toàn tốt

"Nhưng, nhưng..." bạn sẽ trả lời, "làm cách nào để mở rộng ứng dụng của tôi theo chiều ngang bây giờ?" . Không hoảng loạn. Điểm chính cần lưu ý ở đây là bảo mật có trạng thái. Bạn không thể có một ứng dụng an toàn, phi trạng thái. Vậy bạn sẽ lưu trữ trạng thái ở đâu? . Rob Winch đã có một bài nói chuyện rất hữu ích và sâu sắc tại Spring Exchange 2014 giải thích nhu cầu về trạng thái (và tính phổ biến của nó - TCP và SSL đều có trạng thái, vì vậy hệ thống của bạn có trạng thái dù bạn có biết hay không), điều này có lẽ đáng để xem

Tin tốt là bạn có một sự lựa chọn. Lựa chọn đơn giản nhất là lưu trữ dữ liệu phiên trong bộ nhớ và dựa vào các phiên cố định trong bộ cân bằng tải của bạn để định tuyến các yêu cầu từ cùng một phiên trở lại cùng một JVM (tất cả đều hỗ trợ điều đó bằng cách nào đó). Điều đó đủ tốt để giúp bạn bắt đầu và sẽ hoạt động cho một số lượng lớn các trường hợp sử dụng. Lựa chọn khác là chia sẻ dữ liệu phiên giữa các phiên bản ứng dụng của bạn. Miễn là bạn nghiêm ngặt và chỉ lưu trữ dữ liệu bảo mật, dữ liệu này nhỏ và không thường xuyên thay đổi (chỉ khi người dùng đăng nhập và đăng xuất hoặc phiên của họ hết thời gian), do đó sẽ không có bất kỳ sự cố lớn nào về cơ sở hạ tầng. Nó cũng rất dễ thực hiện với Phiên mùa xuân. Chúng ta sẽ sử dụng Phiên mùa xuân trong phần tiếp theo của loạt bài này, vì vậy không cần phải đi sâu vào bất kỳ chi tiết nào về cách thiết lập nó ở đây, nhưng nó thực sự là một vài dòng mã và máy chủ Redis, siêu nhanh

Một cách dễ dàng khác để thiết lập trạng thái phiên chia sẻ là triển khai ứng dụng của bạn dưới dạng tệp WAR cho Cloud Foundry Pivotal Web Services và liên kết nó với dịch vụ Redis

Tuy nhiên, còn việc triển khai mã thông báo tùy chỉnh của tôi (nó không trạng thái, hãy nhìn xem) thì sao?

Nếu đó là câu trả lời của bạn cho phần trước, thì hãy đọc lại vì có thể bạn không hiểu ngay lần đầu tiên. Nó có thể không phải là không trạng thái nếu bạn lưu trữ mã thông báo ở đâu đó, nhưng ngay cả khi bạn không (e. g. bạn sử dụng mã thông báo được mã hóa JWT), bạn sẽ cung cấp bảo vệ CSRF như thế nào? . Đây là một quy tắc ngón tay cái (do Rob Winch). nếu ứng dụng hoặc API của bạn sẽ được truy cập bởi trình duyệt, bạn cần bảo vệ CSRF. Không phải là bạn không thể làm điều đó nếu không có phiên, chỉ là bạn phải tự viết tất cả mã đó và vấn đề là gì bởi vì nó đã được triển khai và hoạt động hoàn hảo trên

$ spring init --dependencies web,security ui/ && cd ui
26 (do đó là một phần . trình duyệt luôn gửi cookie và máy chủ luôn có phiên (trừ khi bạn tắt nó). Mã đó không phải là logic kinh doanh và nó không giúp bạn kiếm tiền, nó chỉ là chi phí hoạt động, thậm chí tệ hơn, nó còn khiến bạn mất tiền

Phần kết luận

Ứng dụng mà chúng ta có bây giờ gần giống với những gì người dùng có thể mong đợi ở một ứng dụng "thực" trong môi trường trực tiếp và nó có thể được sử dụng làm mẫu để xây dựng thành một ứng dụng giàu tính năng hơn với kiến ​​trúc đó (máy chủ đơn có tĩnh . Chúng tôi đang sử dụng

$ spring init --dependencies web,security ui/ && cd ui
26 để lưu trữ dữ liệu bảo mật, dựa vào việc khách hàng của chúng tôi tôn trọng và sử dụng cookie mà chúng tôi gửi cho họ và chúng tôi cảm thấy thoải mái với điều đó vì nó cho phép chúng tôi tập trung vào lĩnh vực kinh doanh của riêng mình. Trong phần này, chúng tôi mở rộng kiến ​​trúc thành một máy chủ giao diện người dùng và xác thực riêng biệt, cùng với một máy chủ tài nguyên độc lập cho JSON. Điều này rõ ràng dễ dàng được khái quát hóa cho nhiều máy chủ tài nguyên. Chúng tôi cũng sẽ giới thiệu Phiên mùa xuân vào ngăn xếp và chỉ ra cách có thể sử dụng phiên đó để chia sẻ dữ liệu xác thực

Máy chủ tài nguyên

Trong phần này, chúng ta tiếp tục về cách sử dụng Spring Security với Angular trong một "ứng dụng trang đơn". Ở đây, chúng tôi bắt đầu bằng cách tách tài nguyên "lời chào" mà chúng tôi đang sử dụng làm nội dung động trong ứng dụng của mình thành một máy chủ riêng biệt, đầu tiên là tài nguyên không được bảo vệ, sau đó được bảo vệ bằng mã thông báo mờ. This is the third in a series of sections, and you can catch up on the basic building blocks of the application or build it from scratch by reading the , or you can just go straight to the source code in Github, which is in two parts. một nơi tài nguyên không được bảo vệ và một nơi tài nguyên được bảo vệ bằng mã thông báo

nếu bạn đang làm việc qua phần này với ứng dụng mẫu, hãy đảm bảo xóa bộ nhớ cache của trình duyệt khỏi cookie và thông tin xác thực HTTP Basic. Trong Chrome, cách tốt nhất để làm điều đó cho một máy chủ là mở một cửa sổ ẩn danh mới

Một máy chủ tài nguyên riêng biệt

Thay đổi phía khách hàng

Về phía khách hàng, không có nhiều việc phải làm để chuyển tài nguyên sang một chương trình phụ trợ khác. Đây là thành phần "nhà" trong phần trước

Trang Chủ. thành phần. ts

$ mvn spring-boot:run
6

Tất cả những gì chúng ta cần làm là thay đổi URL. Ví dụ: nếu chúng ta sẽ chạy tài nguyên mới trên máy chủ cục bộ, thì nó có thể trông như thế này

Trang Chủ. thành phần. ts

$ mvn spring-boot:run
7

Thay đổi phía máy chủ

Máy chủ giao diện người dùng không đáng kể để thay đổi. chúng ta chỉ cần xóa

$ spring init --dependencies web,security ui/ && cd ui
18 cho tài nguyên lời chào (đó là "/resource"). Sau đó, chúng ta cần tạo một máy chủ tài nguyên mới, chúng ta có thể thực hiện thao tác này giống như cách sử dụng Spring Boot Initializr. e. g. sử dụng curl trên hệ thống giống UN*X

$ mvn spring-boot:run
8

Sau đó, bạn có thể nhập dự án đó (theo mặc định là dự án Maven Java bình thường) vào IDE yêu thích của mình hoặc chỉ làm việc với các tệp và "mvn" trên dòng lệnh

Chỉ cần thêm một

$ spring init --dependencies web,security ui/ && cd ui
18 vào lớp ứng dụng chính, sao chép cách triển khai từ giao diện người dùng cũ

Ứng dụng tài nguyên. java

$ mvn spring-boot:run
9

Khi đã xong, ứng dụng của bạn sẽ có thể tải được trong trình duyệt. Trên dòng lệnh bạn có thể làm điều này

$ mvn package
$ java -jar target/*.jar
0

và truy cập trình duyệt tại http. //máy chủ cục bộ. 9000 và bạn sẽ thấy JSON kèm theo lời chào. Bạn có thể chỉnh sửa cổng trong

$ spring init --dependencies web,security ui/ && cd ui
91 (trong"src/main/resources")

ứng dụng. của cải

$ mvn package
$ java -jar target/*.jar
1

Nếu bạn thử tải tài nguyên đó từ giao diện người dùng (trên cổng 8080) trong trình duyệt, bạn sẽ thấy rằng nó không hoạt động vì trình duyệt không cho phép yêu cầu XHR

Đàm phán CORS

Trình duyệt cố gắng đàm phán với máy chủ tài nguyên của chúng tôi để tìm hiểu xem nó có được phép truy cập theo giao thức Chia sẻ tài nguyên gốc chéo hay không. Đó không phải là trách nhiệm của Góc, vì vậy giống như hợp đồng cookie, nó sẽ hoạt động như thế này với tất cả JavaScript trong trình duyệt. Hai máy chủ không khai báo rằng chúng có nguồn gốc chung, vì vậy trình duyệt từ chối gửi yêu cầu và giao diện người dùng bị hỏng

Để khắc phục điều đó, chúng tôi cần hỗ trợ giao thức CORS bao gồm yêu cầu TÙY CHỌN "trước chuyến bay" và một số tiêu đề để liệt kê hành vi được phép của người gọi. mùa xuân 4. 2 có một số hỗ trợ CORS chi tiết, vì vậy chúng tôi chỉ cần thêm một chú thích vào ánh xạ bộ điều khiển của mình, chẳng hạn

Ứng dụng tài nguyên. java

$ mvn package
$ java -jar target/*.jar
2

Hoàn toàn sử dụng

$ spring init --dependencies web,security ui/ && cd ui
92 nhanh và bẩn, và nó hoạt động, nhưng nó không an toàn và không được khuyến nghị theo bất kỳ cách nào

Bảo mật máy chủ tài nguyên

Tuyệt vời. Chúng tôi có một ứng dụng đang hoạt động với kiến ​​trúc mới. Vấn đề duy nhất là máy chủ tài nguyên không có bảo mật

Adding Spring Security

Chúng ta cũng có thể xem cách thêm bảo mật cho máy chủ tài nguyên dưới dạng lớp bộ lọc, như trong máy chủ giao diện người dùng. Bước đầu tiên thực sự dễ dàng. chỉ cần thêm Bảo mật mùa xuân vào đường dẫn lớp trong Maven POM

quả bông. xml

$ mvn package
$ java -jar target/*.jar
3

Khởi chạy lại máy chủ tài nguyên và xin chào. nó an toàn

$ mvn package
$ java -jar target/*.jar
4

Chúng tôi đang nhận được chuyển hướng đến trang đăng nhập (nhãn trắng) vì curl không gửi cùng tiêu đề mà ứng dụng khách Angular của chúng tôi sẽ gửi. Sửa đổi lệnh để gửi nhiều tiêu đề tương tự hơn

$ mvn package
$ java -jar target/*.jar
5

Vì vậy, tất cả những gì chúng ta cần làm là hướng dẫn khách hàng gửi thông tin xác thực với mọi yêu cầu

Xác thực mã thông báo

Internet và các dự án phụ trợ mùa xuân của mọi người tràn ngập các giải pháp xác thực dựa trên mã thông báo tùy chỉnh. Spring Security cung cấp triển khai

$ spring init --dependencies web,security ui/ && cd ui
93 cơ bản để giúp bạn tự bắt đầu (xem ví dụ về
$ spring init --dependencies web,security ui/ && cd ui
94 và
$ spring init --dependencies web,security ui/ && cd ui
95). Mặc dù vậy, không có triển khai chính tắc nào trong Spring Security và một trong những lý do tại sao có lẽ là có một cách dễ dàng hơn

Hãy nhớ từ loạt bài này rằng Spring Security sử dụng

$ spring init --dependencies web,security ui/ && cd ui
26 để lưu trữ dữ liệu xác thực theo mặc định. Nó không tương tác trực tiếp với phiên mặc dù. có một lớp trừu tượng (
$ spring init --dependencies web,security ui/ && cd ui
97) ở giữa mà bạn có thể sử dụng để thay đổi phụ trợ lưu trữ. Nếu chúng tôi có thể trỏ kho lưu trữ đó, trong máy chủ tài nguyên của chúng tôi, tới một cửa hàng có xác thực được xác minh bởi giao diện người dùng của chúng tôi, thì chúng tôi có cách để chia sẻ xác thực giữa hai máy chủ. Máy chủ giao diện người dùng đã có một cửa hàng như vậy (_______1_______26), vì vậy nếu chúng tôi có thể phân phối cửa hàng đó và mở nó lên máy chủ tài nguyên, thì chúng tôi có hầu hết các giải pháp

phiên mùa xuân

Đó là một phần của giải pháp khá dễ dàng với Phiên mùa xuân. Tất cả những gì chúng ta cần là một kho lưu trữ dữ liệu dùng chung (Redis và JDBC được hỗ trợ ngay lập tức) và một vài dòng cấu hình trong máy chủ để thiết lập một

$ spring init --dependencies web,security ui/ && cd ui
93

Trong ứng dụng giao diện người dùng, chúng tôi cần thêm một số phụ thuộc vào POM của mình

quả bông. xml

$ mvn package
$ java -jar target/*.jar
6

Spring Boot và Spring Session hoạt động cùng nhau để kết nối với Redis và lưu trữ dữ liệu phiên tập trung

Với 1 dòng mã đó và máy chủ Redis chạy trên máy chủ cục bộ, bạn có thể chạy ứng dụng giao diện người dùng, đăng nhập bằng một số thông tin xác thực người dùng hợp lệ và dữ liệu phiên (xác thực) sẽ được lưu trữ trong redis

nếu bạn không có máy chủ redis chạy cục bộ, bạn có thể dễ dàng tạo một máy chủ bằng Docker (trên Windows hoặc MacOS, điều này yêu cầu máy ảo). Có một tệp

$ mvn spring-boot:run
00 trong mã nguồn trên Github mà bạn có thể chạy thực sự dễ dàng trên dòng lệnh với
$ mvn spring-boot:run
01. Nếu bạn thực hiện việc này trong máy ảo, máy chủ Redis sẽ chạy trên một máy chủ khác với máy chủ cục bộ, vì vậy bạn cần chuyển nó vào máy chủ cục bộ hoặc định cấu hình ứng dụng để trỏ đến đúng
$ mvn spring-boot:run
02 trong
$ spring init --dependencies web,security ui/ && cd ui
91 của bạn

Gửi Mã thông báo tùy chỉnh từ giao diện người dùng

Phần còn thiếu duy nhất là cơ chế vận chuyển khóa cho dữ liệu trong cửa hàng. Khóa là ID

$ spring init --dependencies web,security ui/ && cd ui
26, vì vậy nếu chúng tôi có thể giữ khóa đó trong ứng dụng khách giao diện người dùng, chúng tôi có thể gửi nó dưới dạng tiêu đề tùy chỉnh tới máy chủ tài nguyên. Vì vậy, bộ điều khiển "nhà" sẽ cần thay đổi để nó gửi tiêu đề như một phần của yêu cầu HTTP cho tài nguyên lời chào. Ví dụ

Trang Chủ. thành phần. ts

$ mvn package
$ java -jar target/*.jar
7

(Một giải pháp tao nhã hơn có thể là lấy mã thông báo khi cần và sử dụng

$ mvn spring-boot:run
05 của chúng tôi để thêm tiêu đề vào mọi yêu cầu tới máy chủ tài nguyên. )

Thay vì truy cập trực tiếp vào "http. //máy chủ cục bộ. 9000", chúng tôi đã gói cuộc gọi đó trong cuộc gọi lại thành công của cuộc gọi đến điểm cuối tùy chỉnh mới trên máy chủ giao diện người dùng tại "/token". Việc thực hiện đó là tầm thường

UiỨng dụng. java

$ mvn package
$ java -jar target/*.jar
8

Vì vậy, ứng dụng giao diện người dùng đã sẵn sàng và sẽ bao gồm ID phiên trong tiêu đề có tên "X-Auth-Token" cho tất cả các cuộc gọi đến phần phụ trợ

Xác thực trong Máy chủ tài nguyên

Có một thay đổi nhỏ đối với máy chủ tài nguyên để nó có thể chấp nhận tiêu đề tùy chỉnh. Cấu hình CORS phải chỉ định tiêu đề đó là tiêu đề được phép từ các máy khách từ xa, ví dụ:. g

Ứng dụng tài nguyên. java

$ mvn package
$ java -jar target/*.jar
9

Kiểm tra trước chuyến bay từ trình duyệt hiện sẽ được xử lý bởi Spring MVC, nhưng chúng tôi cần thông báo cho Spring Security rằng nó được phép thông qua

Ứng dụng tài nguyên. java

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'};
}
0

Không cần

$ mvn spring-boot:run
06 truy cập vào tất cả các tài nguyên và có thể có một trình xử lý vô tình gửi dữ liệu nhạy cảm vì không biết rằng yêu cầu là trước chuyến bay. Tiện ích cấu hình
$ mvn spring-boot:run
07 giảm thiểu điều này bằng cách xử lý tất cả các yêu cầu trước chuyến bay trong lớp bộ lọc

Tất cả những gì còn lại là lấy mã thông báo tùy chỉnh trong máy chủ tài nguyên và sử dụng nó để xác thực người dùng của chúng tôi. This turns out to be pretty straightforward because all we need to do is tell Spring Security where the session repository is, and where to look for the token (session ID) in an incoming request. Trước tiên, chúng ta cần thêm các phụ thuộc Spring Session và Redis, sau đó chúng ta có thể thiết lập

$ spring init --dependencies web,security ui/ && cd ui
93

Ứng dụng tài nguyên. java

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'};
}
1

$ spring init --dependencies web,security ui/ && cd ui
93 được tạo này là hình ảnh phản chiếu của cái trong máy chủ giao diện người dùng, do đó, nó thiết lập Redis làm nơi lưu trữ phiên. Điểm khác biệt duy nhất là nó sử dụng một
$ mvn spring-boot:run
10 tùy chỉnh trông trong tiêu đề ("X-Auth-Token" theo mặc định) thay vì mặc định (cookie có tên "JSESSIONID"). Chúng tôi cũng cần ngăn trình duyệt bật lên hộp thoại trong ứng dụng khách chưa được xác thực - ứng dụng được bảo mật nhưng gửi 401 với
$ mvn spring-boot:run
11 theo mặc định, vì vậy trình duyệt sẽ phản hồi bằng hộp thoại cho tên người dùng và mật khẩu. Có nhiều cách để đạt được điều này, nhưng chúng tôi đã yêu cầu Angular gửi tiêu đề "X-Requested-With", vì vậy Spring Security xử lý nó cho chúng tôi theo mặc định

Có một thay đổi cuối cùng đối với máy chủ tài nguyên để nó hoạt động với sơ đồ xác thực mới của chúng tôi. Bảo mật mặc định của Spring Boot là không trạng thái và chúng tôi muốn điều này lưu trữ xác thực trong phiên, vì vậy chúng tôi cần phải rõ ràng trong

$ mvn spring-boot:run
12 (hoặc
$ spring init --dependencies web,security ui/ && cd ui
91)

ứng dụng. yml

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'};
}
2

Điều này nói với Spring Security "không bao giờ tạo phiên, nhưng hãy sử dụng phiên nếu nó ở đó" (nó sẽ ở đó vì xác thực trong giao diện người dùng)

Khởi chạy lại máy chủ tài nguyên và mở giao diện người dùng trong cửa sổ trình duyệt mới

Chúng tôi đã phải sử dụng một tiêu đề tùy chỉnh và viết mã trong ứng dụng khách để điền vào tiêu đề, điều này không quá phức tạp, nhưng có vẻ như nó mâu thuẫn với lời khuyên sử dụng cookie và phiên bất cứ khi nào có thể. Lập luận ở đó là không làm như vậy sẽ dẫn đến sự phức tạp không cần thiết bổ sung và chắc chắn rằng việc triển khai mà chúng ta có bây giờ là phức tạp nhất mà chúng ta từng thấy cho đến nay. phần kỹ thuật của giải pháp vượt xa logic kinh doanh (được thừa nhận là rất nhỏ). Đây chắc chắn là một lời chỉ trích công bằng (và chúng tôi dự định sẽ giải quyết trong phần tiếp theo của loạt bài này), nhưng chúng ta hãy xem xét ngắn gọn tại sao nó không đơn giản như chỉ sử dụng cookie và phiên cho mọi thứ

Ít nhất chúng tôi vẫn đang sử dụng phiên, điều này có ý nghĩa vì Spring Security và bộ chứa Servlet biết cách thực hiện điều đó mà chúng tôi không cần nỗ lực. Nhưng chúng tôi không thể tiếp tục sử dụng cookie để vận chuyển mã thông báo xác thực sao? . Bạn chỉ có thể tìm kiếm trong cửa hàng cookie của trình duyệt từ ứng dụng khách JavaScript, nhưng có một số hạn chế và vì lý do chính đáng. Cụ thể là bạn không có quyền truy cập vào cookie được gửi bởi máy chủ dưới dạng "HttpOnly" (bạn sẽ thấy trường hợp này theo mặc định đối với cookie phiên). Bạn cũng không thể đặt cookie trong các yêu cầu gửi đi, vì vậy chúng tôi không thể đặt cookie "SESSION" (là tên cookie mặc định của Phiên mùa xuân), chúng tôi phải sử dụng tiêu đề "X-Session" tùy chỉnh. Cả hai hạn chế này đều nhằm mục đích bảo vệ chính bạn nên các tập lệnh độc hại không thể truy cập tài nguyên của bạn nếu không có sự cho phép thích hợp

TL; DR giao diện người dùng và máy chủ tài nguyên không có nguồn gốc chung, vì vậy chúng không thể chia sẻ cookie (mặc dù chúng tôi có thể sử dụng Phiên mùa xuân để buộc chúng chia sẻ phiên)

Phần kết luận

Chúng tôi đã sao chép các tính năng của ứng dụng trong. một trang chủ có lời chào được tìm nạp từ một chương trình phụ trợ từ xa, với các liên kết đăng nhập và đăng xuất trong thanh điều hướng. Sự khác biệt là lời chào đến từ một máy chủ tài nguyên độc lập, thay vì được nhúng trong máy chủ giao diện người dùng. Điều này làm tăng thêm độ phức tạp đáng kể cho việc triển khai, nhưng tin tốt là chúng tôi có giải pháp chủ yếu dựa trên cấu hình (và thực tế là khai báo 100%). Chúng tôi thậm chí có thể làm cho giải pháp mang tính khai báo 100% bằng cách trích xuất tất cả mã mới vào thư viện (Cấu hình mùa xuân và chỉ thị tùy chỉnh góc). Chúng tôi sẽ trì hoãn nhiệm vụ thú vị đó sau một vài phần tiếp theo. Trong phần này, chúng ta sẽ xem xét một cách thực sự tuyệt vời khác để giảm tất cả sự phức tạp trong quá trình triển khai hiện tại. Mẫu cổng API (máy khách gửi tất cả các yêu cầu của nó đến một nơi và xác thực được xử lý ở đó)

Chúng tôi đã sử dụng Phiên mùa xuân tại đây để chia sẻ các phiên giữa 2 máy chủ không cùng một ứng dụng về mặt logic. Đó là một thủ thuật gọn gàng và không thể thực hiện được với các phiên phân phối JEE "thông thường"

Cổng API

Trong phần này, chúng ta tiếp tục về cách sử dụng Spring Security với Angular trong một "ứng dụng trang đơn". Ở đây chúng tôi trình bày cách xây dựng Cổng API để kiểm soát xác thực và truy cập vào tài nguyên phụ trợ bằng Spring Cloud. Đây là phần thứ tư trong một loạt các phần và bạn có thể bắt kịp các khối xây dựng cơ bản của ứng dụng hoặc xây dựng ứng dụng từ đầu bằng cách đọc hoặc bạn có thể truy cập thẳng vào mã nguồn trong Github. Trong phần này, chúng tôi đã xây dựng một ứng dụng phân tán đơn giản sử dụng Phiên mùa xuân để xác thực các tài nguyên phụ trợ. Trong phần này, chúng tôi biến máy chủ giao diện người dùng thành một proxy ngược với máy chủ tài nguyên phụ trợ, khắc phục sự cố với lần triển khai cuối cùng (độ phức tạp kỹ thuật do xác thực mã thông báo tùy chỉnh gây ra) và cung cấp cho chúng tôi nhiều tùy chọn mới để kiểm soát quyền truy cập từ ứng dụng khách trình duyệt

Lời nhắc nhở. nếu bạn đang làm việc qua phần này với ứng dụng mẫu, hãy đảm bảo xóa bộ nhớ cache của trình duyệt khỏi cookie và thông tin xác thực HTTP Basic. Trong Chrome, cách tốt nhất để làm điều đó cho một máy chủ là mở một cửa sổ ẩn danh mới

Tạo một cổng API

Cổng API là một điểm truy cập (và kiểm soát) duy nhất cho ứng dụng khách giao diện người dùng, có thể dựa trên trình duyệt (như ví dụ trong phần này) hoặc thiết bị di động. Máy khách chỉ cần biết URL của một máy chủ và phần phụ trợ có thể được cấu trúc lại theo ý muốn mà không cần thay đổi, đây là một lợi thế đáng kể. Có những lợi thế khác về tập trung và kiểm soát. giới hạn tốc độ, xác thực, kiểm toán và ghi nhật ký. Và triển khai một proxy ngược đơn giản thực sự đơn giản với Spring Cloud

Nếu bạn đang theo dõi mã, bạn sẽ biết rằng việc triển khai ứng dụng ở phần cuối hơi phức tạp, vì vậy đây không phải là nơi tuyệt vời để lặp đi lặp lại. Tuy nhiên, có một nửa điểm mà chúng ta có thể bắt đầu dễ dàng hơn, khi tài nguyên phụ trợ chưa được bảo mật bằng Spring Security. Mã nguồn cho dự án này là một dự án riêng biệt trong Github nên chúng tôi sẽ bắt đầu từ đó. Nó có một máy chủ UI và một máy chủ tài nguyên và chúng đang nói chuyện với nhau. Máy chủ tài nguyên chưa có Spring Security nên chúng tôi có thể làm cho hệ thống hoạt động trước rồi thêm lớp đó

Proxy đảo ngược khai báo trong một dòng

Để biến nó thành Cổng API, máy chủ giao diện người dùng cần một tinh chỉnh nhỏ. Ở đâu đó trong cấu hình Spring, chúng ta cần thêm một chú thích

$ mvn spring-boot:run
14, e. g. trong lớp ứng dụng chính (chỉ)

UiỨng dụng. java

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'};
}
3

và trong tệp cấu hình bên ngoài, chúng tôi cần ánh xạ tài nguyên cục bộ trong máy chủ giao diện người dùng sang tài nguyên từ xa trong cấu hình bên ngoài ("ứng dụng. yml")

ứng dụng. yml

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'};
}
4

Điều này cho biết "ánh xạ các đường dẫn có mẫu/tài nguyên/** trong máy chủ này tới cùng một đường dẫn trong máy chủ từ xa tại máy chủ cục bộ. 9000". Đơn giản và hiệu quả (Được rồi, đó là 6 dòng bao gồm cả YAML, nhưng không phải lúc nào bạn cũng cần điều đó)

Tất cả những gì chúng ta cần để thực hiện công việc này là đúng nội dung trên đường dẫn lớp. Với mục đích đó, chúng tôi có một vài dòng mới trong Maven POM của chúng tôi

quả bông. xml

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'};
}
5

Lưu ý việc sử dụng "spring-cloud-starter-zuul" - đó là POM khởi động giống như POM khởi động mùa xuân, nhưng nó chi phối các phụ thuộc mà chúng tôi cần cho proxy Zuul này. Chúng tôi cũng đang sử dụng

$ mvn spring-boot:run
15 vì chúng tôi muốn có thể phụ thuộc vào tất cả các phiên bản của phụ thuộc bắc cầu là chính xác

Sử dụng Proxy trong Máy khách

Với những thay đổi đó, ứng dụng của chúng tôi vẫn hoạt động, nhưng chúng tôi chưa thực sự sử dụng proxy mới cho đến khi chúng tôi sửa đổi ứng dụng khách. May mắn thay đó là tầm thường. Chúng tôi chỉ cần hoàn nguyên thay đổi mà chúng tôi đã thực hiện từ mẫu "đơn" sang mẫu "vani" trong

Trang Chủ. thành phần. ts

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'};
}
6

Bây giờ khi chúng tôi kích hoạt máy chủ, mọi thứ đều hoạt động và các yêu cầu đang được ủy quyền thông qua giao diện người dùng (Cổng API) đến máy chủ tài nguyên

Further Simplifications

Thậm chí còn tốt hơn. chúng tôi không cần bộ lọc CORS nữa trong máy chủ tài nguyên. Dù sao thì chúng tôi cũng đã ném cái đó vào nhau khá nhanh và lẽ ra phải là đèn đỏ mà chúng tôi phải làm bất cứ điều gì tập trung vào kỹ thuật bằng tay (đặc biệt là khi liên quan đến bảo mật). Fortunately it is now redundant, so we can just throw it away, and go back to sleeping at night

Bảo mật máy chủ tài nguyên

You might remember in the intermediate state that we started from there is no security in place for the resource server

Aside. Lack of software security might not even be a problem if your network architecture mirrors the application architecture (you can just make the resource server physically inaccessible to anyone but the UI server). As a simple demonstration of that we can make the resource server only accessible on localhost. Just add this to

$ spring init --dependencies web,security ui/ && cd ui
91 in the resource server

ứng dụng. của cải

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'};
}
7

Wow, that was easy. Do that with a network address that’s only visible in your data center and you have a security solution that works for all resource servers and all user desktops

Suppose that we decide we do need security at the software level (quite likely for a number of reasons). That’s not going to be a problem, because all we need to do is add Spring Security as a dependency (in the resource server POM)

quả bông. xml

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'};
}
8

That’s enough to get us a secure resource server, but it won’t get us a working application yet, for the same reason that it didn’t in . there is no shared authentication state between the two servers

Sharing Authentication State

We can use the same mechanism to share authentication (and CSRF) state as we did in the last, i. e. Spring Session. We add the dependency to both servers as before

quả bông. xml

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'};
}
9

but this time the configuration is much simpler because we can just add the same

$ spring init --dependencies web,security ui/ && cd ui
93 declaration to both. First the UI server, declaring explicitly that we want all headers to be forwarded (i. e. none are "sensitive")

ứng dụng. yml

Welcome {{title}}!

Id: {{greeting.id}}

Message: {{greeting.content}}!

0

Then we can move on to the resource server. There are two small changes to make. one is to explicitly disable HTTP Basic in the resource server (to prevent the browser from popping up authentication dialogs)

Ứng dụng tài nguyên. java

Welcome {{title}}!

Id: {{greeting.id}}

Message: {{greeting.content}}!

1

Aside. an alternative, which would also prevent the authentication dialog, would be to keep HTTP Basic but change the 401 challenge to something other than "Basic". You can do that with a one-line implementation of

$ spring init --dependencies web,security ui/ && cd ui
69 in the
$ spring init --dependencies web,security ui/ && cd ui
70 configuration callback

and the other is to explicitly ask for a non-stateless session creation policy in

$ spring init --dependencies web,security ui/ && cd ui
91

ứng dụng. của cải

Welcome {{title}}!

Id: {{greeting.id}}

Message: {{greeting.content}}!

2

As long as redis is still running in the background (use the

$ mvn spring-boot:run
00 if you like to start it) then the system will work. Load the homepage for the UI at http. //localhost. 8080 and login and you will see the message from the backend rendered on the homepage

Làm thế nào nó hoạt động?

What is going on behind the scenes now? First we can look at the HTTP requests in the UI server (and API Gateway)

Động từĐường dẫnTrạng tháiPhản hồi

ĐƯỢC

/

200

mục lục. html

ĐƯỢC

/*. js

200

Assets form angular

ĐƯỢC

/người sử dụng

401

Trái phép (bỏ qua)

ĐƯỢC

/nguồn

401

Unauthenticated access to resource

ĐƯỢC

/người sử dụng

200

JSON authenticated user

ĐƯỢC

/nguồn

200

(Proxied) JSON greeting

That’s identical to the sequence at the end of except for the fact that the cookie names are slightly different ("SESSION" instead of "JSESSIONID") because we are using Spring Session. But the architecture is different and that last request to "/resource" is special because it was proxied to the resource server

We can see the reverse proxy in action by looking at the "/trace" endpoint in the UI server (from Spring Boot Actuator, which we added with the Spring Cloud dependencies). Go to http. //localhost. 8080/trace in a new browser (if you don’t have one already get a JSON plugin for your browser to make it nice and readable). You will need to authenticate with HTTP Basic (browser popup), but the same credentials are valid as for your login form. At or near the start you should see a pair of requests something like this

Try to use a different browser so that there is no chance of authentication crossover (e. g. use Firefox if yoused Chrome for testing the UI) - it won’t stop the app from working, but it will make the traces harder to read if they contain a mixture of authentication from the same browser

/trace

Welcome {{title}}!

Id: {{greeting.id}}

Message: {{greeting.content}}!

3

The second entry there is the request from the client to the gateway on "/resource" and you can see the cookies (added by the browser) and the CSRF header (added by Angular as discussed in Part II). The first entry has

$ mvn spring-boot:run
22 and that means it’s tracing the call to the resource server. You can see it went out to a uri path "/" and you can see that (crucially) the cookies and CSRF headers have been sent too. Without Spring Session these headers would be meaningless to the resource server, but the way we have set it up it can now use those headers to re-constitute a session with authentication and CSRF token data. So the request is permitted and we are in business

Phần kết luận

We covered quite a lot in this section but we got to a really nice place where there is a minimal amount of boilerplate code in our two servers, they are both nicely secure and the user experience isn’t compromised. That alone would be a reason to use the API Gateway pattern, but really we have only scratched the surface of what that might be used for (Netflix uses it for a lot of things). Read up on Spring Cloud to find out more on how to make it easy to add more features to the gateway. The in this series will extend the application architecture a bit by extracting the authentication responsibilities to a separate server (the Single Sign On pattern)

Single Sign On with OAuth2

In this section we continue of how to use Spring Security with Angular in a "single page application". Here we show how to use Spring Security OAuth together with Spring Cloud to extend our API Gateway to do Single Sign On and OAuth2 token authentication to backend resources. This is the fifth in a series of sections, and you can catch up on the basic building blocks of the application or build it from scratch by reading the , or you can just go straight to the source code in Github. In the we built a small distributed application that used Spring Session to authenticate the backend resources and Spring Cloud to implement an embedded API Gateway in the UI server. In this section we extract the authentication responsibilities to a separate server to make our UI server the first of potentially many Single Sign On applications to the authorization server. This is a common pattern in many applications these days, both in the enterprise and in social startups. We will use an OAuth2 server as the authenticator, so that we can also use it to grant tokens for the backend resource server. Spring Cloud will automatically relay the access token to our backend, and enable us to further simplify the implementation of both the UI and resource servers

Lời nhắc nhở. nếu bạn đang làm việc qua phần này với ứng dụng mẫu, hãy đảm bảo xóa bộ nhớ cache của trình duyệt khỏi cookie và thông tin xác thực HTTP Basic. Trong Chrome, cách tốt nhất để làm điều đó cho một máy chủ là mở một cửa sổ ẩn danh mới

Creating an OAuth2 Authorization Server

Our first step is to create a new server to handle authentication and token management. Following the steps in we can begin with Spring Boot Initializr. E. g. using curl on a UN*X like system

Welcome {{title}}!

Id: {{greeting.id}}

Message: {{greeting.content}}!

4

Sau đó, bạn có thể nhập dự án đó (theo mặc định là dự án Maven Java bình thường) vào IDE yêu thích của mình hoặc chỉ làm việc với các tệp và "mvn" trên dòng lệnh

Adding the OAuth2 Dependencies

We need to add the Spring OAuth dependencies, so in our POM we add

quả bông. xml

Welcome {{title}}!

Id: {{greeting.id}}

Message: {{greeting.content}}!

5

The authorization server is pretty easy to implement. A minimal version looks like this

AuthserverApplication. java

Welcome {{title}}!

Id: {{greeting.id}}

Message: {{greeting.content}}!

6

We only have to do 1 more thing (after adding

$ mvn spring-boot:run
23)

ứng dụng. của cải

Welcome {{title}}!

Id: {{greeting.id}}

Message: {{greeting.content}}!

7

This registers a client "acme" with a secret and some authorized grant types including "authorization_code"

Now let’s get it running on port 9999, with a predictable password for testing

ứng dụng. của cải

Welcome {{title}}!

Id: {{greeting.id}}

Message: {{greeting.content}}!

8

We also set the context path so that it doesn’t use the default ("/") because otherwise you can get cookies for other servers on localhost being sent to the wrong server. So get the server running and we can make sure it is working

$ mvn spring-boot:run

or start the

$ spring init --dependencies web,security ui/ && cd ui
10 method in your IDE

Testing the Authorization Server

Our server is using the Spring Boot default security settings, so like the server in it will be protected by HTTP Basic authentication. To initiate an you visit the authorization endpoint, e. g. at http. //localhost. 9999/uaa/oauth/authorize?response_type=code&client_id=acme&redirect_uri=http. //example. com once you have authenticated you will get a redirect to example. com with an authorization code attached, e. g. http. //example. com/?code=jYWioI

for the purposes of this sample application we have created a client "acme" with no registered redirect, which is what enables us to get a redirect the example. com. Trong ứng dụng sản xuất, bạn phải luôn đăng ký chuyển hướng (và sử dụng HTTPS)

Mã này có thể được đổi lấy mã thông báo truy cập bằng thông tin xác thực ứng dụng khách "acme" trên điểm cuối mã thông báo

@SpringBootApplication
@RestController
public class UiApplication {

  @RequestMapping("/resource")
  public Map home() {
    Map model = new HashMap();
    model.put("id", UUID.randomUUID().toString());
    model.put("content", "Hello World");
    return model;
  }

  public static void main(String[] args) {
    SpringApplication.run(UiApplication.class, args);
  }

}
0

Mã thông báo truy cập là một UUID ("2219199c…"), được hỗ trợ bởi kho lưu trữ mã thông báo trong bộ nhớ trong máy chủ. Chúng tôi cũng đã nhận được mã thông báo làm mới mà chúng tôi có thể sử dụng để nhận mã thông báo truy cập mới khi mã thông báo hiện tại hết hạn

vì chúng tôi đã cho phép cấp "mật khẩu" cho ứng dụng khách "acme", chúng tôi cũng có thể nhận mã thông báo trực tiếp từ điểm cuối mã thông báo bằng thông tin xác thực người dùng và curl thay vì mã ủy quyền. This is not suitable for a browser based client, but it’s useful for testing

If you followed the link above you would have seen the whitelabel UI provided by Spring OAuth. To start with we will use this and we can come back later to beef it up like we did in for the self-contained server

Changing the Resource Server

If we follow on from , our resource server is using Spring Session for authentication, so we can take that out and replace it with Spring OAuth. We also need to remove the Spring Session and Redis dependencies, so replace this

quả bông. xml

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'};
}
9

with this

quả bông. xml

Welcome {{title}}!

Id: {{greeting.id}}

Message: {{greeting.content}}!

5

and then remove the session

$ spring init --dependencies web,security ui/ && cd ui
93 from the main application class, replacing it with the convenient
$ mvn spring-boot:run
26 annotation (from Spring Security OAuth2)

Ứng dụng tài nguyên. java

@SpringBootApplication
@RestController
public class UiApplication {

  @RequestMapping("/resource")
  public Map home() {
    Map model = new HashMap();
    model.put("id", UUID.randomUUID().toString());
    model.put("content", "Hello World");
    return model;
  }

  public static void main(String[] args) {
    SpringApplication.run(UiApplication.class, args);
  }

}
3

With that one change the app is ready to challenge for an access token instead of HTTP Basic, but we need a config change to actually finish the process. We are going to add a small amount of external configuration (in "application. properties") to allow the resource server to decode the tokens it is given and authenticate a user

ứng dụng. của cải

@SpringBootApplication
@RestController
public class UiApplication {

  @RequestMapping("/resource")
  public Map home() {
    Map model = new HashMap();
    model.put("id", UUID.randomUUID().toString());
    model.put("content", "Hello World");
    return model;
  }

  public static void main(String[] args) {
    SpringApplication.run(UiApplication.class, args);
  }

}
4

This tells the server that it can use the token to access a "/user" endpoint and use that to derive authentication information (it’s a bit like the "/me" endpoint in the Facebook API). Effectively it provides a way for the resource server to decode the token, as expressed by the

$ mvn spring-boot:run
27 interface in Spring OAuth2

Run the application and hit the home page with a command line client

@SpringBootApplication
@RestController
public class UiApplication {

  @RequestMapping("/resource")
  public Map home() {
    Map model = new HashMap();
    model.put("id", UUID.randomUUID().toString());
    model.put("content", "Hello World");
    return model;
  }

  public static void main(String[] args) {
    SpringApplication.run(UiApplication.class, args);
  }

}
5

and you will see a 401 with a "WWW-Authenticate" header indicating that it wants a bearer token

the

$ mvn spring-boot:run
28 is by far not the only way of hooking a resource server up with a way to decode tokens. In fact it’s sort of a lowest common denominator (and not part of the spec), but quite often available from OAuth2 providers (like Facebook, Cloud Foundry, Github), and other choices are available. For instance you can encode the user authentication in the token itself (e. g. with JWT), or use a shared backend store. There is also a
$ mvn spring-boot:run
29 endpoint in CloudFoundry, which provides more detailed information than the user info endpoint, but which requires more thorough authentication. Different options (naturally) provide different benefits and trade-offs, but a full discussion of those is outside the scope of this section

Implementing the User Endpoint

On the authorization server we can easily add that endpoint

AuthserverApplication. java

@SpringBootApplication
@RestController
public class UiApplication {

  @RequestMapping("/resource")
  public Map home() {
    Map model = new HashMap();
    model.put("id", UUID.randomUUID().toString());
    model.put("content", "Hello World");
    return model;
  }

  public static void main(String[] args) {
    SpringApplication.run(UiApplication.class, args);
  }

}
6

We added a

$ spring init --dependencies web,security ui/ && cd ui
18 the same as the UI server in , and also the
$ mvn spring-boot:run
26 annotation from Spring OAuth, which by default secures everything in an authorization server except the "/oauth/*" endpoints

With that endpoint in place we can test it and the greeting resource, since they both now accept bearer tokens that were created by the authorization server

@SpringBootApplication
@RestController
public class UiApplication {

  @RequestMapping("/resource")
  public Map home() {
    Map model = new HashMap();
    model.put("id", UUID.randomUUID().toString());
    model.put("content", "Hello World");
    return model;
  }

  public static void main(String[] args) {
    SpringApplication.run(UiApplication.class, args);
  }

}
7

(substitute the value of the access token that you obtain from your own authorization server to get that working yourself)

The UI Server

The final piece of this application we need to complete is the UI server, extracting the authentication part and delegating to the authorization server. So, as with , we first need to remove the Spring Session and Redis dependencies and replace them with Spring OAuth2. Because we are using Zuul in the UI layer we actually use

$ mvn spring-boot:run
32 instead of
$ mvn spring-boot:run
33 directly (this sets up some autoconfiguration for relaying tokens through the proxy)

Once that is done we can remove the session filter and the "/user" endpoint as well, and set up the application to redirect to the authorization server (using the

$ mvn spring-boot:run
34 annotation)

UiỨng dụng. java

@SpringBootApplication
@RestController
public class UiApplication {

  @RequestMapping("/resource")
  public Map home() {
    Map model = new HashMap();
    model.put("id", UUID.randomUUID().toString());
    model.put("content", "Hello World");
    return model;
  }

  public static void main(String[] args) {
    SpringApplication.run(UiApplication.class, args);
  }

}
8

Recall from that the UI server, by virtue of the

$ mvn spring-boot:run
14, acts an API Gateway and we can declare the route mappings in YAML. So the "/user" endpoint can be proxied to the authorization server

ứng dụng. yml

@SpringBootApplication
@RestController
public class UiApplication {

  @RequestMapping("/resource")
  public Map home() {
    Map model = new HashMap();
    model.put("id", UUID.randomUUID().toString());
    model.put("content", "Hello World");
    return model;
  }

  public static void main(String[] args) {
    SpringApplication.run(UiApplication.class, args);
  }

}
9

Lastly, we need to change the application to a

$ mvn spring-boot:run
36 since now it is going to be used to modify the defaults in the SSO filter chain set up by
$ mvn spring-boot:run
34

SecurityConfiguration. java

$ curl localhost:8080/resource
{"timestamp":1420442772928,"status":401,"error":"Unauthorized","message":"Full authentication is required to access this resource","path":"/resource"}
0

The main changes (apart from the base class name) are that the matchers go into their own method, and there is no need for

$ mvn spring-boot:run
38 any more. The explicit
$ spring init --dependencies web,security ui/ && cd ui
80 configuration explicitly adds a success url that is unprotected, so that an XHR request to
$ mvn spring-boot:run
40 will return successfully

There are also some mandatory external configuration properties for the

$ mvn spring-boot:run
34 annotation to be able to contact and authenticate with the right authorization server. So we need this in
$ mvn spring-boot:run
12

ứng dụng. yml

$ curl localhost:8080/resource
{"timestamp":1420442772928,"status":401,"error":"Unauthorized","message":"Full authentication is required to access this resource","path":"/resource"}
1

The bulk of that is about the OAuth2 client ("acme") and the authorization server locations. There is also a

$ mvn spring-boot:run
28 (just like in the resource server) so that the user can be authenticated in the UI app itself

If you want the UI application be able to refresh expired access tokens automatically, you have to get an

$ mvn spring-boot:run
44 injected into the Zuul filter that does the relay. You can do this by just creating a bean of that type (check the
$ mvn spring-boot:run
45 for details)

$ curl localhost:8080/resource
{"timestamp":1420442772928,"status":401,"error":"Unauthorized","message":"Full authentication is required to access this resource","path":"/resource"}
2

In the Client

There are some tweaks to the UI application on the front end that we still need to make to trigger the redirect to the authorization server. In this simple demo we can strip the Angular app down to its bare essentials so you can see what is going on more clearly. So we forgo, for now, the use of forms or routes, and we go back to a single Angular component

ứng dụng. thành phần. ts

$ curl localhost:8080/resource
{"timestamp":1420442772928,"status":401,"error":"Unauthorized","message":"Full authentication is required to access this resource","path":"/resource"}
3

The

$ spring init --dependencies web,security ui/ && cd ui
12 handles everything, fetching the user details and, if successful, the greeting. It also provides the
$ mvn spring-boot:run
47 function

Now we need to create the template for this new component

ứng dụng. thành phần. html

$ curl localhost:8080/resource
{"timestamp":1420442772928,"status":401,"error":"Unauthorized","message":"Full authentication is required to access this resource","path":"/resource"}
4

and include it in the home page as

$ mvn spring-boot:run
48

Note that the navigation link for "Login" is a regular link with an

$ mvn spring-boot:run
49 (not an Angular route). The "/login" endpoint that this goes to is handled by Spring Security and if the user is not authenticated it will result in a redirect to the authorization server

Làm thế nào nó hoạt động?

Run all the servers together now, and visit the UI in a browser at http. //localhost. 8080. Click on the "login" link and you will be redirected to the authorization server to authenticate (HTTP Basic popup) and approve the token grant (whitelabel HTML), before being redirected to the home page in the UI with the greeting fetched from the OAuth2 resource server using the same token as we authenticated the UI with

Có thể nhìn thấy các tương tác giữa trình duyệt và chương trình phụ trợ trong trình duyệt của bạn nếu bạn sử dụng một số công cụ dành cho nhà phát triển (thường là F12 mở ra, hoạt động trong Chrome theo mặc định, có thể yêu cầu plugin trong Firefox). Đây là một bản tóm tắt

Động từĐường dẫnTrạng tháiPhản hồi

ĐƯỢC

/

200

mục lục. html

ĐƯỢC

/*. js

200

Tài sản từ góc

ĐƯỢC

/người sử dụng

302

Redirect to login page

ĐƯỢC

/login

302

Redirect to auth server

ĐƯỢC

(uaa)/oauth/authorize

401

(ignored)

ĐƯỢC

/login

302

Redirect to auth server

ĐƯỢC

(uaa)/oauth/authorize

200

HTTP Basic auth happens here

POST

(uaa)/oauth/authorize

302

User approves grant, redirect to /login

ĐƯỢC

/login

302

Redirect to home page

ĐƯỢC

/người sử dụng

200

(Proxied) JSON authenticated user

ĐƯỢC

/app. html

200

HTML partial for home page

ĐƯỢC

/nguồn

200

(Proxied) JSON greeting

The requests prefixed with (uaa) are to the authorization server. The responses that are marked "ignored" are responses received by Angular in an XHR call, and since we aren’t processing that data they are dropped on the floor. We do look for an authenticated user in the case of the "/user" resource, but since it isn’t there in the first call, that response is dropped

In the "/trace" endpoint of the UI (scroll down to the bottom) you will see the proxied backend requests to "/user" and "/resource", with

$ mvn spring-boot:run
50 and the bearer token instead of the cookie (as it would have been in ) being used for authentication. Spring Cloud Security has taken care of this for us. by recognising that we has
$ mvn spring-boot:run
34 and
$ mvn spring-boot:run
14 it has figured out that (by default) we want to relay the token to the proxied backends

As in previous sections, try to use a different browser for "/trace" so that there is no chance of authentication crossover (e. g. use Firefox if you used Chrome for testing the UI)

The Logout Experience

If you click on the "logout" link you will see that the home page changes (the greeting is no longer displayed) so the user is no longer authenticated with the UI server. Click back on "login" though and you actually don’t need to go back through the authentication and approval cycle in the authorization server (because you haven’t logged out of that). Các ý kiến ​​sẽ được chia ra về việc liệu đó có phải là trải nghiệm người dùng mong muốn hay không và đó có phải là một vấn đề nổi tiếng phức tạp hay không (Đăng xuất một lần. Science Direct article and Shibboleth docs). The ideal user experience might not be technically feasible, and you also have to be suspicious sometimes that users really want what they say they want. "I want 'logout' to log me out" sounds simple enough, but the obvious response is, "Logged out of what? Do you want to be logged out of all the systems controlled by this SSO server, or just the one that you clicked the 'logout' link in?" If you are interested then there is of this tutorial where it is discussed in more depth

Phần kết luận

This is almost the end of our shallow tour through the Spring Security and Angular stack. We have a nice architecture now with clear responsibilities in three separate components, UI/API Gateway, resource server and authorization server/token granter. The amount of non-business code in all layers is now minimal, and it’s easy to see where to extend and improve the implementation with more business logic. The next steps will be to tidy up the UI in our authorization server, and probably add some more tests, including tests on the JavaScript client. Another interesting task is to extract all the boiler plate code and put it in a library (e. g. "spring-security-angular") containing Spring Security and Spring Session autoconfiguration and some webjars resources for the navigation controller in the Angular piece. Having read the sections in thir series, anyone who was hoping to learn the inner workings of either Angular or Spring Security will probably be disappointed, but if you wanted to see how they can work well together and how a little bit of configuration can go a long way, then hopefully you will have had a good experience. Spring Cloud is new and these samples required snapshots when they were written, but there are release candidates available and a GA release coming soon, so check it out and send some feedback via Github or gitter. Tôi

Loạt bài này nói về các quyết định truy cập (ngoài xác thực) và sử dụng nhiều ứng dụng giao diện người dùng phía sau cùng một proxy

Addendum. Bootstrap UI and JWT Tokens for the Authorization Server

You will find another version of this application in the source code in Github which has a pretty login page and user approval page implemented similarly to the way we did the login page in . It also uses JWT to encode the tokens, so instead of using the "/user" endpoint, the resource server can pull enough information out of the token itself to do a simple authentication. The browser client still uses it, proxied through the UI server, so that it can determine if a user is authenticated (it doesn’t need to do that very often, compared to the likely number of calls to a resource server in a real application)

Multiple UI Applications and a Gateway

In this section we continue of how to use Spring Security with Angular in a "single page application". Here we show how to use Spring Session together with Spring Cloud to combine the features of the systems we built in parts II and IV, and actually end up building 3 single page applications with quite different responsibilities. The aim is to build a Gateway (like in ) that is used not only for API resources but also to load the UI from a backend server. We simplify the token-wrangling bits of by using the Gateway to pass through the authentication to the backends. Then we extend the system to show how we can make local, granular access decisions in the backends, while still controlling identity and authentication at the Gateway. This is a very powerful model for building distributed systems in general, and has a number of benefits that we can explore as we introduce the features in the code we build

Reminder. if you are working through this section with the sample application, be sure to clear your browser cache of cookies and HTTP Basic credentials. In Chrome the best way to do that is to open a new incognito window

Target Architecture

Here’s a picture of the basic system we are going to build to start with

Mã hóa html góc

Like the other sample applications in this series it has a UI (HTML and JavaScript) and a Resource server. Like the sample in it has a Gateway, but here it is separate, not part of the UI. The UI effectively becomes part of the backend, giving us even more choice to re-configure and re-implement features, and also bringing other benefits as we will see

The browser goes to the Gateway for everything and it doesn’t have to know about the architecture of the backend (fundamentally, it has no idea that there is a back end). One of the things the browser does in this Gateway is authentication, e. g. it sends a username and password like in , and it gets a cookie in return. On subsequent requests it presents the cookie automatically and the Gateway passes it through to the backends. No code needs to be written on the client to enable the cookie passing. The backends use the cookie to authenticate and because all components share a session they share the same information about the user. Contrast this with where the cookie had to be converted to an access token in the Gateway, and the access token then had to be independently decoded by all the backend components

As in the Gateway simplifies the interaction between clients and servers, and it presents a small, well-defined surface on which to deal with security. For example, we don’t need to worry about Cross Origin Resource Sharing, which is a welcome relief since it is easy to get wrong

The source code for the complete project we are going to build is in Github here, so you can just clone the project and work directly from there if you want. There is an extra component in the end state of this system ("double-admin") so ignore that for now

Building the Backend

In this architecture the backend is very similar to the "spring-session" sample we built in , with the exception that it doesn’t actually need a login page. The easiest way to get to what we want here is probably to copy the "resource" server from Section III and take the UI from the "basic" sample in . To get from the "basic" UI to the one we want here, we need only to add a couple of dependencies (like when we first used Spring Session in Section III)

quả bông. xml

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'};
}
9

Since this is now a UI there is no need for the "/resource" endpoint. When you have done that you will have a very simple Angular application (the same as in the "basic" sample), which simplifies testing and reasoning about its behaviour greatly

Lastly, we want this server to run as a backend, so we’ll give it a non-default port to listen on (in

$ spring init --dependencies web,security ui/ && cd ui
91)

ứng dụng. của cải

$ curl localhost:8080/resource
{"timestamp":1420442772928,"status":401,"error":"Unauthorized","message":"Full authentication is required to access this resource","path":"/resource"}
6

If that’s the whole content

$ spring init --dependencies web,security ui/ && cd ui
91 then the application will be secure and accessible to a user called "user" with a password that is random, but printed on the console (at log level INFO) on startup. The "security. sessions" setting means that Spring Security will accept cookies as authentication tokens but won’t create them unless they already exist

Máy chủ tài nguyên

The Resource server is easy to generate from one of our existing samples. It is the same as the "spring-session" Resource server in . just a "/resource" endpoint Spring Session to get the distributed session data. We want this server to have a non-default port to listen on, and we want to be able to look up authentication in the session so we need this (in

$ spring init --dependencies web,security ui/ && cd ui
91)

ứng dụng. của cải

$ curl localhost:8080/resource
{"timestamp":1420442772928,"status":401,"error":"Unauthorized","message":"Full authentication is required to access this resource","path":"/resource"}
7

We are going to be POSTing changes to our message resource, which is a new feature in this tutorial. It means that we are going to need CSRF protection in the backend, and we need to do the usual trick to make Spring Security play nicely with Angular

$ curl localhost:8080/resource
{"timestamp":1420442772928,"status":401,"error":"Unauthorized","message":"Full authentication is required to access this resource","path":"/resource"}
8

The completed sample is here in github if you want to take a peek

The Gateway

For an initial implementation of a Gateway (the simplest thing that could possibly work) we can just take an empty Spring Boot web application and add the

$ mvn spring-boot:run
14 annotation. As we saw in there are several ways to do that, and one is to use the Spring Initializr to generate a skeleton project. Even easier, is to use the Spring Cloud Initializr which is the same thing, but for Spring Cloud applications. Using the same sequence of command line operations as in Section I

$ curl localhost:8080/resource
{"timestamp":1420442772928,"status":401,"error":"Unauthorized","message":"Full authentication is required to access this resource","path":"/resource"}
9

You can then import that project (it’s a normal Maven Java project by default) into your favourite IDE, or just work with the files and "mvn" on the command line. There is a version in github if you want to go from there, but it has a few extra features that we don’t need yet

Starting from the blank Initializr application, we add the Spring Session dependency (like in the UI above). The Gateway is ready to run, but it doesn’t yet know about our backend services, so let’s just set that up in its

$ mvn spring-boot:run
12 (renaming from
$ spring init --dependencies web,security ui/ && cd ui
91 if you did the curl thing above)

ứng dụng. yml

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);
  }
}
0

There are 2 routes in the proxy, both of which pass cookies downstream using the

$ mvn spring-boot:run
59 property, one each for the UI and resource server, and we have set up a default password and a session persistence strategy (telling Spring Security to always create a session on authentication). This last bit is important because we want authentication and therefore sessions to be managed in the Gateway

Up and Running

We now have three components, running on 3 ports. If you point the browser at http. //localhost. 8080/ui/ you should get an HTTP Basic challenge, and you can authenticate as "user/password" (your credentials in the Gateway), and once you do that you should see a greeting in the UI, via a backend call through the proxy to the Resource server

Có thể nhìn thấy các tương tác giữa trình duyệt và chương trình phụ trợ trong trình duyệt của bạn nếu bạn sử dụng một số công cụ dành cho nhà phát triển (thường là F12 mở ra, hoạt động trong Chrome theo mặc định, có thể yêu cầu plugin trong Firefox). Đây là một bản tóm tắt

Động từĐường dẫnTrạng tháiPhản hồi

ĐƯỢC

/ui/

401

Trình duyệt nhắc xác thực

ĐƯỢC

/ui/

200

mục lục. html

ĐƯỢC

/ui/*. js

200

Angular assets

ĐƯỢC

/ui/js/hello. js

200

logic ứng dụng

ĐƯỢC

/ui/user

200

authentication

ĐƯỢC

/resource/

200

Lời chào JSON

You might not see the 401 because the browser treats the home page load as a single interaction. All requests are proxied (there is no content in the Gateway yet, beyond the Actuator endpoints for management)

Hurrah, it works. You have two backend servers, one of which is a UI, each with independent capabilities and able to be tested in isolation, and they are connected together with a secure Gateway that you control and for which you have configured the authentication. If the backends are not accessible to the browser it doesn’t matter (in fact it’s probably an advantage because it gives you yet more control over physical security)

Adding a Login Form

Just as in the "basic" sample in we can now add a login form to the Gateway, e. g. by copying the code from . When we do that we can also add some basic navigation elements in the Gateway, so the user doesn’t have to know the path to the UI backend in the proxy. So let’s first copy the static assets from the "single" UI into the Gateway, delete the message rendering and insert a login form into our home page (in the

$ mvn spring-boot:run
60 somewhere)

app. html

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);
  }
}
1

Instead of the message rendering we will have a nice big navigation button

mục lục. html

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);
  }
}
2

If you are looking at the sample in github, it also has a minimal navigation bar with a "Logout" button. Here’s the login form in a screenshot

Mã hóa html góc

To support the login form we need some TypeScript with a component implementing the

$ spring init --dependencies web,security ui/ && cd ui
57 function we declared in the
$ mvn spring-boot:run
62, and we need to set the
$ spring init --dependencies web,security ui/ && cd ui
45 flag so that the home page will render differently depending on whether or not the user is authenticated. For example

ứng dụng. thành phần. ts

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);
  }
}
3

where the implementation of the

$ spring init --dependencies web,security ui/ && cd ui
57 function is similar to that in

We can use the

$ mvn spring-boot:run
65 to store the
$ spring init --dependencies web,security ui/ && cd ui
45 flag because there is only one component in this simple application

Nếu chúng tôi chạy Cổng nâng cao này, thay vì phải nhớ URL cho giao diện người dùng, chúng tôi chỉ cần tải trang chủ và theo các liên kết. Here’s the home page for an authenticated user

Mã hóa html góc

Granular Access Decisions in the Backend

Up to now our application is functionally very similar to the one in or , but with an additional dedicated Gateway. The advantage of the extra layer may not be yet apparent, but we can emphasise it by expanding the system a bit. Suppose we want to use that Gateway to expose another backend UI, for users to "administrate" the content in the main UI, and that we want to restrict access to this feature to users with special roles. So we will add an "Admin" application behind the proxy, and the system will look like this

Mã hóa html góc

There is a new component (Admin) and a new route in the Gateway in

$ mvn spring-boot:run
12

ứng dụng. yml

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);
  }
}
4

Thực tế là giao diện người dùng hiện tại có sẵn cho người dùng trong vai trò "NGƯỜI DÙNG" được biểu thị trên sơ đồ khối ở trên trong hộp Cổng (chữ màu xanh lục), cũng như thực tế là cần có vai trò "QUẢN VIÊN" để truy cập ứng dụng Quản trị viên. Quyết định truy cập cho vai trò "QUẢN TRỊ" có thể được áp dụng trong Cổng, trong trường hợp đó, nó sẽ xuất hiện trong

$ mvn spring-boot:run
36 hoặc có thể được áp dụng trong chính ứng dụng Quản trị viên (và chúng ta sẽ xem cách thực hiện điều đó bên dưới)

Vì vậy, trước tiên, hãy tạo một ứng dụng Spring Boot mới hoặc sao chép giao diện người dùng và chỉnh sửa nó. You won’t need to change much in the UI app except the name to start with. The finished app is in Github here

Suppose that within the Admin application we want to distinguish between "READER" and "WRITER" roles, so that we can permit (let’s say) users who are auditors to view the changes made by the main admin users. This is a granular access decision, where the rule is only known, and should only be known, in the backend application. Trong Cổng, chúng tôi chỉ cần đảm bảo rằng tài khoản người dùng của chúng tôi có các vai trò cần thiết và thông tin này có sẵn, nhưng Cổng không cần biết cách diễn giải thông tin đó. In the Gateway we create user accounts to keep the sample application self-contained

SecurityConfiguration. class

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);
  }
}
5

where the "admin" user has been enhanced with 3 new roles ("ADMIN", "READER" and "WRITER") and we have also added an "audit" user with "ADMIN" access, but not "WRITER"

In a production system the user account data would be managed in a backend database (most likely a directory service), not hard coded in the Spring Configuration. Sample applications connecting to such a database are easy to find on the internet, for example in the Spring Security Samples

The access decisions go in the Admin application. For the "ADMIN" role (which is required globally for this backend) we do it in Spring Security

SecurityConfiguration. java

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);
  }
}
6

For the "READER" and "WRITER" roles the application itself is split, and since the application is implemented in JavaScript, that is where we need to make the access decision. One way to do this is to have a home page with a computed view embedded in it via the router

ứng dụng. thành phần. html

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);
  }
}
7

The route is computed when the component loads

ứng dụng. thành phần. ts

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);
  }
}
8

the first thing the application does is look at check if the user is authenticated, and computes the route by looking at the user data. The routes are declared in the main module

ứng dụng. mô-đun. ts

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);
  }
}
9

Each of those components (one for each route) has to be implemented separately. Here’s the

$ mvn spring-boot:run
69 as an example

read. component. ts

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 { }
0

read. component. html

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 { }
1

The

$ mvn spring-boot:run
70 is similar, but has a form to change the message in the backend

write. component. ts

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 { }
2

write. component. html

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 { }
3

The

$ spring init --dependencies web,security ui/ && cd ui
30 also needs to provide the data to compute the route, so in the
$ spring init --dependencies web,security ui/ && cd ui
41 function we see this

ứng dụng. Dịch vụ. ts

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 { }
4

To support this function on the backend we need the

$ spring init --dependencies web,security ui/ && cd ui
71 endpoint, e. g. in our main application class

AdminApplication. java

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 { }
5

the role names come back from the "/user" endpoint with the "ROLE_" prefix so we can distinguish them from other kinds of authorities (it’s a Spring Security thing). Thus the "ROLE_" prefix is needed in the JavaScript, but not in the Spring Security configuration, where it is clear from the method names that "roles" are the focus of the operations

Changes in the Gateway to Support Admin UI

We are going to use the roles to make access decisions in the Gateway as well (so we can conditionally display a link to the admin UI), so we should add the "roles" to the "/user" endpoint in the Gateway as well. Once that is in place we can add some JavaScript to set up a flag to indicate that the current user is an "ADMIN". In the

$ spring init --dependencies web,security ui/ && cd ui
48 function

ứng dụng. thành phần. ts

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 { }
6

and we also need to reset the

$ mvn spring-boot:run
75 flag to
$ mvn spring-boot:run
76 when a user logs out

ứng dụng. thành phần. ts

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 { }
7

and then in the HTML we can conditionally show a new link

ứng dụng. thành phần. html

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 { }
8

Chạy tất cả các ứng dụng và truy cập http. //localhost. 8080 to see the result. Everything should be working fine, and the UI should change depending on the currently authenticated user

Why are we Here?

Now we have a nice little system with 2 independent user interfaces and a backend Resource server, all protected by the same authentication in a Gateway. The fact that the Gateway acts as a micro-proxy makes the implementation of the backend security concerns extremely simple, and they are free to concentrate on their own business concerns. The use of Spring Session has (again) avoided a huge amount of hassle and potential errors

A powerful feature is that the backends can independently have any kind of authentication they like (e. g. you can go directly to the UI if you know its physical address and a set of local credentials). The Gateway imposes a completely unrelated set of constraints, as long as it can authenticate users and assign metadata to them that satisfy the access rules in the backends. This is an excellent design for being able to independently develop and test the backend components. If we wanted to, we could go back to an external OAuth2 server (like in , or even something completely different) for the authentication at the Gateway, and the backends would not need to be touched

A bonus feature of this architecture (single Gateway controlling authentication, and shared session token across all components) is that "Single Logout", a feature we identified as difficult to implement in , comes for free. To be more precise, one particular approach to the user experience of single logout is automatically available in our finished system. if a user logs out of any of the UIs (Gateway, UI backend or Admin backend), he is logged out of all the others, assuming that each individual UI implemented a "logout" feature the same way (invalidating the session)

Thanks. I would like to thank again everyone who helped me develop this series, and in particular Rob Winch and Thorsten Späth for their careful reviews of the sections and sources code. Since was published it hasn’t changed much but all the other parts have evolved in response to comments and insights from readers, so thank you also to anyone who read the sections and took the trouble to join in the discussion

Testing an Angular Application

In this section we continue of how to use Spring Security with Angular in a "single page application". Here we show how to write and run unit tests for the client-side code using the Angular test framework. You can catch up on the basic building blocks of the application or build it from scratch by reading the , or you can just go straight to the source code in Github (the same source code as Part I, but with tests now added). This section actually has very little code using Spring or Spring Security, but it covers the client-side testing in a way that might not be so easy to find in the usual Angular community resources, and one which we feel will be comfortable for the majority of Spring users

Lời nhắc nhở. nếu bạn đang làm việc qua phần này với ứng dụng mẫu, hãy đảm bảo xóa bộ nhớ cache của trình duyệt khỏi cookie và thông tin xác thực HTTP Basic. Trong Chrome, cách tốt nhất để làm điều đó cho một máy chủ là mở một cửa sổ ẩn danh mới

Writing a Specification

Our "app" component in the "basic" application is very simple, so it won’t take a lot to test it thoroughly. Here’s a reminder of the code

ứng dụng. thành phần. ts

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 { }
9

The main challenge we face is to provide the

$ spring init --dependencies web,security ui/ && cd ui
21 object in the test, so we can make assertions about how they are used in the component. Actually, even before we face that challenge we need to be able to create a component instance, so we can test what happens when it loads. Here’s how you can do that

The Angular build in an app created from

$ mvn spring-boot:run
78 already has a spec and some configuration to run it. The generated spec is in "src/app", and it starts like this

ứng dụng. thành phần. ts

$ spring init --dependencies web,security ui/ && cd ui
00

In this very basic test suite we have these important elements

  1. We

    $ mvn spring-boot:run
    79 the thing that is being tested (the "AppComponent" in this case) with a function

  2. Inside that function we provide a

    $ mvn spring-boot:run
    80 callback, which loads the Angular component

  3. Behaviour is expressed through a call to

    $ mvn spring-boot:run
    81, where we state in words what the expectation is, and then provide a function that makes assertions

  4. The test environment is initialized before anything else happens. This is boilerplate for most Angular apps

The test function here is so trivial it actually only asserts that the component exists, so if that fails then the test will fail

Improving the Unit Test. Mocking HTTP Backend

To improve the spec to production grade we need to actually assert something about what happens when the controller loads. Since it makes a call to

$ mvn spring-boot:run
82 we need to mock that call to avoid having to run the whole application just for a unit test. To do that we use the Angular
$ mvn spring-boot:run
83

app. component. spec

$ spring init --dependencies web,security ui/ && cd ui
01

Các mảnh mới ở đây là

  • Tuyên bố của

    $ mvn spring-boot:run
    83 là hàng nhập khẩu trong
    $ mvn spring-boot:run
    85 trong
    $ mvn spring-boot:run
    80

  • In the test function we set expectations for the backend before we create the component, telling it to expect a call to 'resource/',and what the response should be

Running the Specs

Để chạy mã thử nghiệm của chúng tôi, chúng tôi có thể thực hiện

$ mvn spring-boot:run
87 (hoặc
$ mvn spring-boot:run
88) bằng cách sử dụng tập lệnh tiện lợi được tạo khi dự án được thiết lập. It also runs as part of the Maven lifecycle, so
$ mvn spring-boot:run
89 is also a good way to run the tests, and this is what will happen in your CI build

End-to-End Tests

Angular also has a standard build set up for "end-to-end tests" using a browser and your generated JavaScript. These are written as "specs" in the top-level

$ mvn spring-boot:run
90 directory. All the samples in this tutorial contain a really simple end-to-end test which run in the Maven lifecycle (hence you will see a browser window popup if you run
$ mvn spring-boot:run
91 in any of the "ui" apps)

Phần kết luận

Being able to run unit tests for Javascript is important in a modern web application and it’s a topic that we’ve ignored (or dodged) up to now in this series. With this installment we have presented the basic ingredients of how to write the tests, how to run them at development time and also, importantly, in a continuous integration setting. The approach we have taken is not going to suit everyone, so please don’t feel bad about doing it in a different way, but make sure you have all those ingredients. The way we did it here will probably feel comfortable to traditional Java enterprise developers, and integrates well with their existing tools and processes, so if you are in that category I hope you will find it useful as a starting point. More examples of testing with Angular and Jasmine can be found in plenty of places on the internet, but the first point of call might be the "single" sample from this series, which now has some up to date test code which is a bit less trivial than the code we needed to write for the "basic" sample in this tutorial

Logout from an OAuth2 Client Application

In this section we continue of how to use Spring Security with Angular in a "single page application". Here we show how to take the OAuth2 samples and add a different logout experience. Many people who implement OAuth2 single sign on find that they have a puzzle to solve of how to logout "cleanly"? The reason it’s a puzzle is that there isn’t a single correct way to do it, and the solution you choose will be determined by the user experience you are looking for, and also the amount of complexity you are willing to take on. The reasons for the complexity stem from the fact that there are potentially multiple browser sessions in the system, all with different backend servers, so when a user logs out from one of them, what should happen to the others? This is the ninth section of a tutorial, and you can catch up on the basic building blocks of the application or build it from scratch by reading the , or you can just go straight to the source code in Github

Logout Patterns

The user experience with logout of the

$ mvn spring-boot:run
92 sample in this tutorial is that you logout of the UI app, but not from the authserver, so when you log back into the UI app the autheserver does not challenge again for credentials. This is completely expected, normal, and desirable when the autheserver is external - Google and other external authserver providers neither want nor allow you to logout from their servers from an untrusted application - but it isn’t the best user experience if the authserver is really part of the same system as the UI

There are, broadly speaking, three patterns for logout from a UI app that is authenticated as an OAuth2 client

  1. External Authserver (EA, the original sample). The user perceives the authserver as a 3rd party (e. g. using Facebook or Google to authenticate). You don’t want to log out of the authserver when the app session ends. You do want approval for all grants. The

    $ mvn spring-boot:run
    92 (and
    $ mvn spring-boot:run
    94) sample from this tutorial implement this pattern

  2. Gateway and Internal Authserver (GIA). You only need to log out of 2 apps, and they are part of the same system, as perceived by the user. Usually you want to autoapprove all grants

  3. Single Logout (SL). One authserver and multiple UI apps all with their own authentication, and when the user logs out of one, you want them all to follow suit. Likely to fail with a naive implementation because of network partitions and server failures - you basically need globally consistent storage

Sometimes, even if you have an external authserver, you want to control the authentication and add an internal layer of access control (e. g. scopes or roles that the authserver doesn’t support). Then it’s a good idea to use the EA for authentication, but have an internal authserver that can add the additional details you need to the tokens. The

$ mvn spring-boot:run
95 sample from this other OAuth2 Tutorial shows you how to do that in a very simple way. You can then apply the GIA or SL patterns to the system that includes the internal authserver

Here are some options if you don’t want EA

  • Log out from authserver as well as UI app in browser client. Simple approach and works with some careful CRSF and CORS configuration. No SL

  • Logout from authserver as soon as a token is available. Hard to implement in the UI, where the token is acquired, because you don’t have the session cookie for the authserver there. There is a feature request in Spring OAuth which shows an interesting approach. invalidate the session in the authserver as soon as an auth code is generated. The Github issue contains an aspect that implements the session invalidation, but it’s easier to do as a

    $ mvn spring-boot:run
    96. No SL

  • Proxy authserver through the same gateway as UI and hope that one cookie is enough to manage the state for the whole system. Doesn’t work because unless there is a shared session, which defeats the object to some extent (otherwise there is no session storage for the authserver). SL only if the session is shared between all apps

  • Cookie relay in gateway. You are using the gateway as the source of truth for authentication, and the authserver has all the state it needs because the gateway manages the cookie instead of the browser. The browser never has a cookie from more than one server. No SL

  • Sử dụng mã thông báo làm xác thực toàn cầu và vô hiệu hóa nó khi người dùng đăng xuất khỏi ứng dụng giao diện người dùng. Downside. requires tokens to be invalidated by client apps, which isn’t really what they were designed to do. SL possible, but usual constraints apply

  • Create and manage a global session token (in addition to the user token) in the authserver. This is the approach taken by OpenId Connect, and it does provide some options for SL, at the cost of some extra machinery. None of the options is immune from the usual distributed system limitations. if networks and application nodes are not stable there are no guarantees that a logout signal is shared among all participants when needed. All of the logout specs are still in draft form, and here are some links to the specs. Session Management, Front Channel Logout, and Back Channel Logout

Note that where SL is hard or impossible, it might be better to put all the UIs behind a single gateway anyway. Then you can use GIA, which is easier, to control logout from your whole estate

The easiest two options, which apply nicely in the GIA pattern can be implemented in the tutorial sample as follows (take the

$ mvn spring-boot:run
92 sample and work from there)

Logout of Both Servers from Browser

It’s quite easy to add a couple of lines of code to the browser client that logout from the authserver as soon as the UI app is logged out. E. g

$ spring init --dependencies web,security ui/ && cd ui
02

In this sample we hardcoded the authserver logout endpoint URL into the JavaScript, but it would be easy to externalize that if you needed to. It has to be a POST directly to the authserver because we want the session cookie to go along too. The XHR request will only go out from the browser with a cookie attached if we specifically ask for

$ mvn spring-boot:run
98

Conversely, on the server we need some CORS configuration because the request is coming from a different domain. E. g. in the

$ mvn spring-boot:run
36

$ spring init --dependencies web,security ui/ && cd ui
03

The "/logout" endpoint has been given some special treatment. It is allowed to be called from any origin, and explicitly allows credentials (e. g. cookies) to be sent. The allowed headers are just the ones that Angular sends in teh sample app

In addition to the CORS configuration we also need to disable CSRF for the logout endpoint, because Angular will not send the

$ mvn package
$ java -jar target/*.jar
00 header in a cross-domain request. The authserver didn’t require any CSRF configuration before now, but it’s easy to add an ignore for the logout endpoint

$ spring init --dependencies web,security ui/ && cd ui
04

Dropping CSRF protection is not really advisable, but you might be prepared to tolerate it for this restricted use case

With those two simple changes, one in the UI app client, and one in the authserver, you will find that once you logout of the UI app, when you log back in, you will always be prompted for a password

Another useful change is to set the OAuth2 client to autoapprove, so that the user doesn’t have to approve the token grant. This is common in a internal authserver, where the user doesn’t perceive it as a separate system. In the

$ mvn package
$ java -jar target/*.jar
01 you just need a flag when the client is initialized

$ spring init --dependencies web,security ui/ && cd ui
05

Invalidate Session in Authserver

If you don’t like to give up the CSRF protection on the logout endpoint, you can try the other easy approach, which is to invalidate the user session in the authserver as soon as a token is granted (actually as soon as an auth code is generated). This is also super easy to implement. bắt đầu từ mẫu

$ mvn spring-boot:run
92, chỉ cần thêm
$ mvn spring-boot:run
96 vào điểm cuối OAuth2

$ spring init --dependencies web,security ui/ && cd ui
06

Thiết bị chặn này tìm kiếm một

$ mvn package
$ java -jar target/*.jar
04, đây là tín hiệu cho thấy người dùng đang được chuyển hướng trở lại ứng dụng khách và kiểm tra xem vị trí đó có chứa mã xác thực hay lỗi không. You could add "token=" if you were using implicit grants as well

With this simple change, as soon as you authenticate, the session in the authserver is already dead, so there’s no need to try and manage it from the client. When you log out of the UI app, and then log back in, the authserver doesn’t recognize you and prompts for credentials. This pattern is the one implemented by the

$ mvn package
$ java -jar target/*.jar
05 sample in the source code for this tutorial. Nhược điểm của phương pháp này là bạn không thực sự có dấu hiệu duy nhất thực sự nữa - bất kỳ ứng dụng nào khác thuộc hệ thống của bạn sẽ thấy rằng phiên máy chủ xác thực đã chết và chúng phải nhắc xác thực lại - không phải vậy

Phần kết luận

Trong phần này, chúng ta đã thấy cách triển khai một số mẫu khác nhau để đăng xuất khỏi ứng dụng khách OAuth2 (lấy ứng dụng làm điểm bắt đầu trong hướng dẫn) và một số tùy chọn cho các mẫu khác đã được thảo luận. Các tùy chọn này không đầy đủ, nhưng sẽ cung cấp cho bạn ý tưởng tốt về sự đánh đổi có liên quan và một số công cụ để suy nghĩ về giải pháp tốt nhất cho trường hợp sử dụng của bạn. Chỉ có một vài dòng JavaScript trong phần này và điều đó không thực sự dành riêng cho Angular (nó thêm cờ cho các yêu cầu XHR), vì vậy tất cả các bài học và mẫu đều có thể áp dụng ngoài phạm vi hẹp của các ứng dụng mẫu trong hướng dẫn này. A recurring theme is that all approaches to single logout (SL) where there are multiple UI apps and a single authserver tend to be flawed in some way. the best you can do is choose the approach that makes your users the least uncomfortable. Nếu bạn có một máy chủ xác thực nội bộ và một hệ thống bao gồm nhiều thành phần, thì có thể kiến ​​trúc duy nhất khiến người dùng cảm thấy giống như một hệ thống duy nhất là cổng cho tất cả các tương tác của người dùng

Want to write a new guide or contribute to an existing one? Check out our contribution guidelines

Tất cả các hướng dẫn được phát hành với giấy phép ASLv2 cho mã và giấy phép Commons sáng tạo Attribution, NoDerivatives cho văn bản