dropwizard-auth
客户端使用HTTP基本认证或OAuth2的令牌提供身份验证。
Authenticators 验证器
验证器是一个策略类,在给定一组客户端提供的凭证credentials的情况下,可能会返回一个principal(即代表您的服务将执行某些操作的人或实体)。
Authenticators实现了接口,它有一个方法: Authenticator<C, P extends Principal>
public class ExampleAuthenticator implements Authenticator<BasicCredentials, User> {
@Override
public Optional<User> authenticate(BasicCredentials credentials) throws AuthenticationException {
if ("secret".equals(credentials.getPassword())) {
return Optional.of(new User(credentials.getUsername()));
}
return Optional.empty();
}
}
此身份验证器采用基本身份验证凭据,如果客户端提供的密码是secret
,则使用客户User
端提供的用户名对客户端进行身份验证。
如果密码不匹配,Optional
则返回空,表示凭据无效。
警告
对于身份验证服务而言,重要的是不要在错误中提供太多信息。用户名或电子邮件具有帐户这一事实对攻击者而言可能是有意义的,因此该
Authenticator
界面不允许您区分错误的用户名和错误的密码。如果验证者无法检查凭据(例如,您的数据库已关闭),则应该只抛出一个AuthenticationException
。
缓存
由于验证者的后备数据存储可能无法处理高吞吐量(例如,RDBMS或LDAP服务器),因此Dropwizard提供了一个提供缓存的装饰器类:
SimpleAuthenticator simpleAuthenticator = new SimpleAuthenticator();
CachingAuthenticator<BasicCredentials, User> cachingAuthenticator = new CachingAuthenticator<>(
metricRegistry, simpleAuthenticator,
config.getAuthenticationCachePolicy());
Dropwizard可以从配置策略中解析Guava的CacheBuilderSpec
,配置文件如下所示:
authenticationCachePolicy: maximumSize=10000, expireAfterAccess=10m
这将使用LRU策略缓存多达10,000个主体,在10分钟后驱逐过时的条目。
授权者 Authorizer
授权者是一个策略类,在给定主体和角色的情况下,决定是否授予主体访问权限。授权者实现了接口,它有一个方法:Authorizer<P extends Principal>
public class ExampleAuthorizer implements Authorizer<User> {
@Override
public boolean authorize(User user, String role) {
return user.getName().equals("good-guy") && role.equals("ADMIN");
}
}
基本认证
AuthDynamicFeature
与BasicCredentialAuthFilter
和RolesAllowedDynamicFeature
启用HTTP基本身份验证和授权;
需要一个实例的验证器BasicCredentials
。如果您不使用授权,则RolesAllowedDynamicFeature
不需要。
@Override
public void run(ExampleConfiguration configuration,
Environment environment) {
environment.jersey().register(new AuthDynamicFeature(
new BasicCredentialAuthFilter.Builder<User>()
.setAuthenticator(new ExampleAuthenticator())
.setAuthorizer(new ExampleAuthorizer())
.setRealm("SUPER SECRET STUFF")
.buildAuthFilter()));
environment.jersey().register(RolesAllowedDynamicFeature.class);
//If you want to use @Auth to inject a custom Principal type into your resource
environment.jersey().register(new AuthValueFactoryProvider.Binder<>(User.class));
}
OAuth2
AuthDynamicFeature
与OAuthCredentialAuthFilter
和RolesAllowedDynamicFeature
能够承载的OAuth2令牌认证和授权; 需要一个实例的验证器String
。如果您不使用授权,则RolesAllowedDynamicFeature
不需要。
@Override
public void run(ExampleConfiguration configuration,
Environment environment) {
environment.jersey().register(new AuthDynamicFeature(
new OAuthCredentialAuthFilter.Builder<User>()
.setAuthenticator(new ExampleOAuthAuthenticator())
.setAuthorizer(new ExampleAuthorizer())
.setPrefix("Bearer")
.buildAuthFilter()));
environment.jersey().register(RolesAllowedDynamicFeature.class);
//If you want to use @Auth to inject a custom Principal type into your resource
environment.jersey().register(new AuthValueFactoryProvider.Binder<>(User.class));
}
链式工厂Chained Factories
ChainedAuthFilter
可以同时使用各种认证的工厂。
@Override
public void run(ExampleConfiguration configuration,
Environment environment) {
AuthFilter basicCredentialAuthFilter = new BasicCredentialAuthFilter.Builder<>()
.setAuthenticator(new ExampleBasicAuthenticator())
.setAuthorizer(new ExampleAuthorizer())
.setPrefix("Basic")
.buildAuthFilter();
AuthFilter oauthCredentialAuthFilter = new OAuthCredentialAuthFilter.Builder<>()
.setAuthenticator(new ExampleOAuthAuthenticator())
.setAuthorizer(new ExampleAuthorizer())
.setPrefix("Bearer")
.buildAuthFilter();
List<AuthFilter> filters = Lists.newArrayList(basicCredentialAuthFilter, oauthCredentialAuthFilter);
environment.jersey().register(new AuthDynamicFeature(new ChainedAuthFilter(filters)));
environment.jersey().register(RolesAllowedDynamicFeature.class);
//If you want to use @Auth to inject a custom Principal type into your resource
environment.jersey().register(new AuthValueFactoryProvider.Binder<>(User.class));
}
为了使其正常工作,所有链式工厂必须生成相同类型的主体User
。
保护资源
有两种方法可以保护资源。您可以使用以下注释之一标记资源方法:
-
@PermitAll
. 所有经过身份验证的用户都可以访问该方法。 -
@RolesAllowed
. 将为具有指定角色的用户授予访问权限。 -
@DenyAll
. 不会授予任何人访问权限。
注意
您可以在class上使用
@RolesAllowed
,@PermitAll
。方法注释优先于类注释。
或者,您可以使用表示主体的参数进行注释@Auth
。请注意,您必须注册jersey提供商才能使其发挥作用。
environment.jersey().register(new AuthValueFactoryProvider.Binder<>(User.class));
@RolesAllowed("ADMIN")
@GET
public SecretPlan getSecretPlan(@Auth User user) {
return dao.findPlanForUser(user);
}
您还可以通过向方法@Context SecurityContext context
添加参数来访问Principal 。 请注意,这不会自动注册执行身份验证的servlet过滤器。您仍然需要添加一个 @PermitAll
, @RolesAllowed
, 或 @DenyAll
。 但是 @Auth
不是这样的, auth过滤器会自动注册以方便用户从旧版本的Dropwizard升级。
@RolesAllowed("ADMIN")
@GET
public SecretPlan getSecretPlan(@Context SecurityContext context) {
User userPrincipal = (User) context.getUserPrincipal();
return dao.findPlanForUser(user);
}
如果没有提供请求的凭据,或者凭据无效,则提供程序将返回与方案相关的响应,而不调用您的资源方法。401 Unauthorized
可选保护
通过将主体表示为一个Optional
,可以选择性保护资源方法。在这种情况下, 如果主体存在,资源方法将填充主体,否则是空。
例如,假设您有一个应该显示已登录用户名的端点,但是对未经身份验证的请求返回匿名回复。您需要实现一个自定义过滤器,该过滤器会注入包含主体的安全上下文(如果存在),而不执行身份验证。
@GET
public String getGreeting(@Auth Optional<User> userOpt) {
if (userOpt.isPresent()) {
return "Hello, " + userOpt.get().getName() + "!";
} else {
return "Greetings, anonymous visitor!"
}
}
对于受选择保护的资源,具有无效身份验证的请求将被视为与未提供身份验证凭据的请求相同。也就是说,fail满足验证者或授权者要求的请求导致将空主体传递给资源方法。
测试受保护的资源
将此依赖项添加到您的pom.xml
文件中:
<dependencies>
<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-testing</artifactId>
<version>${dropwizard.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.test-framework.providers</groupId>
<artifactId>jersey-test-framework-provider-grizzly2</artifactId>
<version>${jersey.version}</version>
<exclusions>
<exclusion>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</exclusion>
<exclusion>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
在构建ResourceTestRule
时,添加GrizzlyWebTestContainerFactory
行。
@Rule
public ResourceTestRule rule = ResourceTestRule
.builder()
.setTestContainerFactory(new GrizzlyWebTestContainerFactory())
.addProvider(new AuthDynamicFeature(new OAuthCredentialAuthFilter.Builder<User>()
.setAuthenticator(new MyOAuthAuthenticator())
.setAuthorizer(new MyAuthorizer())
.setRealm("SUPER SECRET STUFF")
.setPrefix("Bearer")
.buildAuthFilter()))
.addProvider(RolesAllowedDynamicFeature.class)
.addProvider(new AuthValueFactoryProvider.Binder<>(User.class))
.addResource(new ProtectedResource())
.build();
在此示例中,我们正在测试oauth身份验证,因此我们需要手动设置Header。
@Test
public void testProtected() throws Exception {
final Response response = rule.target("/protected")
.request(MediaType.APPLICATION_JSON_TYPE)
.header("Authorization", "Bearer TOKEN")
.get();
assertThat(response.getStatus()).isEqualTo(200);
}