Spring Cloud - Добавяне на ъглова 4

1. Общ преглед

В последната ни статия от Spring Cloud добавихме поддръжка за Zipkin в нашето приложение. В тази статия ще добавим приложение за интерфейс към нашия стек.

Досега работихме изцяло върху задната част, за да изградим нашето облачно приложение. Но каква полза от уеб приложението, ако няма потребителски интерфейс? В тази статия ще решим този проблем, като интегрираме приложение от една страница в нашия проект.

Ще пишем това приложение, използвайки Angular и Bootstrap . Стилът на Angular 4 код прилича много на кодиране на приложение Spring, което е естествен кросоувър за разработчик Spring! Въпреки че кодът на предния край ще използва Angular, съдържанието на тази статия може лесно да бъде разширено до всяка рамка на предния край с минимални усилия.

В тази статия ще изградим приложение Angular 4 и ще го свържем с нашите облачни услуги. Ще демонстрираме как да интегрираме вход между SPA и Spring Security. Ще покажем и как да осъществим достъп до данните на нашето приложение, използвайки поддръжката на Angular за HTTP комуникация.

2. Промени в шлюза

С предния край на място, ние ще преминем към базиран на формуляри вход и защитени части на потребителския интерфейс за привилегировани потребители. Това изисква извършване на промени в конфигурацията ни за сигурност на шлюза.

2.1. Актуализирайте HttpSecurity

Първо, нека актуализираме метода за конфигуриране (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 . Това ще делегира всички проверки за сигурност на back-end услуги.

След това премахнахме 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 с връзка към страницата за вход:

    Book Rater Landing   

So many great things about the books

Login

3. Ъглов CLI и стартовият проект

Преди да започнете нов Angular проект, не забравяйте да инсталирате най-новите версии на Node.js и npm.

3.1. Инсталирайте Angular CLI

За начало ще трябва да използваме npm, за да изтеглим и инсталираме интерфейса на Angular от командния ред. Отворете терминал и стартирайте:

npm install -g @angular/cli

Това ще изтегли и инсталира CLI глобално.

3.2. Инсталирайте нов проект

Още докато сте в терминала, отидете до проекта на шлюза и влезте в основната папка gateway / src /. Създайте директория, наречена „ъглова“ и отидете до нея. Оттук изпълнете:

ng new ui

Бъди търпелив; CLI създава чисто нов проект и изтегля всички зависимости на JavaScript с npm. Не е необичайно този процес да отнеме много минути.

Командата ng е пряк път за Angular CLI, новият параметър инструктира CLI да създаде нов проект, а командата ui дава име на нашия проект.

3.3. Стартирайте проекта

След като новата команда приключи. Придвижете се до папката ui, която е създадена и изпълнена:

ng serve

След изграждането на проекта се придвижете до // localhost: 4200. Трябва да видим това в браузъра:

Честито! Току-що изградихме ъглово приложение!

3.4. Инсталирайте Bootstrap

Нека използваме npm, за да инсталираме bootstrap. От директорията ui изпълнете тази команда:

npm install [email protected] --save

Това ще изтегли bootstrap в папката node_modules.

In the ui directory, open the .angular-cli.json file. This is the file that configures some properties about our project. Find the apps > styles property and add a file location of our Bootstrap CSS class:

"styles": [ "styles.css", "../node_modules/bootstrap/dist/css/bootstrap.min.css" ],

This will instruct Angular to include Bootstrap in the compiled CSS file that's built with the project.

3.5. Set the Build Output Directory

Next, we need to tell Angular where to put the build files so that our spring boot app can serve them. Spring Boot can serve files from two locations in the resources folder:

  • src/main/resources/static
  • src/main/resource/public

Since we're already using the static folder to serve some resources for Eureka, and Angular deletes this folder each time a build is run, let's build our Angular app into the public folder.

Open the .angular-cli.json file again and find the apps > outDir property. Update that string:

"outDir": "../../resources/static/home",

If the Angular project's located in src/main/angular/ui, then it will build to the src/main/resources/public folder. If the app in another folder this string will need to be modified to set the location correctly.

3.6. Automate the Build With Maven

Lastly, we will set up an automated build to run when we compile our code. This ant task will run the Angular CLI build task whenever “mvn compile” is run. Add this step to the gateway's POM.xml to ensure that each time we compile we get the latest ui changes:

 maven-antrun-plugin   generate-resources               run    

We should note that this set up does require that the Angular CLI be available on the classpath. Pushing this script to an environment that does not have that dependency will result in build failures.

Now let's start building our Angular application!

4. Angular

In this section of the tutorial, we build an authentication mechanism in our page. We use basic authentication and follow a simple flow to make it work.

Users have a login form where they can enter their username and password.

Next, we use their credentials to create a base64 authentication token and request the “/me” endpoint. The endpoint returns a Principal object containing the roles of this user.

Lastly, we will store the credentials and the principal on the client to use in subsequent requests.

Let's see how this's done!

4.1. Template

In the gateway project, navigate to src/main/angular/ui/src/app and open the app.component.html file. This's the first template that Angular loads and will be where our users will land after logging in.

In here, we're going to add some code to display a navigation bar with a login form:

    Book Rater Admin 
    
    Logout

    Anyone can view the books.

    Users can view and create ratings

    Admins can do anything!

    This code sets up a navigation bar with Bootstrap classes. Embedded in the bar is an inline login form. Angular uses this markup to interact with JavaScript dynamically to render various parts of the page and control things like form submission.

    Statements like (ngSubmit)=”onLogin(f)” simply indicate that when the form is submitted call the method “onLogin(f)” and pass the form to that function. Within the jumbotron div, we have paragraph tags that will display dynamically depending on the state of our principal object.

    Next, let's code up the Typescript file that will support this template.

    4.2. Typescript

    From the same directory open the app.component.ts file. In this file we will add all the typescript properties and methods required to make our template function:

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

    This class hooks into the Angular life cycle method, ngOnInit(). In this method, we call the /me endpoint to get user's current role and state. This determine's what the user's sees on the main page. This method will be fired whenever this component's created which is a great time to be checking the user's properties for permissions in our app.

    We also have an onLogout() method that logs our user out and restores the state of this page to its original settings.

    There's some magic going on here though. The httpService property that's declared in the constructor. Angular is injecting this property into our class at runtime. Angular manages singleton instances of service classes and injects them using constructor injection, just like Spring!

    Next, we need to define the HttpService class.

    4.3. HttpService

    In the same directory create a file named “http.service.ts”. In this file add this code to support the login and logout methods:

    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 { return this.http.get("/me", this.makeOptions()) } logout(): Observable { return this.http.post("/logout", '', this.makeOptions()) } private makeOptions(): RequestOptions { let headers = new Headers({'Content-Type': 'application/json'}); return new RequestOptions({headers: headers}); } }

    In this class, we're injecting another dependency using Angular's DI construct. This time it's the Http class. This class handles all HTTP communication and is provided to us by the framework.

    These methods each perform an HTTP request using angular's HTTP library. Each request also specifies a content type in the headers.

    Now we need to do one more thing to get the HttpService registered in the dependency injection system. Open the app.module.ts file and find the providers property. Add the HttpService to that array. The result should look like this:

    providers: [HttpService],

    4.4. Add Principal

    Next, let's add our Principal DTO object in our Typescript code. In the same directory add a file called “principal.ts” and add this code:

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

    We added the Principal class and an Authority class. These are two DTO classes, much like POJOs in a Spring app. Because of that, we do not need to register these classes with the DI system in angular.

    Next, let's configure a redirect rule to redirect unknown requests to the root of our application.

    4.5. 404 Handling

    Let's navigate back into the Java code for the gateway service. In the where GatewayApplication class resides add a new class called ErrorPageConfig:

    @Component public class ErrorPageConfig implements ErrorPageRegistrar { @Override public void registerErrorPages(ErrorPageRegistry registry) { registry.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/home/index.html")); } }

    This class will identify any 404 response and redirect the user to “/home/index.html”. In a single page app, this's how we handle all traffic not going to a dedicated resource since the client should be handling all navigable routes.

    Now we're ready to fire this app up and see what we built!

    4.6. Build and View

    Now run “mvn compile” from the gateway folder. This will compile our java source and build the Angular app to the public folder. Let's start the other cloud applications: config, discovery, and zipkin. Then run the gateway project. When the service starts, navigate to //localhost:8080 to see our app. We should see something like this:

    Next, let's follow the link to the login page:

    Log in using the user/password credentials. Click “Login”, and we should be redirected to /home/index.html where our single page app loads.

    It looks like our jumbotron is indicating we're logged in as a user! Now log out by clicking the link the upper right corner and log in using the admin/admin credentials this time.

    Looks good! Now we're logged in as an admin.

    5. Conclusion

    In this article, we have seen how easy it's to integrate a single page app into our cloud system. We took a modern framework and integrated a working security configuration into our application.

    Using these examples, try to write some code to make a call to the book-service or rating-service. Since we now have examples of making HTTP calls and wiring data to the templates, this should be relatively easy.

    Ако искате да видите как останалата част от сайта е изградена както винаги, можете да намерите изходния код на Github.