spring boot Jersey基于角色的安全控制使用JAX-RS的注解

位置:首页>文章>详情   分类: 教程分享 > Java教程   阅读(2804)   2023-03-28 11:29:14

一、摘要说明

    学习使用spring boot和Jersey框架来创建JAX-RS 2.0 REST APIs,并且使用JAX-RS注解来添加基于角色的安全控制。例如注解@PermitAll@RolesAllowed 或者 @DenyAll。
   
    本博客学习纲要:
  • 项目结构    
  • 创建REST api
  • 使用jax-rs注释的安全REST api
  • 使用jax-rs容器请求过滤器编写安全过滤器
  • 一个例子演示

二、项目结构

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

三、创建 REST APIs

1.去 Spring Initializr网站,创建一个spring boot项目并且添加 Jersey (JAX-RS) 依赖
创建项目2.导入项目到eclipse中
以zip文件的格式生成项目。在你电脑的某个地方把它取出来。将该项目作为“Existing maven application”导入eclipse。

3.检查maven的依赖
检查maven的依赖文件中必须有spring-boot-starter-jersey
<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>
4.创建 REST APIs
现在创建一些jax-rs资源,我们将访问测试阶段。我已经创建了UserResource类。

UserResource.java
 
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

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

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
}
5.配置Jersey
现在我们有了一个jax-rs资源,我们希望从spring启动应用程序中访问它,其中包括Jersey依赖项。让我们将这个资源注册为泽西资源。
import org.glassfish.jersey.server.ResourceConfig;
import org.springframework.stereotype.Component;
 
@Component
public class JerseyConfig extends ResourceConfig 
{
    public JerseyConfig() 
    {
        register(SecurityFilter.class);
        register(UserResource.class);
    }
}
 
  • 查看@component注释。它使这个类可以注册,而spring引导自动扫描源文件夹中的java类。
  • 资源econfig提供了简化jax-rs组件注册的高级功能。
  • SecurityFilter类是实际的auth细节处理器,我们将在本教程后面看到。

使用SpringBootServletInitializer扩展spring应用程序
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);
    }
}

四、使用JAX-RS Annotations创建安全的REST APIs

现在,当我们的api准备好时,我们将开始保护它们。让我们使用jax-rs注释对api进行注释,基于它们所需的访问级别和允许访问它们的用户角色。
@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")
    @PermitAll
    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")
    @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);
    }
}
你可以看到注解 角色注解@RolesAllowed

五、使用JAX-RS ContainerRequestFilter来编写security filter

  现在是编写我们的安全过滤器的时候了,它将检查传入的请求,获取授权信息(在本例中是基本身份验证),然后将匹配用户名和密码,最后它将通过它的角色来验证用户的访问级别。如果一切都匹配,API将被访问,否则用户将获得访问被拒绝的响应。
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
 * */
@Provider
public 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();
     
    @Context
    private ResourceInfo resourceInfo;
      
    @Override
    public void filter(ContainerRequestContext requestContext)
    {
        Method method = resourceInfo.getResourceMethod();
        //Access allowed for all 
        if( ! method.isAnnotationPresent(PermitAll.class))
        {
            //Access denied for all 
            if(method.isAnnotationPresent(DenyAll.class))
            {
                requestContext.abortWith(ACCESS_FORBIDDEN);
                return;
            }
              
            //Get request headers
            final MultivaluedMap<String, String> headers = requestContext.getHeaders();
              
            //Fetch authorization header
            final List<String> authorization = headers.get(AUTHORIZATION_PROPERTY);
              
            //If no authorization information present; block access
            if(authorization == null || authorization.isEmpty())
            {
                requestContext.abortWith(ACCESS_DENIED);
                return;
            }
              
            //Get encoded username and password
            final String encodedUserPassword = authorization.get(0).replaceFirst(AUTHENTICATION_SCHEME + " ", "");
              
            //Decode username and password
            String usernameAndPassword = null;
            try {
                usernameAndPassword = new String(Base64.getDecoder().decode(encodedUserPassword));
            } catch (Exception e) {
                requestContext.abortWith(SERVER_ERROR);
                return;
            }
  
            //Split username and password tokens
            final StringTokenizer tokenizer = new StringTokenizer(usernameAndPassword, ":");
            final String username = tokenizer.nextToken();
            final String password = tokenizer.nextToken();
              
            //Verifying Username and password
            if(!(username.equalsIgnoreCase("admin") && password.equalsIgnoreCase("password"))){
                requestContext.abortWith(ACCESS_DENIED);
                return;
            }
              
            //Verify user access
            if(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 role
        if(rolesSet.contains(userRole))
        {
            isAllowed = true;
        }
        return isAllowed;
    }
}
 

六、例子演示

运行这个项目作为Spring引导应用程序。现在测试rest资源。
访问 GET /users resource
例子演示
使用POST方式并且没有认证信息去访问/users 资源接口

可以看到返回的状态 status code 401.
401
使用POST方式并且添加认证信息去访问/users 资源接口
使用此链接生成base64编码的用户名和密码组合,以传递到授权头。
成功访问user
 
标签: spring boot Jersey
地址:https://www.leftso.com/article/226.html

相关阅读

spring boot整合Jersey2.x实现JAX-RS webservice
Spring Boot 基于角色的安全控制使用JAX-RS的注解,spring boot,Jersey
Java编程之Spring Boot通过JMSTemplate 整合ActiveMQ
Spring Boot 2.0 有哪些新特性_Spring Boot 2.0新功能,在本文中,我们将探讨为Spring Boot 2.0计划的一些更改和功能。我们还会描述这些变化如何帮助我们提高...
引言    通过之前spring boot mybatis 整合的讲解: spring boot mybaties整合  (spring boot mybaties 整合 基于Java注解方式写...
Spring Boot 2.0,Spring框架的Spring Boot 中的Spring Boot Actuator变化讲解。并且了解如何在Spring Boot 2.0中使用Actuator...
Spring Boot 2.0 绑定properties属性资源文件 Spring Boot 2.0 读取properties配置文件值 Spring Boot 2.0获取properties配...
spring boot入门,spring boot是一个崭新的spring框架分支项目,本文讲解其属性配置相关
spring boot 1.5整合redis实现spring的缓存框架,spring boot,redis
spring boot是一个崭新的spring框架分支项目,本文讲解基本的数据库配置