The sample application described in this article demonstrates how to use OpenID Connect and JSON Web Tokens (JWTs) to secure an application that makes calls to other applications on behalf of the user.

Microservice-based architecture, described as “a particular way of designing software applications as suites of independently deployable services”, is becoming more common. The result of the transition to this kind of application composition is a lot of network traffic.

A single request from the user can lead to many subsequent requests being made across the collection of services that together implement the app. Each of these subsequent requests should ideally be made in a secure way: You want to know that any given request being handled by any given service originated from a known user. In other words, any given request should be part of the normal expected flow within the application, and not an abuse of your app architecture by someone who noticed that your backend services just happened to also be reachable from wherever they were.

An overview of OpenID Connect

“The RP has the user talk to the OP which then talks back to the RP which then talks back to the OP before talking to the RS that replies to the RP that then replies to the user.”

Clear? Excellent!

If not, then don’t worry: the only thing that’s really clear is that OpenID Connect seems to love using lots of acronyms (it even abbreviates its own name as OIDC). Let’s start by translating that overview from OpenID-security-speak into something a little more consumable by a human (the definitive source for all this info is really in the OpenID Connect specs, but they can be hard going if you are coming at them cold).

From the user experience point of view, you have the ugly-but-functional browser-provided form used for Basic Auth, and the much better experience of customized login forms with I forgot my password buttons. From the server side, both of these approaches involve validating the the user credentials against some configured data store (database, LDAP, … ). If that data store hasn’t been set up by folks who know what they’re doing, you have a repository full of user credentials that look like a frosted cupcake to a hungry hacker.

There has been a realization that you (mostly) don’t (actually) want to look after that data yourself. All you really want to know is: “Who is the user?”. As many of those users already have email accounts or social media accounts that they authorize themselves with regularly, it would be fantastic (for all concerned) to allow the user to use those credentials to log into your web app, instead of them (and you) creating yet another user ID and password to look after.

And that’s where OpenID Connect steps in. It provides a way for applications to verify the identity of their users without being responsible for managing the authentication or authorization of those users.

OpenID Connect requires at least two services: one that acts as the authenticator, and one that relies upon the authenticator. The first is known as the OpenID Provider (OP) and the second is known as the Relying Party (RP). In our scenario, we also have a third service in the mix: the app the user talks to is the RP, but it talks to another service, which we’ll call the Relying Service (RS).

Liberty can be configured to act as an OpenID Provider (that’s the OP), or it can be configured to protect applications as Relying Parties (RPs). Out of the box however, it does not currently offer a built-in way to protect the onward invocation of other services or apps (RS) by the Relying Party application.

Propagating identity using signed-JWTs

Once you have established identity using OpenID Connect (or another route…) you start considering how to propagate that identity to to your back-end services.

You want to be able to:

  • Know the request initiated from a user request
  • Know the identity that the request was made on behalf of
  • Know that this request isn’t a malicious replay of a previous request

One solution is to use a signed JWT, which is like a small document stating who the user was, when the request was made, and possibly other information such as what the request was for, or the types of information the user agreed to you handling. This document is signed in a way that the services can know that the document has not been altered or tampered with since it was created.

JSON Web Token (JWT) is a compact, URL-safe means of representing claims to be transferred between two parties” (RFC 7519) (JSON means JavaScript Object Notation, because nesting acronyms like this is apparently totally fair game… and I guess JSONWT sounded too much like “Jason What?”. Ironically, the preferred pronounciation for JWT is given as ‘JOT’ which could even be a contraction of JSON-What 😉 )

Once identity is established, say via OpenID Connect, you can use signed JWTs to pass on that identity to other microservice invocations. They can, in turn, also pass it on. Each consumer of the token is able to verify that they can trust the claims made by it, by verifying that they trust the signature of the token. Once a consumer verifies the signature, they know the content of the token has not been altered after it was signed by the person they trust.

The creator and signer of the token can embed information such as creation time and expiry time, along with the user’s identity. This can give the token a finite life during which it may be considered valid when passed from microservice to microservice. If the life is suitably small, say 5 to 10 seconds, then the opportunity for replay attacks (where a 3rd party captures the token to masquerade as the user in an alternate transaction) can be massively reduced.

Additionally, the creator could embed information about the request the creator was handling for the user, and subsequent consumers of the token could test to see that the token they have is suitable for the action they are performing on behalf of the user. In this manner you could prevent a token captured for a read operation being used maliciously to perform a write operation.

About the sample

We’ve devised a sample application (available on GitHub!) to demonstrate use of signed JWTs (JSON Web Tokens). It has five Liberty servers configured, each performing one of the three roles of OpenID Provider (OP), Relying Party (RP), and Relying Service (RS). The OP is showing how Liberty can act as an OpenID Provider but it could just as easily be the stand-in for IBM Cloud Single Sign On or App ID. There are five in total because there are Spring and Java EE versions of both the RP and RS, and each RP invokes both RSs using the same authentication approach. Although it’s possible to combine all three roles into a single server, they have been split out to keep their responsibilities and configuration separate for clarity.

OpenID Provider (OP)

We’ve created a sample OpenID Provider (OP) to demonstrate what that actor does without relying on correctly configuring an external OAuth Provider (e.g. Facebook, Google, etc).

User registry

The OP has the job of authenticating the users. Liberty provides a simple way to define a basic user registry right within the server.xml, which is good enough for our purposes.

The server configuration we need for this sample is in the liberty-op project:

    <!-- This is our example user registry.. it has just the one user -->
    <basicRegistry id="basic" realm="BasicRealm"> 
        <user name="user" password="password" /> 
    </basicRegistry>

This snippet defines a user registry with just one user called user and a password of password. Clearly this isn’t intended for production! Liberty can be configured to other methods for authenticating (like LDAP, for example) but we’re trying to focus on the use of signed JWTs here so we’ll keep things simple.

The basicRegistry configuration element requires the appSecurity feature to be present, so we add this to our featureManager block in the configuration:

    <feature>appSecurity-2.0</feature>

OAuth Provider

We need to configure the server as an OAuth provider, configuring which clients are allowed to connect to the OP for authentication. In this example we have only two direct clients of the OP: The Spring and Java EE Relying Parties:

    <!-- For simplicity, we're using a client db defined here in the
          server.xml In the localStore element we register the clients 
          that will access the provider. For this example, that's just the RPs.
      -->
    <oauthProvider id="Oauth" jwtAccessToken="true">
        <localStore>
            <client name="jee-rp"
                    secret="fish"                
                    displayname="The jee RP application"
                    redirect="${env.JEE_REDIRECT_URL}"
                    scope="openid jee-api spring-api jee"
                    preAuthorizedScope="openid"
                    enabled="true"/>
            <client name="spring-rp"
                    secret="fish"
                    displayname="The spring RP application"
                    redirect="${env.SPRING_REDIRECT_URL}"
                    scope="openid jee-api spring-api spring"
                    preAuthorizedScope="openid"
                    enabled="true"/>
        </localStore>
    </oauthProvider>

    <oauth-roles>
        <authenticated>
            <special-subject type="ALL_AUTHENTICATED_USERS" />
        </authenticated>
    </oauth-roles>

For any client, the name and secret are used to authenticate their connection to the OP. We’ll see the credentials again in the RP server config. The redirect URL is the post-authentication redirect target: The OP will redirect the client (usually a browser) there after the user has authenticated. We supply the redirect URL as an environment variable; this is a deployment choice that allows us to customise the URLs based on the runtime environment (a.k.a. “the redirect URL will be different when we deploy locally from when we deploy to IBM Cloud but we’d rather like to have just the one server.xml to maintain”).

We also set jwtAccessToken to be true here; this allows the OP to issue access tokens that are also JWTs (that will be signed using the same configuration as configured for the OpenID Connect Provider). This allows the RPs to introspect information in the access token without needing to go back to the OP to discover allowed scopes etc.

We don’t need to explicitly add any features to the server.xml for our OAuth configuration because we’ll be using OpenID Connect, which will automatically bring in the required OAuth functionality for us.

There’s no client entry for either RS here because they will be processing a signed JWT from the RP, which they can do without needing to know about the OP at all.

OpenID Connect Provider (OP)

Now that we have a user registry and an OAuth provider defined, we need to put the OpenID Connect ribbon on and configure the server as an OpenID Provider (OP):

    <keyStore id="oidckeystore" .... />
     
    <!-- This is the OpenID Provider configuration.
           We're using Asymmetric RS256 signing, so we must supply a
           keystore and identify which key we should sign with.
    -->
    <openidConnectProvider id="OP"
           oauthProviderRef="Oauth"
           signatureAlgorithm="RS256"
           keyStoreRef="oidckeystore"
           keyAliasName="jwtsampleapp"
           issuerIdentifier="https://thesampleop/"
    />

There are a few things to explain here:

  • The id attribute is used as part of the URL for the OpenID Provider. We’ve used OP for obviousness; you’ll see below where it ends up in the RP configuration URLs.

  • The keystoreRef is required because we’ve opted to use asymmetrically signed id_tokens. We have to declare which keyAliasName from which keystore we’d like to use to sign the tokens.

  • The oauthProviderRef is the reference to the OAuth element we defined above.

In a production configuration, it’s unlikely the clients would be declared like this within the server.xml and it’s more likely that they will be managed in a database; see the Knowledge Center for more information.

There’s no application declared for the OP server. The functionality to act as the OP comes purely from features provided by Liberty itself as part of the openidConnectServer feature. We’re also going to be using SSL, which brings our total combined featureManager block for the OP to look like this:

    <featureManager>
        <feature>openidConnectServer-1.0</feature>
        <feature>ssl-1.0</feature>
        <feature>appSecurity-2.0</feature>
    </featureManager>

Relying Party (RP)

The Relying Party (RP) is the first point of contact with a user. The only function of the RP in this sample is to show the chained invocation of a RS with the propagated identity within a JWT.

We have both Java EE and Spring implementations of the RP, available in GitHub in the liberty-jee–rp and liberty-spring-rp projects.

The projects contain the OpenID Connect configuration for the RP, which is simpler than that for the OP:

    <!--oidc client config.. 
         We must have a secret to obtain a token, and 
         we need a trust store to verify the signing key of the token from the OP. 
         The EndpointUrls must point back to the OP. 
    -->
	<openidConnectClient id="RP" 
		clientId="spring-rp"
		clientSecret="fish" 
		signatureAlgorithm="RS256" 
		trustAliasName="jwtsampleapp"
		trustStoreRef="oidctruststore" 
		scope="openid jee-api spring-api spring"
		authorizationEndpointUrl="${env.AUTH_ENDPOINT_URL}" 
		tokenEndpointUrl="${env.TOKEN_ENDPOINT_URL}" 
		issuerIdentifier="https://thesampleop/"
		/>
  • The id is important, it is part of the URL that we register in the OP for the redirect. We’ve chosen the value RP, which gives us a redirect URL of https://127.0.0.1:9080/oidcclient/redirect/RP. We set
    this in the server.xml of the OP via the appropriate redirect URL environment variable. While 127.0.0.1:9080/oidcclient/redirect/RP makes sense inside the container, we have to give the RP the external URL that corresponds to this address and that changes depending on whether we’re running locally with Docker or remotely in Cloud Foundry. You have to know how the RP will be accessed to be able to configure the OP redirect URL appropriately.

  • The clientId and clientSecret are required and must match the RP’s client definition in the OP.

  • We tell the client to expect asymmetrically signed tokens and tell it which keystore and key to use to verify that signature by specifying the trustStoreRef and trustAliasName.

  • We configure the authorization and token endpoint URLs for the OP. Again we are setting these in server.xml using environment variables, so we can configure the endpoints from outside a container. If we know the host & port to use, our urls would have the form https://OP-HOST:OP-PORT/oidc/endpoint/OP/authorize and https://OP-HOST:OP-PORT/oidc/endpoint/OP/token, the “OP” in the urls coming from the id we set for our OP. If we were running against a 3rd party OP, then we’d use the URLs they supplied here.

As with the server, we enable support for the openidConnectClient by adding the feature to our featureManager block in the server.xml. Again, we’re using SSL and the appSecurity feature but this time we include the OpenID Connect Client feature and servlet to host the app.

For the Java EE RP, we’ll be using JNDI and CDI for injection of environment variables to our code, and JAXRS as our outbound client to talk to the RS. This leaves our final featureManager list looking like:

    <featureManager>
		<feature>ssl-1.0</feature>
		<feature>jsp-2.3</feature>
		<feature>jndi-1.0</feature>
		<feature>jwt-1.0</feature>
		<feature>cdi-1.2</feature>
		<feature>jaxrs-2.0</feature>
		<feature>servlet-3.1</feature>
		<feature>appSecurity-2.0</feature>
		<feature>openidConnectClient-1.0</feature>
    </featureManager>

For the Spring RP, we use fewer Liberty features (because Spring is doing the work), and that leaves us with a featureManager block that looks like this:

	<featureManager>
		<feature>ssl-1.0</feature>
		<feature>servlet-3.1</feature>
		<feature>jwt-1.0</feature>
		<feature>appSecurity-2.0</feature>
		<feature>openidConnectClient-1.0</feature>
	</featureManager>

Our app is going to need to verify the signature of responses from the OP so it needs a private key with which to sign the JWTs. We can use the Liberty JWT API to achieve this. That allows us to define jwtBuilders and jwtConsumers in the server.xml, and configure them with the appropriate keystores and algorithms:

	<jwtBuilder id="rsBuilder" 
				issuer="https://thesamplerp/"
	  			keyStoreRef="defaultKeyStore"
	  			keyAlias="jwtsampleapp"
	/>
	
	<jwtConsumer id="oidcConsumer"
				 signatureAlgorithm="RS256"
				 issuer="https://thesampleop/"
				 trustStoreRef="defaultTrustStore"
				 trustedAlias="jwtsampleapp"
	/>

The rsBuilder will be used to create JWTs that we send onwards to the RSs. The oidcConsumer is used to verify JWTs that come from the OP. In each case, we’re using RS256 as our signature algorithm type. We’re setting a keystore/truststore and an alias to use to sign/verify our JWTs. The trust store contains the public certificate for the key configured in the OP’s openidConnectProvider element. The keystore contains the key we plan to use to sign JWTs for the RSs which, in turn, will have the public certificate they can use to verify those JWTs.

Noteworthy here is the customisation of the issuer attribute of the JWTs being generated. By default, Liberty uses the calculated endpoint address for your Liberty server as the issuer but, when running Liberty within a container, it may not always be able to correctly determine the actual endpoint address being used (eg, Docker port forwards, front-facing proxies etc). By overriding the issuer like this, we can guarantee the result is as expected. In a real deployment, this would likely be configured like the redirect URLs as an environment variable, injected into the container at runtime depending on the execution environment. For this example, fixed values work just fine.

That just leaves the definition for the app itself:

	<!-- The app protected by OIDC, the security role enables security for the 
		app, and by default when is oidc security is configured it applies to all 
		secured apps -->
	<application type="war" id="signed-jwt-jee-rp-application"
		name="signed-jwt-jee-rp-application" location="${server.config.dir}/apps/signed-jwt-jee-rp-application.war">
		<application-bnd>
			<security-role name="OIDCUser">
				<special-subject type="ALL_AUTHENTICATED_USERS" />
			</security-role>
		</application-bnd>
	</application>

You’ll note that there’s no explicit binding saying “protect this app with openID Connect”. When you declare an openidConnectClient within your Liberty server configuration, it applies to all apps. It is possible to configure a server to have multiple openidConnectClients using authFilterRefs to decide which apps are protected by which client definitions (see the Knowledge Center for details).

The Java EE RP application implementation…

The application is available in the liberty-jee-rp project on Github.

The openIdConnectClient configured above handles a lot for the application. By the time the request reaches the application, the user has already been redirected to the OP, authenticated, and Liberty has performed the token exchange to obtain the id_token and access_token from the OP. This is good stuff!

Within our app, we just annotate our servlet with a @ServletSecurity annotation to ensure that accesses should only be allowed when the user has the role OIDCUser (which we configured as the role name for ALL_AUTHENTICATED_USERS back in our server.xml inside the application element):

    @ServletSecurity(@HttpConstraint(rolesAllowed = "OIDCUser"))

The RP will be invoking the two RSs so we need to pass on the identity of the invoking user. We now obtain the id_token from that Oauth exchange that protected the RP, to discover who we are authenticated as, and the scopes etc the user agreed to. The OpenID Connect feature in Liberty offers an easy API to obtain the id_token:

    IdToken id_token = PropagationHelper.getIdToken();

And then to obtain the scopes, we can process the access_token as a JWT (that’s why we picked that option earlier!). We just ask the id_token for the access_token and then use our configured oidcConsumer JwtConsumer to process the JWT for us:

    // lets extract the scopes from the access token, we can send them along to the rs.
    ArrayList<String> scopes=new ArrayList<>();
    // process the access_token as a jwt to obtain scopes.	
    try {
        JwtConsumer jwtConsumer = JwtConsumer.create("oidcConsumer");
        JwtToken access_Token =  jwtConsumer.createJwt(PropagationHelper.getAccessToken());
        scopes = access_Token.getClaims().getClaim("scope", ArrayList.class);
    } catch (InvalidConsumerException | InvalidTokenException e1) {
        e1.printStackTrace();
        throw new WebApplicationException(e1);
    }

Once we have the id_token we want to extract some information from it and build up a signed JWT to send on to the RS as our auth token. We’ll use the rsBuilder JwtBuilder we configured to build a simple JWT with the subject and scopes from the id_token and give it a 30-second lifespan:

		// use liberty to build the new jwt, 'rsBuilder' identifies the jwtBuilder 
		// defined in server.xml which already knows which keystore / key to use
		// to sign the jwt.
		JwtBuilder jwtBuilder;
		try {			
			jwtBuilder = JwtBuilder.create("rsBuilder");

			// add the subject, and scopes from the existing request.
			jwtBuilder.subject(id_token.getSubject());				
			jwtBuilder.claim("scopes", scopes);
			
			// set a very short lifespan for the new jwt of 30 seconds.
			Calendar calendar = Calendar.getInstance();
			calendar.add(Calendar.SECOND, 30);
			jwtBuilder.expirationTime(calendar.getTime().getTime());

			// build the new encoded token
			JwtToken jwtToken = jwtBuilder.buildJwt();
			String newJwt = jwtToken.compact();
			
			... use newJwt ... 
			
		} catch (InvalidBuilderException | InvalidClaimException | JwtException e) {
			e.printStackTrace();
			//e.printStackTrace(out);
			throw new WebApplicationException(e);
		}			

If you wanted to improve this, you might choose to use Joda-Time or the Java 8 Date & Time API to handle creating that date 30 seconds in the future. You may also choose to add additional claims to the JWT relating to the type of action you are about to invoke on the RS, or even information regarding the path through which the user’s request arrived.

And then we’ll build a request, and invoke the RS, with the token as a header. Here, we are using JAX-RS as the client:

    ClientBuilder cb = ClientBuilder.newBuilder();
    cb.property("com.ibm.ws.jaxrs.client.disableCNCheck", true);
    cb.property("com.ibm.ws.jaxrs.client.ssl.config", "defaultSSLConfig");
    
    Client c = cb.build();
    String result = "";
    try{
        result = c.target(RS_JEE_ENDPOINT)
                  .request()
                  .header("jwt", newJwt)
                  .get(String.class);
    }finally{
        c.close();
    }

We’re disabling the common name check in SSL because we’re using self-signed certificates in the example and the host names will almost certainly not match the ones in the generated certificates. But if you are deploying to a more stable environment, with real SSL certificates, you’ll should able to remove that disableCNCheck line.

The new JWT is added as a header as part of the request. Which is all pretty neat but it can be improved, if we make all the code that builds and adds the JWT into a JAXR-RS ClientRequestFilter. Then JAX-RS will automatically add the header for us each time. We’ve included an example filter in the sample.

Using the filter means the RP implementation can collapse to just:

    ClientBuilder cb = ClientBuilder.newBuilder();
    cb.property("com.ibm.ws.jaxrs.client.disableCNCheck", true);
    cb.property("com.ibm.ws.jaxrs.client.ssl.config", "defaultSSLConfig");
    cb.register(JwtJaxRSClientFilter.class);
    
    Client c = cb.build();
    String result = "";
    try {
        result = c.target(RS_JEE_ENDPOINT).request().get(String.class);
    } finally {
        c.close();
    }
    ... use result ...

The filter is registered on the ClientBuilder so that any clients built from it will automatically add the JWT header when invoking services. For the complete code, look at the Java class in the sample in GitHub.

For comparison, the liberty-jee-rp project shows the approach with and without use of a filter.

The Spring RP implementation

For Spring, we’ll use the Spring Security ability to delegate to the container for authentication (preauthentication). This lets Spring allow Liberty to handle the initial authentication and then populate its Spring security context from the Java EE Container authentication. That’s why the server.xmls are similar: Both RPs are using an openidConnectClient to talk to the OP. Additionally, since both Java EE and Spring need to know the result of the Java EE authentication, and need to parse and construct JWTs, we have the Spring implementation of the RP use the Liberty OIDC and JWT APIs.

We’ll start with the WebSecurityConfigurerAdapter. This class is responsible for configuring the security for the Spring web application. Because we are delegating to the Java EE Container, it’s rather a simple configuration:

    @Configuration
    @EnableWebSecurity
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                    .anyRequest().authenticated()
                    .and()
                .jee()
                    .mappableAuthorities("OIDCUser");
        }
    }

All we are saying here is that every request should be authenticated and that we’ll allow the OIDCUser role within Spring. We’re not really using the role so it doesn’t matter too much. We’re just protecting the entire app with Java EE security and we want to be able to ensure we’re authenticated both in Spring and Java EE.

Because we’re using Spring, we don’t have a convenient @ServletSecurity annotation to enable security for the web app for us so we have to go back to using a web.xml (feels kinda odd in today’s super-annotated world!):

    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
             http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
             version="3.1">
        <security-role>
            <role-name>OIDCUser</role-name>
        </security-role>
        <security-constraint>
            <web-resource-collection>
                <web-resource-name>Secured Areas</web-resource-name>
                <url-pattern>/*</url-pattern>
            </web-resource-collection>
            <auth-constraint>
                <role-name>OIDCUser</role-name>
            </auth-constraint>
        </security-constraint>
    </web-app>

Here we basically say that all URLs for the application should require the role OIDCUser which ties back to the server.xml where we defined OIDCUser as the role name to be used for ALL_AUTHENTICATED_USERS.

That’s enough to ensure our Spring app requires authentication via the OP. Next, we need to code the client that will invoke the RSs. In Spring, we can do this using a RestTemplate and, much like JAX-RS allows us to register a filter to add the JWT, Spring allows us to add an Interceptor that can serve the same purpose:

    @Service
    public class RsRestTemplate {
    
        private final RestTemplate restTemplate;
    
        public RsRestTemplate(RestTemplateBuilder builder){
            this.restTemplate = builder.build();
            this.restTemplate.setInterceptors(Collections.singletonList(new JWTAuthenticationInterceptor()));
        }
    
        public String getResponseFromRs(String url) {
            return this.restTemplate.getForObject(url, String.class);
        }
    }

This creates us an injectable RestTemplate that we can use to invoke the RS’s. The JWTAuthenticationInterceptor is an implementation of ClientHttpRequestInterceptor and has code almost identical to the JEE RP’s filter, except for the hook point where it’s connected to Spring:

    @Override
    public ClientHttpResponse intercept(
            HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
            throws IOException {

        HttpHeaders headers = request.getHeaders();
        headers.add("jwt", getJwt());
        return execution.execute(request, body);
    }

The intercept method is driven by Spring when the RestTemplate is invoked. We just build the JWT and add it to the headers, then return the result back to Spring. The full code for the interceptor can be seen in the Java class on GitHub.

That just leaves the @RestController that implements the RP itself. We want the URLs for the RSs and the RestTemplate we built to be injected to us:

    @Autowired
    RsRestTemplate restTemplate;

    @Value("${RS_JEE_ENDPOINT_URL}")
    String jeersurl;

    @Value("${RS_SPRING_ENDPOINT_URL}")
    String springrsurl;

And then we want to define the RP endpoint, and invoke the RS…

    @RequestMapping("/Test")
    public String greeting(@RequestParam(value="name", required=false, defaultValue="World") String name) {
        String jeeRsResp = null;
        try {
            jeeRsResp = restTemplate.getResponseFromRs(jeersurl);
        }catch(HttpClientErrorException|HttpServerErrorException e){
            return "JEE RS Invoke failed : "+e.getStatusCode()+" :: "+e.getResponseBodyAsString();
        }  
 

And the same for the invocation of the Spring RS endpoint. The full source for the RestController is in the Java class on GitHub.

Relying Service (RS)

So that leaves the Relying Service (RS) implementations which, compared to their sibling RP code, look remarkably spartan. Their config has the usual SSL keystore elements and then just the app declaration (and, in this case, even that is optional; you could chuck this one in dropins). As with the other examples, the RS configuration is available at Github over in the liberty-jee-rs and liberty-spring-rs projects.

Here’s our simple application element:

    <application type="war" id="signed-jwt-jee-rs-application" name="signed-jwt-spring-rs-application"
                 location="${server.config.dir}/apps/signed-jwt-jee-rs-application.war">
    </application>

Because the RSs will be verifying a JWT, we also declare a jwtConsumer, the same way we did in the RP, except this time it’s for the RS consuming JWTs from the RP, where as the RP was consuming JWTs from the OP. Although in this example they are using the same keys, in a real deployment it’s more likely that these would be different, even using different keystores/truststores:

     <jwtConsumer id="rpConsumer"
                  signatureAlgorithm="RS256"
                  issuer="https://thesamplerp/"
                  trustStoreRef="defaultTrustStore"
                  trustedAlias="jwtsampleapp"
     />

Here we configure the JwtConsumer to expect JWTs with the RS256 algorithm, the appropriate public certificate from the trust store, and, critically, we set the expected issuer to match the value we’ve configured for the JWTs being built over in the RPs. This makes sure we keep working when running in different environments, where Liberty may not be able to guess the external address used to access the server.

The RS has no server configuration for OpenID Connect because the authentication is all handled in the application code using the JWT.

We’re using the SSL and servlet features to host the RS application. That gives us a relatively short feature manager list for both the Spring and Java EE RSs:

    <featureManager>
        <feature>ssl-1.0</feature>
        <feature>jwt-1.0</feature>
        <feature>servlet-3.0</feature>
    </featureManager>

The Java EE RS application implementation

The full code for this one can be read over in the liberty-jee-rs project on Github.

We will use an annotated servlet filter (via @WebFilter) to intercept all requests to the RS, and to verify the JWT as present and valid:

    @WebFilter(filterName="JWTFilter", urlPatterns = {"/*"})
    public class JwtAuthFilter implements Filter{
    ...
    }

Using the JwtConsumer we declared in the server.xml makes the implementation of this filter pretty straightforward. We check for the JWT header within the request (and reject if missing):

		//read the jwt header & reject if missing or empty.
		String jwt = request.getHeader("jwt");		
		if(jwt==null || jwt.isEmpty()){
			response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
			return;
		}

And then we use the JwtConsumer to process the header value. For simplicity’s sake, we set the parsed token directly into the request as an attribute. This allows the RS application to obtain the token and query it for information:

		JwtConsumer jwtConsumer;
		try {
			//use the consumer 'rpConsumer' declared in server.xml
			jwtConsumer = JwtConsumer.create("rpConsumer");
			JwtToken jwt_Token =  jwtConsumer.createJwt(jwt);
			
			//put the parsed jwt object into the request for the app to use.
			request.setAttribute("jwt", jwt_Token);
			
			//invoke rest of chain
			chain.doFilter(req, resp);	
		} catch (InvalidConsumerException e) {
			response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
			return;
		} catch (InvalidTokenException e) {
			response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
		}

If the JWT is invalid, has expired, or has a bad issuer, an ‘InvalidTokenException’ is thrown. So we only allow the onward chain invocation if everything is ok.

With the filter defined, the RS implementation itself looks very simple:

    @WebServlet("/Test")
    public class TestServlet extends HttpServlet {
    
        private static final long serialVersionUID = 1L;
    
        @Override
        public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
            PrintWriter out = response.getWriter();
    
            //added by the rp when it invoked us.
            String jwtParam = request.getHeader("jwt");			
            out.println("JEE RS App invoked with valid JWT<br>");
    
            //added by the auth filter after validating the jwt from the rp.
            JwtToken jwt_Token = (JwtToken) request.getAttribute("jwt");
            ArrayList<String> scopes = jwt_Token.getClaims().getClaim("scopes", ArrayList.class);
            out.println("JEE RS JWT Had scopes "+scopes.toString());
        }
    }

As this is just an example of how to propagate identity using a signed JWT, we don’t really have any processing to do so we just dump a little info on the scopes from the JWT to our output.

The Spring RS application implementation

The Spring RS is a little more complex than the Java EE one. We start by defining the WebSecurityConfigurerAdapter to add a custom Filter that will perform our JWT authentication:

    @Configuration
    @EnableWebSecurity
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                    .authorizeRequests()
                        .anyRequest().authenticated()
                    .and()
                        // And filter other requests to check the presence of JWT in header
                        .addFilterBefore(new JWTAuthenticationFilter(),
                            UsernamePasswordAuthenticationFilter.class);
        }
    }

The filter itself is an instance of a GenericFilterBean that delegates pretty much all of the real work it does to another bean that validates the JWT & transforms it to a Spring Authentication object:

    public class JWTAuthenticationFilter extends GenericFilterBean {
    
        @Override
        public void doFilter(ServletRequest request,
                             ServletResponse response,
                             FilterChain filterChain)
                throws IOException, ServletException {
            Authentication authentication = TokenAuthenticationService
                    .getAuthentication((HttpServletRequest)request);
            SecurityContextHolder.getContext()
                    .setAuthentication(authentication);
            filterChain.doFilter(request,response);
        }
    }

The TokenAuthenticationService has a static method that will look pretty similar to the Java EE filter logic, with the exception of the last part where it glues into Spring.

We still retrieve the JWT from the HTTP headers. We still validate it with a JwtConsumer but if the JWT is valid, instead of invoking a chain, we create a UsernamePasswordAuthentication object to return. We populate the object with a custom UserDetails object that allows us to query details from the JWT later in the application:

    JwtUserDetails jud = new JwtUserDetails(jwt_Token);
    return new UsernamePasswordAuthenticationToken(jud, "", jud.getAuthorities());

The JwtUserDetails object is a simple implementation of Spring’s UserDetails interface, with an extra method to allow retrieval of the JWT token.

With all that out of the way, the Spring RS application looks really quite simple indeed:

 
     @RestController
     public class GreetingController {
     
         @RequestMapping("/Test")
         public String greeting(@AuthenticationPrincipal JwtUserDetails userDetails,
                                @RequestParam(value="name", required=false, defaultValue="World") String name) {
     
             ArrayList<String> scopes = userDetails.getJwt().getClaims().getClaim("scopes", ArrayList.class);
     
             return "Spring RS App invoked with valid JWT "+name+
                     "<br>You authenticated to me as "+userDetails.getUsername()+
                     " and have scopes of "+scopes.toString();
         }
     
     }

You can see here the RestController is able to have the JwtUserDetails object injected directly as an argument to the request method itself. The @AuthenticationPrincipal annotation allows the application code to easily get access to the token, to extract the scopes, and to return them as a message.

A proper RS implementation would likely perform real work at this stage but, again, as this is just a sample, it’s enough to show that we’re able to access information from the JWT inside the app.

A word or two on keys and keystores

This sample uses both SSL and signed ID tokens (as part of the OpenID Connect flows). As such, we end up having to play with SSL certificates and keys.

We have quite a few of them being used by the five servers:

  1. Keystores used by the OpenID Provider server:
    • OpenID Connect KeyStore: Contains the private key used by the provider to sign the id_tokens returned to relying parties.
    • OpenID Provider KeyStore: Contains the private key used by the provider to protect its SSL communications.
  2. Keystores used by the relying party servers:
    • Relying Party KeyStore: Contains the private key used by the relying party to protect its SSL communications. Also used to sign each signed-jwt created to be sent to the RS.
    • OpenID Provider TrustStore: Contains the public key used by the relying party to verify the signatures on the id_token‘s from the OpenID provider.
    • Relying Party TrustStore: Contains the public keys for the relying service SSL, and for the OpenID provider SSL.
  3. Keystores used by the relying service servers:
    • Relying Service KeyStore: Contains the private key used to protect its SSL communications.
    • Relying Service TrustStore: Contains the public key for the OP SSL, allowing the app to communicate with the OP. And contains the public key used by the RS to verify the signatures of the signed-jwt created by the RP.

For simplicity’s sake in the sample, many of these keystores are the same. It makes no difference to the validity sample if the SSL certificate for the OP happens to also be the same one we use to sign the JWTs between the RP and RS. In a real deployment, many of these will likely be different. It’s only important to ensure that the truststores for the appropriate places have certificates that allow them to trust the output of the corresponding service.

Running the sample

The full project with sample code is available in GitHub at https://github.com/WASdev/sample.microservices.security.jwt. The sample uses Maven to build the keystores. You can deploy the sample using Docker or remotely to IBM Cloud using Cloud Foundry apps.

Check the project readme for the best up-to-date guide on running the sample.

8 comments on"Using signed JSON Web Tokens (JWTs) to secure microservices"

  1. Denis Forveille April 16, 2018

    Excellent article! Thanks
    Q: In case of protecting JAX_RS apps, is there a reason to use a standard @WebFilter instead of using the “more JAX_RS way” by using a class that implements ContainerRequestFilter with an annotation that is added to each JAX_RS endpoint we want to protect ?
    @Secured
    @Provider
    @Priority(Priorities.AUTHENTICATION)
    public class SecuredFilter implements ContainerRequestFilter {
    public void filter(ContainerRequestContext requestContext) throws IOException {…}
    —-
    @javax.ws.rs.NameBinding
    @Retention(RUNTIME)
    @Target({TYPE, METHOD})
    public @interface Secured {}
    —–
    @GET
    @Secured
    public Response mySecuredMethod() {…}

  2. Ernani Joppert July 24, 2016

    Doesn’t openidConnectClient validate the token with the certificate existing in indicated alias in trust store? I thought so.
    The same goes with WAS Full Profile where signVerifyAlias can be used to map a certificate alias in CellDefaultTrustStore, isn’t it?
    BTW, this post provides a lot of great tips to validate the id_token with its given trust store and certificate alias programatically, but is this really required? I’ve been new to openid connect and I also have heard about the term downstream delegation, which in essence sends the authorization token and refresh token to a 3rd party service, but, is the id_token also possible to be sent donwstream? Is this the reason why it is required to perform its signature validation again? Thanks a lot!

    • Ernani Joppert July 24, 2016

      It seems I’ve missed reading this section {Propagating identity using signed-JWTs}, it outlines identity propagation like a blueberry loaded cheescake…it seems identity propagation is the word to use instead of downstream delegation. Sorry for my miss!

  3. […] Using signed JSON Web Tokens (JWTs) to secure microservices builds on the previous article to explain how to use Signed JWTs to propagate identity instead of access tokens. […]

  4. Great post Ozzy!

  5. […] by fadar [link] [3 comments] Full article:Secure Microservices with signed JSON Web Tokens […]

  6. Stephan Knitelius November 12, 2015

    Good post, can you please fix the escaped characters in the xml.

Join The Discussion

Your email address will not be published. Required fields are marked *