Dropwizard官方教程(六) 身份认证

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

基本认证

AuthDynamicFeatureBasicCredentialAuthFilterRolesAllowedDynamicFeature 启用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

AuthDynamicFeatureOAuthCredentialAuthFilterRolesAllowedDynamicFeature 能够承载的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);
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,589评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,615评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,933评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,976评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,999评论 6 393
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,775评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,474评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,359评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,854评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,007评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,146评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,826评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,484评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,029评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,153评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,420评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,107评论 2 356

推荐阅读更多精彩内容