在Jersey Jax-rs Web服务上启用OAuth1支持

时间:2014-02-15 14:33:01

标签: authentication oauth oauth-2.0 jersey jax-rs

我想在我的restful Web服务上启用OAuth1 Provider支持。泽西岛支持这一点,如 Jersey OAuth1 Provider support所述。 我一直试图将其注册为:

public ApplicationConfig(){
    super();
    addRestResourceClasses(getMyResourceClasses());                 
    register(new OAuth1ServerFeature(new DefaultOAuth1Provider(),"/oauth/access_token","/oauth/request_token"));
}

但是,当我注册OAuth1ServerFeature时,在尝试访问我的资源时会得到404。 似乎无法在任何地方找到任何实施泽西oauth支持的示例/教程!

是否有一个简单的组件可以插入我的jax-rs服务以启用oauth支持?

1 个答案:

答案 0 :(得分:7)

我意识到这个线程有点老了 - 但是自己刚刚开始工作,我感觉回复是有序的!有了时间,我甚至可以创建一个更全面的博客文章。请注意 - 这不是一个简短的答案!

关于在泽西岛使用OAuth1服务器(又名提供商)功能的信息绝对缺乏示例 - 我无法记住一个技术主题,该主题揭示了如此有用的Google信息。我几乎继续寻找另一种解决方案,因为它让我想到也许它没有用。但是,有了一些毅力,我可以说它不仅可以使用,而且似乎运作得相当好。当然,如果您已经使用Jersey作为REST API - 那么您就不需要任何额外的库。

我不是OAuth1专家 - 我强烈建议尝试此操作的人阅读背景知识。我也假设你有泽西岛工作,理解像ContainerRequestFilters这样的东西,还有一些内部手段来授权用户。

我的示例还使用了优秀的JAX-RS OSGi连接器 - 唯一真正的区别是我们使用OSGi捆绑上下文通过OSGI服务注册OAuth1功能,常规Jersey用户需要通过其正常的Application /服务器配置模型。

<强> 初始化

您必须创建OAuth1功能 - 并为其提供提供商:

DefaultOAuth1Provider oap = new DefaultOAuth1Provider();
Feature oaFeature = new OAuth1ServerFeature(oap, "oauth1/request_token", "oauth1/access_token");       

别忘了在泽西岛注册oaFeature!

DefaultOAuth1Provider完全基于内存 - 这对我们来说很好。许多人希望持久访问令牌以便在服务器重启时使用,这将需要扩展的子类(或干净的实现)

添加您的消费者密钥和秘密

我花了一段时间才意识到消费者不是用户,而是客户,即应用程序。如果您没有为希望连接的每个消费者(也称为客户端应用程序)注册密钥和秘密,那么泽西岛的实施将无法运作

oap.registerConsumer("some-owner-id", 
                      "abcdef" ,
                      "123456",
                      new MultivaluedHashMap<String,String> ());

你显然永远不会硬编码这些,并进一步使用某种形式的安全存储来保密(参数3)。

如果你不添加这些,你就不会再进一步​​了。

OAuth协议第1步 - 获取请求令牌

在此阶段,您已准备好客户端获取请求令牌 - 此处有perfectly good example on GitHub

ConsumerCredentials consumerCredentials = new ConsumerCredentials("abcdef","123456");

//TODO - user proper client builder with real location + any ssl context
OAuth1AuthorizationFlow authFlow = OAuth1ClientSupport.builder(consumerCredentials)
            .authorizationFlow(
                    "http://myhost:8080/myapi/oauth1/request_token",
                    "http://myhost:8080/myapi/oauth1/access_token",
                    "http://myhost:8080/myapi/oauth1/authorize")
            .build();
String authorizationUri = authFlow.start();
System.out.println("Auth URI: " + authorizationUri); 

显然,您会将URL更改为指向您的服务器,而且 - 至关重要的是 - 客户端需要使用您在服务器中注册的相同Conumer密钥和密钥。

您将收到带有oauth_token字符串的回复,例如

http://myhost:8080/myapi/oauth/authorize?oauth_token=a1ec37598da
b47f6b9d770b1b23a5f99

OAuth协议第2步 - 授权用户

正如您将在任何文章中看到的那样,实际的用户授权超出了OAuth1的范围 - 在此阶段您必须调用您的服务器授权过程。

然而!!!!不在OAuth1范围之外的是您的服务器在用户成功授权时需要执行的操作。您必须告诉DefaultOAuth1Provider有关成功的身份验证:

// Dummy code - make out like we're auth'd
Set<String> dummyRoles = new HashSet<> (Arrays.asList( new String[] { "my-role-1", "my-role-2" }));
DefaultOAuth1Provider.Token tok1 = getRequestToken("a1ec37598da
b47f6b9d770b1b23a5f99");
String verifier = authorizeToken(tok1, new Principal()
            {
                public String getName()
                {
                    return "my-user";
                }
            }, 
            dummyRoles);
System.out.println("***** verifier: " + verifier);

请注意,请求令牌字符串来自第1步。显然,真正的实现会为授权用户传递真实的Principal和一组角色。

当然,打印验证器并没有多大用处 - 您需要以某种方式将其返回给客户端,可以通过独立通道或者作为auth响应中的标头 - 这可能需要加密以增加保护。

OAuth协议第3步 - 交换请求令牌以获取访问令牌

一旦客户端收到或手动输入验证者,它就可以完成该过程并交换请求令牌以获取访问令牌,例如

String verifier = System.console().readLine("%s", "Verifier: ");
final AccessToken accessToken = authFlow.finish(verifier);        
System.out.println("Access token: " + accessToken.getToken());      

同样,这不是一个现实的例子 - 但它显示了这个过程。

如果您的OAuth1Provider将访问权限保存到服务器上的某个持久性存储,则可以在未来的会话中重复使用此处返回的任何访问令牌,而无需执行上述所有步骤。

它就是 - 你只需要确保客户端在此过程中创建的每个请求都使用该访问令牌。