Jetty не может найти login.jsp при использовании стека Jersey-Guice-Shiro

(Обновление от 2 января 2013 г.) Теперь я добавил весь код плюс pom.xml в github по адресу https://github.com/AndyWi/GuiceJerseyJettyShiroExample (конец обновления)

Я пытаюсь добавить аутентификацию на основе форм в свое простое приложение с помощью Широ. Приложение использует Guice 3.0, Jersey 1.16 и Shiro 1.2.1, работающие на встроенном Jetty 9.0.0.M4.

Моя проблема в том, что (насколько я понимаю) Широ нужно, чтобы login.jsp был доступен через Guice, а затем добавлялся в цепочку фильтров Широ. Однако, когда я это делаю, Jetty не может найти login.jsp. Когда я исключаю login.jsp из фильтра Guice, Jetty может найти jsp, но тогда он недоступен Широ, поэтому аутентификация не работает.

Итак, в моем коде начальной загрузки я использую эту строку, чтобы добавить login.jsp в фильтр Guice:

webAppContext.addFilter(GuiceFilter.class, "/login.jsp", null);

В моем ShiroWebModule я добавляю login.jsp следующим образом:

addFilterChain("/login.jsp", AUTHC);

Я потратил смехотворно много времени на поиск ответа на этот вопрос без каких-либо намеков на то, что у кого-то еще была такая же проблема, а это значит, что я, очевидно, делаю что-то очень простое неправильно! Но я не могу понять, что это такое. Я был бы очень признателен, если бы кто-нибудь помог мне пройти через это.

Я сократил свой проект до небольшого примера, чтобы продемонстрировать проблему. Все, что нужно сделать, это принять остаточный URL-адрес /api/uuid, перенаправить пользователя на login.jsp, принять любую комбинацию имени пользователя и пароля для аутентификации, а затем вернуть новый UUID из службы /api/uuid; пользователь также должен оставаться в системе для будущих запросов. Вот полный код в надежде, что он поможет кому-нибудь обнаружить проблему:

Начальная загрузка:

package eg.guicejerseyjettyshiro;

import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.DefaultServlet;
import org.eclipse.jetty.webapp.WebAppContext;
import com.google.inject.servlet.GuiceFilter;
import eg.guicejerseyjettyshiro.modules.EgGuiceServletContextListener;

public class Bootstrap {

    public static void main(String[] args) throws Exception {
        Server server = new Server(8081);

        WebAppContext webAppContext = new WebAppContext();
        webAppContext.setContextPath("/");
        webAppContext.setResourceBase("src/main/webapp/");
        webAppContext.setParentLoaderPriority(true);

        webAppContext.addEventListener(new EgGuiceServletContextListener());

        webAppContext.addFilter(GuiceFilter.class, "/api/*", null);

        // **** Shiro needs login.jsp to go through the GuiceFilter,
        // but Jetty can't find the jsp when this happens. Commenting
        // out this line lets Jetty find the jsp, but Shiro can't see it:
        webAppContext.addFilter(GuiceFilter.class, "/login.jsp", null);

        webAppContext.addServlet(DefaultServlet.class, "/");

        server.setHandler(webAppContext);

        server.start();
        server.join();
    }

}

EgGuiceServletContextListener:

package eg.guicejerseyjettyshiro.modules;

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.servlet.GuiceServletContextListener;

public class EgGuiceServletContextListener extends GuiceServletContextListener {

    private ServletContext servletContext;

    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        this.servletContext = servletContextEvent.getServletContext();
        super.contextInitialized(servletContextEvent);
    }

    @Override
    protected Injector getInjector() {
        return Guice.createInjector(
                new EgJerseyServletModule(),
                new EgShiroWebModule(this.servletContext));
    }

}

Например, модуль сервлетов Джерси:

package eg.guicejerseyjettyshiro.modules;

import org.apache.shiro.guice.web.GuiceShiroFilter;
import com.sun.jersey.guice.JerseyServletModule;
import com.sun.jersey.guice.spi.container.servlet.GuiceContainer;
import eg.guicejerseyjettyshiro.dao.UuidDao;
import eg.guicejerseyjettyshiro.services.UuidService;

public class EgJerseyServletModule extends JerseyServletModule {

    @Override
    protected void configureServlets() {
        bindings();
        filters();
    }

    private void bindings() {
        bind(UuidDao.class);
        bind(UuidService.class);
    }

    private void filters() {
        filter("/*").through(GuiceShiroFilter.class);
        filter("/*").through(GuiceContainer.class);
    }

}

ЭгШироВебМодуле:

package eg.guicejerseyjettyshiro.modules;

import javax.servlet.ServletContext;
import org.apache.shiro.guice.web.ShiroWebModule;
import com.google.inject.name.Names;
import eg.guicejerseyjettyshiro.realms.EgAuthorizingRealm;

public class EgShiroWebModule extends ShiroWebModule {

    public EgShiroWebModule(ServletContext servletContext) {
        super(servletContext);
    }

    @Override
    protected void configureShiroWeb() {
        bindConstant().annotatedWith(Names.named("shiro.globalSessionTimeout")).to(30000L);

        bindRealm().to(EgAuthorizingRealm.class).asEagerSingleton();

        addFilterChain("/login.jsp", AUTHC);
        addFilterChain("/api/*", AUTHC);
    }

}

Например, область авторизации:

package eg.guicejerseyjettyshiro.realms;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

public class EgAuthorizingRealm extends AuthorizingRealm {

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
            throws AuthenticationException {
        UsernamePasswordToken upToken = (UsernamePasswordToken) token;
        System.out.println("In EgAuthorizingRealm.doGetAuthenticationInfo for: " + upToken.getUsername() + "/" + new String(upToken.getPassword()) + " - remember=" + upToken.isRememberMe());
        return new SimpleAuthenticationInfo(upToken.getUsername(), upToken.getPassword(), getName());
    }

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection pc) {
        System.out.println("In EgAuthorizingRealm.doGetAuthorizationInfo");
        // Doing nothing just now
        return null;
    }

}

UuidService:

package eg.guicejerseyjettyshiro.services;

import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import eg.guicejerseyjettyshiro.dao.UuidDao;

@Path("/api/uuid")
@Produces({MediaType.APPLICATION_XML})
public class UuidService {

    private final UuidDao uuidDao;

    @Inject
    public UuidService(UuidDao uuidDao) {
        this.uuidDao = uuidDao;
    }

    @GET
    public String get() {
        Subject currentUser = SecurityUtils.getSubject();
        System.out.println("UuidService current user: " + currentUser.getPrincipal().toString());
        return "<uuid>" + this.uuidDao.generateUuid().toString() + "</uuid>";
    }

}

УидДао:

package eg.guicejerseyjettyshiro.dao;

import java.util.UUID;

public class UuidDao {

    public UUID generateUuid() {
        return UUID.randomUUID();
    }
}

логин.jsp:

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Please Login</title>
</head>
<body>

<form name="loginForm" action="" method="post">
<table align="left" border="0" cellspacing="0" cellpadding="3">
    <tr>
        <td>Username:</td>
        <td><input type="text" name="username" maxlength="30"></td>
    </tr>
    <tr>
        <td>Password:</td>
        <td><input type="password" name="password" maxlength="30"></td>
    </tr>
    <tr>
        <td colspan="2" align="left"><input type="checkbox" name="rememberMe"><font size="2">Remember Me</font></td>
    </tr>
    <tr>
        <td colspan="2" align="right"><input type="submit" name="submit" value="Login"></td>
    </tr>
</table> 
</form>

</body>
</html>

Хорошо, я думаю, это все. Я понимаю, что это довольно долго, поэтому большое спасибо, что дочитали до этого места. Это сводило меня с ума, поэтому любая помощь, которую вы можете мне оказать, будет очень признательна!

Спасибо, Энди.


person AndyW    schedule 27.12.2012    source источник
comment
Этот вопрос заметно очень подробный, теперь кто-то, кто знает, что значит переопределить GuiceServletContextListener JerseyServletWebModule ShiroWebModule AuthorizingRealm, должен прийти и дать вам ответ. У меня даже нет этих частей, чтобы проверить ваш пример. Единственная пара вещей, которые приходят на ум, — должен ли login.jsp фильтроваться как в WebAppContext, так и в ShiroWebModule? действительно ли файл login.jsp добавлен в банку в src/main/webapp?   -  person dlamblin    schedule 28.12.2012
comment
Привет dlamblin, спасибо за ответ. Что касается login.jsp, он добавлен — когда я комментирую строку в Bootstrap (webAppContext.addFilter(GuiceFilter.class, /login.jsp, null)), я могу попасть в /login.jsp. Кроме того, если я затем остановлю сервер, раскомментирую строку, а затем снова запущу сервер, я смогу отправить форму входа (предварительно загруженную до перезапуска сервера), которая затем войдет в систему, как и ожидалось. Когда я затем перехожу к /api/uuid, все работает, и я вхожу в систему, но я не могу перейти к login.jsp. Так что на данный момент я могу либо загрузить login.jsp, либо проверить подлинность, но не то и другое одновременно.   -  person AndyW    schedule 28.12.2012
comment
Привет dlamblin, и все, кто еще заинтересован в этом. Я добавил исходный код на github по адресу github.com/AndyWi/GuiceJerseyJettyShiroExample, поэтому, если вы если вы заинтересованы в том, чтобы решить эту проблему, пожалуйста, не стесняйтесь взять код и дайте мне знать, могу ли я чем-нибудь помочь. Спасибо, Энди.   -  person AndyW    schedule 02.01.2013
comment
+1 за очень подробный вопрос, который я когда-либо видел в SO   -  person Shmil The Cat    schedule 05.05.2013


Ответы (1)


На этот вопрос ответил Милан Баран на форумах пользователей Широ. Репозиторий github был обновлен, вот краткий обзор, если кому-то интересно:

В классе Bootstrap нам нужно добавить только один GuiceFilter для /*, а сервер по умолчанию вообще не нужен. Итак, это становится:

public static void main(String[] args) throws Exception {
    Server server = new Server(8081);

    WebAppContext webAppContext = new WebAppContext();
    webAppContext.setContextPath("/");
    webAppContext.setResourceBase("src/main/webapp/");
    webAppContext.setParentLoaderPriority(true);

    webAppContext.addEventListener(new EgGuiceServletContextListener());

    webAppContext.addFilter(GuiceFilter.class, "/*", null);

    server.setHandler(webAppContext);

    server.start();
    server.join();
}

Затем нам нужно обновить модуль сервлета трикотажа, чтобы связать DefaultServlet и GuiceContainer, и изменить фильтр через GuiceContainer, чтобы он проходил через /api вместо /*, например:

public class EgJerseyServletModule extends JerseyServletModule {

@Override
protected void configureServlets() {
    bindings();
    filters();
}

private void bindings() {
    bind(UuidDao.class);
    bind(UuidService.class);
    bind(DefaultServlet.class).asEagerSingleton();
    bind(GuiceContainer.class).asEagerSingleton();
    serve("/*").with(DefaultServlet.class);
}

private void filters() {
    filter("/*").through(GuiceShiroFilter.class);
    filter("/api/*").through(GuiceContainer.class);
}

}

Спасибо всем за помощь в этом! Энди.

person AndyW    schedule 03.01.2013