如何在没有会话的情况下使用 Spring Security?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2504590/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-08 04:01:18  来源:igfitidea点击:

How can I use Spring Security without sessions?

springspring-securityload-balancingamazon-ec2

提问by Jarrod Carlson

I am building a web application with Spring Security that will live on Amazon EC2 and use Amazon's Elastic Load Balancers. Unfortunately, ELB does not support sticky sessions, so I need to ensure my application works properly without sessions.

我正在使用 Spring Security 构建一个 Web 应用程序,该应用程序将运行在 Amazon EC2 上并使用 Amazon 的弹性负载均衡器。不幸的是,ELB 不支持粘性会话,所以我需要确保我的应用程序在没有会话的情况下正常工作。

So far, I have setup RememberMeServices to assign a token via a cookie, and this works fine, but I want the cookie to expire with the browser session (e.g. when the browser closes).

到目前为止,我已经设置了 RememberMeServices 以通过 cookie 分配令牌,这工作正常,但我希望 cookie 随着浏览器会话而过期(例如,当浏览器关闭时)。

I have to imagine I'm not the first one to want to use Spring Security without sessions... any suggestions?

我必须想象我不是第一个想要在没有会话的情况下使用 Spring Security 的人......有什么建议吗?

采纳答案by Jarrod Carlson

It seems to be even easier in Spring Securitiy 3.0. If you're using namespace configuration, you can simply do as follows:

在 Spring Securitiy 3.0 中似乎更容易。如果您使用命名空间配置,您可以简单地执行以下操作:

<http create-session="never">
  <!-- config -->
</http>

Or you could configure the SecurityContextRepository as null, and nothing would ever get saved that way as well.

或者你可以配置SecurityContextRepository为空,并没有什么会永远得救这样

回答by Ben Hutchison

In Spring Security 3 with Java Config, you can use HttpSecurity.sessionManagement():

在带有Java Config 的Spring Security 3 中,您可以使用HttpSecurity.sessionManagement()

@Override
protected void configure(final HttpSecurity http) throws Exception {
    http
        .sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}

回答by Basri Kahveci

We worked on the same issue (injecting a custom SecurityContextRepository to SecurityContextPersistenceFilter) for 4-5 hours today. Finally, we figured it out. First of all, in the section 8.3 of Spring Security ref. doc, there is a SecurityContextPersistenceFilter bean definition

我们今天处理了同样的问题(将自定义的 SecurityContextRepository 注入到 SecurityContextPersistenceFilter) 4-5 个小时。最后,我们想通了。首先,在 Spring Security ref 的第 8.3 节中。doc,有一个 SecurityContextPersistenceFilter bean 定义

<bean id="securityContextPersistenceFilter" class="org.springframework.security.web.context.SecurityContextPersistenceFilter">
    <property name='securityContextRepository'>
        <bean class='org.springframework.security.web.context.HttpSessionSecurityContextRepository'>
            <property name='allowSessionCreation' value='false' />
        </bean>
    </property>
</bean>

And after this definition, there is this explanation: "Alternatively you could provide a null implementation of the SecurityContextRepository interface, which will prevent the security context from being stored, even if a session has already been created during the request."

在这个定义之后,有这样的解释:“或者,您可以提供 SecurityContextRepository 接口的空实现,这将阻止存储安全上下文,即使在请求期间已经创建了会话。”

We needed to inject our custom SecurityContextRepository into the SecurityContextPersistenceFilter. So we simply changed the bean definition above with our custom impl and put it into the security context.

我们需要将我们的自定义 SecurityContextRepository 注入到 SecurityContextPersistenceFilter 中。因此,我们简单地使用我们的自定义 impl 更改了上面的 bean 定义,并将其放入安全上下文中。

When we run the application, we traced the logs and saw that SecurityContextPersistenceFilter was not using our custom impl, it was using the HttpSessionSecurityContextRepository.

当我们运行应用程序时,我们跟踪日志并看到 SecurityContextPersistenceFilter 没有使用我们的自定义实现,它正在使用 HttpSessionSecurityContextRepository。

After a few other things we tried, we figured out that we had to give our custom SecurityContextRepository impl with the "security-context-repository-ref" attribute of "http" namespace. If you use "http" namespace and want to inject your own SecurityContextRepository impl, try "security-context-repository-ref" attribute.

在我们尝试了其他一些事情之后,我们发现我们必须为我们的自定义 SecurityContextRepository impl 提供“http”命名空间的“security-context-repository-ref”属性。如果您使用“http”命名空间并希望注入您自己的 SecurityContextRepository impl,请尝试使用“security-context-repository-ref”属性。

When "http" namespace is used, a seperate SecurityContextPersistenceFilter definition is ignored. As I copied above, the reference doc. does not state that.

使用“http”命名空间时,将忽略单独的 SecurityContextPersistenceFilter 定义。正如我在上面复制的,参考文档。没有说明。

Please correct me if I misunderstood the things.

如果我误解了这些事情,请纠正我。

回答by Lukas Herman

Take a look at SecurityContextPersistenceFilterclass. It defines how the SecurityContextHolderis populated. By default it uses HttpSessionSecurityContextRepositoryto store security context in http session.

看看SecurityContextPersistenceFilter班级。它定义了如何SecurityContextHolder填充。默认情况下,它用于HttpSessionSecurityContextRepository在 http 会话中存储安全上下文。

I have implemented this mechanism quite easily, with custom SecurityContextRepository.

我使用 custom 很容易地实现了这种机制SecurityContextRepository

See the securityContext.xmlbelow:

请参阅securityContext.xml以下内容:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:sec="http://www.springframework.org/schema/security"
       xmlns:jee="http://www.springframework.org/schema/jee"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
       http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd
       http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd">

    <context:annotation-config/>

    <sec:global-method-security secured-annotations="enabled" pre-post-annotations="enabled"/>

    <bean id="securityContextRepository" class="com.project.server.security.TokenSecurityContextRepository"/>

    <bean id="securityContextFilter" class="com.project.server.security.TokenSecurityContextPersistenceFilter">
        <property name="repository" ref="securityContextRepository"/>
    </bean>

    <bean id="logoutFilter" class="org.springframework.security.web.authentication.logout.LogoutFilter">
        <constructor-arg value="/login.jsp"/>
        <constructor-arg>
            <list>
                <bean class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler"/>
            </list>
        </constructor-arg>
    </bean>

    <bean id="formLoginFilter"
          class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
        <property name="authenticationManager" ref="authenticationManager"/>
        <property name="authenticationSuccessHandler">
            <bean class="com.project.server.security.TokenAuthenticationSuccessHandler">
                <property name="defaultTargetUrl" value="/index.html"/>
                <property name="passwordExpiredUrl" value="/changePassword.jsp"/>
                <property name="alwaysUseDefaultTargetUrl" value="true"/>
            </bean>
        </property>
        <property name="authenticationFailureHandler">
            <bean class="com.project.server.modules.security.CustomUrlAuthenticationFailureHandler">
                <property name="defaultFailureUrl" value="/login.jsp?failure=1"/>
            </bean>
        </property>
        <property name="filterProcessesUrl" value="/j_spring_security_check"/>
        <property name="allowSessionCreation" value="false"/>
    </bean>

    <bean id="servletApiFilter"
          class="org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter"/>

    <bean id="anonFilter" class="org.springframework.security.web.authentication.AnonymousAuthenticationFilter">
        <property name="key" value="ClientApplication"/>
        <property name="userAttribute" value="anonymousUser,ROLE_ANONYMOUS"/>
    </bean>


    <bean id="exceptionTranslator" class="org.springframework.security.web.access.ExceptionTranslationFilter">
        <property name="authenticationEntryPoint">
            <bean class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
                <property name="loginFormUrl" value="/login.jsp"/>
            </bean>
        </property>
        <property name="accessDeniedHandler">
            <bean class="org.springframework.security.web.access.AccessDeniedHandlerImpl">
                <property name="errorPage" value="/login.jsp?failure=2"/>
            </bean>
        </property>
        <property name="requestCache">
            <bean id="nullRequestCache" class="org.springframework.security.web.savedrequest.NullRequestCache"/>
        </property>
    </bean>

    <alias name="filterChainProxy" alias="springSecurityFilterChain"/>

    <bean id="filterChainProxy" class="org.springframework.security.web.FilterChainProxy">
        <sec:filter-chain-map path-type="ant">
            <sec:filter-chain pattern="/**"
                              filters="securityContextFilter, logoutFilter, formLoginFilter,
                                        servletApiFilter, anonFilter, exceptionTranslator, filterSecurityInterceptor"/>
        </sec:filter-chain-map>
    </bean>

    <bean id="filterSecurityInterceptor"
          class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor">
        <property name="securityMetadataSource">
            <sec:filter-security-metadata-source use-expressions="true">
                <sec:intercept-url pattern="/staticresources/**" access="permitAll"/>
                <sec:intercept-url pattern="/index.html*" access="hasRole('USER_ROLE')"/>
                <sec:intercept-url pattern="/rpc/*" access="hasRole('USER_ROLE')"/>
                <sec:intercept-url pattern="/**" access="permitAll"/>
            </sec:filter-security-metadata-source>
        </property>
        <property name="authenticationManager" ref="authenticationManager"/>
        <property name="accessDecisionManager" ref="accessDecisionManager"/>
    </bean>

    <bean id="accessDecisionManager" class="org.springframework.security.access.vote.AffirmativeBased">
        <property name="decisionVoters">
            <list>
                <bean class="org.springframework.security.access.vote.RoleVoter"/>
                <bean class="org.springframework.security.web.access.expression.WebExpressionVoter"/>
            </list>
        </property>
    </bean>

    <bean id="authenticationManager" class="org.springframework.security.authentication.ProviderManager">
        <property name="providers">
            <list>
                <bean name="authenticationProvider"
                      class="com.project.server.modules.security.oracle.StoredProcedureBasedAuthenticationProviderImpl">
                    <property name="dataSource" ref="serverDataSource"/>
                    <property name="userDetailsService" ref="userDetailsService"/>
                    <property name="auditLogin" value="true"/>
                    <property name="postAuthenticationChecks" ref="customPostAuthenticationChecks"/>
                </bean>
            </list>
        </property>
    </bean>

    <bean id="customPostAuthenticationChecks" class="com.project.server.modules.security.CustomPostAuthenticationChecks"/>

    <bean name="userDetailsService" class="com.project.server.modules.security.oracle.UserDetailsServiceImpl">
        <property name="dataSource" ref="serverDataSource"/>
    </bean>

</beans>

回答by hleinone

Actually create-session="never"doesn't mean being completely stateless. There's an issuefor that in Spring Security issue management.

实际上create-session="never"并不意味着完全无国籍。在 Spring Security 问题管理中存在一个问题

回答by Jon Vaughan

Just a quick note: it's "create-session" rather than "create-sessions"

只是一个简短的说明:它是“创建会话”而不是“创建会话”

create-session

创建会话

Controls the eagerness with which an HTTP session is created.

控制创建 HTTP 会话的渴望。

If not set, defaults to "ifRequired". Other options are "always" and "never".

如果未设置,则默认为“ifRequired”。其他选项是“总是”和“从不”。

The setting of this attribute affect the allowSessionCreation and forceEagerSessionCreation properties of HttpSessionContextIntegrationFilter. allowSessionCreation will always be true unless this attribute is set to "never". forceEagerSessionCreation is "false" unless it is set to "always".

此属性的设置会影响 HttpSessionContextIntegrationFilter 的 allowSessionCreation 和 forceEagerSessionCreation 属性。除非此属性设置为“从不”,否则 allowSessionCreation 将始终为真。forceEagerSessionCreation 是“false”,除非它被设置为“always”。

So the default configuration allows session creation but does not force it. The exception is if concurrent session control is enabled, when forceEagerSessionCreation will be set to true, regardless of what the setting is here. Using "never" would then cause an exception during the initialization of HttpSessionContextIntegrationFilter.

所以默认配置允许创建会话但不强制它。例外情况是,如果启用了并发会话控制,则​​ forceEagerSessionCreation 将设置为 true,无论此处的设置如何。使用“never”会在 HttpSessionContextIntegrationFilter 的初始化过程中导致异常。

For specific details of the session usage, there is some good documentation in the HttpSessionSecurityContextRepository javadoc.

有关会话使用的具体细节,HttpSessionSecurityContextRepository javadoc 中有一些很好的文档。

回答by Jeff Evans

After struggling with the numerous solutions posted in this answer, to try to get something working when using the <http>namespace config, I finally found an approach that actually works for my use case. I don't actually require that Spring Security doesn't start a session (because I use session in other parts of the application), just that it doesn't "remember" authentication in the session at all (it should be re-checked every request).

在努力解决此答案中发布的众多解决方案之后,为了在使用<http>命名空间配置时尝试使某些工作正常工作,我终于找到了一种实际适用于我的用例的方法。我实际上并不要求 Spring Security 不启动会话(因为我在应用程序的其他部分使用会话),只是它根本不“记住”会话中的身份验证(应该重新检查)每个请求)。

To begin with, I wasn't able to figure out how to do the "null implementation" technique described above. It wasn't clear whether you are supposed to set the securityContextRepository to nullor to a no-op implementation. The former does not work because a NullPointerExceptiongets thrown within SecurityContextPersistenceFilter.doFilter(). As for the no-op implementation, I tried implementing in the simplest way I could imagine:

首先,我无法弄清楚如何执行上述“空实现”技术。不清楚您是否应该将 securityContextRepository 设置null为无操作实现。前者不起作用,因为 aNullPointerException被抛出SecurityContextPersistenceFilter.doFilter()。至于无操作实现,我尝试以我能想象的最简单的方式实现:

public class NullSpringSecurityContextRepository implements SecurityContextRepository {

    @Override
    public SecurityContext loadContext(final HttpRequestResponseHolder requestResponseHolder_) {
        return SecurityContextHolder.createEmptyContext();
    }

    @Override
    public void saveContext(final SecurityContext context_, final HttpServletRequest request_,
            final HttpServletResponse response_) {
    }

    @Override
    public boolean containsContext(final HttpServletRequest request_) {
        return false;
    }

}

This doesn't work in my application, because of some strange ClassCastExceptionhaving to do with the response_type.

这在我的应用程序中不起作用,因为ClassCastExceptionresponse_类型有一些奇怪的关系。

Even assuming I did manage to find an implementation that works (by simply not storing the context in session), there is still the problem of how to inject that into the filters built by the <http>configuration. You cannot simply replace the filter at the SECURITY_CONTEXT_FILTERposition, as per the docs. The only way I found to hook into the SecurityContextPersistenceFilterthat is created under the covers was to write an ugly ApplicationContextAwarebean:

即使假设我确实设法找到了一个有效的实现(通过简单地不在会话中存储上下文),仍然存在如何将其注入由<http>配置构建的过滤器的问题。SECURITY_CONTEXT_FILTER根据文档,您不能简单地更换该位置的过滤器。我发现挂钩到在幕后SecurityContextPersistenceFilter创建的唯一方法是编写一个丑陋的ApplicationContextAwarebean:

public class SpringSecuritySessionDisabler implements ApplicationContextAware {

    private final Logger logger = LoggerFactory.getLogger(SpringSecuritySessionDisabler.class);

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(final ApplicationContext applicationContext_) throws BeansException {
        applicationContext = applicationContext_;
    }

    public void disableSpringSecuritySessions() {
        final Map<String, FilterChainProxy> filterChainProxies = applicationContext
                .getBeansOfType(FilterChainProxy.class);
        for (final Entry<String, FilterChainProxy> filterChainProxyBeanEntry : filterChainProxies.entrySet()) {
            for (final Entry<String, List<Filter>> filterChainMapEntry : filterChainProxyBeanEntry.getValue()
                    .getFilterChainMap().entrySet()) {
                final List<Filter> filterList = filterChainMapEntry.getValue();
                if (filterList.size() > 0) {
                    for (final Filter filter : filterList) {
                        if (filter instanceof SecurityContextPersistenceFilter) {
                            logger.info(
                                    "Found SecurityContextPersistenceFilter, mapped to URL '{}' in the FilterChainProxy bean named '{}', setting its securityContextRepository to the null implementation to disable caching of authentication",
                                    filterChainMapEntry.getKey(), filterChainProxyBeanEntry.getKey());
                            ((SecurityContextPersistenceFilter) filter).setSecurityContextRepository(
                             new NullSpringSecurityContextRepository());
                        }
                    }
                }

            }
        }
    }
}

Anyway, to the solution that actually does work, albeit very hackish. Simply use a Filterthat deletes the session entry that the HttpSessionSecurityContextRepositorylooks for when it does its thing:

无论如何,对于实际有效的解决方案,尽管非常hackish。只需使用 aFilter删除HttpSessionSecurityContextRepository它在执行其操作时查找的会话条目:

public class SpringSecuritySessionDeletingFilter extends GenericFilterBean implements Filter {

    @Override
    public void doFilter(final ServletRequest request_, final ServletResponse response_, final FilterChain chain_)
            throws IOException, ServletException {
        final HttpServletRequest servletRequest = (HttpServletRequest) request_;
        final HttpSession session = servletRequest.getSession();
        if (session.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY) != null) {
            session.removeAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY);
        }

        chain_.doFilter(request_, response_);
    }
}

Then in the configuration:

然后在配置中:

<bean id="springSecuritySessionDeletingFilter"
    class="SpringSecuritySessionDeletingFilter" />

<sec:http auto-config="false" create-session="never"
    entry-point-ref="authEntryPoint">
    <sec:intercept-url pattern="/**"
        access="IS_AUTHENTICATED_REMEMBERED" />
    <sec:intercept-url pattern="/static/**" filters="none" />
    <sec:custom-filter ref="myLoginFilterChain"
        position="FORM_LOGIN_FILTER" />

    <sec:custom-filter ref="springSecuritySessionDeletingFilter"
        before="SECURITY_CONTEXT_FILTER" />
</sec:http>