JAX-RS-EJB中的SecurityContext为空

问题描述

我的后端JAX-RS应用程序中存在与我的EJB中的SecurityContext null有关的问题。

该应用程序涉及Users和Todo对象。一个用户可以有许多待办事项,这些待办事项基本上是表示便利贴的对象,描述了要做的事情。

我的项目中类的一般结构如下:

前端------> SecurityFilter->资源类(JAX-RS)->服务类(EJB)-> DB

对于此特定示例,这些类是:

前端------> SecurityFilter.java -> TodoRest.java(JAX-RS)-> TodoService.java(EJB)->数据库

将方法公开到前端的资源类称为“ TodoRest”,它具有一个名为getTodosByTodoUser的方法,该方法返回用户的所有待办事项。

由于此方法使用@Authz进行注释(报告如下:它是将方法链接到我的SecurityFilter的注释类),因此当我们调用它时,将触发SecurityFilter类。

这是 SecurityFilter 类:

package academy.learnprogramming.config;
 
import academy.learnprogramming.services.SecurityUtil;
import academy.learnprogramming.utils.Constants;
import io.jsonwebtoken.Jwts;
import org.apache.log4j.Logger;
 
import javax.annotation.Priority;
import javax.ejb.Stateless;
import javax.inject.Inject;
import javax.ws.rs.NotAuthorizedException;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.SecurityContext;
import javax.ws.rs.ext.Provider;
import javax.ws.rs.Priorities;
import java.io.IOException;
import java.security.Key;
import java.security.Principal;
 
@Provider
@Authz
@Priority(Priorities.AUTHENTICATION)
public class SecurityFilter implements ContainerRequestFilter {
 
    @Inject
    private Logger LOG;
 
    @Inject
    SecurityUtil securityUtil;
 
    @Inject
    private ApplicationState applicationState;
 
    @Override
    public void filter(ContainerRequestContext requestContext) throws IOException {
 
        String authHeader = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION);
        if(authHeader == null || authHeader.isEmpty() || !authHeader.startsWith(Constants.AUTH_TOKEN_BEARER_PREFIX)) {
            LOG.error("Wrong or no authorization header found: " + authHeader);
            //TODO - Beautify this exception
            throw new NotAuthorizedException(Response
                    .status(Response.Status.UNAUTHORIZED)
                    .entity("No authorization header provided")
                    .build());
        }
 
        String token = authHeader.substring(Constants.AUTH_TOKEN_BEARER_PREFIX.length()).trim();
        String email = applicationState.getEmail();
        try {
            //TODO - Parse the token
 
            LOG.info("applicationState.getEmail(): " + email);
            Key key = securityUtil.generateKey(applicationState.getEmail());
 
            //If it doesn't throw exception,then the token is valid
            Jwts.parserBuilder()
                    .setSigningKey(key)
                    .build()
                    .parseClaimsJws(token)
                    .getBody()
                    .getSubject()
                    .equals(applicationState.getEmail());
 
            LOG.info("originalSecurityContext.getUserPrincipal before the token setting: " + requestContext.getSecurityContext().getUserPrincipal());
 
            SecurityContext originalSecurityContext = requestContext.getSecurityContext();
 
            requestContext.setSecurityContext(new SecurityContext() { // <---------- THIS IS THE CHANGE OF THE SECURITY CONTEXT
 
                @Override
                public Principal getUserPrincipal() {
                    return new Principal() {
                        @Override
                        public String getName() {
                            return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody().getSubject();
                        }
                    };
                }
 
                @Override
                public boolean isUserInRole(String role) {
                    return originalSecurityContext.isUserInRole(role);
                }
 
                @Override
                public boolean isSecure() {
                    return originalSecurityContext.isSecure();
                }
 
                @Override
                public String getAuthenticationScheme() {
                    return originalSecurityContext.getAuthenticationScheme();
                }
            });
 
            LOG.info("securityContext.getUserPrincipal after the token setting: " + requestContext.getSecurityContext().getUserPrincipal().getName());
            LOG.debug("User identified by mail " + applicationState.getEmail() + " - Token parsed successfully: " + token);
            LOG.info("Token parsed successfully");
 
        } catch (Exception e) {
            LOG.error("Invalid token: " + token);
            requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED).build());
        }
 
     }
}

SecurityFilter类成功更改了请求的SecurityContext,将用户电子邮件作为主体插入。实际上,我在TodoRest类中注入了SecurityContext(带有@Context注释)(在下面进行报告),​​并且在执行过滤器之后,我在getTodosByTodoUser方法中记录了securityContext.getUserPrincipal(),该方法正确地返回了请求的用户电子邮件。

这是JAX-RS TodoRest 类:

package academy.learnprogramming.rest;
 
import academy.learnprogramming.config.Authz;
import academy.learnprogramming.dto.TodoDTO;
import academy.learnprogramming.entities.Todo;
import academy.learnprogramming.entities.TodoUser;
import academy.learnprogramming.services.TodoService;
import academy.learnprogramming.services.TodoUserService;
import academy.learnprogramming.utils.Constants;
import org.apache.log4j.Logger;
import javax.inject.Inject;
import javax.ws.rs.*;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.SecurityContext;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
 
@Path("todo")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public class TodoRest {
 
    @Inject
    private Logger LOG;
 
    @Inject
    TodoService todoService;
 
    @Inject
    TodoUserService todoUserService;
 
    @Context
    SecurityContext securityContext;
 
    @Authz
    @Path("new")
    @POST
    public Response createTodo(Todo todo) {
        LOG.info("rest service - createTodo");
        todoService.createTodo(todo);
        return Response.ok(todo).build();
    }
 
    @Authz
    @Path("todosByTodoUser")
    @GET
    public List<Todo> getTodosByTodoUser(TodoUser todoUser) {
        LOG.info("securityContext: " + securityContext.getUserPrincipal().getName()); // <---------- HERE I LOG THE SECURITY CONTEXT,WHICH IS CORRECTLY CHANGED BY SECURITY FILTER CLASS
        return todoService.getAllTodos();
 
    }
}

现在在getTodosByTodoUser中,我将调用TodoService EJB,并在其中注入了SecurityContext。可悲的是,在EJB内部,SecurityContext是nullB,我无法检索到getUserPrincipal()。

这是EJB TodoService 类:

package academy.learnprogramming.services;
 
import academy.learnprogramming.entities.Todo;
import academy.learnprogramming.entities.TodoUser;
import org.apache.log4j.Logger;
import org.dozer.DozerBeanMapper;
 
import javax.annotation.PostConstruct;
import javax.ejb.SessionContext;
import javax.ejb.Stateless;
import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.SecurityContext;
import java.util.List;
 
@Stateless
public class TodoService {
 
    @Inject
    private Logger LOG;
 
    @Inject
    private EntityManager entityManager;
 
    @Inject
    private TodoUserService todoUserService;
 
    @Inject
    private DozerBeanMapper dozenBeanMapper;
 
    @Context
    SecurityContext securityContext;
 
 
    private String todoUserEmail;
 
    @PostConstruct
    private void init() {
        //TODO - Get user mail
        todoUserEmail = "";
    }
 
    public Todo createTodo(Todo todo) {
        //Persist into db
        TodoUser todoUser = todoUserService.findUserByEmail(todo.getTodoOwner().getEmail());
        if(todoUser != null) {
            todo.setTodoOwner(todoUser);
            todoUser.getTodos().add(todo);
            entityManager.persist(todo);
        }
        return todo;
    }
 
 
    public List<Todo> getAllTodos() {
        String securityContextEmail = securityContext.getUserPrincipal().getName();  // <---------- NULL,everything explodes
        LOG.info("securityContextEmail:" + securityContextEmail);
 
        List<Todo> todos = entityManager.createNamedQuery(Todo.TODO_FIND_ALL_TODOS_BY_OWNER,Todo.class)
                .setParameter("email",securityContext.getUserPrincipal().getName())
                .getResultList();
 
        return todos;
    }
 
    public List<Todo> getAllTodosByTask(String taskText) {
        return entityManager.createNamedQuery(Todo.TODO_FIND_TODO_BY_TASK,Todo.class)
                .setParameter("task","%" + taskText + "%")
                .setParameter("email","[email protected]")
                .getResultList();
    }
 
 
}

为完整起见,我添加了 @Authz 注释类,该注释类将注释@Authz链接到SecurityFilter类:

package academy.learnprogramming.config;

import javax.ws.rs.NameBinding;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@NameBinding
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE})
public @interface Authz {

}

我在线检查了,发现:

  • 有人在方法内部将securityContext从REST类传递到EJB服务类,但这非常丑陋,因为我们必须为每个方法传递securityContext;
  • 有人使用@EJBContext,但是我尝试使用它而没有成功。

有人可以帮助我了解我在做什么错吗? 我特别想了解:

  • 如何将安全上下文从Jax-RS类传播到ejb bean?
  • 是否按照我的预期自动管理了安全信息?或..
  • 是否需要改进或添加其他jboss-?. xml配置文件?或..
  • 我是否需要在调用的Jax-RS Bean中进行某些更改,以便将安全信息传播到被调用的bean?或..
  • 我是否假设有不正确的地方?

非常感谢您的宝贵时间。

编辑:我添加了此链接,他们说他们有解决办法,但引用的页面不再存在。 JAX-RS + EJB,SecurityContext inside ejb is null,on WildFly 10.1。 我尝试按照解决方案的建议进行操作,但是没有用。

解决方法

暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!

如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。

小编邮箱:dio#foxmail.com (将#修改为@)