web-dev-qa-db-ja.com

セッションなしでSpring Securityを使用するにはどうすればよいですか?

私は、Amazon EC2で動作し、AmazonのElastic Load Balancerを使用するSpring SecurityでWebアプリケーションを構築しています。残念ながら、ELBはスティッキーセッションをサポートしていないため、セッションがなくてもアプリケーションが正常に動作することを確認する必要があります。

これまで、Cookieを介してトークンを割り当てるようにRememberMeServicesをセットアップしましたが、これは正常に機能しますが、ブラウザーセッションでCookieの有効期限が切れるようにします(ブラウザーが閉じたときなど)。

セッションなしでSpring Securityを使用したいのは私が初めてではないことを想像する必要があります...提案はありますか?

91
Jarrod Carlson

Spring Securitiy 3.0ではさらに簡単になりそうです。名前空間の構成を使用している場合は、次のように簡単に実行できます。

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

または、SecurityContextRepositoryをnullとして設定しても、そのように保存されるものはありません 同様に

28
Jarrod Carlson

Java Config を指定したSpring Security 3では、 HttpSecurity.sessionManagement() を使用できます。

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

本日4〜5時間、同じ問題(カスタムSecurityContextRepositoryをSecurityContextPersistenceFilterに挿入)に取り組みました。最後に、私たちはそれを理解しました。まず、Spring Securityのセクション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>

そして、この定義の後、この説明があります:「代わりに、SecurityContextRepositoryインターフェースのnull実装を提供できます。これにより、リクエスト中にセッションが既に作成されていても、セキュリティコンテキストが保存されなくなります。」

カスタムSecurityContextRepositoryをSecurityContextPersistenceFilterに注入する必要がありました。したがって、上記のBean定義をカスタム実装で変更し、セキュリティコンテキストに入れるだけです。

アプリケーションを実行すると、ログをトレースし、SecurityContextPersistenceFilterがカスタム実装を使用していないこと、HttpSessionSecurityContextRepositoryを使用していることを確認しました。

他のいくつかのことを試した後、「http」名前空間の「security-context-repository-ref」属性を持つカスタムSecurityContextRepository implを提供する必要があることがわかりました。 「http」名前空間を使用し、独自のSecurityContextRepository implを注入する場合は、「security-context-repository-ref」属性を試してください。

「http」名前空間が使用される場合、別のSecurityContextPersistenceFilter定義は無視されます。上記でコピーしたように、参照ドキュメント。それは述べていません。

物事を誤解した場合は私を修正してください。

26
Basri Kahveci

SecurityContextPersistenceFilterクラスを見てください。 SecurityContextHolderに値を設定する方法を定義します。デフォルトでは、HttpSessionSecurityContextRepositoryを使用して、httpセッションにセキュリティコンテキストを保存します。

カスタムSecurityContextRepositoryを使用して、このメカニズムを非常に簡単に実装しました。

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>
10
Lukas Herman

実際create-session="never"は完全にステートレスであることを意味しません。 Spring Securityの問題管理には、 問題 があります。

8
hleinone

簡単なメモ:「セッションの作成」ではなく「セッションの作成」です

セッションの作成

HTTPセッションを作成する際の熱心さを制御します。

設定しない場合、デフォルトは「ifRequired」になります。他のオプションは「常に」と「しない」です。

この属性の設定は、HttpSessionContextIntegrationFilterのallowSessionCreationおよびforceEagerSessionCreationプロパティに影響します。この属性が「never」に設定されていない限り、allowSessionCreationは常にtrueになります。 forceEagerSessionCreationは、「always」に設定されていない限り「false」です。

そのため、デフォルト構成ではセッションの作成は許可されますが、強制はされません。例外は、同時セッション制御が有効になっている場合、ここでの設定に関係なく、forceEagerSessionCreationがtrueに設定される場合です。 「never」を使用すると、HttpSessionContextIntegrationFilterの初期化中に例外が発生します。

セッションの使用法の具体的な詳細については、HttpSessionSecurityContextRepository javadocにいくつかの優れたドキュメントがあります。

3
Jon Vaughan

_<http>_名前空間構成を使用するときに何かを機能させるために、この回答に投稿された多数のソリューションに苦労した後、私は最終的に私のユースケースで実際に機能するアプローチを見つけました。私は実際にSpring Securityがセッションを開始しないことを要求しません(アプリケーションの他の部分でセッションを使用するため)、セッションで認証を「記憶」しないだけです(再チェックする必要があります)すべてのリクエスト)。

そもそも、上記の「null実装」手法の実行方法を理解できませんでした。 securityContextRepositoryをnullに設定するか、no-op実装に設定するかは明確ではありませんでした。前者は、SecurityContextPersistenceFilter.doFilter()内でNullPointerExceptionがスローされるため機能しません。 no-op実装については、想像できる最も簡単な方法で実装しようとしました。

_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;
    }

}
_

_response__タイプに関係する奇妙なClassCastExceptionがあるため、これは私のアプリケーションでは機能しません。

(単にセッションにコンテキストを保存しないことで)動作する実装を見つけることができたとしても、_<http>_構成によって構築されたフィルターにそれを挿入する方法の問題がまだあります。 docs のように、_SECURITY_CONTEXT_FILTER_の位置にあるフィルターを単に置き換えることはできません。カバーの下に作成されるSecurityContextPersistenceFilterにフックすることがわかった唯一の方法は、いApplicationContextAware Beanを記述することでした。

_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());
                        }
                    }
                }

            }
        }
    }
}
_

とにかく、非常にハック的ではありますが、実際に機能するソリューションには。単純にFilterを使用して、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_);
    }
}
_

次に、構成で:

_<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>
_
2
Jeff Evans