How to Secure Your Web Application Part I: Creating a Web App With Spring Boot And Angular

Dustin Noah Young
edataconsulting

--

Picture this scenario: a customer comes to us with an initial idea for a web application and gives us the freedom to make our own suggestions on how to build it (how cool is that?). We choose the technologies that best suit the needs of our customer and preferably ones in which our team is proficient (a dream come true). Then we start coming up with ideas on how to implement the different functionalities, which libraries to use, etc. when suddenly a thought arises: how are we going to secure this thing? (it was too good to be true)

As developers we love to write interesting algorithms to conquer complex problems and fulfill the customers’ needs. Ideally that is all we’d need to do but the truth is that securing web applications these days is a must and it’s becoming more and more relevant. Luckily, there are some great frameworks and libraries out there that can help us with these tasks. One such framework is called Spring Security and, as the name states, it is part of the vast Spring Framework.

What is Spring Security?

Here is a quick introduction to Spring Security taken from the official documentation:

‘Spring Security is a framework that focuses on providing both authentication and authorization to Java applications.

In a nutshell, you can control who can access your application (authentication) and what parts of the application they can access or which actions they can perform (authorization). In fact, Spring Security offers much more than that, like protection against cross site request forgery (CSRF). Since exploring each feature of the framework would likely turn this tutorial into an eBook, we are just going to look at the authentication and authorization part.

Prerequisites

  • Make sure you have OpenJDK 11 installed. I use Amazon Corretto for this tutorial but any other distribution should work as well
  • Make sure you have Maven 3.8.4 or higher installed
  • Make sure you have Node.js 16.14.0 or higher installed
  • Make sure you have Angular 13 installed
  • Make sure you have your favorite IDE installed. I’m using IntelliJ IDEA
  • Create a root project folder with a fitting name. We will be using one project folder for both the back end and the front end

The Premise

We are going to create an application that functions as a fan page for a space faring agency. The announcement page, or home page, will be accessible to anyone, the Q&A page will be accessible to all authenticated users, and the user admin page will be accessible to authenticated users that are authorized to do so. We do not want unauthenticated and unauthorized users to see our Q&A and user admin pages so we will use Spring Security to prevent that from happening.

Please note that this is just a very simple example for demonstration purposes only. You can find the code to this tutorial on GitHub (git-checkout the branch ‘app-unsecured’). Now, without further ado, let’s begin.

Step 1: Creating the Spring Boot Back End

There are many ways to create a Spring Boot application and one of them is using the browser-based Spring Initializr tool. Click on the link and fill in the form as per the example depicted in the image below.

You may change the values for the fields Group, Artifact, Name, Description, and Package name to whatever you prefer.

Once you are done with that just click on ‘Generate’ and your new Spring Boot project will be downloaded to your computer as a ZIP file. Locate that file, extract its contents to your project folder, and open your root project folder in an IDE. Now we can create the Java packages we are going to need in the java folder:

You may choose the package structure that you prefer, however, please note that the packages config, controller, model, and service are often used in enterprise applications.

Next, we can go to the pom.xml file and add the Lombok dependency. This will allow us to reduce some clutter in our model classes (we will get into that later):

...
<dependencies>
...
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
<scope>provided</scope>
</dependency>
...
</dependencies>
...

In the application.properties file we can then add a single line to define our base API path:

server.servlet.context-path=/rest/api

With all that being done, we can go ahead and write the actual code for our API. Let’s start with the controller classes that will be located in our controller package. Here we will leverage annotations provided by the Spring Framework to define our API endpoints.

AnnouncementController.java

@RestController
@RequestMapping("/announcement")
public class AnnouncementController {
@Autowired
private AnnouncementService announcementService;

@GetMapping("/")
public List<Announcement> getAnnouncements() {
return announcementService.getAnnouncements();
}
}

QuestionAndAnswerController.java

@RestController
@RequestMapping("/questionAndAnswer")
public class QuestionAndAnswerController {
@Autowired
private QuestionAndAnswerService questionsAndAnswersService;

@GetMapping("/")
public List<QuestionAndAnswer> getQuestionsAndAnswers() {
return this.questionsAndAnswersService.getQuestionsAndAnswers();
}
}

UserController.java

@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;

@GetMapping("/")
public List<User> getUsers() {
return this.userService.getUsers();
}
}

Let’s move on to the model classes that represent the data that will be retrieved from the back end and displayed in the front end. We will add a class for each information type — announcements, questions and answers, and users — to our model package.
Since questions and answers will share the same structure, we are only going to create one model class called Post, for both. Then we will create a QuestionAndAnswer class that contains a post for the question and one for the answer.

Announcement.java

@Getter
public class Announcement {
private String headline;
private String text;
private Date date;

public Announcement(String headline, String text, Date date){
this.headline = headline;
this.text = text;
this.date = date;
}
}

Post.java

@Getter
public class Post {
private String text;
private Date date;
private String user;
public Post(String text, Date date, String user){
this.text = text;
this.date = date;
this.user = user;
}
}

QuestionAndAnswer.java

@Getter
public class QuestionAndAnswer {
private Post question;
private Post answer;

public QuestionAndAnswer(Post question, Post answer){
this.question = question;
this.answer = answer;
}
}

User.java

@Getter
public class User {
private String username;
private String email;
private String role;

public User(String username, String email, String role) {
this.username = username;
this.email = email;
this.role = role;
}
}

The @Getter annotation provided by the Lombok library automatically generates getters for the class members at compile time, so we don’t have to write them ourselves. The reason we need getters here is because we are using the Spring Framework for our REST API. Looking back at our controller classes, we can see that our endpoints return objects of the types we just defined. When sending a response, Spring will turn these objects into JSON objects as per our class definitions. Under the hood Jackson is used for the serialization and deserialization process and it requires the classes in question to provide getters for their members.

In our service package we will add services that will retrieve the information for us depending on which endpoint is called. Notice how we are skipping the repository and/or DAO layer for the sake of simplicity. Instead, our services will generate the objects we need.

AnnouncementService.java

@Service
public class AnnouncementService {
public List<Announcement> getAnnouncements() {
Instant now = Instant.now();
return new ArrayList<>() {{
add(new Announcement("Important Announcement!",
"We are going to fly to Mars.",
Date.from(now)));
add(new Announcement("Important Announcement!",
"We are going to fly to the moon.",
Date.from(now.minus(1, ChronoUnit.DAYS))));
add(new Announcement("Important Announcement!",
"We are going to fly to space.",
Date.from(now.minus(2, ChronoUnit.DAYS))));
}};
}
}

QuestionAndAnswerService.java

@Service
public class QuestionAndAnswerService {
public List<QuestionAndAnswer> getQuestionsAndAnswers(){
Instant now = Instant.now();

QuestionAndAnswer questionAndAnswer1 = new QuestionAndAnswer(
new Post("Why are you going to fly to space?",
Date.from(now.minus(15, ChronoUnit.DAYS)),
"ilovespace78"),
new Post("Because we love weightlessness.",
java.util.Date.from(now.minus(14, ChronoUnit.DAYS)),
"Bob"));

QuestionAndAnswer questionAndAnswer2 = new QuestionAndAnswer(
new Post("Why are you going to fly to the moon?",
Date.from(now.minus(5, ChronoUnit.DAYS)),
"ilovethemoon88"),
new Post("Because we love rocks.",
java.util.Date.from(now.minus(4, ChronoUnit.DAYS)),
"Sheila"));

QuestionAndAnswer questionAndAnswer3 = new QuestionAndAnswer(
new Post("Why are you going to fly to Mars?",
Date.from(now.minus(1, ChronoUnit.DAYS)),
"ilovemars98"),
new Post("Because we can.",
java.util.Date.from(now),
"Hope"));

return new ArrayList<QuestionAndAnswer>() {{
add(questionAndAnswer1);
add(questionAndAnswer2);
add(questionAndAnswer3);
}};
}
}

UserService.java

@Service
public class UserService {
public List<User> getUsers(){
return new ArrayList<>() {{
add(new User("ilovespace78",
"ilovespace78@spacespace.com", "Member"));
add(new User("ilovethemoon88",
"ilovethemoon88@moonmoon.com", "Member"));
add(new User("ilovemars98",
"ilovemars98@marsmars.com", "Member"));
add(new User("Bob",
"bob@spacetravels.com", "Admin"));
add(new User("Sheila",
"sheila@spacetravels.com", "Admin"));
add(new User("Hope",
"hope@spacetravels.com", "Admin"));
}};
}
}

Finally, to prevent CORS error messages from appearing when we call an API endpoint via our front end, we are going to add a web configuration class to our config package that will enable our front end (only our front end) to call any of our endpoints.

WebConfiguration.java

@Configuration
@EnableWebMvc
public class WebConfiguration implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:4200");
}
}

To run the project, you just need to open a terminal in the back end’s project folder and type in the following command:

$ mvn spring-boot:run

After this, we can test the REST API with a tool like Postman:

Step 2: Creating the Angular Front End

To create the Angular front end, we can use the Angular CLI. Go into your root project folder, open a terminal, and write the following lines:

$ ng new front-end-spring-security-example

This will generate a new Angular project, and now we can simply add the folders we need:

In the model package we should have the same data types we defined for the back end, just this time we need to think in TypeScript. We need to specify interfaces and not classes, since TypeScript interfaces are used for type-checking at compile time. To avoid redundancy, I will just show one of the interfaces here. Feel free to check out the GitHub code or try converting the Java classes yourself:

export interface Announcement {
headline: string;
text: string;
date: Date;
}

Next, we can create the services that send the GET requests to our back-end API. These will be in the service/ folder. Notice that we are using an object of the type HttpClient via dependency injection. This object allows us to easily build and send requests to REST APIs.

announcement.service.ts

@Injectable()
export class AnnouncementService {
constructor(private httpClient: HttpClient) {
}

public getAnnouncements(): Observable<any> {
return this.httpClient.get(environment.backEndUrl
.concat(RestEndpointConstants.ANNOUNCEMENT_ENDPOINT),
{observe: 'body', responseType: 'json'})
}
}

question-and-answer.service.ts

@Injectable()
export class QuestionAndAnswerService {

constructor(private http: HttpClient) {
}
public getQuestionsAndAnswers(): Observable<any> {
return this.http.get(environment.backEndUrl
.concat(RestEndpointConstants.QUESTION_AND_ANSWER_ENDPOINT),
{observe: 'body', responseType: 'json'})
}
}

user.service.ts

@Injectable()
export class UserService {
constructor(private http: HttpClient) {
}

public getUsers(): Observable<any> {
return this.http.get(environment.backEndUrl
.concat(RestEndpointConstants.USER_ENDPOINT),
{observe: 'body', responseType: 'json'});
}
}

The classes above use a constant called environment and an abstract class called RestEndpointConstants,java. As the name states, the latter is an abstract class that only contains the API endpoints in the form of constants. The corresponding file can be placed in the config/ folder:

export abstract class RestEndpointConstants {
static readonly ANNOUNCEMENT_ENDPOINT: string = "/announcement/";
static readonly QUESTION_AND_ANSWER_ENDPOINT: string =
"/questionAndAnswer/";
static readonly USER_ENDPOINT: string = "/user/";
}

The environment constant is used to retrieve certain properties based on which environment is currently active (e.g., development, test, or production). In our case we are using the environment.ts file located in environments/, in which we can store the URL to our back-end API:

export const environment = {
production: false,
backEndUrl: 'http://localhost:8080/rest/api'
};

For each page that we want to have we can now create the corresponding component in the component/ folder. Each component is built in a similar fashion, so we will go into detail on one of them, the question-and-answer component. In our example only the .component.ts and .component.html files are important. In the TypeScript files we will use the services we defined earlier to get the desired information from our back end.

question-and-answer.component.ts

@Component({
selector: 'app-question-and-answer',
templateUrl: './question-and-answer.component.html',
styleUrls: ['./question-and-answer.component.scss']
})
export class QuestionAndAnswerComponent implements OnInit {
questionsAndAnswers: QuestionAndAnswer[] = [];

constructor(private questionAndAnswerService:
QuestionAndAnswerService) { }
ngOnInit(): void {
this.questionAndAnswerService
.getQuestionsAndAnswers()
.subscribe(questionsAndAnswers =>
this.questionsAndAnswers = questionsAndAnswers)
}
}

Then in the template files we will use HTML and Angular’s text interpolation to display the fetched information. Note that we are using the date pipe which is needed to transform the dates from timestamps to a human-readable format.

question-and-answer.component.html

<div *ngIf="(questionsAndAnswers && questionsAndAnswers.length) else missingDataMessage">
<h1 class="header">Q&A</h1>
<ul class="contentList list-group list-group-flush">
<li class="contentListItem list-group-item text-center"
*ngFor="let questionAndAnswer of questionsAndAnswers">
<p class="postHeader">Question from
{{questionAndAnswer.question.user}} on
{{questionAndAnswer.question.date | date:'MM/dd/yyyy,
\'at\' hh:mm:ss a'}}</p>
<p>{{questionAndAnswer.question.text}}</p>
<p class="postHeader">Answer from
{{questionAndAnswer.answer.user}} on
{{questionAndAnswer.answer.date | date:'MM/dd/yyyy,
\'at\' hh:mm:ss a'}}</p>
<p>{{questionAndAnswer.answer.text}}</p>
</li>
</ul>
</div>
<ng-template #missingDataMessage>
<p class="missingDataMessage">Warning: Unable to fetch questions
and answers.</p>
</ng-template>

To tie everything together, we must use the app-routing module and the app module. In app-routing.module.ts we can define the routes to our three pages. Then in the app.module.ts file, we declare all the components we’ve defined, and all the providing services and the date pipe, and we also import the app-routing module.

app-routing.module.ts

const routes: Routes = [
{ path: '', component: AnnouncementComponent},
{ path: 'questionsAndAnswers', component:
QuestionAndAnswerComponent},
{ path: 'users', component: UserComponent}
];

@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }

app.module.ts

@NgModule({
declarations: [
AppComponent,
UserComponent,
QuestionAndAnswerComponent,
AnnouncementComponent
],
imports: [
BrowserModule,
HttpClientModule,
AppRoutingModule
],
providers: [
AnnouncementService,
QuestionAndAnswerService,
UserService,
DatePipe],
bootstrap: [AppComponent]
})
export class AppModule { }

And as a final touch we must add the navigational elements to our app.component.html file:

<nav class="navbar navbar-expand-md navbar-dark bg-dark">
<div class="navigationBarTitle navbar-brand">{{title}}</div>
<a class="nav-tabs navigationBarLink" routerLink="/">Home</a>
<a class="nav-tabs navigationBarLink"
routerLink="/questionsAndAnswers">Q&A</a>
<a class="nav-tabs navigationBarLink"
routerLink="/users">Users</a>
</nav>
<div class="componentContent">
<router-outlet></router-outlet>
</div>

At this point I would like to point out that we have not gone into the styling process for our front end since it is not the subject of this article. However, I would like to help you get started if you are interested in the app’s UI design. I’ve styled the app you can find on GitHub, so if you like you can just apply the ones I added there.
In order to quickly apply some styling you just need to open a terminal in your front-end folder and install the bootstrap and jquery packages:

$ npm install bootstrap jquery --save

Next, you need to open the angular.json file to add some styles and some scripts:

"styles": [
"src/styles.scss",
"node_modules/bootstrap/dist/css/bootstrap.min.css"
],
"scripts": [
"node_modules/jquery/dist/jquery.min.js",
"node_modules/bootstrap/dist/js/bootstrap.min.js"
]

And after that you are all set to add your own styles to the different SCSS files that you will find for each component.

At last, the moment of truth: running both applications and testing the endpoints. With the back end still running, open a command line or terminal in the front-end app and type in this command:

$ npm run start

Open a browser and type in the URL http://localhost:4200, and voila! We now have a web app that is totally unsecured.

Conclusion

In this article we discussed how to build an unsecured web application with a REST API back end based on Spring Boot and Java, and a front end based on Node.js and Angular.

In part 2 of this series, we are going to go into detail on how to secure the web app. You can check it out here.
Also, don’t forget to follow us on Medium to receive notifications about other articles.

--

--

Dustin Noah Young
edataconsulting

Your friendly traveler, gamer, and nerd. I'm also a software developer. 📍New Zealand