How to secure REST with Spring Security – InfoWorld

Matthew Tyson By
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 /resources/static.
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. This will give you a sense of how a JavaScript front end interacts with the server security.
This simple 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: /open/login and /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.
The file is the center of the security setup. Let’s begin there and move outward.
The class is annotated with @configuration and @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 /open/login.
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 SecurityConfig.
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 — findByToken and login — rely on TokenService and UserService. findByToken takes a token, then uses tokenService to verify its validity. If the token is good, findByToken uses 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 to set a couple of standard claims (issuer and 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.