In my previous blog, I focused on showing the benefits and basics of Angular 2 in combination with a PrimeNG UI component library. However, majority of applications are not formed only by the front-end part (Angular 2 in this case), but also have their complex business logic, data storage and more. In my case, I decided to show the back-end using Spring framework; specifically, SpringBoot as a convention over the configuration support.
I’m going to walk you through the security authentication and basic routing. In other words, you will see how to connect the Angular2 world with the Spring’s Java world. As a UI component library, I’m going to use PrimeNG, so I don’t have to create any components by myself.
The sample application for this tutorial has a simple domain. It displays a list of money transactions from the database. Each transaction has its date, amount, description, and category. For easy understanding, you can clone the repositories on these URLs:
You can build and start the Angular part using npm install and Yarn. Spring back-end uses Maven so standard mvn clean install will work. Server side can be started afterwards as a standard Spring boot application using java -jar {builtJarFile}.
To enforce a user-based restricted access to certain URLs and data, we will use Spring security with user’s information stored in a SQL database. For exchanging security information between front-end and back-end (client and server), I’m using JWT (JSON Web Token) tokens.
Let’s start with the SpringBoot project. Using SpringBoot makes things easier, because it does automatically a lot of configuration for us. It is necessary to define a parent in the main pom.xml as follows:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.4.RELEASE</version>
</parent>
This way, when we define certain SpringBoot dependencies, for example, for JPA or SpringSecurity, it does the default basic configuration for us. In this tutorial, I’m going to use the following dependencies:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.1</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.2</version>
</dependency>
</dependencies>
As you can see, I’m using H2 in memory SQL database and an additional JJWT library to work with JWT token creation and parsing.
The demonstration app model is located in resources folder of the BudgetAppServer project. There are 2 SQL files: schema and data. For the sake of security there are 2 tables: users and authorities. By using the default SpringSecurity database configuration, Spring can find these tables by itself. The Users table consists of a username, password (SHA256 encoded), information whether the user is still active and a foreign key to the Authorities table, which takes care of the roles. You don’t need to load these SQL scripts manually, because after starting this project a H2 database is automatically set up and the scripts are executed, so after each start you a have a fresh H2 database with data.
To successfully start a SpringBoot app we need 2 more things. First of them is to create an application.properties config under resources folder. In this case, it looks like this:
# IN MEMORY DATABASE CONFIGURATION
spring.datasource.url=jdbc:h2:mem:TEST;MVCC=true;DB_CLOSE_DELAY=-1;MODE=Oracle
spring.datasource.username=sa
spring.datasource.password=
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.platform=h2
spring.datasource.initialize=true
# SQL GENERATE CONFIGURATION
#datasource.schema= the schema sql script to load. By default it is schema-${platform}.sql then schema.sql;
#datasource.data= the data sql script. By default, it is data-${platform}.sql then data.sql;
spring.h2.console.enabled=true
spring.jpa.hibernate.ddl-auto=none
app.jwt.secret=qwerty
The major part of this config is a H2 in memory database default configuration. However, at the end there is a JWT secret password, that will be used for encoding JWT tokens.
The second thing we will need to start our app is SpringBoot’s main entry point, which is pretty simple and looks as follows:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
There is only a declaration that this is a SpringBoot application. Additionally, there can be several other configuration annotations used in this place, but for our case, it is sufficient this way. Now, you only have to build the project and start it as java -jar [your_JAR_file].
In this part, we are going to add a REST controller, which allows the client to send the credentials and obtain a JWT token. This tutorial assumes that you are familiar with JPA mapping and how repositories with SpringData work. In model package, there are 4 JPA classes. In this case, two of them are involved in the security mechanism: Users and Authorities. What is different from other JPA classes in this project, is the Users class. It implements UserDetails interface. SpringSecurity can work with this kind of interface. It actually provides getUsername, getPassword and getAuthorities methods to the security mechanism.The first 2 are basically set/get methods. The getAuthorities in our case returns a join Authorities table as a singleton list. Snippet from the Users class:
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Collections.singleton(() -> authority.getAuthority());
}
Under repository package, there are SpringData repositories. The mechanism how they work can be found in official Spring documentation.
In endpoint package, you can find an AuthController, with /api/auth HTTP POST operation. Our request (AuthParams) will carry email and password as user credentials. Response for this request is AuthResponse which has only token attribute, where the JWT token is stored. The Method serving this request looks as follows:
@RequestMapping(method = RequestMethod.POST) public AuthResponse auth(@RequestBody AuthParams params) throws AuthenticationException { final UsernamePasswordAuthenticationToken loginToken = params.toAuthenticationToken(); final Authentication authentication = authenticationManager.authenticate(loginToken); SecurityContextHolder.getContext().setAuthentication(authentication); return securityContextService.currentUser().map(u -> { final String token = tokenHandler.createTokenForUser(u); return new AuthResponse(token); }).orElseThrow(RuntimeException::new); // it does not happen. }
First of all, it is necessary to obtain Spring’s UsernamePasswordAuthenticationToken (not the JWT token). This can be easily done directly in AuthParams request class:
UsernamePasswordAuthenticationToken toAuthenticationToken() {
return new UsernamePasswordAuthenticationToken(email, password);
}
Email serves as a principal and the password as credentials. After getting the token we can use Spring AuthenticationManager to authenticate and obtain the Authentication class, which can be afterwards set to Security context. Up to this point, we authenticated the user against SpringSecurity mechanism. Now, we have to create a JWT token and send it to the client. In the SecurityContext we are authenticated, so we can take the current user and pass it to our custom TokenHandler, which can be found in auth package. It has a method createTokenForUser:
@Override
public String createTokenForUser(User user) {
final ZonedDateTime afterOneWeek = ZonedDateTime.now().plusWeeks(1);
return Jwts.builder()
.setSubject(user.getId().toString()) // expiration + session
.signWith(SignatureAlgorithm.HS512, secret)
.setExpiration(Date.from(afterOneWeek.toInstant()))
.compact();
}
In this class, I’m using JJWT library to create the token. JWT itself consists of 3 basic parts:
Furthermore, there are commonly used fields, that are present in payload part (sub – subject, iat – issued at, jti – JWT ID). We will use the subject field to store the database ID of the user. SignWith sets the signature, in our case the algorithm is HS512. And finally, the token will expire afterOneWeek. As you can see, the Jwts.builder() creates a JWT token string.
After getting back to AuthController, we only need to set the created string to AuthResponse and the token creation part is done.
return new AuthResponse(token);
Now, that we have the JWT token delivered to the client, we can filter the requests for endpoints based on this token. Core of this part is the StatelessAuthenticationFilter which deals with filtering requests using JWT tokens.
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
try {
Authentication authentication = tokenAuthenticationService.getAuthentication((HttpServletRequest) req);
…
}
First of all, in doFilter method the JWT token is extracted from the header and an Authentication object is created. This happens in TokenAuthenticationService.
public Authentication getAuthentication(HttpServletRequest request) {
final String authHeader = request.getHeader("authorization");
if (authHeader == null) return null;
if (!authHeader.startsWith("Bearer")) return null;
final String jwt = authHeader.substring(7);
if (jwt.isEmpty()) return null;
return tokenHandler
.parseUserFromToken(jwt)
.map(UserAuthentication::new)
.orElse(null);
}
In the beginning, we get authorization header from request. That means, we check if it has “Bearer” string in itself, because after Bearer part there is the token we are looking for. In this part, I’m going to use again the TokenHandler’s method parseUserFromToken:
public Optional parseUserFromToken(String token) {
final String subject = Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody()
.getSubject();
final User user = userRepository.findOne(Integer.valueOf(subject));
return Optional.ofNullable(user);
}
I’m using the JWTS library to parse JWT using a signing key. In subject of the JWT there is an ID of the user who has this token. Afterwards, I’m selecting this user from database and returning it. Note that I’m returning UserDetails, because the User I use extends UserDetails overriding getAuthorities method.
Returning to the previous class TokenAuthenticationService we have our user and we can map it to UserAuthentication, which is returned as a product of this method. Below you can see the content of the doFilter method from our custom filter.
Authentication authentication = tokenAuthenticationService.getAuthentication((HttpServletRequest) req);SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(req, res);
SecurityContextHolder.getContext().setAuthentication(null);
In the above example, I covered the first line of obtaining the authentication object. Afterwards, I set the authentication to the security authentication context, perform filtering of the request and then setting authentication back to null. The code mentioned above performs the filtering.
We have a request filter, but it is not yet configured to do its job. The configuration is done in SecurityConfig class. You need to do one more thing before we’ll have a look on the core configuration:
@Bean
public FilterRegistrationBean registration(StatelessAuthenticationFilter filter) {
FilterRegistrationBean registration = new FilterRegistrationBean(filter);
registration.setEnabled(false);
return registration;
}
This piece of code is part of SecurityConfig, and it disables FilterRegistration, so our StatelessAuthenticationFilter is included only in SpringSecurity usage and not in all filter chains.
Now, let’s check the configure method. In the first line you can see
http.csrf().disable().cors().disable();
We can disable CSRF and CORS because we are using our own JWT tokens.
As a next step, we will deal with filtering specific URLs:
http.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/transactions/list").permitAll()
.anyRequest().fullyAuthenticated()
.and()
.exceptionHandling()
.authenticationEntryPoint(new Http401AuthenticationEntryPoint("'Bearer token_type="JWT"'"));
In this part, I setup paths which will be filtered by our security filter chain. There is permitAll, because in this case I perform role filtering using annotation on endpoint. But of course, it is possible to define hasAuthorities also in here instead of permitAll.
Last part of configuration method is adding our custom JWT filter as follows:
http.addFilterBefore(statelessAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
After performing this configuration, our /transactions/list endpoint defined in AccountTransactionEndpoint is secured using JWT authentication.
Let’s have a look on client now.
I’m going to demonstrate how we can authenticate against SpringBoot server from Angular2 application. I won’t go into detail with configuration and build of an angular application, but I will focus on the authentication details instead.
First of all, let’s introduce a JsonHttp class defined in /app/core/services. It will handle (intercept) our HTTP calls and enhance them with a JWT token. As you can see, it is a standard @Injectable class that performs post, get, put, delete etc. It encapsulates standard angular Http class and merges extra data to RequestOptionsArguments. You can see this happening at the top of the class (in sample source code) in mergeAuthToken const. It accepts existing request options and adds the JWT token to it.
const jwt = localStorage.getItem('jwt');
if (jwt) {
newHeaders.set('authorization', `Bearer ${jwt}`);
}
newOptions.headers = newHeaders;
In the above snippet, I take jwt entry from browser local storage, and if it exists (I authenticated before and I have this token in local storage), I set new header to the HTTP request with name authorization. Its value is Bearer [jwtToken]. The Bearer part is part of the HTTP standard. The rest of the class is clear, it bypasses its attributes except the RequestOptionsArgs, which are merged with JWT authentication token.
get(url: string, options?: RequestOptionsArgs): Observable {
return this.http.get(url, mergeAuthToken(options));
}
post(url: string, body: any, options?: RequestOptionsArgs): Observable {
return this.http.post(url, body, mergeAuthToken(options));
}
Next, we can check the AuthService class defined in /app/core/services. There are 3 main operations that we want to perform: login, logout and check if the user is signed in. All these operations are defined in this class, let’s check them.
login(email: string, password: string): Observable {
const body = {
email: email,
password: password,
};
return this.http.post('/api/auth', body).do((resp: Response) => {
localStorage.setItem('jwt', resp.json().token);
this.authEvents.next(new DidLogin());
});
}
Login in our Spring server part is defined as HTTP POST. To authenticate we need an email (username) and password. I create POST body as the first operation in the method. Then I use standard Angular http (not the one we described above) to call the /api/auth endpoint defined in our server. This endpoint doesn’t check the JWT token, because there is no JWT token yet. It will be provided by this endpoint. When POST is done, we store the JWT token into browser local storage, with ‘jwt’ key and trigger an event, that login was successfull (explained later).
Logout is pretty simple. We just remove the item with jwt key from the local storage, that was created in login method and also again trigger logout event.
logout(): void {
localStorage.removeItem('jwt');
this.authEvents.next(new DidLogout());
}
Check if user is signed in is as simple as logout.
isSignedIn(): boolean {
return localStorage.getItem('jwt') !== null;
}
The example application is designed to show public and private (restricted) URLs in application. In angular this can be achieved by defining routes with guards. It is defined by default in /app/app.module.ts. In our case we define app.module.ts as follows. In @NgModule in imports part we define only reference for routes to app.routes.ts.
RouterModule.forRoot(ROUTES, {
preloadingStrategy: PreloadAllModules
})
Defining routes under app.routes.ts (in separate ts file) makes them more readable.
I defined 3 basic routes: home, transactions and login.
export const ROUTES: Routes = [
{
path: 'home',
component: HomeComponent,
canActivate: [PrivatePageGuard]
},
{
path: 'transactions',
component: TransactionsComponent,
canActivate: [PrivatePageGuard]
},
{
path: 'login',
component: AuthComponent,
canActivate: [PublicPageGuard]
},
];
In order to perform a login, I created AuthComponent. It has a login method which calls the /api/auth endpoint to get the JWT token.
login(email, password) {
this.authService.login(email, password)
.subscribe(() => {
this.router.navigate(['/home']);
}, e => this.handleError(e));
}
After getting the token, it redirects the user to /home page.
A component and a guard are defined for each route. A guard basically decides if the component on path will be shown (loaded) or not. As I mentioned earlier, we are going to have 2 types of access: public (PublicPageGuard) and private (PrivatePageGuard). I will describe them in more detail.
Each guard has to implement CanActivate interface. In the canActivate method we decide if the route can be activated (shown/loaded). Our PublicPageGuard looks as follows:
canActivate() {
if (this.authService.isSignedIn()) {
this.router.navigate(['/home']);
}
return !this.authService.isSignedIn();
}
If a user is signed in, he is redirected to /home and the public login page is displayed only when the user is not signed in – so the user cannot sign in twice. This route can be activated only if the user is not signed in (maybe a better name would be LoginPageGuard).
On the other hand, the PrivatePageGuard handles this situation a little differently:
canActivate() {
if (!this.authService.isSignedIn()) {
this.router.navigate(['/login']);
}
return this.authService.isSignedIn();
}
If the user is not signed in, he is redirected to login page. In each case, route can be activated if the user is signed in.
Guard mechanism allows us to filter whole routes (pages/components), but sometimes it is necessary to show/hide only certain components or parts of the page based on security constraints. Of course, this is possible.
Let’s talk about the HeaderComponent in /app/components/header. A HeaderComponent class is defined in header.component.ts. You can see that it injects AuthService in constructor. In the ngOnInit method it initializes signedIn boolean as follows:
ngOnInit(): void {
this.isSignedIn = this.authService.isSignedIn();
this.authService.events.subscribe(() => {
this.isSignedIn = this.authService.isSignedIn();
});
}
AuthService can check the local storage for presence of JWT token, furthermore in the second line we subscribe for login state change event. After a successful login or logout, the isSignedIn boolean is updated. This way we can use the isSignedIn boolean in ngIf to show or hide certain components on the screen. Further modification of this model with roles would also allow role-based route and component visibility.
I hope you enjoyed reading this article. I tried to show you how an angular application can use Spring security to enforce user login and protect routes and components from unauthorized access. There are several possibilities how to improve this example e.g. with LDAP authentication, role-based permissions etc. In the next article, I would like to explore other parts of angular PrimeNG components and spring integration like lazy load pagination in data table, server side push and many more.
Do you see yourself working with us? Check out our vacancies. Is your ideal vacancy not in the list? Please send an open application. We are interested in new talents, both young and experienced.
Join us