当前位置: 首页 > 知识库问答 >
问题:

映射Shiro的认证异常与Jersey的异常映射

赖淇
2023-03-14

首先,我对这个问题太长表示最诚挚的歉意,但老实说,我不知道如何缩短它,因为每个部分都是一个特例。诚然,我可能对此视而不见,因为我已经把头撞到墙上好几天了,我开始绝望了。

我向所有通读这本书的人表示最大的尊重和感谢。

我希望能够通过使用Jersey ExceptionMapers将Shiro的AuthenticationException及其子类映射到JAX-RS响应,Jersey例外映射器是使用Guice 3.0注入器创建的,它创建了一个嵌入式Jetty。

  • Guice 3.0
  • 码头9.2.12.v20150709
  • 球衣1.19.1
  • Shiro 1.2.4

嵌入式Jetty是使用Guice注入器创建的

// imports omitted for brevity
public class Bootstrap {

    public static void main(String[] args) throws Exception {

      /*
       * The ShiroWebModule is passed as a class
       * since it needs a ServletContext to be initialized
       */
        Injector injector = Guice.createInjector(new ServerModule(MyShiroWebModule.class));

        Server server = injector.getInstance(Server.class);

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

服务器模块绑定 Jetty 服务器的提供程序

public class ServerModule extends AbstractModule {

    Class<? extends ShiroWebModule> clazz;

    public ServerModule(Class <?extends ShiroWebModule> clazz) {
        this.clazz = clazz;
    }

    @Override
    protected void configure() {
        bind(Server.class)
         .toProvider(JettyProvider.withShiroWebModule(clazz))
         .in(Singleton.class);
    }

}

JettyProvider设置Jetty WebApplicationContext,注册Guice所需的ServletContextListener和其他一些东西,我留下了这些东西以确保不会隐藏“副作用”:

public class JettyProvider implements Provider<Server>{

    @Inject
    Injector injector;

    @Inject
    @Named("server.Port")
    Integer port;

    @Inject
    @Named("server.Host")
    String host;

    private Class<? extends ShiroWebModule> clazz;

    private static Server server;

    private JettyProvider(Class<? extends ShiroWebModule> clazz){
        this.clazz = clazz;
    }

    public static JettyProvider withShiroWebModule(Class<? extends ShiroWebModule> clazz){
        return new JettyProvider(clazz);
    }

    public Server get() {       

        WebAppContext webAppContext = new WebAppContext();
        webAppContext.setContextPath("/");

        // Set during testing only
        webAppContext.setResourceBase("src/main/webapp/");
        webAppContext.setParentLoaderPriority(true);

        webAppContext.addEventListener(
          new MyServletContextListener(injector,clazz)
        );

        webAppContext.addFilter(
          GuiceFilter.class, "/*",
          EnumSet.allOf(DispatcherType.class)
        );

        webAppContext.setThrowUnavailableOnStartupException(true);

        QueuedThreadPool threadPool = new QueuedThreadPool(500, 10);

        server = new Server(threadPool);

        ServerConnector connector = new ServerConnector(server);
        connector.setHost(this.host);
        connector.setPort(this.port);

        RequestLogHandler requestLogHandler = new RequestLogHandler();
        requestLogHandler.setRequestLog(new NCSARequestLog());

        HandlerCollection handlers = new HandlerCollection(true);

        handlers.addHandler(webAppContext);
        handlers.addHandler(requestLogHandler);

        server.addConnector(connector);
        server.setStopAtShutdown(true);
        server.setHandler(handlers);
        return server;
    }

}

在<code>MyServletContextListener

public class MyServletContextListener extends GuiceServletContextListener {

    private ServletContext servletContext;

    private Injector injector;

    private Class<? extends ShiroWebModule> shiroModuleClass;
    private ShiroWebModule module;

    public ServletContextListener(Injector injector,
            Class<? extends ShiroWebModule> clazz) {
        this.injector = injector;
        this.shiroModuleClass = clazz;
    }

    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {

        this.servletContext = servletContextEvent.getServletContext();
        super.contextInitialized(servletContextEvent);

    }

    @Override
    protected Injector getInjector() {
        /*
         * Since we finally have our ServletContext
         * we can now instantiate our ShiroWebModule
         */
        try {
            module = shiroModuleClass.getConstructor(ServletContext.class)
                    .newInstance(this.servletContext);
        } catch (InstantiationException | IllegalAccessException
                | IllegalArgumentException | InvocationTargetException
                | NoSuchMethodException | SecurityException e) {
            e.printStackTrace();
        }

    /*
     * Now, we create a child injector with the JerseyModule
     */
        Injector child = injector.createChildInjector(module,
                new JerseyModule());

        SecurityManager securityManager = child
                .getInstance(SecurityManager.class);
        SecurityUtils.setSecurityManager(securityManager);

        return child;
    }

}

JerseyModule是JerseyServletModule的一个子类,现在将所有内容放在一起:

public class JerseyModule extends JerseyServletModule {

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

    private void bindings() {

        bind(DefaultServlet.class).asEagerSingleton();
        bind(GuiceContainer.class).asEagerSingleton();
        serve("/*").with(DefaultServlet.class);
    }

    private void filters() {
        Map<String, String> params = new HashMap<String, String>();

    // Make sure Jersey scans the package
        params.put("com.sun.jersey.config.property.packages",
                "com.example.webapp");

        params.put("com.sun.jersey.config.feature.Trace", "true");

        filter("/*").through(GuiceShiroFilter.class,params);
        filter("/*").through(GuiceContainer.class, params);

        /* 
         * Although the ExceptionHandler is already found by Jersey
         * I bound it manually to be sure
         */
        bind(ExceptionHandler.class);

        bind(MyService.class);

    }

}

ExceptionHandler非常简单,如下所示:

@Provider
@Singleton
public class ExceptionHandler implements
        ExceptionMapper<AuthenticationException> {

    public Response toResponse(AuthenticationException exception) {
        return Response
                .status(Status.UNAUTHORIZED)
                .entity("auth exception handled")
                .build();
    }

}

现在,当我想访问受限资源并输入正确的主体/凭证组合时,一切都正常了。但是一旦输入一个不存在的用户或错误的密码,我希望Shiro抛出一个< code > AuthenticationException ,并希望它由上面的< code>ExceptionHandler处理。

利用Shiro在开始时提供的默认<code>AUTHC</code>过滤器,我注意到AuthenticationExceptions被无声地吞下,用户再次重定向到登录页面。

因此,我对Shiro的FormAuthenticationFilter进行了子类化,以在存在以下情况时抛出<code>AuthenticationException</code>:

public class MyFormAutheticationFilter extends FormAuthenticationFilter {

    @Override
    protected boolean onLoginFailure(AuthenticationToken token,
            AuthenticationException e, ServletRequest request,
            ServletResponse response) {
        if(e != null){
            throw e;
        }
        return super.onLoginFailure(token, e, request, response);
    }
}

我还尝试了抛出包装在MappableContainerException中的异常e

这两种方法都导致相同的问题:不是由定义的< code>ExceptionHandler处理异常,而是引发< code > javax . servlet . servlet exception :

  javax.servlet.ServletException: org.apache.shiro.authc.AuthenticationException: Unknown Account!
    at org.apache.shiro.web.servlet.AdviceFilter.cleanup(AdviceFilter.java:196)
    at org.apache.shiro.web.filter.authc.AuthenticatingFilter.cleanup(AuthenticatingFilter.java:155)
    at org.apache.shiro.web.servlet.AdviceFilter.doFilterInternal(AdviceFilter.java:148)
    at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:125)
    at org.apache.shiro.guice.web.SimpleFilterChain.doFilter(SimpleFilterChain.java:41)
    at org.apache.shiro.web.servlet.AbstractShiroFilter.executeChain(AbstractShiroFilter.java:449)
    at org.apache.shiro.web.servlet.AbstractShiroFilter$1.call(AbstractShiroFilter.java:365)
    at org.apache.shiro.subject.support.SubjectCallable.doCall(SubjectCallable.java:90)
    at org.apache.shiro.subject.support.SubjectCallable.call(SubjectCallable.java:83)
    at org.apache.shiro.subject.support.DelegatingSubject.execute(DelegatingSubject.java:383)
    at org.apache.shiro.web.servlet.AbstractShiroFilter.doFilterInternal(AbstractShiroFilter.java:362)
    at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:125)
    at com.google.inject.servlet.FilterDefinition.doFilter(FilterDefinition.java:163)
    at com.google.inject.servlet.FilterChainInvocation.doFilter(FilterChainInvocation.java:58)
    at com.google.inject.servlet.ManagedFilterPipeline.dispatch(ManagedFilterPipeline.java:118)
    at com.google.inject.servlet.GuiceFilter.doFilter(GuiceFilter.java:113)
    at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
    at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:585)
    at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143)
    at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:577)
    at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:223)
    at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1127)
    at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:515)
    at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:185)
    at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1061)
    at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)
    at org.eclipse.jetty.server.handler.HandlerCollection.handle(HandlerCollection.java:110)
    at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:97)
    at org.eclipse.jetty.server.Server.handle(Server.java:499)
    at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:310)
    at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:257)
    at org.eclipse.jetty.io.AbstractConnection$2.run(AbstractConnection.java:540)
    at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:635)
    at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:555)
    at java.lang.Thread.run(Thread.java:744)
Caused by: org.apache.shiro.authc.AuthenticationException: Unknown Account!
    at com.example.webapp.security.MyAuthorizingRealm.doGetAuthenticationInfo(MyAuthorizingRealm.java:27)
    at org.apache.shiro.realm.AuthenticatingRealm.getAuthenticationInfo(AuthenticatingRealm.java:568)
    at org.apache.shiro.authc.pam.ModularRealmAuthenticator.doSingleRealmAuthentication(ModularRealmAuthenticator.java:180)
    at org.apache.shiro.authc.pam.ModularRealmAuthenticator.doAuthenticate(ModularRealmAuthenticator.java:267)
    at org.apache.shiro.authc.AbstractAuthenticator.authenticate(AbstractAuthenticator.java:198)
    at org.apache.shiro.mgt.AuthenticatingSecurityManager.authenticate(AuthenticatingSecurityManager.java:106)
    at org.apache.shiro.mgt.DefaultSecurityManager.login(DefaultSecurityManager.java:270)
    at org.apache.shiro.subject.support.DelegatingSubject.login(DelegatingSubject.java:256)
    at org.apache.shiro.web.filter.authc.AuthenticatingFilter.executeLogin(AuthenticatingFilter.java:53)
    at org.apache.shiro.web.filter.authc.FormAuthenticationFilter.onAccessDenied(FormAuthenticationFilter.java:154)
    at org.apache.shiro.web.filter.AccessControlFilter.onAccessDenied(AccessControlFilter.java:133)
    at org.apache.shiro.web.filter.AccessControlFilter.onPreHandle(AccessControlFilter.java:162)
    at org.apache.shiro.web.filter.PathMatchingFilter.isFilterChainContinued(PathMatchingFilter.java:203)
    at org.apache.shiro.web.filter.PathMatchingFilter.preHandle(PathMatchingFilter.java:178)
    at org.apache.shiro.web.servlet.AdviceFilter.doFilterInternal(AdviceFilter.java:131)
    ... 32 more

鉴于环境无法更改,我如何实现服务器实例仍然可以通过Guice请求,而Shiro的异常则使用泽西岛的自动发现的异常映射器进行处理?

共有2个答案

司徒英卓
2023-03-14

我也没有找到这样做的方法。看起来在认证期间,Jersey过滤器/处理程序在Shiro servlet栈上是不活动的。作为专门针对AuthenticationException的一个变通办法,我选择覆盖AdviceFilter::cleanup(...)方法,并直接返回自定义消息。

public class MyTokenAuthenticatingFilter extends AuthenticatingFilter {

protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
    // regular auth/token creation
}

@Override
protected void cleanup(ServletRequest request, ServletResponse response, Exception existing) throws ServletException, IOException {

    HttpServletResponse httpResponse = (HttpServletResponse)response;
    if ( null != existing ) {
        httpResponse.setContentType(MediaType.APPLICATION_JSON);
        httpResponse.getOutputStream().write(String.format("{\"error\":\"%s\"}", existing.getMessage()).getBytes());
        httpResponse.setStatus(Response.Status.FORBIDDEN.getStatusCode());
        existing = null; // prevent Shiro from tossing a ServletException
    }
    super.cleanup(request, httpResponse, existing);
}
}

当身份验证成功时,ExceptionMapper可以很好地处理Jersey控制器上下文中引发的异常。

韩峰
2023-03-14

这个问题对我来说太复杂了,我无法在我这边重现,但我看到了一个我认为是答案的问题,如果我发现答案是错的,我会删除这个答案。

你这样做:

@Provider
@Singleton
public class ExceptionHandler implements
        ExceptionMapper<AuthenticationException> {

哪一个是正确的,你应该像这个问题一样绑定这两个注释。但是,您的不同之处在于:

/* 
 * Although the ExceptionHandler is already found by Jersey
 * I bound it manually to be sure
 */
bind(ExceptionHandler.class);

类定义中的注释比模块的< code>configure()方法中的注释优先级低,这意味着当您“手动绑定它以确保安全”时,您正在删除注释。试着删除那行代码,看看是否能解决你的问题。如果它不能解决问题,那就把它删除掉,因为我确信这至少是问题的一部分——这个声明删除了那些重要的注释。

 类似资料:
  • 我正在努力使用Java Spring Hibernate,我正在尝试实现Oauth2,在通过@ManyToMany将表用户连接到角色时,我不断遇到错误。我已经阅读了所有关于我的问题的答案,无论我尝试什么,我仍然得到了一个组织。冬眠映射异常。 以下是我正在努力做的事情的全部细节。 数据库结构 角色。JAVA 使用者JAVA 依赖性 问题: org.springframework.beans.fact

  • 是否强制将我的外键实体从ClassA映射到ClassB中的主实体?

  • 我有一个映射到字符串值的特定键的映射列表。 类似于<代码>列表 目标:浏览此地图列表,并收集所有地图中单个键的值。 我是怎么做到的- 问题是:如果没有这样的密钥,我会因为a.get(key)而出现异常!因为求平均值会得到一个空值。如何检查或使lambda忽略任何此类地图并继续前进。 我知道我可以在

  • 我认为错误并不在注释中,因为我更改了几次注释,仍然得到了相同的异常。

  • 我尝试过使用SqlBulkCopy在不同的数据库和模式之间迁移数据。这是sql命令从源数据库获取数据: 来自HAN07M002SHIIRE 这是目标表架构: 主键群集([仕入先コード] ASC)打开[主](PAD\u INDEX=OFF,STATISTICS\u NORECOMPUTE=OFF,IGNORE\u DUP\u KEY=OFF,ALLOW\u ROW\u LOCKS=ON,ALLOW\

  • 问题内容: 我想在我的静态服务中捕获json映射异常,以防输入json无效。 它抛出,但我不知道如何或在何处捕获此异常。我想捕获此异常并发送回适当的错误响应。 一切正常,但是如果json主体格式不正确,则会引发异常。我想抓住这个例外。 问题答案: 最终对我有用的是声明的提供者,例如