Search code examples
javajerseygrizzlygoogle-oauth

Using google signin for web sites in a Java application


I've created a self hosted Java applicataion and I would like to use Google sign in to log in into. I followed the follwong example:

https://developers.google.com/identity/sign-in/web/

That of course work, but now I'm getting a little confuse on how I can authorize the calls on the server. In the backend I'm using Grizzly+Jersey.


Solution

  • As described on the Google Sig-In documentation, you can use Google API Client Library for Java in order to check the authentication token on server side.

    Client side

    After a user successfully signs in, get the user's ID token:

    function onSignIn(googleUser) {
        var idToken = googleUser.getAuthResponse().id_token;
        ...
    }
    

    And send the idToken to the server in every request using the standard HTTP Authorization header.

    Server side

    You can use a filter to perform authentication and/or authorization.

    To bind filters to your REST endpoints, JAX-RS provides the meta-annotation @NameBinding and can be used as following:

    @NameBinding
    @Retention(RUNTIME)
    @Target({TYPE, METHOD})
    public @interface Secured { }
    

    The @Secured annotation will be used to decorate a filter class, which implements ContainerRequestFilter, allowing you to handle the request, get and validate the token.

    The ContainerRequestContext helps you to extract information from the HTTP request.

    The @Provider annotation marks an implementation of an extension interface that should be discoverable by JAX-RS/Jersey runtime during a provider scanning phase.

    @Secured
    @Provider
    @Priority(Priorities.AUTHENTICATION)
    public class AuthenticationFilter implements ContainerRequestFilter {
    
        @Override
        public void filter(ContainerRequestContext requestContext) throws IOException {
    
            // Get the token header from the HTTP Authorization request header
            String token = 
                requestContext.getHeaderString(HttpHeaders.AUTHORIZATION);
    
            // Check if the token is present
            if (token == null || token.isEmpty()) {
                throw new NotAuthorizedException("Token must be provided");
            }
    
            // Validate the token
            validateToken(token);
        }
    
        private void validateToken(String token) {
    
            GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier
                .Builder(new NetHttpTransport(), new GsonFactory())
                    .setAudience(Arrays.asList(CLIENT_ID))
                    .build();
    
            GoogleIdToken idToken = verifier.verify(token);
            if (idToken != null) {
                Payload payload = idToken.getPayload();
                System.out.println("User ID: " + payload.getSubject());
            } else {
                throw new NotAuthorizedException("Invalid token.");
            }
        }
    }
    

    To bind the filter to your endpoints methods or classes, annotate them with the @Secured annotation created above. For the methods and/or classes which are annotated, the filter will be executed.

    @Path("/example")
    public class MyEndpoint {
    
        @GET
        @Path("{id}")
        @Produces("application/json")
        public Response myUnsecuredMethod(@PathParam("id") Long id) {
            // This method is not annotated with @Secured
            // The security filter won't be executed before invoking this method
            ...
        }
    
        @DELETE
        @Secured
        @Path("{id}")
        @Produces("application/json")
        public Response mySecuredMethod(@PathParam("id") Long id) {
            // This method is annotated with @Secured
            // The security filter will be executed before invoking this method
            ...
        }
    }
    

    In the example above, the security filter will be executed only for mySecuredMethod(Long) because it's annotated with @Secured.