强制 Tomcat 通过 http 使用安全的 JSESSIONID cookie

Posted

技术标签:

【中文标题】强制 Tomcat 通过 http 使用安全的 JSESSIONID cookie【英文标题】:Forcing Tomcat to use secure JSESSIONID cookie over http 【发布时间】:2013-02-06 00:25:27 【问题描述】:

有没有办法配置 Tomcat 7 以在所有情况下创建带有安全标志的 JSESSIONID cookie?

仅当通过 https 建立连接时,通常的配置会导致 Tomcat 使用安全标志标记会话 cookie。但是在我的生产场景中,Tomcat 位于一个反向代理/负载均衡器后面,它处理(和终止)https 连接并通过 http 联系 tomcat。

我能否以某种方式强制使用 Tomcat 的会话 cookie 上的安全标志,即使连接是通过纯 http 进行的?

【问题讨论】:

【参考方案1】:

ServletContext.getSessionCookieConfig().setSecure(true)

【讨论】:

自 servlet 3 docs.oracle.com/javaee/6/api/javax/servlet/…【参考方案2】:

最后,与我最初的测试相反,web.xml 解决方案在 Tomcat 7 上为我工作。

例如我将此 sn-p 添加到 web.xml 中,即使反向代理通过纯 HTTP 联系 tomcat,它也会将会话 cookie 标记为安全。

<session-config>
    <cookie-config>
        <http-only>true</http-only>
        <secure>true</secure>
    </cookie-config>
</session-config>

【讨论】:

【参考方案3】:

另一种方法,类似于 Mark 的方法,是使用 SessionCookieConfig,但将其设置在 JNDI 配置的上下文侦听器中:

代码:

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.SessionCookieConfig;


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


public class JndiSessionCookieConfigListener implements ServletContextListener 
    private static final Logger logger = LoggerFactory.getLogger( JndiSessionCookieConfigListener.class );

    private volatile Context jndiSessionCookieConfig;
    private volatile SessionCookieConfig sessionCookieConfig;

    @Override
    public void contextInitialized( ServletContextEvent sce ) 
        String listenerName = getClass().getSimpleName();
        try 
            logger.info( "JNDI override session cookie config found for ", listenerName );
            jndiSessionCookieConfig = (Context) new InitialContext().lookup(
                    "java:comp/env/" + listenerName );
        
        catch ( NamingException e ) 
            logger.info( "No JNDI override session cookie config found for ", listenerName );
        

        sessionCookieConfig = sce.getServletContext().getSessionCookieConfig();

        String comment = getString( "comment" );
        if ( comment != null ) 
            logger.debug( "\t[comment]: []", comment );
            sessionCookieConfig.setComment( comment );
        

        String domain = getString( "domain" );
        if ( domain != null ) 
            logger.debug( "\t[domain]: []", domain );
            sessionCookieConfig.setDomain( domain );
        

        Boolean httpOnly = getBoolean( "http-only" );
        if ( httpOnly == null ) 
            sessionCookieConfig.setHttpOnly( true );
        
        else 
            logger.debug( "\t[http-only]: []", httpOnly );
            sessionCookieConfig.setHttpOnly( httpOnly );
        

        Integer maxAge = getInteger( "max-age" );
        if ( maxAge != null ) 
            sessionCookieConfig.setMaxAge( maxAge );
        

        String name = getString( "name" );
        if ( name != null ) 
            logger.debug( "\t[name]: []", name );
            sessionCookieConfig.setName( name );
        

        String path = getString( "path" );
        if ( path != null ) 
            logger.debug( "\t[path]: []", path );
            sessionCookieConfig.setPath( path );
        

        Boolean secure = getBoolean( "secure" );
        if ( secure == null ) 
            sessionCookieConfig.setSecure( true );
        
        else 
            logger.debug( "\t[secure]: []", secure );
            sessionCookieConfig.setSecure( secure );
        
    

    @Override
    public void contextDestroyed( ServletContextEvent sce ) 
    

    private Boolean getBoolean( String name ) 
        Object value;
        try 
            value = jndiSessionCookieConfig.lookup( name );
            if ( value instanceof Boolean ) 
                return (Boolean)value;
            
            else 
                return Boolean.valueOf( value.toString() );
            
        
        catch ( NamingException e ) 
            return null;
        
    

    private Integer getInteger( String name ) 
        Object value;
        try 
            value = jndiSessionCookieConfig.lookup( name );
            if ( value instanceof Integer ) 
                return (Integer)value;
            
            else 
                return Integer.valueOf( value.toString() );
            
        
        catch ( NamingException e ) 
            return null;
        
    

    private String getString( String name ) 
        Object value;
        try 
            value = jndiSessionCookieConfig.lookup( name );
            return value.toString();
        
        catch ( NamingException e ) 
            return null;
        
    

在 web.xml 中:

...
  <listener>
    <listener-class>
      org.mitre.caasd.servlet.init.JndiSessionCookieConfigListener
    </listener-class>
  </listener>
...

在您的 context.xml 中:

...
<Environment name="JndiSessionCookieConfigListener/secure"
  type="java.lang.String"
  override="false"
  value="true" />
...

这允许您在运行时设置所有会话 cookie 配置在部署环境中。因此,您可以使用相同的 webapp(war 文件)在本地进行开发(您不会有 https)和在生产环境中总是需要 https。

注意,OWASP documentation中提到了这种方法

【讨论】:

在生产中,您总是需要 https。 并非总是如此。是的,您希望客户端连接始终通过 HTTPS,但不是必需的 tomcat。在大多数情况下,tomcat 将位于反向代理之后 - 代理和 tomcat 之间的连接不会是 HTTPS。

以上是关于强制 Tomcat 通过 http 使用安全的 JSESSIONID cookie的主要内容,如果未能解决你的问题,请参考以下文章

在linux的tomcat中配置https及自动跳转

阿里云上部署tomcat启动后,通过http不能访问

安全 | Apache Tomcat HTTP/2可被Dos攻击

Tomcat中的ssl安全信道的实现

spring安全登录成功期间强制Https连接

Tomcat配置8080强制跳转https端口变成8443或者80跳转443