2017-11-02 1 views
1

Mit Dropwizard 1.2.0 und Dropwizard JWT library, ich json Web-Token von einem API-Endpunkt zu schaffen versuchen /tokenErstellen von JSON-Web-Tokens über den Standardauthentifizierungs-Endpunkt? Dropwizard

Dieser Endpunkt den Client erfordert aufgerufene Methode einen Benutzername und ein Passwort übergeben, mit der Standardauthentifizierung. Bei Erfolg enthält die Antwort ein JSON-Web-Token.

Haupt

public class ShepherdAuth implements JwtCookiePrincipal { 

private String name; 
private Set<String> roles; 

public ShepherdAuth(String name, Set<String> roles) { 
    this.name = checkNotNull(name, "User name is required"); 
    this.roles = checkNotNull(roles, "Roles are required"); 
} 

@Override 
public boolean isPersistent() { 
    return false; 
} 

@Override 
public boolean isInRole(final String s) { 
    return false; 
} 

@Override 
public String getName() { 
    return this.name; 
} 

@Override 
public boolean implies(Subject subject) { 
    return false; 
} 

public Set<String> getRoles() { 
    return roles; 
} 
} 

Authenticator

public class ShepherdAuthenticator implements Authenticator<BasicCredentials, ShepherdAuth> { 

private static final Map<String, Set<String>> VALID_USERS = ImmutableMap.of(
     "guest", ImmutableSet.of(), 
     "shepherd", ImmutableSet.of("SHEPHERD"), 
     "admin", ImmutableSet.of("ADMIN", "SHEPHERD") 
); 

@Override 
public Optional<ShepherdAuth> authenticate(BasicCredentials credentials) throws AuthenticationException { 
    if (VALID_USERS.containsKey(credentials.getUsername()) && "password".equals(credentials.getPassword())) { 
     return Optional.of(new ShepherdAuth(credentials.getUsername(), VALID_USERS.get(credentials.getUsername()))); 
    } 
    return Optional.empty(); 
} 
} 

Ressourcen/Regler

public class ShepherdController implements ShepherdApi { 

public ShepherdController() { 
} 

@PermitAll 
@GET 
@Path("/token") 
public ShepherdAuth auth(@Auth final BasicCredentials user) { 
    return new ShepherdAuth(user.getUsername(), 
     ImmutableSet.of("SHEPHERD")); 
} 

App/Config

@Override 
public void run(final ShepherdServiceConfiguration configuration, 
       final Environment environment) { 

    final ShepherdController shepherdController = new ShepherdController(); 

    // app authentication 
    environment.jersey().register(new AuthDynamicFeature(new BasicCredentialAuthFilter.Builder<ShepherdAuth>() 
      .setAuthenticator(new ShepherdAuthenticator()) 
      .setAuthorizer(new ShepherdAuthorizer()) 
      .setRealm(configuration.getName()) 
      .buildAuthFilter())); 

Wenn ich versuche, eine Anfrage an /shepherd/token ich stattdessen keine Aufforderung für grundlegende auth bekommen zu machen, erhalte ich eine HTTP-401-Antwort mit

Credentials erforderlich sind, um den Zugang diese Ressource.

Wie bekomme ich den Controller zur Eingabe von Benutzername und Passwort und generiert eine JWT bei Erfolg?

Antwort

2

Ich implementierte JWT-Token in meinem Projekt mit https://github.com/jwtk/jjwt, aber die Lösung wird problemlos auf eine andere Bibliothek angewendet werden. Der Trick besteht darin, verschiedene Authentifikatoren zu verwenden.

Diese Antwort nicht für Dropwizard JWT Library geeignet ist, aber es erfüllt seinen guten Job JWT für Dropwizard der Bereitstellung :)

Zunächst wird die Anwendung:

environment.jersey().register(new TokenResource(configuration.getJwsSecretKey())); 
environment.jersey().register(new HelloResource()); 
environment.jersey().register(RolesAllowedDynamicFeature.class); 
environment.jersey().register(new AuthValueFactoryProvider.Binder<>(User.class)); 
environment.jersey() 
    .register(
     new AuthDynamicFeature(
      new ChainedAuthFilter<>(
       Arrays 
        .asList(
         new JWTCredentialAuthFilter.Builder<User>() 
          .setAuthenticator(
           new JWTAuthenticator(configuration.getJwsSecretKey())) 
          .setPrefix("Bearer").setAuthorizer(new UserAuthorizer()) 
          .buildAuthFilter(), 
         new JWTDefaultCredentialAuthFilter.Builder<User>() 
          .setAuthenticator(new JWTDefaultAuthenticator()) 
          .setAuthorizer(new UserAuthorizer()).setRealm("SUPER SECRET STUFF") 
          .buildAuthFilter())))); 

Bitte beachten Sie, dass die Konfigurationsklasse eine Konfigurationseinstellung enthalten muss :

String jwsSecretKey; 

Hier ist die TokenResource Zuführen der Token-Ressource, und die i HelloResource s unsere Testressource.User ist das wichtigste, wie folgt aus:

public class User implements Principal { 
    private String name; 
    private String password; 
    ... 
} 

Und es gibt eine Klasse der JWT-Token für die Kommunikation:

public class JWTCredentials { 
    private String jwtToken; 
    ... 
} 

TokenResource bietet Tokens für einen Benutzer "test" mit Passwort "test":

@POST 
@Path("{user}") 
@PermitAll 
public String createToken(@PathParam("user") String user, String password) { 
    if ("test".equals(user) && "test".equals(password)) { 
    SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; 
    long nowMillis = System.currentTimeMillis(); 
    Date now = new Date(nowMillis); 
    byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(this.secretKey); 
    Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName()); 
    JwtBuilder builder = Jwts.builder().setIssuedAt(now).setSubject("test") 
     .signWith(signatureAlgorithm, signingKey); 
    return builder.compact(); 
    } 
    throw new WebApplicationException(Response.Status.UNAUTHORIZED); 
} 

Und die HelloResource Echos nur den Benutzer zurück:

@GET 
@RolesAllowed({"ANY"}) 
public String hello(@Auth User user) { 
    return "hello user \"" + user.getName() + "\""; 
} 

JWTCredentialAuthFilter liefert Berechtigungsnachweise für beide Authentifizierungsschemata:

@Priority(Priorities.AUTHENTICATION) 
public class JWTCredentialAuthFilter<P extends Principal> extends AuthFilter<JWTCredentials, P> { 

    public static class Builder<P extends Principal> 
     extends AuthFilterBuilder<JWTCredentials, P, JWTCredentialAuthFilter<P>> { 

    @Override 
    protected JWTCredentialAuthFilter<P> newInstance() { 
     return new JWTCredentialAuthFilter<>(); 
    } 
    } 

    @Override 
    public void filter(ContainerRequestContext requestContext) throws IOException { 
    final JWTCredentials credentials = 
    getCredentials(requestContext.getHeaders().getFirst(HttpHeaders.AUTHORIZATION)); 
    if (!authenticate(requestContext, credentials, "JWT")) { 
     throw new WebApplicationException(
      this.unauthorizedHandler.buildResponse(this.prefix, this.realm)); 
    } 
    } 

    private static JWTCredentials getCredentials(String authLine) { 
    if (authLine != null && authLine.startsWith("Bearer ")) { 
     JWTCredentials result = new JWTCredentials(); 
     result.setJwtToken(authLine.substring(7)); 
     return result; 
    } 
    return null; 
    } 
} 

JWTAuthenticator ist, wenn JWT Anmeldeinformationen bereitgestellt:

public class JWTAuthenticator implements Authenticator<JWTCredentials, User> { 

    private String secret; 

    public JWTAuthenticator(String jwtsecret) { 
    this.secret = jwtsecret; 
    } 

    @Override 
    public Optional<User> authenticate(JWTCredentials credentials) throws AuthenticationException { 
    try { 
     Claims claims = Jwts.parser().setSigningKey(DatatypeConverter.parseBase64Binary(this.secret)) 
     .parseClaimsJws(credentials.getJwtToken()).getBody(); 
     User user = new User(); 
     user.setName(claims.getSubject()); 
     return Optional.ofNullable(user); 
    } catch (@SuppressWarnings("unused") ExpiredJwtException | UnsupportedJwtException 
     | MalformedJwtException | SignatureException | IllegalArgumentException e) { 
     return Optional.empty(); 
    } 
    } 
} 

JWTDefaultAuthenticator ist, wenn keine Anmeldeinformationen vorhanden sind, wird der Code ein leeres Benutzer geben:

public class JWTDefaultAuthenticator implements Authenticator<JWTCredentials, User> { 

    @Override 
    public Optional<User> authenticate(JWTCredentials credentials) throws AuthenticationException { 
    return Optional.of(new User()); 
    } 
} 

UserAuthorizer erlaubt die "ANY" Rolle, solange der Benutzer nicht null ist:

public class UserAuthorizer implements Authorizer<User> { 
    @Override 
    public boolean authorize(User user, String role) { 
    return user != null && "ANY".equals(role) 
    } 
} 

Wenn alles gut geht,

curl -s -X POST -d 'test' http://localhost:8080/token/test 

geben Ihnen so etwas wie:

eyJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1MDk3MDYwMjYsInN1YiI6InRlc3QifQ.ZrRmWTUDpaA6JlU4ysIcFllxtqvUS2OPbCMJgyou_tY 

und diese Abfrage

curl -s -X POST -d 'xtest' http://localhost:8080/token/test 

wird scheitern mit

{"code":401,"message":"HTTP 401 Unauthorized"} 

(BTW, "test" in der URL der Namen Benutzer und "test" in den Post-Daten ist das Passwort. So einfach wie Grund Auth und kann für CORS konfiguriert werden.)

und die Anfrage

curl -s -X GET -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1MDk3MDYwMjYsInN1YiI6InRlc3QifQ.ZrRmWTUDpaA6JlU4ysIcFllxtqvUS2OPbCMJgyou_tY' http://localhost:8080/hello 

hello user "test" 

während

curl -s -X GET -H 'Authorization: Bearer invalid' http://localhost:8080/hello 

und

curl -s -X GET http://localhost:8080/hello 
zeigen

wird in

{"code":403,"message":"User not authorized."} 
+0

Danke, aber diese Bibliothek ist nicht stabil, es ist nur Version 0.9.0 :( – kaleeway

+0

Zwei Dinge: Lassen Sie sich von der Version nicht irreführen, ich weiß, dass Dropwizard für viele verschiedene Produktionsumgebungen vor Version 1 verwendet wurde. Zweitens verwendet die Dropwizard JWT-Bibliothek die gleiche Bibliothek. Überprüfen Sie die pom.xml davon. –

+0

Danke, ich habe deine Lösung ausprobiert, aber es sagt, dass es die 'JWTCredentials' Klasse nicht finden kann, muss ich das machen oder wird es von einer anderen Bibliothek bereitgestellt? – kaleeway

1

diese Zeile Ihr fehlt in Ihrer Konfiguration führen.

environment.jersey().register(new AuthValueFactoryProvider.Binder<>(User.class));