By Matthew Tyson
Software Architect, InfoWorld |
Securing web applications is an inherently complex proposition. Spring Security offers Java developers a powerful framework for addressing this need, but that power comes with a steep learning curve.
This article offers a concise survey of the essential components behind securing a REST API with Spring Security. We’ll build a simple app that uses a JSON Web Token (JWT) to store the user’s information.
JWT is fast becoming the standard approach to holding auth information because of its simplicity and compactness.
Here’s what we want our simple app to do:
This simple app will demonstrate all of the components required for using Spring with JWT to secure a REST API. The complete, operational version of the example app is here.
Before we begin, I’ll give you a birds-eye overview, and then visit each file in the project once to highlight the most important elements.
The project files and layout are seen in the red highlighted area of Figure 1.
Figure 1. A simple secure REST API project.
The class files involved in the sample app are listed below (linked to their sources).
To keep things as simple as possible and make it easier to get your mind around things, I’ve spurned Java best practice and put all of the classes you will use in a single package.
There is also an
index.html file serving the simple front end from
Spring Web will by default serve files in the
resources/static folder. That is where the client lives in the form of a small
index.html file allows the user to click a button and see the message returned from the protected endpoint. It also provides a simple log-in capability. You can see the JS for handling these interactions in Listing 1.
Listing 1 relies on two API endpoints:
/protected. It uses the results of the
login call to set the value of the
token variable, and if the token is present, the protected call sends the token in the authorization header. The server will use that token to validate the user’s auth when the user accesses the secure endpoint.
Notice that no security wiring is present at the mapped route level.
SecurityConfig.java file is the center of the security setup. Let’s begin there and move outward.
The class is annotated with
@EnableWebSecurity, which alerts Spring to the fact that security is active and that this class will apply settings to it.
The bulk of that work is done in the
configure() method seen in listing 4.
A few comments on Listing 3. The
configure method uses an Ant pattern matcher (
PROTECTED_URLS) to allow requests to the static directory
("/") and anything after the
("/open/") path to pass through without an auth check. This means you can still hit the
/static/index.html file, and the log-in endpoint can be hosted at
Notice that the configuration also adds in
provider, which is a
TokenAuthenticationProvider, and a filter, which is handled by a
TokenAuthenticationFilter. Notice that the filter goes before the
AnonymousAuthenticationFilter, which is part of Spring Security.
TokenAuthenticationFilter is responsible for checking the requests that come into the protected URLs. The work is done in Listing 4.
Basically, the filter pulls the token (the one sent by the front-end JS) out of the authorization header. If it’s not there, an exception is raised. If it’s there, it is handed off to the authentication manager, where it will eventually be handled by the
TokenAuthenticationProvider you just saw in
TokenAuthenticationProvider is in charge of recovering the user based on the auth token. It has just a single method that delegates its work to
UserAuthenticationService, as seen in Listing 5.
If the user is null, an exception is raised.
TokenAuthenticationService is the implementation that will be auto-wired into
TokenAuthenticationProvider. It supplies the
findByToken method used to retrieve the user.
TokenAuthenticationService is also where the log-in flow comes together with the authentication flow. It provides the
login() method used by the
UserController. Both methods are seen in Listing 6.
Both methods —
login — rely on
findByToken takes a token, then uses
tokenService to verify its validity. If the token is good,
UserService to get the actual user object.
login does the reverse: It takes a user name, grabs the user with
userService, verifies that the password matches, then uses
tokenService to create the token.
JWTTokenService is the place where the actual JWT token is handled. It relies on the JJWT library to do the work, as seen in Listing 7.
The JJWT library makes it pretty easy to create, parse, and verify JWT tokens. The
newToken() method uses
Jwts.claims() to set a couple of standard claims (
issuedAt) and any other claims passed in as arguments. In the case of log-ins, this will contain the user name. That means the user name is available to deserialize later in the auth process. At this point, the app could also add other claims like roles or explicit permission types.
Copyright © 2021 IDG Communications, Inc.
By Matthew Tyson