HTTPS SSH

ASAP Authentication - Java

A library that creates and verifies JSON Web Tokens (JWT) for service to service authentication purposes using the Atlassian Service Authentication Protocol (ASAP).

Atlassian S2S Authentication Protocol (ASAP) - Specification

Bamboo build plan (only for releases, branch builds use Pipelines)

JIRA project

Making arbitrary curl requests from the command line

scripts/jwtexec.groovy is a portable Groovy script which lets you easily generate JWT Authorization headers. You can copy just this single file to any server. It will fetch its dependencies at runtime.

The script has builtin help, so run it and see! Here's one simple example:

groovy -Djwt.audience=resource-server -Djwt.issuer=client jwtexec.groovy curl -X POST http://localhost:8080/resource

Development Requirements

  • Java 1.8
  • mvn 3.x

Setting up a development environment

  1. Clone the repository
  2. mvn clean verify to build and run tests (unit and integration)

Running the Server

First install the libraries: mvn install.

If you want to run a development server, use mvn -Pserver -pl examples -nsu and point your client to http://localhost:8080/

To customize the audience and/or base url property of the resource server:

mvn -Pserver -Dasap.resource.server.audience=my-resource-server \
             -Dasap.public.key.repo.url=https://s3-ap-southeast-2.amazonaws.com/keymaker.syd.dev.atlassian.io/ \
             -Dasap.resource.server.authorized.subjects=issuer1,issuer2,issuer3 \
             -pl examples -nsu

If you want to debug the server

export MAVEN_OPTS="-Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,address=5005,server=y,suspend=n"

Note: * The default audience, port and other configuration for the test server are configured SimpleServerRunner.java

Running the Client

First install the libraries: mvn install.

If you want to run a simple client for development, use mvn -Pclient -pl examples -nsu

To customize the client properties, set the relevant system properties

mvn -Pclient -Dasap.client.issuer=issuer1 \
             -Dasap.client.keyId=issuer1/apikey \
             -Dasap.client.audience=my-resource-server \
             -Dasap.client.privateKey=file:///Path/To/Private/Key/ \
             -Dasap.client.resource.server.url=http://localhost:8080 \
             -pl examples -nsu

In the above example, the private key is expected to be located at /Path/To/Private/Key/myclient/apikey

Note: For client authentication to be successful with a resource server, the corresponding public key must exist in the key repository under myclient/apikey

Generating key pairs

For your convenience, there is a simple utility called KeyGen in the test folder. Run it to generate a pair of RSA keys and a pair of EC keys. RSA keys are to be used with the RSxxx and PSxxx families of JWA Algorithms. EC keys are to be used with the ECxxx familiy of JWA Algorithms. The keys are written as PEM files in the current directory.

Alternatively, you can use OpenSSL from the command line to generate the key pairs:

# Generate private key
$ openssl genrsa -out private.pem 2048

# Generate corresponding public key
$ openssl rsa -in private.pem -outform PEM -pubout -out public.pem

Benchmarking

If you want to run a sample benchmark, use mvn -Pbenchmark

To customize the benchmark properties (e.g. to use a differnt algorithm or key) please override the system properties accordingly.

Releasing

Follow Semantic Versioning (SemVer) in pom.xml for project artifacts.

To release, run the manual "Release" stage on the Bamboo build plan for your build.

Usage

Maven dependencies

The library is split in many modules. Most applications will require asap-common and at least one of the client or server modules. For instance, to add the dependency on asap-common using Maven, add the following to the pom.xml of your project:

    <dependencies>
        <dependency>
            <groupId>com.atlassian.asap</groupId>
            <artifactId>asap-common</artifactId>
            <version>${asap.version}</version>
        </dependency>
    </dependencies>

Replace ${asap.version} with the most recent version of this library.

Find the latest released version of the library at: http://search.maven.org/#search%7Cga%7C1%7Ccom.atlassian.asap

Clients

  • Clients can generate a HTTP Authorization header using the class AuthorizationHeaderGenerator
  • Clients using Jersey can use AsapAuthenticationFilter client filter

Server resource authentication using Jersey

  1. Teach Jersey about the JwtAuth annotation by adding com.atlassian.asap.core.server.jersey to your Jersey packages configuration. In your web.xml this will look like:

    <servlet>
        <servlet-name>MyJerseyServlet</servlet-name>
        <servlet-class>com.sun.jersey.spi.spring.container.servlet.SpringServlet</servlet-class>
        <init-param>
            <param-name>com.sun.jersey.config.property.packages</param-name>
            <param-value>
                com.atlassian.myservice.myjerseyresources
                com.atlassian.asap.core.server.jersey
            </param-value>
        </init-param>
    </servlet>
    
  2. Annotate your Jersey resources to require authentication:

    @GET
    @Path("/myresource")
    public String getMyResource(
        @JwtAuth(authorizedSubjects={"alice", "bob"}) Jwt jwt
    )
    {
        // `jwt` is guaranteed to be non-null
    }
    

Server resource authentication with a servlet filter

Resource Servers may implement authentication of incoming client requests using RequestAuthenticationFilterBean.

Options for configuring the servlet filter:

  1. Spring configuration. See the javadoc for the RequestAuthenticationFilterBean default constructor for more details.
  2. Manual configuration by calling static createDefault method with the necessary parameters.

Server authentication/authorization using Spring Security

Use the asap-server-spring-security artifact. See the integration tests and the Javadoc of AsapAuthenticationProvider.

The service API

Alternative, the asap-service-api and its implementation in asap-service-core may be used to configure the application's client and server configuration globally. This is likely to be a good option for larger applications that need to integrate with several external services rather than just one or two, as it allows them to share a common configuration for the entire application.

  1. Add the Service API to your application's API modules:

    <dependencies>
        <dependency>
            <groupId>com.atlassian.asap</groupId>
            <artifactId>asap-service-api</artifactId>
            <version>${asap.version}</version>
        </dependency>
    </dependencies>
    
  2. Add the Service Core to your application's internal implementations:

    <dependencies>
        <dependency>
            <groupId>com.atlassian.asap</groupId>
            <artifactId>asap-service-core</artifactId>
            <version>${asap.version}</version>
        </dependency>
    </dependencies>
    
  3. Use your product's normal strategy for configuring the major components, which are:

  • AsapConfiguration (see the EnvVarAsapConfiguration and StringValuesAsapConfiguration implementations)
  • JwtClaimsValidator
  • AuthorizationHeaderGenerator (see the DefaultAuthorizationHeaderGenerator implementation)
  • AsapService (see the DefaultAsapService implementation)

Only the AsapService itself needs to be accessible through the application's API.

Testing

Let's say we have a Spring application that has a resource that requires ASAP auth. We might want to write a Spring integration test for this that runs locally (doesn't talk to the actual ASAP servers). Here's roughly how to do that:

  1. In your test/resources, create privatekeys/alice and publickeys/alice directories.

  2. Generate a private and public key as described above using openssl and put it in privatekeys/alice/test-key and publickeys/alice/test-key.

  3. In your Spring test, override the asap.public_key_repository.url property to classpath:///publickeys/. This will make the server part look for public keys in the test/resources/publickeys directory, assuming you use that property name to configure ASAP.

  4. When generating the request, point the header generator to test/resources/privatekeys:

    AuthorizationHeaderGenerator headerGenerator =
            AuthorizationHeaderGeneratorImpl.createDefault(URI.create("classpath:///privatekeys/"))
    Jwt jwt = JwtBuilder.newJwt()
            .audience("your-service")
            .issuer("alice")
            .keyId("alice/test-key")
            .build();
    String value = headerGenerator.generateAuthorizationHeader(jwt);
    

Contributors

Pull requests, issues and comments welcome. For pull requests:

  • Add tests for new features and bug fixes
  • Follow the existing style
  • Separate unrelated changes into multiple pull requests

See the existing issues for things to start contributing.

For bigger changes, make sure you start a discussion first by creating an issue and explaining the intended change.

Atlassian requires contributors to sign a Contributor License Agreement, known as a CLA. This serves as a record stating that the contributor is entitled to contribute the code/documentation/translation to the project and is willing to have it used in distributions and derivative works (or is willing to transfer ownership).

Prior to accepting your contributions we ask that you please follow the appropriate link below to digitally sign the CLA. The Corporate CLA is for those who are contributing as a member of an organization and the individual CLA is for those contributing as an individual.

License

Copyright (c) 2017 Atlassian and others. Apache 2.0 licensed, see LICENSE.txt file.