DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Because the DevOps movement has redefined engineering responsibilities, SREs now have to become stewards of observability strategy.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Related

  • Microservices With JHipster
  • Develop a Secure CRUD Application Using Angular and Spring Boot
  • Configuring SSO Using WSO2 Identity Server
  • What D'Hack Is DPoP?

Trending

  • MCP Servers: The Technical Debt That Is Coming
  • Event Driven Architecture (EDA) - Optimizer or Complicator
  • Scaling Microservices With Docker and Kubernetes on Production
  • Using Java Stream Gatherers To Improve Stateful Operations
  1. DZone
  2. Software Design and Architecture
  3. Cloud Architecture
  4. Securing Legacy Apps With OAuth 2.0 and Spring Cloud Gateway

Securing Legacy Apps With OAuth 2.0 and Spring Cloud Gateway

By 
Brian Demers user avatar
Brian Demers
·
Mar. 23, 20 · Tutorial
Likes (4)
Comment
Save
Tweet
Share
12.3K Views

Join the DZone community and get the full member experience.

Join For Free

Do you find it painful to modernize your old, form-based logins? It doesn’t have to be that way. The lack of support in the underlying framework can make adding OAuth 2.0 support to legacy applications tough, but this blog post will show you a low-code way to use Spring Cloud Gateway and Okta to secure your legacy apps.


Learn how to set up Spring Cloud Gateway as a standalone application that proxies HTTP requests and handles OAuth before sending the request to your app.

Create a Spring Cloud Gateway Project

The first step is to create a new project on the Spring Initializr. I typically create it from my IDE or the command line:

Shell
 




x


 
1
curl https://start.spring.io/starter.tgz -d dependencies=okta,cloud-gateway,cloud-security \
2
  -d groupId=com.okta.tutorial \
3
  -d artifactId=example-gateway \
4
  -d packageName=com.okta.tutorial \
5
  -d baseDir=cloud-gateway \
6
  -d bootVersion=2.2.2.RELEASE | tar -xzvf -
7
cd cloud-gateway


If you create the project through your IDE or the Web interface, use the dependencies "Okta", "Gateway", and "Cloud Security."

Rename your src/main/resources/application.properties to application.yml (more on that in a moment).

Next, secure the new application with OIDC/OAuth 2.0.

If you already have an Okta account, see the Create a Web Application in Okta sidebar below. Otherwise, we created a Maven plugin that configures a free Okta developer account + an OIDC app (in under a minute!).

To use it run: ./mvnw com.okta:okta-maven-plugin:setup to create an account and configure your Spring Boot app to work with Okta.

Create a Web Application in Okta

Log in to your Okta Developer account (or sign up if you don’t have an account).

  1. From the Applications page, choose Add Application.

  2. On the Create New Application page, select Web.

  3. Give your app a memorable name, add http://localhost:8080/login/oauth2/code/okta as a Login redirect URI, select Refresh Token (in addition to Authorization Code), and click Done.

Copy the issuer (found under API > Authorization Servers), client ID, and client secret into application.yml for both projects.

YAML
 




xxxxxxxxxx
1


 
1
okta:
2
  oauth2:
3
    issuer: ${yourOktaDomain}/oauth2/default
4
    client-id: ${clientId}
5
    client-secret: ${clientSecret}
6
 
          


Configure Spring Cloud Gateway Routes

Next, you’ll configure Spring Cloud Gateway to forward routes to your legacy application. For this post, the legacy application serves two paths / and /profile.

Once again in your application.yml file, add the following block:

YAML
 




xxxxxxxxxx
1
10


 
1
spring:
2
  cloud:
3
    gateway:
4
      routes:
5
      - id: servlet-app
6
        uri: http://localhost:8000 
7
        predicates:
8
        - Path=/profile,/ 
9
        filters:
10
        - TokenRelay= 



The Base URL for the "legacy" application.

Define the two paths to forward: /profile and /

Includ OAuth access tokens to the downstream request

If this is too much YAML for you, replace the above block with the following Java code in DemoApplication:

Java
 




xxxxxxxxxx
1
25


 
1
package com.okta.tutorial;
2
 
          
3
import org.springframework.boot.SpringApplication;
4
import org.springframework.boot.autoconfigure.SpringBootApplication;
5
import org.springframework.cloud.gateway.route.RouteLocator;
6
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
7
import org.springframework.cloud.security.oauth2.gateway.TokenRelayGatewayFilterFactory;
8
import org.springframework.context.annotation.Bean;
9
 
          
10
@SpringBootApplication
11
public class DemoApplication {
12
 
          
13
    public static void main(String[] args) {
14
        SpringApplication.run(DemoApplication.class, args);
15
    }
16
 
          
17
    @Bean
18
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder, TokenRelayGatewayFilterFactory tokenRelay) {
19
        return builder.routes()
20
                .route("servlet-app", r -> r.path("/profile", "/")
21
                        .filters(f -> f.filter(tokenRelay.apply()))
22
                        .uri("http://localhost:8000"))
23
                .build();
24
    }
25
}


That’s it! Start it up by running ./mvnw spring-boot:run.

A quick recap before we move on: that application.yml has a lot going on. It contains the OAuth 2.0 configuration (issuer, client ID, client secret) and everything needed to securely proxy to the legacy application.

Update a Legacy Application

Updating a legacy application usually isn’t simple; if it were, you probably wouldn’t have assigned the "legacy" label to it! To keep things focused, I’ve created a straightforward servlet application that contains a single servlet:

Java
 




xxxxxxxxxx
1
14


 
1
@WebServlet(name = "UserProfile", urlPatterns = {"/", "/profile"})
2
public class UserProfileServlet extends HttpServlet {
3
 
          
4
    @Override
5
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
6
 
          
7
        request.setAttribute("email", "jill.coder@example.com"); // faking an existing service
8
        Map<String, String> attributes = new HashMap<String, String>();
9
        attributes.put("sub", "jill.coder@example.com"); // more fake data
10
        request.setAttribute("userAttributes", attributes);
11
 
          
12
        request.getRequestDispatcher("/WEB-INF/user-profile.jsp").forward(request, response);
13
    }
14
}


Grab the full code on GitHub (in the original-servlet-app branch):

Shell
 




xxxxxxxxxx
1


 
1
git clone https://github.com/oktadeveloper/okta-legacy-with-cloud-gateway-example.git -b original-servlet-app
2
cd okta-legacy-with-cloud-gateway-example/legacy-servlet-app


The above example uses static strings, a real application likely has a form to collect a username and password along with a user service that connects to a database; you can use your imagination. ��

Start this application with ./mvnw jetty:run and browse to http://localhost:8080.

This application is running on port 8000 and the gateway application above on port 8080. Make sure you are using the 8080 URL in order to access the application through the gateway.

Turn the Legacy Application Into an OAuth Resource Server

You can now access the servlet application through Spring Cloud Gateway! Now it’s time to secure it. To do that, add a servlet Filter to validate the access token added by Spring Cloud Gateway.

Add a new class: src/main/java/com/okta/example/BearerTokenFilter.java

Java
 




xxxxxxxxxx
1
69


 
1
package com.okta.example;
2
 
          
3
import com.okta.jwt.AccessTokenVerifier;
4
import com.okta.jwt.Jwt;
5
import com.okta.jwt.JwtVerificationException;
6
import com.okta.jwt.JwtVerifiers;
7
 
          
8
import javax.servlet.Filter;
9
import javax.servlet.FilterChain;
10
import javax.servlet.FilterConfig;
11
import javax.servlet.ServletException;
12
import javax.servlet.ServletRequest;
13
import javax.servlet.ServletResponse;
14
import javax.servlet.annotation.WebFilter;
15
import javax.servlet.http.HttpServletRequest;
16
import javax.servlet.http.HttpServletResponse;
17
import java.io.IOException;
18
 
          
19
@WebFilter(urlPatterns = "*")
20
public class BearerTokenFilter implements Filter {
21
 
          
22
    public static final String ACCESS_TOKEN = "jwtAccessToken";
23
    private static final String ISSUER_KEY = "okta.oauth2.issuer";
24
 
          
25
    private AccessTokenVerifier tokenVerifier;
26
 
          
27
    public void init(FilterConfig filterConfig) throws ServletException {
28
        String issuer = System.getProperty(ISSUER_KEY, filterConfig.getInitParameter(ISSUER_KEY)); 
29
        tokenVerifier = JwtVerifiers.accessTokenVerifierBuilder() 
30
                .setIssuer(issuer)
31
                .build();
32
    }
33
 
          
34
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
35
 
          
36
        HttpServletRequest request = (HttpServletRequest) servletRequest;
37
        HttpServletResponse response = (HttpServletResponse) servletResponse;
38
        String authHeader = request.getHeader("Authorization"); 
39
 
          
40
        if (authHeader == null || !authHeader.startsWith("Bearer ")) { 
41
            request.getServletContext().log("Missing or invalid 'Authorization' header");
42
            respondWith401(response);
43
            return;
44
        }
45
 
          
46
        String token = authHeader.replaceFirst("^Bearer ", ""); 
47
 
          
48
        try {
49
            Jwt jwtAccessToken = tokenVerifier.decode(token); 
50
            // invalid access tokens will throw an exception
51
            // add the access token as a request attribute
52
            request.setAttribute(ACCESS_TOKEN, jwtAccessToken); 
53
            filterChain.doFilter(request, response); 
54
        } catch (JwtVerificationException e) {
55
            request.getServletContext().log("Failed to parse access token", e);
56
            respondWith401(response);
57
        }
58
    }
59
 
          
60
    private void respondWith401(HttpServletResponse response) throws IOException { 
61
        response.setStatus(401);
62
        response.setHeader("WWW-Authenticate","Bearer");
63
        response.getWriter().write("Authentication required");
64
    }
65
 
          
66
    public void destroy() {
67
        tokenVerifier = null;
68
    }
69
}


Update the Servlet with Access Token Data

The last step is to update the UserProfileServlet with data from the JWT access token. To do so, replace the doGet() method with the one below:

Java
 




xxxxxxxxxx
1


 
1
@Override
2
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
3
 
          
4
    Jwt accessToken = (Jwt) request.getAttribute(BearerTokenFilter.ACCESS_TOKEN); 
5
    request.setAttribute("email", accessToken.getClaims().get("sub")); 
6
    request.setAttribute("userAttributes", accessToken.getClaims()); 
7
 
          
8
    request.getRequestDispatcher("/WEB-INF/user-profile.jsp").forward(request, response);
9
}


Test Out Your Secure Application!

Before restarting the servlet application, grab the "issuer" URL you used in the first step by copying it from src/main/resources/application.yml. You can also find this in your Okta Admin Console under API → Authorization Servers.

Start the legacy application with:

Shell
 




xxxxxxxxxx
1


 
1
./mvnw jetty:run -Dokta.oauth2.issuer=${yourIssuer}


That is it! Open an incognito/private browser and navigate to http://localhost:8080/ where you’ll be redirected to Okta for login and then back to the profile page!

Learn More About Java Web Security

In this post, you learned how to secure a simple servlet application with OAuth 2.0 and just a few lines of code (plus a healthy dose of configuration and error handling). You also used Spring Cloud Gateway to proxy and secure requests before they even get to your application!

The full source code for this post is available on GitHub.

If you want to learn more about Java, Spring, and securing applications, check out the following posts:

  • Spring Method Security with PreAuthorize
  • Angular 8 + Spring Boot 2.2: Build a CRUD App Today!
  • A Quick Guide to Spring Boot Login Options

To discover more posts like this one, follow @oktadev on Twitter and subscribe to our YouTube channel.

Spring Framework Spring Cloud security application app authentication

Published at DZone with permission of Brian Demers, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Microservices With JHipster
  • Develop a Secure CRUD Application Using Angular and Spring Boot
  • Configuring SSO Using WSO2 Identity Server
  • What D'Hack Is DPoP?

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!