原文: https://howtodoinjava.com/resteasy/jax-rs-resteasy-basic-authentication-and-authorization-tutorial/

安全性是任何企业应用不可或缺的一部分。 安全涉及两个阶段,即认证和授权。 认证可验证您的身份。 授权会验证您有权执行的操作。 在本文中,我们将学习为 REST API 构建基于角色的基本认证/授权安全性。

  1. Sections in this post:
  2. Background information
  3. Important classes/annotations used
  4. Building the security interceptor
  5. Testing authorization and authentication on REST APIs

背景信息

在本文中,我将尝试解决以下安全问题

  • 从 http 请求获取用户名和密码
  • 获取适用的方法安全性详细信息
  • 验证用户是否有权访问 API
  • 如果访问无效,则返回有效的错误代码

另外,请注意,我正在为您实现以下部分

  • 访问数据库以获取密码,以验证请求中提供的密码
  • 从数据库中为有效用户获取允许的角色

我正在重用为 ETag 使用演示编写的代码。 该项目有 2 个主要类:

User.java:代表系统中用户资源的模型类

  1. @XmlAccessorType(XmlAccessType.NONE)
  2. @XmlRootElement(name = "user")
  3. public class User implements Serializable
  4. {
  5. @XmlAttribute(name = "id")
  6. private int id;
  7. @XmlAttribute(name="uri")
  8. private String uri;
  9. @XmlElement(name = "firstName")
  10. private String firstName;
  11. @XmlElement(name = "lastName")
  12. private String lastName;
  13. @XmlElement(name="last-modified")
  14. private Date lastModified;
  15. //Getters and setters
  16. }

UserService.java:此类具有 GET 和 PUT API,用于获取/修改用户资源。

  1. @Path("/user-service")
  2. public class UserService
  3. {
  4. @GET
  5. @Path("/users/{id}")
  6. public Response getUserById(@PathParam("id") int id, @Context Request req)
  7. {
  8. Response.ResponseBuilder rb = Response.ok(UserDatabase.getUserById(id));
  9. return rb.build();
  10. }
  11. @PUT
  12. @Path("/users/{id}")
  13. public Response updateUserById(@PathParam("id") int id)
  14. {
  15. //Update the User resource
  16. UserDatabase.updateUser(id);
  17. return Response.status(200).build();
  18. }
  19. }

在本教程中,我将使用上述 2 个 API 并确保它们的安全。

使用的重要类/注解

JAX-RS 提供必要的注解,以在基于 RESTEasy 的应用中实现安全性。 重要的注解是:

除上述类外,org.jboss.resteasy.spi.interception.PreProcessInterceptor将用于创建安全拦截器。 javax.ws.rs.ext.Provider注解将用于在 resteasy 上下文中注册拦截器。

构建安全拦截器

在构建拦截器之前,请确保 API 的安全。 我在 GET/PUT API 中添加了@PermitAll@RolesAllowed注解。 GET API 向所有人开放,即没有访问限制。 PUT API 需要具有ADMIN功能的有效用户。

  1. @Path("/user-service")
  2. public class UserService
  3. {
  4. @PermitAll
  5. @GET
  6. @Path("/users/{id}")
  7. public Response getUserById(@PathParam("id") int id, @Context Request req)
  8. {
  9. Response.ResponseBuilder rb = Response.ok(UserDatabase.getUserById(id));
  10. return rb.build();
  11. }
  12. @RolesAllowed("ADMIN")
  13. @PUT
  14. @Path("/users/{id}")
  15. public Response updateUserById(@PathParam("id") int id)
  16. {
  17. //Update the User resource
  18. UserDatabase.updateUser(id);
  19. return Response.status(200).build();
  20. }
  21. }

通过实现org.jboss.resteasy.spi.interception.PreProcessInterceptor接口来构建安全拦截器。 该接口有一个实现类的类需要实现的方法。

  1. public ServerResponse preProcess(HttpRequest request, ResourceMethod methodInvoked)
  2. throws Failure, WebApplicationException;

methodInvoked参数提供对方法的访问,该方法将由于调用 REST API 而被调用。
请求参数提供对客户端传递的请求标头和参数的访问。

拦截器类的定义如下:

  1. @Provider
  2. @ServerInterceptor
  3. public class SecurityInterceptor implements PreProcessInterceptor
  4. {
  5. //more code
  6. }

@Provider进行 resteasy 上下文扫描,以将该类添加为上下文类。
@ServerInterceptor注解将此类添加到可用的拦截器列表中。

现在,第一步是检查 API 是否对所有人都打开或对所有人都关闭。 这可以通过检查@PermitAll@DenyAll注解来完成。

  1. Method method = methodInvoked.getMethod();
  2. //Access allowed for all
  3. if(method.isAnnotationPresent(PermitAll.class))
  4. {
  5. return null; //Return null to continue request processing
  6. }
  7. //Access denied for all
  8. if(method.isAnnotationPresent(DenyAll.class))
  9. {
  10. return ACCESS_FORBIDDEN; //Return access denied to user
  11. }

下一步是从请求中获取授权标头。 从授权标头中,我们将检索用户发送的用户名和密码。

  1. //Get request headers
  2. final HttpHeaders headers = request.getHttpHeaders();
  3. //Fetch authorization header
  4. final List<String> authorization = headers.getRequestHeader(AUTHORIZATION_PROPERTY);
  5. //If no authorization information present; block access
  6. if(authorization == null || authorization.isEmpty())
  7. {
  8. return ACCESS_DENIED;
  9. }
  10. //Get encoded username and password
  11. final String encodedUserPassword = authorization.get(0).replaceFirst(AUTHENTICATION_SCHEME + " ", "");
  12. //Decode username and password
  13. String usernameAndPassword;
  14. try {
  15. usernameAndPassword = new String(Base64.decode(encodedUserPassword));
  16. } catch (IOException e) {
  17. return SERVER_ERROR;
  18. }
  19. //Split username and password tokens
  20. final StringTokenizer tokenizer = new StringTokenizer(usernameAndPassword, ":");
  21. final String username = tokenizer.nextToken();
  22. final String password = tokenizer.nextToken();

现在,我们将从@RolesAllowed注解中获取访问 API 所需的角色。

  1. RolesAllowed rolesAnnotation = method.getAnnotation(RolesAllowed.class);
  2. Set<String> rolesSet = new HashSet<String>(Arrays.asList(rolesAnnotation.value()));

RolesSet包含所有可以允许 API 访问的角色。

现在,在最后一步中,我们必须做两件事。 首先,从任何数据库服务验证用户名和密码,如果用户有效,则获取分配给他的角色。 该角色将用于检查用户的认证成功。

  1. if(rolesSet.contains(userRole))
  2. {
  3. isAllowed = true;
  4. }

SecurityInterceptor.java的完整源代码如下所示:

  1. /**
  2. * This interceptor verify the access permissions for a user
  3. * based on username and passowrd provided in request
  4. * */
  5. @Provider
  6. @ServerInterceptor
  7. public class SecurityInterceptor implements PreProcessInterceptor
  8. {
  9. private static final String AUTHORIZATION_PROPERTY = "Authorization";
  10. private static final String AUTHENTICATION_SCHEME = "Basic";
  11. private static final ServerResponse ACCESS_DENIED = new ServerResponse("Access denied for this resource", 401, new Headers<Object>());;
  12. private static final ServerResponse ACCESS_FORBIDDEN = new ServerResponse("Nobody can access this resource", 403, new Headers<Object>());;
  13. private static final ServerResponse SERVER_ERROR = new ServerResponse("INTERNAL SERVER ERROR", 500, new Headers<Object>());;
  14. @Override
  15. public ServerResponse preProcess(HttpRequest request, ResourceMethod methodInvoked) throws Failure, WebApplicationException
  16. {
  17. Method method = methodInvoked.getMethod();
  18. //Access allowed for all
  19. if(method.isAnnotationPresent(PermitAll.class))
  20. {
  21. return null;
  22. }
  23. //Access denied for all
  24. if(method.isAnnotationPresent(DenyAll.class))
  25. {
  26. return ACCESS_FORBIDDEN;
  27. }
  28. //Get request headers
  29. final HttpHeaders headers = request.getHttpHeaders();
  30. //Fetch authorization header
  31. final List<String> authorization = headers.getRequestHeader(AUTHORIZATION_PROPERTY);
  32. //If no authorization information present; block access
  33. if(authorization == null || authorization.isEmpty())
  34. {
  35. return ACCESS_DENIED;
  36. }
  37. //Get encoded username and password
  38. final String encodedUserPassword = authorization.get(0).replaceFirst(AUTHENTICATION_SCHEME + " ", "");
  39. //Decode username and password
  40. String usernameAndPassword;
  41. try {
  42. usernameAndPassword = new String(Base64.decode(encodedUserPassword));
  43. } catch (IOException e) {
  44. return SERVER_ERROR;
  45. }
  46. //Split username and password tokens
  47. final StringTokenizer tokenizer = new StringTokenizer(usernameAndPassword, ":");
  48. final String username = tokenizer.nextToken();
  49. final String password = tokenizer.nextToken();
  50. //Verifying Username and password
  51. System.out.println(username);
  52. System.out.println(password);
  53. //Verify user access
  54. if(method.isAnnotationPresent(RolesAllowed.class))
  55. {
  56. RolesAllowed rolesAnnotation = method.getAnnotation(RolesAllowed.class);
  57. Set<String> rolesSet = new HashSet<String>(Arrays.asList(rolesAnnotation.value()));
  58. //Is user valid?
  59. if( ! isUserAllowed(username, password, rolesSet))
  60. {
  61. return ACCESS_DENIED;
  62. }
  63. }
  64. //Return null to continue request processing
  65. return null;
  66. }
  67. private boolean isUserAllowed(final String username, final String password, final Set<String> rolesSet)
  68. {
  69. boolean isAllowed = false;
  70. //Step 1\. Fetch password from database and match with password in argument
  71. //If both match then get the defined role for user from database and continue; else return isAllowed [false]
  72. //Access the database and do this part yourself
  73. //String userRole = userMgr.getUserRole(username);
  74. String userRole = "ADMIN";
  75. //Step 2\. Verify user role
  76. if(rolesSet.contains(userRole))
  77. {
  78. isAllowed = true;
  79. }
  80. return isAllowed;
  81. }
  82. }

在 REST API 上测试授权和认证

要测试安全代码,请将 Web 应用部署在任何应用服务器(例如 Tomcat)中。 现在,发送以下请求:

HTTP GET http://localhost:8080/RESTEasyEtagDemo/user-service/users/1,没有用户名和密码

用户能够成功访问 API。

RESTEasy 基本认证和授权教程 - 图1

HTTP PUT http://localhost:8080/RESTEasyEtagDemo/user-service/users/1,没有用户名和密码

用户无法访问 API。

RESTEasy 基本认证和授权教程 - 图2

添加基本授权凭证

RESTEasy 基本认证和授权教程 - 图3

HTTP PUT http://localhost:8080/RESTEasyEtagDemo/user-service/users/1,其中添加了用户名和密码

用户能够访问受保护的 API

RESTEasy 基本认证和授权教程 - 图4

这就是本教程中的全部内容。 如果您有任何疑问或建议,请给我评论。

下载源代码

祝您学习愉快!