DropWizard Auth by Example
我试图了解DropWizard中的身份validation和授权是如何工作的。 我已经阅读了他们的auth指南以及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 { @Override public Optional 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(new SimpleAuthenticator(), "SUPER SECRET STUFF")); }
然后在资源方法中:
@GET @Path("address/{address_id}") @Override public Address getAddress(@Auth @PathParam("address_id") Long id) { addressDao.getAddressById(id); }
我认为我已经为基本身份validation正确配置了一半,但没有理解SimpleCredential
和SimplePrincipal
扮演的角色。 特别:
- 如何从Jersey / JAX-RS客户端设置基本身份validation用户名/密码?
-
SimpleCredential
和SimplePrincipal
在基本身份validation中扮演什么角色? 我是否需要向它们或其他类添加任何内容以进行基本身份validation工作,以便唯一有效的用户名是simple_user
,唯一有效的密码是12345
? - 如何通过
SimplePrincipal
强制执行访问/ authroization /角色? 或者是Web服务不存在授权的概念?
问题1:
基本身份validation协议规定客户端请求应具有表头forms的标头
Authorization: Basic Base64Encoded(username:password)
其中Base64Encoded(username:password)
是Base64Encoded(username:password)
的实际Base64编码字符串。 例如,如果我的用户名和密码是peeskillet:pass
,则标题应该发送为
Authorization: Basic cGVlc2tpbGxldDpwYXNz
话虽如此,Jersey客户端(假设1.x)有一个HTTPBasicAuthFilter
,它是一个客户端filter,它将为我们处理编码部分。 所以客户端请求可能看起来像
Client client = Client.create(); WebResource resource = client.resource(BASE_URI); client.addFilter(new HTTPBasicAuthFilter("peeskillet", "pass")); String response = resource.get(String.class);
这就是我们需要使用授权标头进行简单的GET请求。
问题2:
SimpleCredential:对于Basic auth,我们实际上需要使用BasicCredentials
,而不是我们自己的凭据。 基本上,请求将通过BasicAuthProvider
。 提供程序将解析Authorization标头并从解析的用户名和密码创建BasicCredentials
对象。 一旦处理完成, BasicCredentials
将传递给我们的SimpleAuthenticator
。 我们使用这些凭据来validation用户身份。
SimplePrincipal:基本上是我们用来授权客户端的。 从身份validation过程中,我们可以构建一个主体,稍后将用于授权(请参阅问题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 { @Override public Optional 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); } }
我稍微修改了SimplePrincipal
类,并创建了一个简单的Roles
类。
public class SimplePrincipal { private String username; private List roles = new ArrayList<>(); public SimplePrincipal(String username) { this.username = username; } public List 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似乎认为授权应该在资源类中发生(我忘记了我在哪里阅读它,但我相信他们的论点是可测试性)。 我们在SimpleAuthenticator
创建的SimplePrincial
所发生的SimpleAuthenticator
是它可以使用@Auth
注释注入到我们的资源方法中。 我们可以使用SimplePrincipal
进行授权。 就像是
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( new SimpleAuthenticator(), "Basic Example Realm") );
以及我之前发布的客户凭据,当我们发出请求时,我们应该返回一个
{"Hello": "peeskillet"}
另外应该提到单独的基本身份validation不安全,建议通过SSL完成
见相关:
- DropWizard Auth领域
- 使用DropWizard的SSL
UPDATE
几件事:
-
对于Dropwizard 0.8.x,Basic Auth的配置有所改变。 你可以在这里看到更多 。 一个简单的例子就是
SimpleAuthenticator auth = new SimpleAuthenticator(); env.jersey().register(AuthFactory.binder( new BasicAuthFactory<>(auth,"Example Realm",SimplePrincipal.class)));
-
有关
AuthenticationException
推荐用法,请参阅上面的链接