我对网络服务非常陌生。我已经使用Jersey 2与Spring集成了一些REST服务。现在我需要使用用户名/密码进行身份验证来保护这些休息服务。我被告知不要使用Spring安全性。
我不知道该怎么做。我确实在网上搜索,但各种链接显示了各种实现,我无法决定如何继续进行。
我知道这是一个模糊的问题,但请在这方面提供帮助。
答案 0 :(得分:12)
使用用户名和密码进行身份验证的常用方法是使用Basic Authentication。基本上,客户端需要发送请求标头Authorization
,标头值为Basic Base64Encoded(username:password)
。我的用户名是peeskillet
,我的密码是pass
,我作为客户端应该将标题设置为
Authorization: Basic cGVlc2tpbGxldDpwYXNz
在servlet环境中,容器应该支持基本身份验证。您可以在web.xml上配置此支持。您可以在Java EE教程的48.2 Securing Web Applications中看到一个示例。您还会在示例中注意到
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
这是针对SSL支持的。建议用于基本身份验证。
如果您不想处理使用安全域和登录模块,领域等的麻烦,那么需要自定义servlet支持,或者如果您不在servlet环境中,在ContainerRequestFilter
中实现Basic Auth实际上并不太难。
您可以在jersey/examples/https-clientserver-grizzly查看完整示例。您应该关注SecurityFilter
过滤器中的基本流程就像这样
获取Authorization
标题。如果它不存在,则抛出AuthenticationException
。在这种情况下,AuthenticationExceptionMapper
会发送标头"WWW-Authenticate", "Basic realm=\"" + e.getRealm() + "\"
,这是基本身份验证协议的一部分
一旦我们有了标题,我们解析它只是为了获得Base64编码的用户名:密码。然后我们解码它,然后拆分它,然后分开用户名和密码。如果此过程中的任何一个失败,请再次抛出映射到400 Bad Request的WebApplicationException
。
检查用户名和密码。示例源代码仅检查用户名是否为user
且密码为password
,但您需要使用过滤器中的某些服务来验证此信息。如果其中任何一个失败,请抛出AuthenticationException
如果一切顺利,则会从User
方法创建authenticate
,并将其注入Authorizer
(SecurityContext
)。在JAX-RS中,SecurityContext
通常用于授权。
对于授权,如果要保护某些资源的某些区域,可以对类或方法使用@RolesAllowed
注释。泽西岛通过registering the RolesAllowedDynamicFeature
支持此注释。
幕后发生的事情是SecurityContext
将从请求中获得。通过我链接到的示例,您可以看到Authorizer
,它有一个重写方法isUserInRole
。将调用此方法来检查@RolesAllowed({"ADMIN"})
中的值。因此,当您创建SecurityContext
时,您应该确保在重写方法中包含用户的角色。
要进行测试,您只需使用浏览器即可。如果一切设置正确,当您尝试访问资源时,您应该看到(在Firefox中)this post中显示的对话框。如果您使用cURL,则可以执行
C:/>curl -v -u username:password http://localhost:8080/blah/resource
这将发出基本身份验证请求。由于-v
开关,您应该看到所有涉及的标头。如果您只想使用客户端API进行测试,可以看到here如何设置它。在上述三种情况中的任何一种情况下,Base64编码都将为您完成,因此您无需担心。
对于SSL,您应该查看容器的文档,以获取有关如何设置它的信息。
答案 1 :(得分:1)
安全性主要有两种:
保护spring应用程序的标准方法是使用Spring Security(以前称为Acegi)。 知道为什么你不被允许使用它会很有趣。
您可以使用基于容器的安全性,但我猜测您对spring的使用也排除了该选项。 由于选择Spring通常是为了避免使用完整的J2EE容器(编辑:虽然如下所述,但是大多数普通的servlet容器都允许你实现各种基于容器的安全方法)
这实际上只留下一个选项,即推出自己的安全性。
您对Jersey的使用表明这可能是一个REST应用程序。 在这种情况下,你真的应该坚持使用标准的HTTP身份验证方法 以相反的强度顺序出现以下风格:
REST应用程序通常应该是“无状态的”,它基本上排除了基于表单的身份验证(因为您需要使用Session) 给你留下BASIC,摘要和证书。
你的下一个问题是,我在验证谁。如果您可以根据他们请求的URL(例如,如果它是所有用户的一组凭据)知道用户的用户名和密码,那么Digest是最好的选择,因为密码永远不会被发送,只有哈希。 如果您无法知道密码(因为您要求第三方系统对其进行验证等),那么您将无法使用BASIC。 但是,您可以通过使用SSL来增强BASIC的安全性,或者更好地将BASIC与客户端证书身份验证相结合。 事实上,基于HTTPS的BASIC身份验证是保护大多数REST应用程序的标准技术。
您可以轻松实现查找身份验证标头的Servlet过滤器并自行验证凭据。 这种过滤器有很多例子,它是一个自包含的类文件。 如果未找到凭据,则过滤器返回401,在响应头中传递基本身份验证的提示。 如果凭据无效,则返回403。 应用程序安全本身几乎是整个职业,但我希望这会有所帮助。
答案 2 :(得分:1)
正如之前的帖子所说,你可以选择不同的选项,实现的开销也各不相同。从实际的角度来看,如果您要从这开始并且正在寻找一种简单实现的舒适方式,我建议使用BASIC身份验证的基于容器的选项。
如果您使用tomcat,则可以设置realm,这实现起来相对简单。您可以使用JDBCRealm,它从数据库中的指定列获取用户和密码,并通过server.xml和web.xml进行配置。 每次您尝试访问您的应用程序时,这都会自动提示您输入凭据。您没有任何应用程序端实现。
答案 3 :(得分:1)
我现在可以告诉你的是,你已经完成了大部分“肮脏”的事情。将Jersey与Spring整合的工作。我建议您使用基于应用程序的解决方案,它不会将您绑定到特定容器。 Spring Security最初可能会令人生畏,但是当你驯服野兽时,你会发现它实际上是一只友善的小狗。
事实上,只需实现接口,Spring Security就可以进行大规模定制。并且有很多文档和支持。此外,您已经有一个基于Spring的应用程序。
因为你所寻求的只是指导,我可以为你提供一些教程。你可以利用这个博客。
http://www.baeldung.com/rest-with-spring-series/ http://www.baeldung.com/2011/10/31/securing-a-restful-web-service-with-spring-security-3-1-part-3/
答案 4 :(得分:1)
昨天实际做了这件事。
所以这真的是你想要实现的目标。我的理由是使用移动设备和One-Page-App JavaScript来运行这个东西。
基本上你需要做的就是生成某种类型的标题,这个标题在客户端发出的每个连续请求中都是必需的。
所以你在等待带有用户/密码的帖子时做一个端点:
@Path("/login")
public class AuthenticationResource {
@POST
@Consumes("application/json")
public Response authenticate(Credentials credential) {
boolean canBeLoggedIn = (...check in your DB or anywher you need to)
if (canBeLoggedIn) {
UUID uuid = UUID.randomUUID();
Token token = new Token();
token.setToken(uuid.toString());
//save your token with associated with user
(...)
return Response.ok(token).type(MediaType.APPLICATION_JSON_TYPE).build();
} else {
return Response.status(Response.Status.UNAUTHORIZED).build();
}
}
}
现在您需要保护需要该令牌的资源:
@Path("/payment")
@AuthorizedWithToken
public class Payments {
@GET
@Produces("application/json")
public Response sync() {
(...)
}
}
注意@AuthorizedWithToken
注释。您可以使用特殊元注释@NameBinding
@NameBinding
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface AuthorizedWithToken {}
现在对于实现标头检查的过滤器:
@AuthorizedWithToken
@Provider
public class XAuthTokenFilter implements ContainerRequestFilter {
private static String X_Auth_Token = "X-Auth-Token";
@Override
public void filter(ContainerRequestContext crc) throws IOException {
String headerValue = crc.getHeaderString(X_Auth_Token);
if (headerValue == null) {
crc.abortWith(Response.status(Response.Status.FORBIDDEN).entity("Missing " + X_Auth_Token + " value").build());
return;
}
if(! TOKEN_FOUND_IN_DB) {
crc.abortWith(Response.status(Response.Status.UNAUTHORIZED).entity("Wrong " + X_Auth_Token + " value").build());
return;
}
}
}
您可以创建任意数量的自己的注释,检查http请求中的各种内容并混合它们。但是你需要注意优先级,但实际上很容易找到。此方法需要使用https
,但这很明显。