Nutzer-Authentifizierung bei REST mit Jersey

Wenn man mit Jersey ab der Version 2.0 eine REST Schnittstelle aufbaut, hat man für die Authentifizierung eines Nutzers eine neue schöne Möglichkeit, mit Annotationen zu arbeiten. Hier sieht man als Beispiel zwei REST Endpunkte, bei beiden wird über eine neue Annotation UserAuthorization definiert, dass eine erfolgreiche Authentifizierung des Nutzers notwendig ist.

1
2
3
4
5
6
7
8
9
10
11
12
13
@Path( "/myapp" )
public class MyResource 
{
    @POST
    @UserAuthorization
    public Response postSomething( )
    {}
 
    @GET
    @Produces( MediaType.APPLICATION_JSON )
    @UserAuthorization
    public List<Itinerary> getSomething( )
    {}

Mit Hilfe der neuen Java Annotation UserAuthorization wird festgelegt, dass vor jedem Aufruf dieser REST Endpunkte bzw. der Methode der mit der Annotation verbundene Code ausgeführt werden soll. Diese Annotation muss zunächst definiert werden:

1
2
3
4
5
@NameBinding
@Target( { ElementType.TYPE, ElementType.METHOD } )
@Retention( value = RetentionPolicy.RUNTIME )
public @interface UserAuthorization
{}

Die Retention Policy gibt an, dass die Annotation zur Laufzeit (also als Teil der Java Class Dateien) verfügbar sein soll. Nun muss noch der Filter definiert werden, der zu dieser Annotation gehört:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@Provider
@UserAuthorization
public class AuthFilter implements ContainerRequestFilter
{
   @Override
   public void filter( ContainerRequestContext requestContext ) throws IOException
   {
      final String authHeader = requestContext.getHeaderString( HttpHeaders.AUTHORIZATION );
      if ( authHeader == null )
      {
         requestContext.abortWith( Response.status( Status.UNAUTHORIZED )
            .header( HttpHeaders.WWW_AUTHENTICATE, "Basic realm=\"realm\"" )
            .entity( "Page requires login." ).build( ) );
      }
      else
      {
         final String withoutBasic = authHeader.replaceFirst( "[Bb]asic ", "" );
	 final String userColonPass = Base64.decodeAsString( withoutBasic );
	 final String[ ] asArray = userColonPass.split( ":" );
 
	 if ( asArray.length == 2 )
	 {
            final String username = asArray[ 0 ];
            final String password = asArray[ 1 ];
 
            // code to check username and password
         }
	 else
	 {
            // abort with error
         }
      }
   }
}

Die Klasse muss mit der soeben definierten Annotation versehen werden, damit die Verbindung zwischen Annotation und auszuführendem Code klar ist. Schließlich muss man den die Klasse AuthFilter noch registrieren, was bei Jersey ab Version 2.0 nicht mehr über die web.xml erfolgt, sondern über die zentrale Konfiguration im Quelltext:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@ApplicationPath( "api" )
public class RestService extends Application
{
	public RestService( )
	{}
 
	@Override
	public Set<Class<?>> getClasses( )
	{
		final Set<Class<?>> returnValue = new HashSet<Class<?>>( );
 
		returnValue.add( MyResource.class );
                // add more resource classes
		returnValue.add( AuthFilter.class );
 
		return returnValue;
	}
}