原文: https://howtodoinjava.com/spring-boot/role-based-security-jaxrs-annotations/

学习使用 Spring Boot 和 Jersey 框架创建 JAX-RS 2.0 REST API ,并使用 JAX-RS 注解添加基于角色的安全性,例如 @PermitAll@RolesAllowed@DenyAll

项目结构

在本教程中创建的应用程序的项目结构如下:

Spring Boot – 带有 JAX-RS 注解的基于角色的安全性 - 图1

Spring Boot JAX-RS 安全性示例 – 项目结构

创建 REST API

  1. 创建 Spring Boot 项目


转到 Spring 初始化器页面,并创建具有 Jersey(JAX-RS)依赖项的 spring boot 应用程序。
Spring Boot – 带有 JAX-RS 注解的基于角色的安全性 - 图2
在 Spring 初始化器中选择 Jersey

  1. 在 Eclipse 中导入


将项目生成为 zip 文件。 将其解压缩到计算机中的某个位置。 将项目作为“现有 maven 应用程序”导入 eclipse。

  1. 检查 Maven 依赖项


检查 Maven 文件中是否具有依赖项

  1. <dependencies>
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-jersey</artifactId>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.springframework.boot</groupId>
  8. <artifactId>spring-boot-starter-test</artifactId>
  9. <scope>test</scope>
  10. </dependency>
  11. </dependencies>
  1. 创建 REST API


现在创建一些 JAX-RS 资源,我们将进入测试阶段。 我创建了UserResource类。
UserResource.java

  1. package com.howtodoinjava.jerseydemo;
  2. import java.net.URI;
  3. import java.net.URISyntaxException;
  4. import java.util.ArrayList;
  5. import java.util.HashMap;
  6. import java.util.Map;
  7. import javax.ws.rs.Consumes;
  8. import javax.ws.rs.DELETE;
  9. import javax.ws.rs.GET;
  10. import javax.ws.rs.POST;
  11. import javax.ws.rs.PUT;
  12. import javax.ws.rs.Path;
  13. import javax.ws.rs.PathParam;
  14. import javax.ws.rs.Produces;
  15. import javax.ws.rs.core.Response;
  16. import javax.xml.bind.annotation.XmlAccessType;
  17. import javax.xml.bind.annotation.XmlAccessorType;
  18. import javax.xml.bind.annotation.XmlRootElement;
  19. @XmlAccessorType(XmlAccessType.NONE)
  20. @XmlRootElement(name = "users")
  21. @Path("/users")
  22. public class UserResource
  23. {
  24. private static Map<Integer, User> DB = new HashMap<>();
  25. @GET
  26. @Produces("application/json")
  27. public Users getAllUsers() {
  28. Users users = new Users();
  29. users.setUsers(new ArrayList<>(DB.values()));
  30. return users;
  31. }
  32. @POST
  33. @Consumes("application/json")
  34. public Response createUser(User user) throws URISyntaxException
  35. {
  36. if(user.getFirstName() == null || user.getLastName() == null) {
  37. return Response.status(400).entity("Please provide all mandatory inputs").build();
  38. }
  39. user.setId(DB.values().size()+1);
  40. user.setUri("/user-management/"+user.getId());
  41. DB.put(user.getId(), user);
  42. return Response.status(201).contentLocation(new URI(user.getUri())).build();
  43. }
  44. @GET
  45. @Path("/{id}")
  46. @Produces("application/json")
  47. public Response getUserById(@PathParam("id") int id) throws URISyntaxException
  48. {
  49. User user = DB.get(id);
  50. if(user == null) {
  51. return Response.status(404).build();
  52. }
  53. return Response
  54. .status(200)
  55. .entity(user)
  56. .contentLocation(new URI("/user-management/"+id)).build();
  57. }
  58. @PUT
  59. @Path("/{id}")
  60. @Consumes("application/json")
  61. @Produces("application/json")
  62. public Response updateUser(@PathParam("id") int id, User user) throws URISyntaxException
  63. {
  64. User temp = DB.get(id);
  65. if(user == null) {
  66. return Response.status(404).build();
  67. }
  68. temp.setFirstName(user.getFirstName());
  69. temp.setLastName(user.getLastName());
  70. DB.put(temp.getId(), temp);
  71. return Response.status(200).entity(temp).build();
  72. }
  73. @DELETE
  74. @Path("/{id}")
  75. public Response deleteUser(@PathParam("id") int id) throws URISyntaxException {
  76. User user = DB.get(id);
  77. if(user != null) {
  78. DB.remove(user.getId());
  79. return Response.status(200).build();
  80. }
  81. return Response.status(404).build();
  82. }
  83. static
  84. {
  85. User user1 = new User();
  86. user1.setId(1);
  87. user1.setFirstName("John");
  88. user1.setLastName("Wick");
  89. user1.setUri("/user-management/1");
  90. User user2 = new User();
  91. user2.setId(2);
  92. user2.setFirstName("Harry");
  93. user2.setLastName("Potter");
  94. user2.setUri("/user-management/2");
  95. DB.put(user1.getId(), user1);
  96. DB.put(user2.getId(), user2);
  97. }
  98. }


Users.java

  1. package com.howtodoinjava.jerseydemo;
  2. import java.util.ArrayList;
  3. import javax.xml.bind.annotation.XmlAccessType;
  4. import javax.xml.bind.annotation.XmlAccessorType;
  5. import javax.xml.bind.annotation.XmlElement;
  6. import javax.xml.bind.annotation.XmlRootElement;
  7. @XmlAccessorType(XmlAccessType.NONE)
  8. @XmlRootElement(name = "users")
  9. public class Users {
  10. @XmlElement(name="user")
  11. private ArrayList<User> users;
  12. public ArrayList<User> getUsers() {
  13. return users;
  14. }
  15. public void setUsers(ArrayList<User> users) {
  16. this.users = users;
  17. }
  18. }


User.java

  1. package com.howtodoinjava.jerseydemo;
  2. import java.io.Serializable;
  3. import javax.xml.bind.annotation.XmlAccessType;
  4. import javax.xml.bind.annotation.XmlAccessorType;
  5. import javax.xml.bind.annotation.XmlAttribute;
  6. import javax.xml.bind.annotation.XmlElement;
  7. import javax.xml.bind.annotation.XmlRootElement;
  8. @XmlAccessorType(XmlAccessType.NONE)
  9. @XmlRootElement(name = "user")
  10. public class User implements Serializable {
  11. private static final long serialVersionUID = 1L;
  12. @XmlAttribute(name = "id")
  13. private int id;
  14. @XmlAttribute(name="uri")
  15. private String uri;
  16. @XmlElement(name = "firstName")
  17. private String firstName;
  18. @XmlElement(name = "lastName")
  19. private String lastName;
  20. // Getters and Setters
  21. }
  1. 配置 Jersey


现在我们有了一个 JAX-RS 资源,我们想从包含 Jersey 依赖项的 spring boot 应用程序中访问它。 让我们将此资源注册为 Jersey 资源。

  1. package com.howtodoinjava.jerseydemo;
  2. import org.glassfish.jersey.server.ResourceConfig;
  3. import org.springframework.stereotype.Component;
  4. @Component
  5. public class JerseyConfig extends ResourceConfig
  6. {
  7. public JerseyConfig()
  8. {
  9. register(SecurityFilter.class);
  10. register(UserResource.class);
  11. }
  12. }
  • 查看@Component注解。 它可以在 Spring Boot 自动扫描源文件夹中的 java 类时注册此类。
  • ResourceConfig提供高级功能以简化 JAX-RS 组件的注册。
  • SecurityFilter类是实际的身份验证详细信息处理器,我们将在本教程的后面部分看到。

`SpringBootServletInitializer````java package com.howtodoinjava.jerseydemo;

import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.web.support.SpringBootServletInitializer;

@SpringBootApplication public class JerseydemoApplication extends SpringBootServletInitializer { public static void main(String[] args) { new JerseydemoApplication().configure(new SpringApplicationBuilder(JerseydemoApplication.class)).run(args); } }

  1. <a name="a26694ef"></a>
  2. ## 使用 JAX-RS 注解的安全 REST API
  3. 现在,当我们的 API 准备就绪时,我们将开始保护它们。 让我们根据 **JAX-RS 注解**对其进行注解,具体取决于它们的期望访问级别和允许访问它们的用户角色。
  4. ```java
  5. package com.howtodoinjava.jerseydemo;
  6. @XmlAccessorType(XmlAccessType.NONE)
  7. @XmlRootElement(name = "users")
  8. @Path("/users")
  9. public class UserResource
  10. {
  11. private static Map<Integer, User> DB = new HashMap<>();
  12. @GET
  13. @PermitAll
  14. @Produces("application/json")
  15. public Users getAllUsers() {
  16. Users users = new Users();
  17. users.setUsers(new ArrayList<>(DB.values()));
  18. return users;
  19. }
  20. @POST
  21. @Consumes("application/json")
  22. @RolesAllowed("ADMIN")
  23. public Response createUser(User user) throws URISyntaxException
  24. {
  25. if(user.getFirstName() == null || user.getLastName() == null) {
  26. return Response.status(400).entity("Please provide all mandatory inputs").build();
  27. }
  28. user.setId(DB.values().size()+1);
  29. user.setUri("/user-management/"+user.getId());
  30. DB.put(user.getId(), user);
  31. return Response.status(201).contentLocation(new URI(user.getUri())).build();
  32. }
  33. @GET
  34. @Path("/{id}")
  35. @Produces("application/json")
  36. @PermitAll
  37. public Response getUserById(@PathParam("id") int id) throws URISyntaxException
  38. {
  39. User user = DB.get(id);
  40. if(user == null) {
  41. return Response.status(404).build();
  42. }
  43. return Response
  44. .status(200)
  45. .entity(user)
  46. .contentLocation(new URI("/user-management/"+id)).build();
  47. }
  48. @PUT
  49. @Path("/{id}")
  50. @Consumes("application/json")
  51. @Produces("application/json")
  52. @RolesAllowed("ADMIN")
  53. public Response updateUser(@PathParam("id") int id, User user) throws URISyntaxException
  54. {
  55. User temp = DB.get(id);
  56. if(user == null) {
  57. return Response.status(404).build();
  58. }
  59. temp.setFirstName(user.getFirstName());
  60. temp.setLastName(user.getLastName());
  61. DB.put(temp.getId(), temp);
  62. return Response.status(200).entity(temp).build();
  63. }
  64. @DELETE
  65. @Path("/{id}")
  66. @RolesAllowed("ADMIN")
  67. public Response deleteUser(@PathParam("id") int id) throws URISyntaxException {
  68. User user = DB.get(id);
  69. if(user != null) {
  70. DB.remove(user.getId());
  71. return Response.status(200).build();
  72. }
  73. return Response.status(404).build();
  74. }
  75. static
  76. {
  77. User user1 = new User();
  78. user1.setId(1);
  79. user1.setFirstName("John");
  80. user1.setLastName("Wick");
  81. user1.setUri("/user-management/1");
  82. User user2 = new User();
  83. user2.setId(2);
  84. user2.setFirstName("Harry");
  85. user2.setLastName("Potter");
  86. user2.setUri("/user-management/2");
  87. DB.put(user1.getId(), user1);
  88. DB.put(user2.getId(), user2);
  89. }
  90. }

您可以在上面突出显示的行中查看与安全相关的 JAX-RS 注解。

使用 JAX-RS ContainerRequestFilter编写安全过滤器

现在是时候编写我们的安全过滤器了,该过滤器将检查传入的请求,获取授权信息(在此示例中为基本认证),然后匹配用户名和密码,最后将通过其角色来验证用户的访问级别。 如果一切都匹配,则将访问 API,否则用户将获得拒绝访问响应。

  1. package com.howtodoinjava.jerseydemo;
  2. import java.lang.reflect.Method;
  3. import java.util.Arrays;
  4. import java.util.Base64;
  5. import java.util.HashSet;
  6. import java.util.List;
  7. import java.util.Set;
  8. import java.util.StringTokenizer;
  9. import javax.annotation.security.DenyAll;
  10. import javax.annotation.security.PermitAll;
  11. import javax.annotation.security.RolesAllowed;
  12. import javax.ws.rs.container.ContainerRequestContext;
  13. import javax.ws.rs.container.ResourceInfo;
  14. import javax.ws.rs.core.Context;
  15. import javax.ws.rs.core.MultivaluedMap;
  16. import javax.ws.rs.core.Response;
  17. import javax.ws.rs.ext.Provider;
  18. /**
  19. * This filter verify the access permissions for a user based on
  20. * user name and password provided in request
  21. * */
  22. @Provider
  23. public class SecurityFilter implements javax.ws.rs.container.ContainerRequestFilter
  24. {
  25. private static final String AUTHORIZATION_PROPERTY = "Authorization";
  26. private static final String AUTHENTICATION_SCHEME = "Basic";
  27. private static final Response ACCESS_DENIED = Response.status(Response.Status.UNAUTHORIZED).build();
  28. private static final Response ACCESS_FORBIDDEN = Response.status(Response.Status.FORBIDDEN).build();
  29. private static final Response SERVER_ERROR = Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
  30. @Context
  31. private ResourceInfo resourceInfo;
  32. @Override
  33. public void filter(ContainerRequestContext requestContext)
  34. {
  35. Method method = resourceInfo.getResourceMethod();
  36. //Access allowed for all
  37. if( ! method.isAnnotationPresent(PermitAll.class))
  38. {
  39. //Access denied for all
  40. if(method.isAnnotationPresent(DenyAll.class))
  41. {
  42. requestContext.abortWith(ACCESS_FORBIDDEN);
  43. return;
  44. }
  45. //Get request headers
  46. final MultivaluedMap<String, String> headers = requestContext.getHeaders();
  47. //Fetch authorization header
  48. final List<String> authorization = headers.get(AUTHORIZATION_PROPERTY);
  49. //If no authorization information present; block access
  50. if(authorization == null || authorization.isEmpty())
  51. {
  52. requestContext.abortWith(ACCESS_DENIED);
  53. return;
  54. }
  55. //Get encoded username and password
  56. final String encodedUserPassword = authorization.get(0).replaceFirst(AUTHENTICATION_SCHEME + " ", "");
  57. //Decode username and password
  58. String usernameAndPassword = null;
  59. try {
  60. usernameAndPassword = new String(Base64.getDecoder().decode(encodedUserPassword));
  61. } catch (Exception e) {
  62. requestContext.abortWith(SERVER_ERROR);
  63. return;
  64. }
  65. //Split username and password tokens
  66. final StringTokenizer tokenizer = new StringTokenizer(usernameAndPassword, ":");
  67. final String username = tokenizer.nextToken();
  68. final String password = tokenizer.nextToken();
  69. //Verifying Username and password
  70. if(!(username.equalsIgnoreCase("admin") && password.equalsIgnoreCase("password"))){
  71. requestContext.abortWith(ACCESS_DENIED);
  72. return;
  73. }
  74. //Verify user access
  75. if(method.isAnnotationPresent(RolesAllowed.class))
  76. {
  77. RolesAllowed rolesAnnotation = method.getAnnotation(RolesAllowed.class);
  78. Set<String> rolesSet = new HashSet<String>(Arrays.asList(rolesAnnotation.value()));
  79. //Is user valid?
  80. if( ! isUserAllowed(username, password, rolesSet))
  81. {
  82. requestContext.abortWith(ACCESS_DENIED);
  83. return;
  84. }
  85. }
  86. }
  87. }
  88. private boolean isUserAllowed(final String username, final String password, final Set<String> rolesSet)
  89. {
  90. boolean isAllowed = false;
  91. //Step 1\. Fetch password from database and match with password in argument
  92. //If both match then get the defined role for user from database and continue; else return isAllowed [false]
  93. //Access the database and do this part yourself
  94. //String userRole = userMgr.getUserRole(username);
  95. String userRole = "ADMIN";
  96. //Step 2\. Verify user role
  97. if(rolesSet.contains(userRole))
  98. {
  99. isAllowed = true;
  100. }
  101. return isAllowed;
  102. }
  103. }

示例

将项目作为 Spring 启动应用程序运行。 现在测试其余资源。

访问 GET /users资源

Spring Boot – 带有 JAX-RS 注解的基于角色的安全性 - 图3

GET 用户集合

访问 POST /users资源,没有身份验证详细信息

查看返回的状态代码 401

Spring Boot – 带有 JAX-RS 注解的基于角色的安全性 - 图4

POST API 所需的认证

已添加身份验证详细信息的访问 POST /users资源

使用此链接生成 base64 编码的用户名和密码组合,以传递到Authorization标头中。

Spring Boot – 带有 JAX-RS 注解的基于角色的安全性 - 图5

使用认证信息请求成功

将我的问题放在评论部分。

学习愉快!