【问题标题】:Creating JSON Web Tokens through Basic Authentication endpoint? Dropwizard通过基本身份验证端点创建 JSON Web 令牌?下拉向导
【发布时间】:2017-11-02 17:06:41
【问题描述】:

使用 Dropwizard 1.2.0 和 Dropwizard JWT library,我正在尝试从名为 /token 的 API 端点创建 json Web 令牌

此端点要求客户端使用基本身份验证方法传递用户名和密码。如果成功,响应将包含 JSON Web 令牌。

校长

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;
}
}

身份验证器

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();
}
}

资源/控制器

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"));
}

应用/配置

    @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()));

当我尝试向/shepherd/token 发出请求时,我没有收到基本身份验证提示,而是收到带有

的 HTTP 401 响应

访问此资源需要凭据。

如何让控制器提示输入用户名和密码并在成功时生成 JWT?

【问题讨论】:

    标签: java jwt dropwizard


    【解决方案1】:

    我在我的项目中使用 https://github.com/jwtk/jjwt 实现了 JWT 令牌,但该解决方案很容易应用于另一个库。诀窍是使用不同的身份验证器。

    这个答案不适合Dropwizard JWT Library,但可以很好地为 Dropwizard 提供 JWT :)

    一、应用:

    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()))));
    

    请注意,配置类必须包含配置设置:

    String jwsSecretKey;
    

    这里,TokenResource 是令牌提供资源,HelloResource 是我们的测试资源。 User 是校长,像这样:

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

    还有一个类用于传递 JWT 令牌:

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

    TokenResource 为密码为“test”的用户“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);
    }
    

    HelloResource 只是回响用户:

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

    JWTCredentialAuthFilter 为两种身份验证方案提供凭据:

    @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 是提供 JWT 凭据的时间:

    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 是在不存在凭据时,给代码一个空用户:

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

    UserAuthorizer 允许“ANY”角色,只要用户不为空:

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

    如果一切顺利,

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

    会给你类似的东西:

    eyJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1MDk3MDYwMjYsInN1YiI6InRlc3QifQ.ZrRmWTUDpaA6JlU4ysIcFllxtqvUS2OPbCMJgyou_tY
    

    还有这个查询

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

    将失败

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

    (顺便说一句,URL 中的“test”是用户名,post 数据中的“test”是密码。就像基本身份验证一样简单,可以为 CORS 配置。)

    和请求

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

    会显示

    hello user "test"
    

    同时

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

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

    会导致

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

    【讨论】:

    • 谢谢,但是这个库不稳定,它只有 0.9.0 版 :(
    • 两件事:不要让版本误导你,我知道Dropwizard在版本1之前已经用于许多不同的生产环境。第二,Dropwizard JWT库使用相同的库。检查它的 pom.xml。
    • 谢谢,我试过你的解决方案,但它说找不到 JWTCredentials 类,我必须这样做还是由另一个库提供?
    • 我的错。编辑了答案以包含它。这只是一个带有 jwtToken 属性的 POJO。
    • 感谢您的帮助,是否可以使用 TokenResource 的基本身份验证而不是 DefaultJWTAuthenticator?我尝试将BasicCredentialAuthFilter 添加到该身份验证器列表中,但在尝试使用该端点获取令牌时,Dropwizard 没有提示登录。有什么想法吗?
    【解决方案2】:

    您的配置中缺少这一行。

    environment.jersey().register(new AuthValueFactoryProvider.Binder&lt;&gt;(User.class));

    【讨论】:

      猜你喜欢
      • 2016-09-11
      • 1970-01-01
      • 2015-10-14
      • 2020-06-29
      • 2021-01-25
      • 2017-04-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多