DropWizard Auth by Example

我试图了解身份验证和授权在DropWizard中是如何工作的。我已经阅读了他们的身份验证指南以及GitHub上的dropwizard-security项目,但感觉我仍然缺少一些重要的概念。

public class SimpleCredential {
    private String password;

    public SimpleCredential(String password) {
        super();

        this.password = password;
    }
}

public class SimplePrincipal {
    pivate String username;

    public SimplePrincipal(String username) {
        super();

        this.username = username;
    }
}

public class SimpleAuthenticator implements Authenticator<SimpleCredential, SimplePrincipal> {
    @Override
    public Optional<SimplePrincipal> authenticate(SimpleCredential credential) throws AuthenticationException {
        if(!"12345".equals(credential.getPassword())) {
            throw new AuthenticationException("Sign in failed.");
        }

        Optional.fromNullable(new SimplePrincipal("simple_user"));
    }
}

然后在我的子类中:Application

@Override
public void run(BackendConfiguration configuration, Environment environment) throws Exception {
    environment.jersey().register(new BasicAuthProvider<SimplePrincipal>(new SimpleAuthenticator(), "SUPER SECRET STUFF"));
}

然后在资源方法中:

@GET
@Path("address/{address_id}")
@Override
public Address getAddress(@Auth @PathParam("address_id") Long id) {
    addressDao.getAddressById(id);
}

我认为我对基本身份验证进行了正确的一半配置,但不了解其作用和作用。具体说来:SimpleCredentialSimplePrincipal

  1. 如何从泽西岛/JAX-RS 客户端设置基本身份验证用户名/密码?
  2. 基本身份验证的作用和作用是什么?我是否需要向它们或其他类添加任何内容以使基本身份验证工作,以便唯一有效的用户名是并且唯一有效的密码是?SimpleCredentialSimplePrincipalsimple_user12345
  3. 如何通过以下方式强制实施访问/授权/角色?还是Web服务不存在授权的概念?SimplePrincipal

答案 1

问题 1:

基本身份验证协议规定客户端请求应具有以下形式的标头

Authorization: Basic Base64Encoded(username:password)

其中 是 的实际 Base64 编码字符串。例如,如果我的用户名和密码是 ,则标头应作为Base64Encoded(username:password)username:passwordpeeskillet:pass

Authorization: Basic cGVlc2tpbGxldDpwYXNz

话虽如此,泽西客户端(假设1.x)有一个,这是一个客户端过滤器,它将为我们处理编码部分。因此,客户端请求可能看起来像这样HTTPBasicAuthFilter

Client client = Client.create();
WebResource resource = client.resource(BASE_URI);
client.addFilter(new HTTPBasicAuthFilter("peeskillet", "pass"));
String response = resource.get(String.class);

这就是我们使用授权标头发出简单 GET 请求所需的全部内容。

问题 2:

简单信:对于基本身份验证,我们实际上需要使用 ,而不是我们自己的凭据。基本上,请求将通过 .提供程序将解析授权标头,并根据解析的用户名和密码创建对象。一旦处理完成,将传递给我们的 ' 。我们使用这些凭据对用户进行身份验证。BasicCredentialsBasicAuthProviderBasicCredentialsBasicCredentialsSimpleAuthenticator

简单原则:基本上是我们将用于授权客户端的内容。从身份验证过程中,我们可以构建一个主体,稍后将用于授权(请参阅问题 3)。因此,示例可能如下所示

import com.google.common.base.Optional;
import io.dropwizard.auth.AuthenticationException;
import io.dropwizard.auth.Authenticator;
import io.dropwizard.auth.basic.BasicCredentials;

public class SimpleAuthenticator implements Authenticator<BasicCredentials,
                                                          SimplePrincipal> {
    @Override
    public Optional<SimplePrincipal> authenticate(BasicCredentials credentials)
            throws AuthenticationException {

        // Note: this is horrible authentication. Normally we'd use some
        // service to identify the password from the user name.
        if (!"pass".equals(credentials.getPassword())) {
            throw new AuthenticationException("Boo Hooo!");
        }

        // from some user service get the roles for this user
        // I am explicitly setting it just for simplicity
        SimplePrincipal prince = new SimplePrincipal(credentials.getUsername());
        prince.getRoles().add(Roles.ADMIN);

        return Optional.fromNullable(prince);
    }
}

我稍微修改了一下类,并创建了一个简单的类。SimplePrincipalRoles

public class SimplePrincipal {

    private String username;
    private List<String> roles = new ArrayList<>();

    public SimplePrincipal(String username) {
        this.username = username;
    }

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

    public boolean isUserInRole(String roleToCheck) {
        return roles.contains(roleToCheck);
    }

    public String getUsername() {
        return username;
    }
}

public class Roles {
    public static final String USER = "USER";
    public static final String ADMIN = "ADMIN";
    public static final String EMPLOYEE = "EMPLOYEE";
}

问题 3:

有些人可能更喜欢有一个额外的过滤层用于授权,但Dropwizard似乎有一种固执己见的观点,即授权应该发生在资源类中(我忘记了我在哪里阅读它,但我相信他们的论点是可测试性)。我们在 中创建的 会发生什么情况是,通过使用注释,它可以注入到我们的资源方法中。我们可以使用 授权。类似的东西SimplePrincialSimpleAuthenticator@AuthSimplePrincipal

import dropwizard.sample.helloworld.security.Roles;
import dropwizard.sample.helloworld.security.SimplePrincipal;
import io.dropwizard.auth.Auth;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

@Path("/simple")
public class SimpleResource {

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public Response getResponse(@Auth SimplePrincipal principal) {
        if (!principal.isUserInRole(Roles.ADMIN)) {
            throw new WebApplicationException(Response.Status.FORBIDDEN);
        }
        return Response.ok(
                "{\"Hello\": \"" + principal.getUsername() + "\"}").build();
    }
}

因此,通过此配置将其全部放在一起

environment.jersey().register(new BasicAuthProvider<SimplePrincipal>(
                                            new SimpleAuthenticator(), 
                                            "Basic Example Realm")
);

和我之前发布的客户端凭据,当我们提出请求时,我们应该得到一个返回

{"Hello": "peeskillet"}

另外应该提到的是,仅靠基本身份验证是不安全的,建议通过SSL完成


请参阅相关内容:


更新

几件事:

  • 对于 Dropwizard 0.8.x,Basic Auth 的配置发生了一些变化。您可以在此处查看更多内容。一个简单的例子是

    SimpleAuthenticator auth = new SimpleAuthenticator();
    env.jersey().register(AuthFactory.binder(
            new BasicAuthFactory<>(auth,"Example Realm",SimplePrincipal.class)));
    
  • 请参阅上面的链接,了解建议的用法AuthenticationException


答案 2

推荐