Mã hóa góc html

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 phần thứ hai 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

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ế. Let’s have a look at how to build a new single page application from nothing using Spring Boot, Angular and Twitter Bootstrap. There’s no particular reason to choose that specific stack, but it is quite popular, especially with the core Spring constituency in enterprise Java shops, so it’s a worthwhile starting point

Create a New Project

We are going to step through creating this application in some detail, so that anyone who isn’t completely au fait with Spring and Angular can follow what is happening. If you prefer to cut to the chase, you can skip to the end where the application is working, and see how it all fits together. Có nhiều tùy chọn khác nhau để tạo một dự án mới

  • Using curl on the command line

  • Using Spring Boot CLI

  • Using the Spring Initializr website

  • Using Spring Tool Suite

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. Then jump to the next section

Using Curl

The easiest way to create a new project to get started is via the Spring Boot Initializr. E. g. using curl on a UN*X like system

$ mkdir ui && cd ui
$ curl //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 đó chuyển sang phần tiếp theo

Using Spring Boot CLI

Bạn có thể tạo cùng một dự án bằng Spring Boot CLI, như thế này

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

Sau đó chuyển sang phần tiếp theo

Sử dụng trang web Initializr

Nếu muốn, bạn cũng có thể nhận mã trực tiếp giống như một. zip từ Spring Boot Initializr. Chỉ cần mở nó lên trong trình duyệt của bạn và chọn phụ thuộc "Web" và "Security", sau đó nhấp vào "Tạo dự án". 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 đó chuyển sang phần tiếp theo

Using Spring Tool Suite

Trong Bộ công cụ Spring [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 đó chuyển sang phần tiếp theo. 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 sự 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]. Chúng tôi đã làm điều này trong mã mẫu bằng cách sử dụng "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

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

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

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

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

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

Đ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

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

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

$ spring init --dependencies web,security ui/ && cd ui
21 do Angular cung cấp thông qua mô-đun
$ spring init --dependencies web,security ui/ && cd ui
21 và sử dụng dịch vụ này để 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 nó 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

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

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

LẤY

/

401

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

LẤY

/

200

mục lục. html

LẤY

/*. js

200

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

LẤY

/chính. bó. js

200

logic ứng dụng

LẤY

/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 tiếp theo của loạt bài 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

Cảm ơn. 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 thảo luận 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 nó từ đầu bằng cách đọc phần đầu tiên 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 biểu mẫu đăng nhập, cung cấp cho người dùng một số quyền kiểm soát về việc 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 đưa 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 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 việc này vô điều kiện thông qua lệnh 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 chức năng
    $ 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ía sau 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 đố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. Về phía máy khách, những thứ này sẽ được triển khai trong

$ spring init --dependencies web,security ui/ && cd ui
56 và trên máy chủ, đó sẽ là cấu hình Bảo mật mùa xuân

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 ở dạng

$ 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 xác thực đượ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ụ chức năng

$ 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, 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 kí. 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 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

$ spring init --dependencies web,security ui/ && cd ui
73 mặc định đượ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 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. Lựa chọn cuối cùng là tốt nhất vì Angular đã tích hợp hỗ trợ cho CSRF [được gọi là "XSRF"] dựa trên cookie

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

LẤY

/

200

mục lục. html

LẤY

/*. js

200

Tài sản từ góc

LẤY

/người sử dụng

401

Trái phép [bỏ qua]

LẤY

/Trang Chủ

200

trang chủ

LẤY

/người sử dụng

401

Trái phép [bỏ qua]

LẤY

/nguồn

401

Trái phép [bỏ qua]

LẤY

/người sử dụng

200

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

LẤY

/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" [_______126 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 Dịch vụ Web Pivotal của Cloud Foundry và liên kết ứng dụng đó 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ì vì nó đã được triển khai và hoạt động hoàn hảo trên đầu trang của

$ 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

Sự 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 tiếp theo, chúng tôi mở rộng kiến ​​trúc sang 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 thảo luận 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ờ. Đây là phần thứ ba 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 nó từ đầu bằng cách đọc phần đầu tiên hoặc bạn có thể truy cập thẳng vào mã nguồn trong Github, nằm trong . 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 như đã làm trong phần đầu tiên bằng 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ể sử dụng thay đổi cổng trong

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

đăng kí. đặc tính

$ 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 quá. 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

Thêm bảo mật mùa xuân

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ụ
$ 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ừ Phần II của 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 [_______126], 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, 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 tôi 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 phải tạo đường hầm cho 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 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 tinh tế 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 truy cập

$ mvn spring-boot:run
06 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. Điều này hóa ra khá đơn giản vì tất cả những gì chúng ta cần làm là cho Spring Security biết kho lưu trữ phiên ở đâu và tìm mã thông báo [ID phiên] ở đâu trong một yêu cầu đến. 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 kí. 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

Tại sao không phải tất cả đều hoạt động với cookie?

Chúng tôi phải sử dụng 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ư mâu thuẫn với lời khuyên trong Phần II là 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]

Sự kết luận

Chúng tôi đã sao chép các tính năng của ứng dụng trong Phần II của loạt bài này. 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ó một giải pháp chủ yếu dựa trên cấu hình [và thực tế là 100% khai báo]. 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 tiếp theo, 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 thảo luận 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 nó từ đầu bằng cách đọc phần đầu tiên hoặc bạn có thể truy cập thẳng vào mã nguồn trong Github. Trong phần trước, chúng ta đã xây dựng một ứng dụng phân tán đơn giản sử dụng Spring Session để 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 ở cuối phần trước hơi phức tạp, vì vậy đây không phải là nơi tuyệt vờ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 kí. 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 phần trước

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

Đơn giản hóa hơn nữa

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]. May mắn thay, nó bây giờ là dư thừa, vì vậy chúng ta có thể vứt nó đi và quay trở lại giấc ngủ vào ban đêm

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

Bạn có thể nhớ ở trạng thái trung gian mà chúng tôi đã bắt đầu từ đó không có bảo mật nào cho máy chủ tài nguyên

Qua một bên. Thiếu bảo mật phần mềm thậm chí có thể không phải là vấn đề nếu kiến ​​trúc mạng của bạn phản ánh kiến ​​trúc ứng dụng [bạn chỉ có thể làm cho máy chủ tài nguyên không thể truy cập được về mặt vật lý đối với bất kỳ ai trừ máy chủ giao diện người dùng]. Như một minh chứng đơn giản về điều đó, chúng tôi có thể làm cho máy chủ tài nguyên chỉ có thể truy cập được trên máy chủ cục bộ. Chỉ cần thêm phần này vào

$ spring init --dependencies web,security ui/ && cd ui
91 trong máy chủ tài nguyên

đăng kí. đặc tính

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, thật dễ dàng. Làm điều đó với một địa chỉ mạng chỉ hiển thị trong trung tâm dữ liệu của bạn và bạn có một giải pháp bảo mật hoạt động cho tất cả các máy chủ tài nguyên và tất cả máy tính để bàn của người dùng

Giả sử rằng chúng tôi quyết định rằng chúng tôi cần bảo mật ở cấp độ phần mềm [rất có thể vì một số lý do]. Điều đó sẽ không thành vấn đề, bởi vì tất cả những gì chúng ta cần làm là thêm Spring Security làm phụ thuộc [trong POM của máy chủ tài nguyên]

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

Điều đó đủ để cung cấp cho chúng tôi một máy chủ tài nguyên an toàn, nhưng nó sẽ không cung cấp cho chúng tôi một ứng dụng hoạt động, vì lý do tương tự mà nó không có trong Phần III. không có trạng thái xác thực được chia sẻ giữa hai máy chủ

Chia sẻ trạng thái xác thực

Chúng tôi có thể sử dụng cùng một cơ chế để chia sẻ trạng thái xác thực [và CSRF] như chúng tôi đã làm trong lần trước, tôi. e. phiên mùa xuân. Chúng tôi thêm phần phụ thuộc vào cả hai máy chủ như trước

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

nhưng lần này cấu hình đơn giản hơn nhiều vì chúng ta chỉ cần thêm cùng một khai báo

$ spring init --dependencies web,security ui/ && cd ui
93 vào cả hai. Đầu tiên, máy chủ giao diện người dùng, tuyên bố rõ ràng rằng chúng tôi muốn tất cả các tiêu đề được chuyển tiếp [i. e. không cái nào là "nhạy cảm"]

đăng kí. yml

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

Sau đó, chúng ta có thể chuyển sang máy chủ tài nguyên. Có hai thay đổi nhỏ để thực hiện. một là tắt rõ ràng HTTP Basic trong máy chủ tài nguyên [để ngăn trình duyệt bật lên hộp thoại xác thực]

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

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

Qua một bên. một giải pháp thay thế, cũng sẽ ngăn hộp thoại xác thực, là giữ HTTP Cơ bản nhưng thay đổi thách thức 401 thành một thứ khác ngoài "Cơ bản". Bạn có thể làm điều đó với việc triển khai một dòng của

$ spring init --dependencies web,security ui/ && cd ui
69 trong cuộc gọi lại cấu hình
$ spring init --dependencies web,security ui/ && cd ui
70

và cách khác là yêu cầu rõ ràng chính sách tạo phiên không trạng thái trong

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

đăng kí. đặc tính

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

Miễn là redis vẫn đang chạy trong nền [sử dụng

$ mvn spring-boot:run
00 nếu bạn muốn khởi động nó] thì hệ thống sẽ hoạt động. Tải trang chủ cho giao diện người dùng tại http. //máy chủ cục bộ. 8080 và đăng nhập và bạn sẽ thấy thông báo từ chương trình phụ trợ được hiển thị trên trang chủ

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

Điều gì đang xảy ra đằng sau hậu trường bây giờ?

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

LẤY

/

200

mục lục. html

LẤY

/*. js

200

Tài sản dạng góc cạnh

LẤY

/người sử dụng

401

Trái phép [bỏ qua]

LẤY

/nguồn

401

Truy cập trái phép vào tài nguyên

LẤY

/người sử dụng

200

Người dùng được xác thực JSON

LẤY

/nguồn

200

[Được ủy quyền] Lời chào JSON

Điều đó giống với trình tự ở cuối Phần II ngoại trừ thực tế là tên cookie hơi khác ["SESSION" thay vì "JSESSIONID"] vì chúng tôi đang sử dụng Phiên mùa xuân. Nhưng kiến ​​trúc thì khác và yêu cầu cuối cùng tới "/resource" là đặc biệt vì nó được ủy quyền cho máy chủ tài nguyên

Chúng ta có thể thấy hoạt động của proxy ngược bằng cách xem điểm cuối "/trace" trong máy chủ giao diện người dùng [từ Spring Boot Actuator, cái mà chúng ta đã thêm cùng với các phụ thuộc Spring Cloud]. truy cập http. //máy chủ cục bộ. 8080/trace trong trình duyệt mới [nếu bạn chưa có, hãy tải plugin JSON cho trình duyệt của bạn để làm cho trình duyệt đẹp và dễ đọc]. Bạn sẽ cần xác thực bằng HTTP Basic [cửa sổ bật lên của trình duyệt], nhưng thông tin đăng nhập tương tự hợp lệ như đối với biểu mẫu đăng nhập của bạn. Tại hoặc gần đầu, bạn sẽ thấy một cặp yêu cầu giống như thế này

Cố gắng sử dụng một trình duyệt khác để không có cơ hội xác thực chéo [e. g. sử dụng Firefox nếu bạn đã sử dụng Chrome để kiểm tra giao diện người dùng] - nó sẽ không ngăn ứng dụng hoạt động nhưng sẽ khiến dấu vết khó đọc hơn nếu chúng chứa hỗn hợp xác thực từ cùng một trình duyệt

/dấu vết

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

Mục thứ hai có yêu cầu từ máy khách đến cổng trên "/resource" và bạn có thể thấy cookie [được thêm bởi trình duyệt] và tiêu đề CSRF [được thêm bởi Angular như đã thảo luận trong Phần II]. Mục đầu tiên có

$ mvn spring-boot:run
22 và điều đó có nghĩa là nó đang theo dõi cuộc gọi đến máy chủ tài nguyên. Bạn có thể thấy nó đi ra đường dẫn uri "/" và bạn có thể thấy rằng [đặc biệt là] cookie và tiêu đề CSRF cũng đã được gửi. Nếu không có Phiên mùa xuân, các tiêu đề này sẽ vô nghĩa đối với máy chủ tài nguyên, nhưng cách chúng tôi thiết lập nó giờ đây có thể sử dụng các tiêu đề đó để tạo lại phiên có xác thực và dữ liệu mã thông báo CSRF. Vì vậy, yêu cầu được cho phép và chúng tôi đang kinh doanh

Sự kết luận

Chúng tôi đã đề cập khá nhiều trong phần này nhưng chúng tôi đã đạt đến một điểm thực sự thú vị khi có rất ít mã soạn sẵn trong hai máy chủ của chúng tôi, cả hai máy chủ đều được bảo mật tốt và trải nghiệm người dùng không bị ảnh hưởng. Chỉ riêng điều đó thôi cũng đã là lý do để sử dụng mẫu Cổng API, nhưng thực sự chúng tôi mới chỉ tìm hiểu sơ qua về những gì có thể được sử dụng [Netflix sử dụng nó cho rất nhiều thứ]. Đọc trên Spring Cloud để tìm hiểu thêm về cách giúp dễ dàng thêm nhiều tính năng hơn vào cổng. Phần tiếp theo trong loạt bài này sẽ mở rộng kiến ​​trúc ứng dụng một chút bằng cách trích xuất các trách nhiệm xác thực cho một máy chủ riêng biệt [mẫu Đăng nhập một lần]

Đăng nhập một lần bằng OAuth2

Trong phần này, chúng ta tiếp tục thảo luận 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 Spring Security OAuth cùng với Spring Cloud để mở rộng Cổng API của chúng tôi để thực hiện Đăng nhập một lần và xác thực mã thông báo OAuth2 cho tài nguyên phụ trợ. Đây là phần thứ năm 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 nó từ đầu bằng cách đọc phần đầu tiên hoặc bạn có thể truy cập thẳng vào mã nguồn trong Github. Trong phần trước, chúng tôi đã xây dựng một ứng dụng phân tán nhỏ sử dụng Phiên mùa xuân để xác thực tài nguyên phụ trợ và Đám mây mùa xuân để triển khai Cổng API nhúng trong máy chủ giao diện người dùng. Trong phần này, chúng tôi trích xuất các trách nhiệm xác thực cho một máy chủ riêng biệt để làm cho máy chủ giao diện người dùng của chúng tôi trở thành máy chủ đầu tiên trong số nhiều ứng dụng Đăng nhập một lần tiềm năng cho máy chủ ủy quyền. Đây là một mô hình phổ biến trong nhiều ứng dụng ngày nay, cả trong doanh nghiệp và các công ty khởi nghiệp xã hội. Chúng tôi sẽ sử dụng máy chủ OAuth2 làm trình xác thực để chúng tôi cũng có thể sử dụng máy chủ này để cấp mã thông báo cho máy chủ tài nguyên phụ trợ. Spring Cloud sẽ tự động chuyển tiếp mã thông báo truy cập đến chương trình phụ trợ của chúng tôi và cho phép chúng tôi đơn giản hóa hơn nữa việc triển khai cả giao diện người dùng và máy chủ tài nguyên

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áy chủ ủy quyền OAuth2

Bước đầu tiên của chúng tôi là tạo một máy chủ mới để xử lý xác thực và quản lý mã thông báo. Làm theo các bước trong Phần I, chúng ta có thể bắt đầu với Spring Boot Initializr. e. g. sử dụng curl trên hệ thống giống UN*X

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

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

Thêm các phụ thuộc OAuth2

Chúng tôi cần thêm các phụ thuộc Spring OAuth, vì vậy trong POM của chúng tôi, chúng tôi thêm

quả bông. xml

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

Máy chủ ủy quyền khá dễ triển khai. Một phiên bản tối thiểu trông như thế này

Ứng dụng Authserver. java

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

Chúng ta chỉ phải làm thêm 1 việc nữa [sau khi thêm

$ mvn spring-boot:run
23]

đăng kí. đặc tính

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

Điều này đăng ký một ứng dụng khách "acme" với một bí mật và một số loại cấp phép được ủy quyền, bao gồm cả "authorization_code"

Bây giờ, hãy để nó chạy trên cổng 9999, với mật khẩu có thể dự đoán được để kiểm tra

đăng kí. đặc tính

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

Chúng tôi cũng đặt đường dẫn ngữ cảnh để nó không sử dụng mặc định ["/"] vì nếu không, bạn có thể nhận cookie cho các máy chủ khác trên máy chủ cục bộ được gửi đến máy chủ sai. Vì vậy, hãy chạy máy chủ và chúng tôi có thể đảm bảo rằng nó đang hoạt động

$ mvn spring-boot:run

hoặc bắt đầu phương thức

$ spring init --dependencies web,security ui/ && cd ui
10 trong IDE của bạn

Kiểm tra máy chủ ủy quyền

Máy chủ của chúng tôi đang sử dụng cài đặt bảo mật mặc định của Spring Boot, vì vậy giống như máy chủ trong Phần I, nó sẽ được bảo vệ bằng xác thực HTTP Basic. Để bắt đầu cấp mã thông báo ủy quyền, bạn truy cập điểm cuối ủy quyền, e. g. tại http. //máy chủ cục bộ. 9999/uaa/oauth/ủy quyền?response_type=code&client_id=acme&redirect_uri=http. //thí dụ. com sau khi bạn đã xác thực, bạn sẽ nhận được một chuyển hướng đến ví dụ. com có ​​đính kèm mã ủy quyền nhé e. g. http. //thí dụ. com/?code=jYWioI

cho các mục đích của ứng dụng mẫu này, chúng tôi đã tạo một ứng dụng khách "acme" không có chuyển hướng đã đăng ký, đây là thứ cho phép chúng tôi lấy ví dụ chuyển hướng. 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

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

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. Điều này không phù hợp với ứng dụng khách dựa trên trình duyệt, nhưng nó hữu ích để thử nghiệm

Nếu bạn nhấp vào liên kết ở trên, bạn sẽ thấy giao diện người dùng nhãn trắng do Spring OAuth cung cấp. Để bắt đầu, chúng tôi sẽ sử dụng cái này và chúng tôi có thể quay lại sau để tăng cường nó như chúng tôi đã làm trong Phần II cho máy chủ khép kín

Thay đổi máy chủ tài nguyên

Nếu chúng tôi tiếp tục từ Phần IV, máy chủ tài nguyên của chúng tôi đang sử dụng Phiên mùa xuân để xác thực, vì vậy chúng tôi có thể loại bỏ nó và thay thế nó bằng Spring OAuth. Chúng tôi cũng cần xóa các phụ thuộc Phiên mùa xuân và Redis, vì vậy hãy thay thế cái này

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

Với cái này

quả bông. xml

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

và sau đó xóa phiên

$ spring init --dependencies web,security ui/ && cd ui
93 khỏi lớp ứng dụng chính, thay thế bằng chú thích
$ mvn spring-boot:run
26 tiện lợi [từ Spring Security OAuth2]

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

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

Với một thay đổi đó, ứng dụng đã sẵn sàng thách thức mã thông báo truy cập thay vì HTTP Basic, nhưng chúng tôi cần thay đổi cấu hình để thực sự kết thúc quy trình. Chúng tôi sẽ thêm một lượng nhỏ cấu hình bên ngoài [trong "ứng dụng. properties"] để cho phép máy chủ tài nguyên giải mã mã thông báo được cung cấp và xác thực người dùng

đăng kí. đặc tính

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

Điều này cho máy chủ biết rằng nó có thể sử dụng mã thông báo để truy cập điểm cuối "/user" và sử dụng điểm cuối đó để lấy thông tin xác thực [nó hơi giống điểm cuối "/me" trong Facebook API]. Thực tế, nó cung cấp một cách để máy chủ tài nguyên giải mã mã thông báo, như được thể hiện bằng giao diện

$ mvn spring-boot:run
27 trong Spring OAuth2

Chạy ứng dụng và truy cập trang chủ bằng ứng dụng khách dòng lệnh

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

và bạn sẽ thấy 401 với tiêu đề "WWW-Authenticate" cho biết rằng nó muốn có mã thông báo mang

$ mvn spring-boot:run
28 cho đến nay không phải là cách duy nhất để kết nối máy chủ tài nguyên với cách giải mã mã thông báo. Trên thực tế, đó là mẫu số chung thấp nhất [và không phải là một phần của thông số kỹ thuật], nhưng thường có sẵn từ các nhà cung cấp OAuth2 [như Facebook, Cloud Foundry, Github] và các lựa chọn khác có sẵn. Chẳng hạn, bạn có thể mã hóa xác thực người dùng trong chính mã thông báo [e. g. với JWT] hoặc sử dụng cửa hàng phụ trợ được chia sẻ. Ngoài ra còn có một điểm cuối
$ mvn spring-boot:run
29 trong CloudFoundry, cung cấp thông tin chi tiết hơn điểm cuối thông tin người dùng nhưng yêu cầu xác thực kỹ lưỡng hơn. Các lựa chọn khác nhau [tự nhiên] mang lại những lợi ích và sự đánh đổi khác nhau, nhưng phần thảo luận đầy đủ về những điều đó nằm ngoài phạm vi của phần này

Triển khai điểm cuối người dùng

Trên máy chủ ủy quyền, chúng tôi có thể dễ dàng thêm điểm cuối đó

Ứng dụng Authserver. java

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

Chúng tôi đã thêm một

$ spring init --dependencies web,security ui/ && cd ui
18 giống như máy chủ giao diện người dùng trong Phần II và cả chú thích
$ mvn spring-boot:run
26 từ Spring OAuth, theo mặc định, bảo mật mọi thứ trong máy chủ ủy quyền ngoại trừ điểm cuối "/oauth/*"

Với điểm cuối đó, chúng tôi có thể kiểm tra nó và tài nguyên lời chào, vì cả hai đều chấp nhận mã thông báo mang được tạo bởi máy chủ ủy quyền

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

[thay thế giá trị của mã thông báo truy cập mà bạn nhận được từ máy chủ ủy quyền của riêng mình để mã đó tự hoạt động]

Máy chủ giao diện người dùng

Phần cuối cùng của ứng dụng này mà chúng ta cần hoàn thành là máy chủ giao diện người dùng, trích xuất phần xác thực và ủy quyền cho máy chủ ủy quyền. Vì vậy, như với máy chủ tài nguyên, trước tiên chúng ta cần xóa các phụ thuộc Spring Session và Redis và thay thế chúng bằng Spring OAuth2. Bởi vì chúng tôi đang sử dụng Zuul trong lớp giao diện người dùng nên chúng tôi thực sự sử dụng trực tiếp

$ mvn spring-boot:run
32 thay vì
$ mvn spring-boot:run
33 [điều này thiết lập một số cấu hình tự động để chuyển tiếp mã thông báo qua proxy]

Khi đã xong, chúng ta cũng có thể xóa bộ lọc phiên và điểm cuối "/user" đồng thời thiết lập ứng dụng để chuyển hướng đến máy chủ ủy quyền [sử dụng chú thích

$ mvn spring-boot:run
34]

UiỨng dụng. java

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

Nhớ lại từ Phần IV rằng máy chủ giao diện người dùng, nhờ có

$ mvn spring-boot:run
14, đóng vai trò là Cổng API và chúng ta có thể khai báo ánh xạ tuyến đường trong YAML. Vì vậy, điểm cuối "/user" có thể được ủy quyền cho máy chủ ủy quyền

đăng kí. yml

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

Cuối cùng, chúng ta cần thay đổi ứng dụng thành

$ mvn spring-boot:run
36 vì bây giờ nó sẽ được sử dụng để sửa đổi các giá trị mặc định trong chuỗi bộ lọc SSO được thiết lập bởi
$ mvn spring-boot:run
34

Bảo MậtCấu Hình. java

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

Những thay đổi chính [ngoài tên lớp cơ sở] là các trình đối sánh đi vào phương thức của riêng chúng và không cần

$ mvn spring-boot:run
38 nữa. Cấu hình rõ ràng của
$ spring init --dependencies web,security ui/ && cd ui
80 thêm rõ ràng một url thành công không được bảo vệ để yêu cầu XHR tới
$ mvn spring-boot:run
40 sẽ trả về thành công

Ngoài ra còn có một số thuộc tính cấu hình bên ngoài bắt buộc để chú thích

$ mvn spring-boot:run
34 có thể liên hệ và xác thực với máy chủ ủy quyền phù hợp. Vì vậy, chúng tôi cần điều này trong
$ mvn spring-boot:run
12

đăng kí. yml

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

Phần lớn trong số đó là về ứng dụng khách OAuth2 ["acme"] và vị trí máy chủ ủy quyền. Ngoài ra còn có một

$ mvn spring-boot:run
28 [giống như trong máy chủ tài nguyên] để người dùng có thể được xác thực trong chính ứng dụng giao diện người dùng

Nếu bạn muốn ứng dụng giao diện người dùng có thể tự động làm mới mã thông báo truy cập đã hết hạn, bạn phải đưa một

$ mvn spring-boot:run
44 vào bộ lọc Zuul thực hiện chuyển tiếp. Bạn có thể làm điều này bằng cách tạo một bean loại đó [kiểm tra
$ mvn spring-boot:run
45 để biết chi tiết]

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

trong khách hàng

Có một số điều chỉnh đối với ứng dụng giao diện người dùng ở giao diện người dùng mà chúng tôi vẫn cần thực hiện để kích hoạt chuyển hướng đến máy chủ ủy quyền. Trong bản demo đơn giản này, chúng tôi có thể loại bỏ ứng dụng Angular xuống các yếu tố cần thiết để bạn có thể thấy những gì đang diễn ra rõ ràng hơn. Vì vậy, hiện tại chúng tôi từ bỏ việc sử dụng các biểu mẫu hoặc tuyến đường và chúng tôi quay lại một thành phần Góc duy nhất

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

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

$ spring init --dependencies web,security ui/ && cd ui
12 xử lý mọi thứ, tìm nạp thông tin chi tiết của người dùng và nếu thành công, lời chào. Nó cũng cung cấp chức năng
$ mvn spring-boot:run
47

Bây giờ chúng ta cần tạo mẫu cho thành phần mới này

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

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

và đưa nó vào trang chủ dưới dạng

$ mvn spring-boot:run
48

Lưu ý rằng liên kết điều hướng cho "Đăng nhập" là liên kết thông thường có

$ mvn spring-boot:run
49 [không phải tuyến đường Góc]. Điểm cuối "/login" được xử lý bởi Spring Security và nếu người dùng không được xác thực, nó sẽ dẫn đến chuyển hướng đến máy chủ ủy quyền

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

Chạy tất cả các máy chủ cùng nhau ngay bây giờ và truy cập giao diện người dùng trong trình duyệt tại http. //máy chủ cục bộ. 8080. Nhấp vào liên kết "đăng nhập" và bạn sẽ được chuyển hướng đến máy chủ ủy quyền để xác thực [cửa sổ bật lên HTTP Cơ bản] và phê duyệt cấp mã thông báo [HTML nhãn trắng], trước khi được chuyển hướng đến trang chủ trong giao diện người dùng với lời chào được tìm nạp từ OAuth2

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

LẤY

/

200

mục lục. html

LẤY

/*. js

200

Tài sản từ góc

LẤY

/người sử dụng

302

Chuyển hướng đến trang đăng nhập

LẤY

/đăng nhập

302

Chuyển hướng đến máy chủ xác thực

LẤY

[uaa]/oauth/ủy quyền

401

[làm ngơ]

LẤY

/đăng nhập

302

Chuyển hướng đến máy chủ xác thực

LẤY

[uaa]/oauth/ủy quyền

200

Xác thực HTTP cơ bản xảy ra ở đây

BƯU KIỆN

[uaa]/oauth/ủy quyền

302

Người dùng phê duyệt cấp, chuyển hướng đến /đăng nhập

LẤY

/đăng nhập

302

Chuyển hướng đến trang chủ

LẤY

/người sử dụng

200

[Được ủy quyền] Người dùng được xác thực JSON

LẤY

/ứng dụng. html

200

HTML một phần cho trang chủ

LẤY

/nguồn

200

[Được ủy quyền] Lời chào JSON

Các yêu cầu có tiền tố [uaa] là tới máy chủ ủy quyền. Các phản hồi được đánh dấu là "bỏ qua" là các phản hồi mà Angular nhận được trong cuộc gọi XHR và vì chúng tôi không xử lý dữ liệu đó nên chúng 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

Trong điểm cuối "/trace" của giao diện người dùng [cuộn xuống dưới cùng], bạn sẽ thấy các yêu cầu phụ trợ được ủy quyền cho "/user" và "/resource", với

$ mvn spring-boot:run
50 và mã thông báo mang thay vì cookie [như nó sẽ có . Spring Cloud Security đã lo việc này cho chúng tôi. bằng cách nhận ra rằng chúng tôi có
$ mvn spring-boot:run
34 và
$ mvn spring-boot:run
14, nó đã phát hiện ra rằng [theo mặc định] chúng tôi muốn chuyển tiếp mã thông báo tới các chương trình phụ trợ được ủy quyền

Như trong các phần trước, hãy thử sử dụng một trình duyệt khác cho "/trace" để không có khả năng xác thực chéo [e. g. sử dụng Firefox nếu bạn đã sử dụng Chrome để kiểm tra giao diện người dùng]

Trải nghiệm đăng xuất

Nếu bạn nhấp vào liên kết "đăng xuất", bạn sẽ thấy trang chủ thay đổi [lời chào không còn hiển thị] vì vậy người dùng không còn được xác thực với máy chủ giao diện người dùng. Tuy nhiên, hãy nhấp lại vào "đăng nhập" và bạn thực sự không cần phải quay lại chu trình xác thực và phê duyệt trong máy chủ ủy quyền [vì bạn chưa đăng xuất khỏi đó]. 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. Bài báo Science Direct và tài liệu Shibboleth]. Trải nghiệm người dùng lý tưởng có thể không khả thi về mặt kỹ thuật và đôi khi bạn cũng phải nghi ngờ rằng người dùng thực sự muốn những gì họ nói họ muốn. "Tôi muốn 'đăng xuất' để đăng xuất tôi" nghe có vẻ đơn giản nhưng câu trả lời rõ ràng là "Đăng xuất khỏi cái gì? Bạn muốn đăng xuất khỏi tất cả các hệ thống do máy chủ SSO này kiểm soát hay chỉ hệ thống mà bạn

Sự kết luận

Đây gần như là kết thúc chuyến tham quan nông cạn của chúng tôi thông qua ngăn xếp Spring Security và Angular. Hiện tại chúng tôi có một kiến ​​trúc đẹp với các trách nhiệm rõ ràng trong ba thành phần riêng biệt, Cổng giao diện người dùng/API, máy chủ tài nguyên và máy chủ ủy quyền/người cấp mã thông báo. Số lượng mã phi kinh doanh trong tất cả các lớp hiện ở mức tối thiểu và thật dễ dàng để biết nơi cần mở rộng và cải thiện việc triển khai với nhiều logic kinh doanh hơn. Các bước tiếp theo sẽ là dọn dẹp giao diện người dùng trong máy chủ ủy quyền của chúng tôi và có thể thêm một số thử nghiệm khác, bao gồm các thử nghiệm trên ứng dụng khách JavaScript. Một nhiệm vụ thú vị khác là trích xuất tất cả mã tấm nồi hơi và đưa nó vào thư viện [e. g. "spring-security-angular"] chứa cấu hình tự động cấu hình Spring Security và Spring Session và một số tài nguyên webjars cho bộ điều khiển điều hướng trong phần Góc. Sau khi đọc các phần trong loạt bài này, bất kỳ ai đang hy vọng tìm hiểu hoạt động bên trong của Angular hoặc Spring Security có thể sẽ thất vọng, nhưng nếu bạn muốn xem cách chúng có thể hoạt động tốt cùng nhau và một chút cấu hình có thể hoạt động như thế nào. . Spring Cloud là phiên bản mới và các mẫu này yêu cầu ảnh chụp nhanh khi chúng được viết, nhưng có sẵn các ứng cử viên phát hành và sắp có bản phát hành GA, vì vậy hãy kiểm tra và gửi một số phản hồi qua Github hoặc gitter. Tôi

Phần tiếp theo trong loạt bài này là 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

phụ lục. Giao diện người dùng Bootstrap và Mã thông báo JWT cho Máy chủ ủy quyền

Bạn sẽ tìm thấy một phiên bản khác của ứng dụng này trong mã nguồn của Github, phiên bản này có trang đăng nhập đẹp mắt và trang phê duyệt người dùng được triển khai tương tự như cách chúng tôi đã thực hiện với trang đăng nhập trong Phần II. Nó cũng sử dụng JWT để mã hóa mã thông báo, vì vậy thay vì sử dụng điểm cuối "/user", máy chủ tài nguyên có thể lấy đủ thông tin ra khỏi mã thông báo để thực hiện xác thực đơn giản. Ứng dụng khách của trình duyệt vẫn sử dụng nó, được ủy quyền thông qua máy chủ giao diện người dùng, để nó có thể xác định xem người dùng có được xác thực hay không [không cần phải làm điều đó thường xuyên, so với số lượng lệnh gọi có thể có đến máy chủ tài nguyên trong một ứng dụng thực

Nhiều ứng dụng giao diện người dùng và một cổng

Trong phần này, chúng ta tiếp tục thảo luận 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 Phiên làm việc mùa xuân cùng với Đám mây mùa xuân để kết hợp các tính năng của hệ thống mà chúng tôi đã xây dựng trong phần II và IV, và thực sự kết thúc việc xây dựng 3 ứng dụng trang đơn với các trách nhiệm hoàn toàn khác nhau. Mục đích là xây dựng một Cổng [như trong phần IV] không chỉ được sử dụng cho tài nguyên API mà còn để tải giao diện người dùng từ máy chủ phụ trợ. Chúng tôi đơn giản hóa các bit sắp xếp mã thông báo của phần II bằng cách sử dụng Cổng để chuyển qua xác thực đến các phụ trợ. Sau đó, chúng tôi mở rộng hệ thống để cho thấy cách chúng tôi có thể đưa ra quyết định truy cập chi tiết, cục bộ trong phần phụ trợ, trong khi vẫn kiểm soát danh tính và xác thực tại Cổng. Đây là một mô hình rất hiệu quả để xây dựng các hệ thống phân tán nói chung và có một số lợi ích mà chúng tôi có thể khám phá khi giới thiệu các tính năng trong mã mà chúng tôi xây dựng

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 đó là mở một cửa sổ ẩn danh mới

Kiến trúc mục tiêu

Đây là hình ảnh của hệ thống cơ bản mà chúng ta sẽ xây dựng để bắt đầu

Giống như các ứng dụng mẫu khác trong loạt bài này, nó có giao diện người dùng [HTML và JavaScript] và máy chủ tài nguyên. Như ví dụ ở mục IV nó có Gateway nhưng ở đây nó tách biệt, không thuộc UI. Giao diện người dùng thực sự trở thành một phần của chương trình phụ trợ, cho chúng ta nhiều lựa chọn hơn để định cấu hình lại và triển khai lại các tính năng, đồng thời mang lại những lợi ích khác như chúng ta sẽ thấy

Trình duyệt truy cập Cổng cho mọi thứ và nó không cần biết về kiến ​​trúc của phần phụ trợ [về cơ bản, nó không biết rằng có phần phụ trợ]. Một trong những điều mà trình duyệt thực hiện trong Cổng này là xác thực, e. g. nó sẽ gửi tên người dùng và mật khẩu như trong Phần II và đổi lại nó sẽ nhận được một cookie. Trong các yêu cầu tiếp theo, nó sẽ tự động hiển thị cookie và Cổng sẽ chuyển cookie đó đến các chương trình phụ trợ. Không cần viết mã trên máy khách để cho phép chuyển cookie. Các chương trình phụ trợ sử dụng cookie để xác thực và vì tất cả các thành phần chia sẻ một phiên nên chúng chia sẻ cùng thông tin về người dùng. Tương phản điều này với Phần V trong đó cookie phải được chuyển đổi thành mã thông báo truy cập trong Cổng và mã thông báo truy cập sau đó phải được giải mã độc lập bởi tất cả các thành phần phụ trợ

Như trong Phần IV, Cổng đơn giản hóa sự tương tác giữa máy khách và máy chủ và nó thể hiện một bề mặt nhỏ, được xác định rõ ràng để xử lý vấn đề bảo mật. Ví dụ: chúng tôi không cần phải lo lắng về Chia sẻ tài nguyên nguồn gốc chéo, đây là một điều đáng hoan nghênh vì rất dễ hiểu sai

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. Có một thành phần bổ sung ở trạng thái kết thúc của hệ thống này ["quản trị viên kép"] vì vậy hãy bỏ qua điều đó ngay bây giờ

Xây dựng phần phụ trợ

Trong kiến ​​trúc này, phần phụ trợ rất giống với mẫu "phiên mùa xuân" mà chúng tôi đã tạo trong Phần III, ngoại trừ việc nó không thực sự cần trang đăng nhập. Cách dễ nhất để đạt được những gì chúng ta muốn ở đây có lẽ là sao chép máy chủ "tài nguyên" từ Phần III và lấy giao diện người dùng từ mẫu "cơ bản" trong Phần I. Để chuyển từ giao diện người dùng "cơ bản" sang giao diện chúng ta muốn ở đây, chúng ta chỉ cần thêm một vài thành phần phụ thuộc [như khi chúng ta sử dụng Phiên mùa xuân lần đầu tiên trong Phần 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

Vì đây hiện là giao diện người dùng nên không cần điểm cuối "/resource". Khi bạn hoàn thành việc đó, bạn sẽ có một ứng dụng Angular rất đơn giản [giống như trong mẫu "cơ bản"], giúp đơn giản hóa rất nhiều việc kiểm tra và suy luận về hành vi của nó

Cuối cùng, chúng tôi muốn máy chủ này chạy dưới dạng phụ trợ, vì vậy chúng tôi sẽ cung cấp cho nó một cổng không mặc định để nghe [trong

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

đăng kí. đặc tính

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

Nếu đó là toàn bộ nội dung

$ spring init --dependencies web,security ui/ && cd ui
91 thì ứng dụng sẽ an toàn và có thể truy cập được đối với người dùng được gọi là "người dùng" bằng mật khẩu ngẫu nhiên, nhưng được in trên bảng điều khiển [INFO ở cấp độ nhật ký] khi khởi động. Bảo vệ. phiên" có nghĩa là Spring Security sẽ chấp nhận cookie làm mã thông báo xác thực nhưng sẽ không tạo chúng trừ khi chúng đã tồn tại

Máy chủ tài nguyên

Máy chủ tài nguyên dễ dàng tạo từ một trong các mẫu hiện có của chúng tôi. Nó giống như máy chủ tài nguyên "phiên mùa xuân" trong Phần III. chỉ là Phiên mùa xuân điểm cuối "/resource" để lấy dữ liệu phiên phân tán. Chúng tôi muốn máy chủ này có cổng không mặc định để lắng nghe và chúng tôi muốn có thể tra cứu xác thực trong phiên, vì vậy chúng tôi cần cổng này [trong

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

đăng kí. đặc tính

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

Chúng tôi sẽ ĐĂNG các thay đổi đối với tài nguyên thông báo của mình, đây là một tính năng mới trong hướng dẫn này. Điều đó có nghĩa là chúng tôi sẽ cần bảo vệ CSRF trong phần phụ trợ và chúng tôi cần thực hiện thủ thuật thông thường để Spring Security hoạt động tốt với Angular

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

Mẫu đã hoàn thành có ở đây trong github nếu bạn muốn xem qua

Cổng

Đối với triển khai ban đầu của Cổng [thứ đơn giản nhất có thể hoạt động], chúng ta chỉ cần lấy một ứng dụng web Spring Boot trống và thêm chú thích

$ mvn spring-boot:run
14. Như chúng ta đã thấy trong Phần I, có một số cách để làm điều đó và một là sử dụng Spring Initializr để tạo một dự án khung. Thậm chí dễ dàng hơn, là sử dụng Spring Cloud Initializr, điều tương tự, nhưng đối với các ứng dụng Spring Cloud. Sử dụng cùng một chuỗi các thao tác dòng lệnh như trong Phần I

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

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. Có một phiên bản trong github nếu bạn muốn tiếp tục từ đó, nhưng nó có một số tính năng bổ sung mà chúng tôi chưa cần

Bắt đầu từ ứng dụng Initializr trống, chúng tôi thêm phần phụ thuộc Phiên mùa xuân [như trong giao diện người dùng ở trên]. Cổng đã sẵn sàng để chạy, nhưng nó chưa biết về các dịch vụ phụ trợ của chúng tôi, vì vậy, hãy thiết lập nó trong

$ mvn spring-boot:run
12 của nó [đổi tên từ
$ spring init --dependencies web,security ui/ && cd ui
91 nếu bạn đã thực hiện điều cuộn tròn ở trên]

đăng kí. yml

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

Có 2 tuyến trong proxy, cả hai đều chuyển cookie xuôi dòng bằng cách sử dụng thuộc tính

$ mvn spring-boot:run
59, mỗi tuyến cho giao diện người dùng và máy chủ tài nguyên, đồng thời chúng tôi đã thiết lập mật khẩu mặc định và chiến lược duy trì phiên [yêu cầu Spring Security luôn tạo phiên . Bit cuối cùng này rất quan trọng vì chúng tôi muốn xác thực và do đó các phiên được quản lý trong Cổng

lên và chạy

Bây giờ chúng tôi có ba thành phần, chạy trên 3 cổng. Nếu bạn trỏ trình duyệt tại http. //máy chủ cục bộ. 8080/ui/, bạn sẽ nhận được thử thách HTTP Basic và bạn có thể xác thực là "người dùng/mật khẩu" [thông tin đăng nhập của bạn trong Cổng] và sau khi thực hiện điều đó, bạn sẽ thấy lời chào trong Giao diện người dùng, thông qua lệnh gọi phụ trợ thông qua

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

LẤY

/ui/

401

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

LẤY

/ui/

200

mục lục. html

LẤY

/ui/*. js

200

Tài sản góc

LẤY

/ui/js/xin chào. js

200

logic ứng dụng

LẤY

/ui/người dùng

200

xác thực

LẤY

/nguồn/

200

Lời chào JSON

Bạn có thể không nhìn thấy 401 vì trình duyệt coi tải trang chủ là một tương tác đơn lẻ. Tất cả các yêu cầu đều được ủy quyền [chưa có nội dung nào trong Cổng, ngoài các điểm cuối của Bộ truyền động để quản lý]

Hoan hô, nó hoạt động. Bạn có hai máy chủ phụ trợ, một trong số đó là giao diện người dùng, mỗi máy chủ có khả năng độc lập và có thể được kiểm tra riêng biệt và chúng được kết nối với nhau bằng một Cổng an toàn mà bạn kiểm soát và bạn đã định cấu hình xác thực cho cổng đó. Nếu các chương trình phụ trợ không thể truy cập được vào trình duyệt thì điều đó không thành vấn đề [trên thực tế, đó có thể là một lợi thế vì nó cho phép bạn kiểm soát nhiều hơn đối với bảo mật vật lý]

Adding a Login Form

Just as in the "basic" sample in Section I we can now add a login form to the Gateway, e. g. bằng cách sao chép mã từ Phần II. 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]

ứng dụng. html

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

Thay vì hiển thị thông báo, chúng tôi sẽ có một nút điều hướng lớn đẹp mắt

mục lục. html

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

Nếu bạn đang xem mẫu trong github, nó cũng có một thanh điều hướng tối thiểu với nút "Đăng xuất". Here’s the login form in a screenshot

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

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

where the implementation of the

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

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

If we run this enhanced Gateway, instead of having to remember the URL for the UI we can just load the home page and follow links. Here’s the home page for an authenticated user

Granular Access Decisions in the Backend

Up to now our application is functionally very similar to the one in Section III or Section IV, 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

There is a new component [Admin] and a new route in the Gateway in

$ mvn spring-boot:run
12

đăng kí. yml

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

The fact that the existing UI is available to users in the "USER" role is indicated on the block diagram above in the Gateway box [green lettering], as is the fact that the "ADMIN" role is needed to go to the Admin application. The access decision for the "ADMIN" role could be applied in the Gateway, in which case it would appear in a

$ mvn spring-boot:run
36, or it could be applied in the Admin application itself [and we will see how to do that below]

So first, create a new Spring Boot application, or copy the UI and edit it. 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. In the Gateway we only need to ensure that our user accounts have the roles needed, and this information is available, but the Gateway doesn’t need to know how to interpret it. In the Gateway we create user accounts to keep the sample application self-contained

Bảo MậtCấu Hình. lớp

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

trong đó người dùng "quản trị viên" đã được tăng cường với 3 vai trò mới ["QUẢN TRỊ", "NGƯỜI ĐỌC" và "NGƯỜI VIẾT"] và chúng tôi cũng đã thêm một người dùng "kiểm toán" với quyền truy cập "QUẢN TRỊ", nhưng không phải "NGƯỜI VIẾT"

Trong hệ thống sản xuất, dữ liệu tài khoản người dùng sẽ được quản lý trong cơ sở dữ liệu phụ trợ [rất có thể là dịch vụ thư mục], không được mã hóa cứng trong Cấu hình mùa xuân. Các ứng dụng mẫu kết nối với cơ sở dữ liệu như vậy có thể dễ dàng tìm thấy trên internet, chẳng hạn như trong Mẫu bảo mật mùa xuân

Các quyết định truy cập đi trong ứng dụng Quản trị. Đối với vai trò "QUẢN TRỊ" [được yêu cầu trên toàn cầu cho phần phụ trợ này], chúng tôi thực hiện trong Spring Security

Bảo MậtCấu Hình. java

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

Đối với các vai trò "READER" và "WRITER", bản thân ứng dụng được phân chia và vì ứng dụng được triển khai bằng JavaScript nên đó là nơi chúng tôi cần đưa ra quyết định về quyền truy cập. Một cách để làm điều này là có một trang chủ với chế độ xem được tính toán được nhúng trong đó thông qua bộ định tuyến

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

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

Lộ trình được tính khi thành phần tải

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

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

điều đầu tiên mà ứng dụng thực hiện là kiểm tra xem người dùng có được xác thực hay không và tính toán tuyến đường bằng cách xem dữ liệu người dùng. Các tuyến đường được khai báo trong mô-đun chính

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

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

Mỗi thành phần đó [một thành phần cho mỗi tuyến đường] phải được triển khai riêng. Đây là

$ mvn spring-boot:run
69 làm ví dụ

đọc. thành phần. ts

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

đọc. thành phần. html

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

$ mvn spring-boot:run
70 cũng tương tự nhưng có form thay đổi thông báo ở backend

viết. thành phần. ts

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

viết. thành phần. html

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

$ spring init --dependencies web,security ui/ && cd ui
30 cũng cần cung cấp dữ liệu để tính toán lộ trình, vì vậy trong hàm
$ spring init --dependencies web,security ui/ && cd ui
41, chúng ta thấy điều này

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

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

Để hỗ trợ chức năng này trên phần phụ trợ, chúng tôi cần điểm cuối

$ spring init --dependencies web,security ui/ && cd ui
71, e. g. trong lớp ứng dụng chính của chúng tôi

Ứng dụng quản trị. java

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

tên vai trò quay trở lại từ điểm cuối "/user" với tiền tố "ROLE_" để chúng tôi có thể phân biệt chúng với các loại cơ quan khác [đó là một điều Bảo mật mùa xuân]. Do đó, tiền tố "ROLE_" là cần thiết trong JavaScript, nhưng không phải trong cấu hình Bảo mật mùa xuân, trong đó rõ ràng từ các tên phương thức rằng "vai trò" là trọng tâm của các hoạt động

Các thay đổi trong Cổng hỗ trợ giao diện người dùng quản trị

Chúng ta cũng sẽ sử dụng vai trò để đưa ra quyết định truy cập trong Cổng [để chúng ta có thể hiển thị một liên kết đến giao diện người dùng quản trị một cách có điều kiện], vì vậy chúng ta cũng nên thêm "vai trò" vào điểm cuối "/user" trong Cổng. Khi đã có, chúng ta có thể thêm một số JavaScript để thiết lập cờ để cho biết rằng người dùng hiện tại là "QUẢN VIÊN". Trong hàm

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

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

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

và chúng tôi cũng cần đặt lại cờ

$ mvn spring-boot:run
75 thành
$ mvn spring-boot:run
76 khi người dùng đăng xuất

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

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

và sau đó trong HTML, chúng tôi có thể hiển thị một liên kết mới một cách có điều kiện

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

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

Chạy tất cả các ứng dụng và truy cập http. //máy chủ cục bộ. 8080 để xem kết quả. Mọi thứ sẽ hoạt động tốt và giao diện người dùng sẽ thay đổi tùy thuộc vào người dùng hiện được xác thực

Tại sao chúng ta ở đây?

Bây giờ chúng tôi có một hệ thống nhỏ xinh xắn với 2 giao diện người dùng độc lập và máy chủ Tài nguyên phụ trợ, tất cả đều được bảo vệ bởi cùng một xác thực trong Cổng. Thực tế là Cổng hoạt động như một micro-proxy khiến việc triển khai các vấn đề bảo mật phụ trợ trở nên cực kỳ đơn giản và họ có thể tự do tập trung vào các mối quan tâm kinh doanh của riêng mình. Việc sử dụng Phiên mùa xuân đã [một lần nữa] tránh được rất nhiều rắc rối và lỗi tiềm ẩn

A powerful feature is that the backends can independently have any kind of authentication they like [e. g. bạn có thể truy cập trực tiếp vào giao diện người dùng nếu bạn biết địa chỉ thực của nó và một bộ thông tin đăng nhập cục bộ]. Cổng áp đặt một tập hợp các ràng buộc hoàn toàn không liên quan, miễn là nó có thể xác thực người dùng và gán siêu dữ liệu cho họ đáp ứng các quy tắc truy cập trong phần phụ trợ. Đây là một thiết kế tuyệt vời để có thể phát triển và kiểm tra độc lập các thành phần phụ trợ. If we wanted to, we could go back to an external OAuth2 server [like in Section V, 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 Section V, 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 Section I 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 our discussion 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 first section, 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

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

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

The new pieces here are

  • The declaration of the

    $ mvn spring-boot:run
    83 as an imports in the
    $ mvn spring-boot:run
    85 in
    $ 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

To run our test" code we can do

$ mvn spring-boot:run
87 [or
$ mvn spring-boot:run
88] using the convenience script created when the project was set up. 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]

Sự 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. Với phần này, chúng tôi đã trình bày các thành phần cơ bản về cách viết bài kiểm tra, cách chạy chúng trong thời gian phát triển và quan trọng là trong môi trường tích hợp liên tục. 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 our discussion 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 first section, 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

  • Máy chủ ủy quyền thông qua cùng một cổng với giao diện người dùng và hy vọng rằng một cookie là đủ để quản lý trạng thái cho toàn hệ thống. 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

  • Use the token as global authentication and invalidate it when user logs out of the UI app. 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

Phiên không hợp lệ trong 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. starting from the

$ mvn spring-boot:run
92 sample, just add a
$ mvn spring-boot:run
96 to the OAuth2 endpoints

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

This interceptor looks for a

$ mvn package
$ java -jar target/*.jar
04, which is a signal that the user is being redirected back to the client app, and checks if the location contains an auth code or an error. 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. The downside of this approach is that you don’t really have true single sign on any more - any other apps that are part of your system will find that the authserver session is dead and they have to prompt for authentication again - it isn’t a great user experience if there are multiple apps

Sự kết luận

In this section we have seen how to implement a couple of different patterns for logout from an OAuth2 client application [taking as a starting point the application from section five of the tutorial], and some options for other patterns were discussed. These options are not exhaustive, but should give you a good idea of the trade offs involved, and some tools for thinking about the best solution for your use case. There were only couple of lines of JavaScript in this section, and that wasn’t really specific to Angular [it adds a flag to XHR requests], so all the lessons and patterns are applicable beyond the narrow scope of the sample apps in this guide. 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. If you have an internal authserver and a system that is composed of many components, then possibly the only architecture that feels to the user like a single system is a gateway for all user interactions

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

All guides are released with an ASLv2 license for the code, and an Attribution, NoDerivatives creative commons license for the writing

Chủ Đề