原文: 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 安全性示例 – 项目结构
创建 REST API
转到 Spring 初始化器页面,并创建具有 Jersey(JAX-RS)依赖项的 spring boot 应用程序。
在 Spring 初始化器中选择 Jersey
将项目生成为 zip 文件。 将其解压缩到计算机中的某个位置。 将项目作为“现有 maven 应用程序”导入 eclipse。
检查 Maven 文件中是否具有依赖项。
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jersey</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies>
现在创建一些 JAX-RS 资源,我们将进入测试阶段。 我创建了UserResource类。
UserResource.java
package com.howtodoinjava.jerseydemo;import java.net.URI;import java.net.URISyntaxException;import java.util.ArrayList;import java.util.HashMap;import java.util.Map;import javax.ws.rs.Consumes;import javax.ws.rs.DELETE;import javax.ws.rs.GET;import javax.ws.rs.POST;import javax.ws.rs.PUT;import javax.ws.rs.Path;import javax.ws.rs.PathParam;import javax.ws.rs.Produces;import javax.ws.rs.core.Response;import javax.xml.bind.annotation.XmlAccessType;import javax.xml.bind.annotation.XmlAccessorType;import javax.xml.bind.annotation.XmlRootElement;@XmlAccessorType(XmlAccessType.NONE)@XmlRootElement(name = "users")@Path("/users")public class UserResource{private static Map<Integer, User> DB = new HashMap<>();@GET@Produces("application/json")public Users getAllUsers() {Users users = new Users();users.setUsers(new ArrayList<>(DB.values()));return users;}@POST@Consumes("application/json")public Response createUser(User user) throws URISyntaxException{if(user.getFirstName() == null || user.getLastName() == null) {return Response.status(400).entity("Please provide all mandatory inputs").build();}user.setId(DB.values().size()+1);user.setUri("/user-management/"+user.getId());DB.put(user.getId(), user);return Response.status(201).contentLocation(new URI(user.getUri())).build();}@GET@Path("/{id}")@Produces("application/json")public Response getUserById(@PathParam("id") int id) throws URISyntaxException{User user = DB.get(id);if(user == null) {return Response.status(404).build();}return Response.status(200).entity(user).contentLocation(new URI("/user-management/"+id)).build();}@PUT@Path("/{id}")@Consumes("application/json")@Produces("application/json")public Response updateUser(@PathParam("id") int id, User user) throws URISyntaxException{User temp = DB.get(id);if(user == null) {return Response.status(404).build();}temp.setFirstName(user.getFirstName());temp.setLastName(user.getLastName());DB.put(temp.getId(), temp);return Response.status(200).entity(temp).build();}@DELETE@Path("/{id}")public Response deleteUser(@PathParam("id") int id) throws URISyntaxException {User user = DB.get(id);if(user != null) {DB.remove(user.getId());return Response.status(200).build();}return Response.status(404).build();}static{User user1 = new User();user1.setId(1);user1.setFirstName("John");user1.setLastName("Wick");user1.setUri("/user-management/1");User user2 = new User();user2.setId(2);user2.setFirstName("Harry");user2.setLastName("Potter");user2.setUri("/user-management/2");DB.put(user1.getId(), user1);DB.put(user2.getId(), user2);}}
Users.java
package com.howtodoinjava.jerseydemo;import java.util.ArrayList;import javax.xml.bind.annotation.XmlAccessType;import javax.xml.bind.annotation.XmlAccessorType;import javax.xml.bind.annotation.XmlElement;import javax.xml.bind.annotation.XmlRootElement;@XmlAccessorType(XmlAccessType.NONE)@XmlRootElement(name = "users")public class Users {@XmlElement(name="user")private ArrayList<User> users;public ArrayList<User> getUsers() {return users;}public void setUsers(ArrayList<User> users) {this.users = users;}}
User.java
package com.howtodoinjava.jerseydemo;import java.io.Serializable;import javax.xml.bind.annotation.XmlAccessType;import javax.xml.bind.annotation.XmlAccessorType;import javax.xml.bind.annotation.XmlAttribute;import javax.xml.bind.annotation.XmlElement;import javax.xml.bind.annotation.XmlRootElement;@XmlAccessorType(XmlAccessType.NONE)@XmlRootElement(name = "user")public class User implements Serializable {private static final long serialVersionUID = 1L;@XmlAttribute(name = "id")private int id;@XmlAttribute(name="uri")private String uri;@XmlElement(name = "firstName")private String firstName;@XmlElement(name = "lastName")private String lastName;// Getters and Setters}
现在我们有了一个 JAX-RS 资源,我们想从包含 Jersey 依赖项的 spring boot 应用程序中访问它。 让我们将此资源注册为 Jersey 资源。
package com.howtodoinjava.jerseydemo;import org.glassfish.jersey.server.ResourceConfig;import org.springframework.stereotype.Component;@Componentpublic class JerseyConfig extends ResourceConfig{public JerseyConfig(){register(SecurityFilter.class);register(UserResource.class);}}
- 查看
@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); } }
<a name="a26694ef"></a>## 使用 JAX-RS 注解的安全 REST API现在,当我们的 API 准备就绪时,我们将开始保护它们。 让我们根据 **JAX-RS 注解**对其进行注解,具体取决于它们的期望访问级别和允许访问它们的用户角色。```javapackage com.howtodoinjava.jerseydemo;@XmlAccessorType(XmlAccessType.NONE)@XmlRootElement(name = "users")@Path("/users")public class UserResource{private static Map<Integer, User> DB = new HashMap<>();@GET@PermitAll@Produces("application/json")public Users getAllUsers() {Users users = new Users();users.setUsers(new ArrayList<>(DB.values()));return users;}@POST@Consumes("application/json")@RolesAllowed("ADMIN")public Response createUser(User user) throws URISyntaxException{if(user.getFirstName() == null || user.getLastName() == null) {return Response.status(400).entity("Please provide all mandatory inputs").build();}user.setId(DB.values().size()+1);user.setUri("/user-management/"+user.getId());DB.put(user.getId(), user);return Response.status(201).contentLocation(new URI(user.getUri())).build();}@GET@Path("/{id}")@Produces("application/json")@PermitAllpublic Response getUserById(@PathParam("id") int id) throws URISyntaxException{User user = DB.get(id);if(user == null) {return Response.status(404).build();}return Response.status(200).entity(user).contentLocation(new URI("/user-management/"+id)).build();}@PUT@Path("/{id}")@Consumes("application/json")@Produces("application/json")@RolesAllowed("ADMIN")public Response updateUser(@PathParam("id") int id, User user) throws URISyntaxException{User temp = DB.get(id);if(user == null) {return Response.status(404).build();}temp.setFirstName(user.getFirstName());temp.setLastName(user.getLastName());DB.put(temp.getId(), temp);return Response.status(200).entity(temp).build();}@DELETE@Path("/{id}")@RolesAllowed("ADMIN")public Response deleteUser(@PathParam("id") int id) throws URISyntaxException {User user = DB.get(id);if(user != null) {DB.remove(user.getId());return Response.status(200).build();}return Response.status(404).build();}static{User user1 = new User();user1.setId(1);user1.setFirstName("John");user1.setLastName("Wick");user1.setUri("/user-management/1");User user2 = new User();user2.setId(2);user2.setFirstName("Harry");user2.setLastName("Potter");user2.setUri("/user-management/2");DB.put(user1.getId(), user1);DB.put(user2.getId(), user2);}}
您可以在上面突出显示的行中查看与安全相关的 JAX-RS 注解。
使用 JAX-RS ContainerRequestFilter编写安全过滤器
现在是时候编写我们的安全过滤器了,该过滤器将检查传入的请求,获取授权信息(在此示例中为基本认证),然后匹配用户名和密码,最后将通过其角色来验证用户的访问级别。 如果一切都匹配,则将访问 API,否则用户将获得拒绝访问响应。
package com.howtodoinjava.jerseydemo;import java.lang.reflect.Method;import java.util.Arrays;import java.util.Base64;import java.util.HashSet;import java.util.List;import java.util.Set;import java.util.StringTokenizer;import javax.annotation.security.DenyAll;import javax.annotation.security.PermitAll;import javax.annotation.security.RolesAllowed;import javax.ws.rs.container.ContainerRequestContext;import javax.ws.rs.container.ResourceInfo;import javax.ws.rs.core.Context;import javax.ws.rs.core.MultivaluedMap;import javax.ws.rs.core.Response;import javax.ws.rs.ext.Provider;/*** This filter verify the access permissions for a user based on* user name and password provided in request* */@Providerpublic class SecurityFilter implements javax.ws.rs.container.ContainerRequestFilter{private static final String AUTHORIZATION_PROPERTY = "Authorization";private static final String AUTHENTICATION_SCHEME = "Basic";private static final Response ACCESS_DENIED = Response.status(Response.Status.UNAUTHORIZED).build();private static final Response ACCESS_FORBIDDEN = Response.status(Response.Status.FORBIDDEN).build();private static final Response SERVER_ERROR = Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();@Contextprivate ResourceInfo resourceInfo;@Overridepublic void filter(ContainerRequestContext requestContext){Method method = resourceInfo.getResourceMethod();//Access allowed for allif( ! method.isAnnotationPresent(PermitAll.class)){//Access denied for allif(method.isAnnotationPresent(DenyAll.class)){requestContext.abortWith(ACCESS_FORBIDDEN);return;}//Get request headersfinal MultivaluedMap<String, String> headers = requestContext.getHeaders();//Fetch authorization headerfinal List<String> authorization = headers.get(AUTHORIZATION_PROPERTY);//If no authorization information present; block accessif(authorization == null || authorization.isEmpty()){requestContext.abortWith(ACCESS_DENIED);return;}//Get encoded username and passwordfinal String encodedUserPassword = authorization.get(0).replaceFirst(AUTHENTICATION_SCHEME + " ", "");//Decode username and passwordString usernameAndPassword = null;try {usernameAndPassword = new String(Base64.getDecoder().decode(encodedUserPassword));} catch (Exception e) {requestContext.abortWith(SERVER_ERROR);return;}//Split username and password tokensfinal StringTokenizer tokenizer = new StringTokenizer(usernameAndPassword, ":");final String username = tokenizer.nextToken();final String password = tokenizer.nextToken();//Verifying Username and passwordif(!(username.equalsIgnoreCase("admin") && password.equalsIgnoreCase("password"))){requestContext.abortWith(ACCESS_DENIED);return;}//Verify user accessif(method.isAnnotationPresent(RolesAllowed.class)){RolesAllowed rolesAnnotation = method.getAnnotation(RolesAllowed.class);Set<String> rolesSet = new HashSet<String>(Arrays.asList(rolesAnnotation.value()));//Is user valid?if( ! isUserAllowed(username, password, rolesSet)){requestContext.abortWith(ACCESS_DENIED);return;}}}}private boolean isUserAllowed(final String username, final String password, final Set<String> rolesSet){boolean isAllowed = false;//Step 1\. Fetch password from database and match with password in argument//If both match then get the defined role for user from database and continue; else return isAllowed [false]//Access the database and do this part yourself//String userRole = userMgr.getUserRole(username);String userRole = "ADMIN";//Step 2\. Verify user roleif(rolesSet.contains(userRole)){isAllowed = true;}return isAllowed;}}
示例
将项目作为 Spring 启动应用程序运行。 现在测试其余资源。
访问 GET /users资源

GET 用户集合
访问 POST /users资源,没有身份验证详细信息
查看返回的状态代码 401 。

POST API 所需的认证
已添加身份验证详细信息的访问 POST /users资源
使用此链接生成 base64 编码的用户名和密码组合,以传递到Authorization标头中。

使用认证信息请求成功
将我的问题放在评论部分。
学习愉快!
