1. Обзор
В нашей последней статье Spring Cloud мы добавили поддержку Zipkin в наше приложение. В этой статье мы собираемся добавить интерфейсное приложение в наш стек.
До сих пор мы полностью работали над серверной частью для создания нашего облачного приложения. Но что хорошего в веб-приложении, если в нем нет пользовательского интерфейса? В этой статье мы собираемся решить эту проблему, интегрировав одностраничное приложение в наш проект.
Мы будем писать это приложение, используя Angular
и Bootstrap
. Стиль кода Angular 4 очень похож на кодирование приложения Spring, что является естественным кроссовером для разработчика Spring! Хотя внешний код будет использовать Angular, содержание этой статьи можно легко распространить на любой интерфейсный фреймворк с минимальными усилиями.
В этой статье мы собираемся создать приложение Angular 4 и подключить его к нашим облачным сервисам. Мы продемонстрируем, как интегрировать вход между SPA и Spring Security. Мы также покажем, как получить доступ к данным нашего приложения, используя поддержку Angular для связи по протоколу HTTP.
2. Изменения шлюза
Имея внешний интерфейс, мы собираемся переключиться на вход в систему на основе форм и безопасные части пользовательского интерфейса для привилегированных пользователей. Для этого необходимо внести изменения в конфигурацию безопасности нашего шлюза.
2.1. Обновить HttpSecurity
Во-первых, давайте обновим метод configure(HttpSecurity http)
в нашем классе шлюза SecurityConfig.java :
@Override
protected void configure(HttpSecurity http) {
http
.formLogin()
.defaultSuccessUrl("/home/index.html", true)
.and()
.authorizeRequests()
.antMatchers("/book-service/**", "/rating-service/**", "/login*", "/")
.permitAll()
.antMatchers("/eureka/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.logout()
.and()
.csrf().disable();
}
Во-первых, мы добавляем URL-адрес успеха по умолчанию, чтобы указать на /home/index.html
, так как это будет место, где живет наше приложение Angular. Затем мы настраиваем сопоставители муравьев, чтобы разрешить любой запрос через шлюз, кроме ресурсов Eureka .
Это делегирует все проверки безопасности внутренним службам.
Затем мы удалили URL-адрес успешного выхода, так как перенаправление по умолчанию обратно на страницу входа будет работать нормально.
2.2. Добавить основную конечную точку
Далее давайте добавим конечную точку для возврата аутентифицированного пользователя. Это будет использоваться в нашем приложении Angular для входа в систему и определения ролей, которые есть у нашего пользователя. Это поможет нам контролировать, какие действия они могут совершать на нашем сайте.
В проекте шлюза добавьте класс AuthenticationController :
@RestController
public class AuthenticationController {
@GetMapping("/me")
public Principal getMyUser(Principal principal) {
return principal;
}
}
Контроллер возвращает объект пользователя, вошедшего в систему, вызывающей стороне. Это дает нам всю информацию, необходимую для управления нашим приложением Angular.
2.3. Добавить целевую страницу
Давайте добавим очень простую целевую страницу, чтобы пользователи что-то видели, когда переходят в корень нашего приложения.
В src/main/resources/static
добавим файл index.html
со ссылкой на страницу входа:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Book Rater Landing</title>
</head>
<body>
<h1>Book Rater</h1>
<p>So many great things about the books</p>
<a href="/login">Login</a>
</body>
</html>
3. Angular CLI и стартовый проект
Перед запуском нового проекта Angular обязательно установите последние версии Node.js и npm .
3.1. Установите угловой интерфейс командной строки
Для начала нам нужно будет использовать npm
для загрузки и установки интерфейса командной строки Angular. Откройте терминал и запустите:
npm install -g @angular/cli
Это загрузит и установит CLI глобально.
3.2. Установить новый проект
Находясь в терминале, перейдите к проекту шлюза и перейдите в папку gateway/src/main. Создайте каталог с именем «angular» и перейдите к нему. Отсюда запустить:
ng new ui
Потерпи; CLI настраивает новый проект и загружает все зависимости JavaScript с помощью npm. Нередко этот процесс занимает много минут.
Команда ng
— это ярлык для Angular CLI, новый
параметр указывает этому CLI создать новый проект, а команда ui
дает нашему проекту имя.
3.3. Запустить проект
Как только новая
команда будет завершена. Перейдите в созданную папку ui и запустите:
ng serve
После сборки проекта перейдите по адресу http://localhost:4200. Мы должны увидеть это в браузере:
Поздравляем! Мы только что создали приложение Angular!
3.4. Установить Bootstrap
Давайте используем npm для установки bootstrap. Из каталога ui запустите эту команду:
npm install bootstrap@4.0.0-alpha.6 --save
Это загрузит загрузчик в папку node_modules.
В каталоге ui откройте файл
.angular-cli.json
. Это файл, который настраивает некоторые свойства нашего проекта. Найдите свойство apps > styles
и добавьте расположение файла нашего CSS-класса Bootstrap:
"styles": [
"styles.css",
"../node_modules/bootstrap/dist/css/bootstrap.min.css"
],
Это даст указание Angular включить Bootstrap в скомпилированный файл CSS, созданный вместе с проектом.
3.5. Установите выходной каталог сборки
Затем нам нужно указать Angular, куда поместить файлы сборки, чтобы наше приложение Spring Boot могло их обслуживать. Spring Boot может обслуживать файлы из двух мест в папке ресурсов:
- src/основной/ресурсы/статические
- источник/основной/ресурс/общественность
Поскольку мы уже используем статическую папку для предоставления некоторых ресурсов для Eureka, а Angular удаляет эту папку каждый раз при запуске сборки, давайте встроим наше приложение Angular в общедоступную папку.
Снова откройте файл .angular-cli.json и найдите свойства
apps > outDir
. Обновите эту строку:
"outDir": "../../resources/static/home",
Если проект Angular находится в папке src/main/angular/ui, он будет собран в папку src/main/resources/public. Если приложение находится в другой папке, эту строку необходимо изменить, чтобы правильно указать местоположение.
3.6. Автоматизируйте сборку с помощью Maven
Наконец, мы настроим автоматическую сборку для запуска при компиляции нашего кода. Эта задача ant будет запускать задачу сборки Angular CLI всякий раз, когда запускается «mvn compile». Добавьте этот шаг в POM.xml шлюза, чтобы каждый раз при компиляции мы получали последние изменения пользовательского интерфейса:
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<phase>generate-resources</phase>
<configuration>
<tasks>
<exec executable="cmd" osfamily="windows"
dir="${project.basedir}/src/main/angular/ui">
<arg value="/c"/>
<arg value="ng"/>
<arg value="build"/>
</exec>
<exec executable="/bin/sh" osfamily="mac"
dir="${project.basedir}/src/main/angular/ui">
<arg value="-c"/>
<arg value="ng build"/>
</exec>
</tasks>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
Следует отметить, что для этой настройки требуется, чтобы Angular CLI был доступен в пути к классам. Отправка этого скрипта в среду, в которой нет этой зависимости, приведет к сбоям сборки.
Теперь давайте начнем создавать наше приложение Angular!
4. Угловой
В этом разделе руководства мы создадим механизм аутентификации на нашей странице. Мы используем базовую аутентификацию и следуем простому алгоритму, чтобы заставить ее работать.
У пользователей есть форма входа, где они могут ввести свое имя пользователя и пароль.
Затем мы используем их учетные данные для создания токена аутентификации base64 и запрашиваем конечную точку «/me»
. Конечная точка возвращает объект Principal
, содержащий роли этого пользователя.
Наконец, мы сохраним учетные данные и участника на клиенте для использования в последующих запросах.
Давайте посмотрим, как это делается!
4.1. Шаблон
В проекте шлюза перейдите к src/main/angular/ui/src/app
и откройте файл app.component.html
. Это первый шаблон, который загружает Angular, и именно на него наши пользователи попадут после входа в систему.
Здесь мы собираемся добавить код для отображения панели навигации с формой входа:
<nav class="navbar navbar-toggleable-md navbar-inverse fixed-top bg-inverse">
<button class="navbar-toggler navbar-toggler-right" type="button"
data-toggle="collapse" data-target="#navbarCollapse"
aria-controls="navbarCollapse" aria-expanded="false"
aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<a class="navbar-brand" href="#">Book Rater
<span *ngIf="principal.isAdmin()">Admin</span></a>
<div class="collapse navbar-collapse" id="navbarCollapse">
<ul class="navbar-nav mr-auto">
</ul>
<button *ngIf="principal.authenticated" type="button"
class="btn btn-link" (click)="onLogout()">Logout</button>
</div>
</nav>
<div class="jumbotron">
<div class="container">
<h1>Book Rater App</h1>
<p *ngIf="!principal.authenticated" class="lead">
Anyone can view the books.
</p>
<p *ngIf="principal.authenticated && !principal.isAdmin()" class="lead">
Users can view and create ratings</p>
<p *ngIf="principal.isAdmin()" class="lead">Admins can do anything!</p>
</div>
</div>
Этот код устанавливает панель навигации с классами Bootstrap. В панель встроена встроенная форма входа. Angular использует эту разметку для динамического взаимодействия с JavaScript для отображения различных частей страницы и управления такими вещами, как отправка формы.
Утверждения типа (ngSubmit)="onLogin(f)"
просто указывают, что при отправке формы вызывается метод "onLogin(f)"
и передается форма этой функции. В div jumbotron
у нас есть теги абзаца, которые будут отображаться динамически в зависимости от состояния нашего основного объекта.
Далее давайте напишем файл Typescript, который будет поддерживать этот шаблон.
4.2. Машинопись
В том же каталоге откройте файл app.component.ts. В этот файл мы добавим все свойства и методы машинописного текста, необходимые для работы нашего шаблона:
import {Component} from "@angular/core";
import {Principal} from "./principal";
import {Response} from "@angular/http";
import {Book} from "./book";
import {HttpService} from "./http.service";
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
selectedBook: Book = null;
principal: Principal = new Principal(false, []);
loginFailed: boolean = false;
constructor(private httpService: HttpService){}
ngOnInit(): void {
this.httpService.me()
.subscribe((response: Response) => {
let principalJson = response.json();
this.principal = new Principal(principalJson.authenticated,
principalJson.authorities);
}, (error) => {
console.log(error);
});
}
onLogout() {
this.httpService.logout()
.subscribe((response: Response) => {
if (response.status === 200) {
this.loginFailed = false;
this.principal = new Principal(false, []);
window.location.replace(response.url);
}
}, (error) => {
console.log(error);
});
}
}
Этот класс подключается к методу жизненного цикла Angular, ngOnInit()
. В этом методе мы вызываем конечную точку /me
, чтобы получить текущую роль и состояние пользователя. Это определяет то, что пользователь видит на главной странице. Этот метод будет запускаться всякий раз, когда будет создан этот компонент, что является прекрасным временем для проверки свойств пользователя на наличие разрешений в нашем приложении.
У нас также есть метод onLogout()
, который выводит нашего пользователя из системы и восстанавливает состояние этой страницы до исходных настроек.
Хотя здесь происходит какое-то волшебство. Свойство httpService
, объявленное в конструкторе. Angular внедряет это свойство в наш класс во время выполнения. Angular управляет одноэлементными экземплярами классов обслуживания и внедряет их с помощью внедрения конструктора, как и Spring!
Далее нам нужно определить класс HttpService
.
4.3. HttpService
В том же каталоге создайте файл с именем «http.service.ts»
. В этот файл добавьте этот код для поддержки методов входа и выхода:
import {Injectable} from "@angular/core";
import {Observable} from "rxjs";
import {Response, Http, Headers, RequestOptions} from "@angular/http";
import {Book} from "./book";
import {Rating} from "./rating";
@Injectable()
export class HttpService {
constructor(private http: Http) { }
me(): Observable<Response> {
return this.http.get("/me", this.makeOptions())
}
logout(): Observable<Response> {
return this.http.post("/logout", '', this.makeOptions())
}
private makeOptions(): RequestOptions {
let headers = new Headers({'Content-Type': 'application/json'});
return new RequestOptions({headers: headers});
}
}
В этом классе мы внедряем другую зависимость, используя конструкцию DI Angular. На этот раз это класс Http .
Этот класс обрабатывает всю HTTP-связь и предоставляется нам фреймворком.
Каждый из этих методов выполняет HTTP-запрос с использованием HTTP-библиотеки angular. Каждый запрос также указывает тип контента в заголовках.
Теперь нам нужно сделать еще одну вещь, чтобы зарегистрировать HttpService
в системе внедрения зависимостей. Откройте файл app.module.ts
и найдите свойство provider. Добавьте HttpService
в этот массив. Результат должен выглядеть так:
providers: [HttpService],
4.4. Добавить принципала
Далее давайте добавим наш главный объект DTO в наш код Typescript. В том же каталоге добавьте файл с именем «principal.ts» и добавьте этот код:
export class Principal {
public authenticated: boolean;
public authorities: Authority[] = [];
public credentials: any;
constructor(authenticated: boolean, authorities: any[], credentials: any) {
this.authenticated = authenticated;
authorities.map(
auth => this.authorities.push(new Authority(auth.authority)))
this.credentials = credentials;
}
isAdmin() {
return this.authorities.some(
(auth: Authority) => auth.authority.indexOf('ADMIN') > -1)
}
}
export class Authority {
public authority: String;
constructor(authority: String) {
this.authority = authority;
}
}
Мы добавили класс Principal и класс
Authority
. Это два класса DTO, очень похожие на POJO в приложении Spring. Из-за этого нам не нужно регистрировать эти классы в системе DI в angular.
Далее давайте настроим правило перенаправления для перенаправления неизвестных запросов в корень нашего приложения.
4.5. 404 Обработка
Вернемся к коду Java для службы шлюза. В том месте, где находится класс GatewayApplication
, добавьте новый класс с именем ErrorPageConfig
:
@Component
public class ErrorPageConfig implements ErrorPageRegistrar {
@Override
public void registerErrorPages(ErrorPageRegistry registry) {
registry.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND,
"/home/index.html"));
}
}
Этот класс идентифицирует любой ответ 404 и перенаправит пользователя на «/home/index.html»
. В одностраничном приложении именно так мы обрабатываем весь трафик, не направляемый на выделенный ресурс, поскольку клиент должен обрабатывать все навигационные маршруты.
Теперь мы готовы запустить это приложение и посмотреть, что мы создали!
4.6. Построить и просмотреть
Теперь запустите « mvn compile
» из папки шлюза. Это скомпилирует наш исходный код Java и создаст приложение Angular в общедоступной папке. Запустим другие облачные приложения: config
, discovery
и zipkin
. Затем запустите проект шлюза. Когда служба запустится, перейдите по адресу http://localhost:8080
, чтобы увидеть наше приложение. Мы должны увидеть что-то вроде этого:
Далее переходим по ссылке на страницу входа:
Войдите в систему, используя учетные данные пользователя/пароля. Нажмите «Войти», и мы должны быть перенаправлены на /home/index.html, где загружается наше одностраничное приложение.
Похоже, наш джамботрон
показывает, что мы вошли в систему как пользователь! Теперь выйдите из системы, щелкнув ссылку в правом верхнем углу, и войдите, используя на этот раз учетные данные администратора/администратора .
Выглядит неплохо! Теперь мы вошли в систему как администратор.
5. Вывод
В этой статье мы увидели, как легко интегрировать одностраничное приложение в нашу облачную систему. Мы взяли современный фреймворк и интегрировали в наше приложение работающую конфигурацию безопасности.
Используя эти примеры, попробуйте написать какой-нибудь код, чтобы сделать вызов book-service
или rating-service
. Поскольку теперь у нас есть примеры выполнения HTTP-вызовов и подключения данных к шаблонам, это должно быть относительно легко.
Если вы хотите посмотреть, как устроена остальная часть сайта, вы можете найти исходный код на Github .