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


99

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

これまでのところ、Cookieを介してトークンを割り当てるようにRememberMeServicesを設定しましたが、これは正常に機能しますが、Cookieをブラウザーセッションで(たとえば、ブラウザーが閉じたときに)期限切れにしたいと考えています。

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

回答:


124

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

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

2
これは、Java構成の正解です。@ sappeninが、受け入れられた回答のコメントでxml構成に対して正しく述べた内容を反映しています。この方法を使用しており、実際にアプリケーションはセッションレスです。
ポール

これには副作用があります。Tomcatコンテナは、「; jsessionid = ...」を画像、スタイルシートなどのリクエストに追加します。これは、Tomcatがステートレスであることが望ましくないためです。また、「URLに潜在的に悪意のある文字列「;」」。
workerjoe

@workerjoeでは、このJava構成で何を言おうとしているのか、セッションはTomcatではなくSpring Securityによって作成されたのではないでしょうか。
Vishwas Atrey

@VishwasAtrey私の理解では(これは間違っているかもしれません)、Tomcatがセッションを作成して維持しています。Springはそれらを利用して、独自のデータを追加します。上で述べたように、ステートレスWebアプリケーションを作成しようとしましたが、機能しませんでした。詳細については、私自身の質問に対するこの回答を参照してください。
workerjoe

28

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

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

それとも、ヌルとしてSecurityContextRepositoryを設定することができ、そして何が今までそのように保存されていないになるだろうとしても


5
これは思ったほどうまくいきませんでした。代わりに、「なし」と「ステートレス」を区別するコメントが下にあります。「なし」を使用して、私のアプリはまだセッションを作成していました。「ステートレス」を使用すると、私のアプリは実際にはステートレスになり、他の回答で言及されているオーバーライドを実装する必要がありませんでした。次のJIRAの問題を参照してください:jira.springsource.org/browse/SEC-1424
サッペニン

27

今日、同じ問題(カスタム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>

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

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

アプリケーションを実行すると、ログを追跡したところ、SecurityContextPersistenceFilterがカスタム実装を使用しておらず、HttpSessionSecurityContextRepositoryを使用していたことがわかりました。

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

「http」名前空間が使用される場合、別個のSecurityContextPersistenceFilter定義は無視されます。上記をコピーしたように、リファレンスドキュメント。それは述べていない。

誤解した場合は訂正してください。


おかげで、これは貴重な情報です。私のアプリケーションで試してみます。
Jeff Evans

おかげで、それは私が春3.0に必要だったものです
ジャスティンルートヴィヒ

1
http名前空間はカスタムのSecurityContextPersistenceFilterを許可しないと言うとき、あなたは非常に正確です、それを理解するのに数時間のデバッグが必要でした
Jaime Hablutzel

これを投稿してくれてありがとう!私はどんな小さな髪を引き裂こうとしていた。SecurityContextPersistenceFilterのsetSecurityContextRepositoryメソッドが廃止された理由を知りたくありませんでした(コンストラクターインジェクションを使用するように言っているドキュメントで、これも正しくありません)。
fool4jesus 2013

10

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>

1
こんにちはルーカス、セキュリティコンテキストリポジトリの実装の詳細について教えてください。
ジムダウニング

1
TokenSecurityContextRepositoryクラスには、HashMap <String、SecurityContext> contextMapが含まれています。loadContext()メソッドでは、requestParameter sid、cookie、またはカスタムrequestHeaderまたはこれらの組み合わせによって渡されたセッションハッシュコードのSecurityContextが存在するかどうかを確認します。コンテキストを解決できなかった場合は、SecurityContextHolder.createEmptyContext()を返します。メソッドsaveContextは、解決されたコンテキストをcontextMapに配置します。
Lukas Herman

8

実際にcreate-session="never"は、完全に無国籍であるという意味ではありません。Spring Securityの課題管理にはそのための課題があります。


3

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

そもそも、上記の「null実装」の方法を理解することができませんでした。securityContextRepositoryをnull何もしない実装に設定するかどうかは明確ではありませんでした。前者はNullPointerExceptionが内でスローされるため機能しませんSecurityContextPersistenceFilter.doFilter()。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;
    }

}

これは私ClassCastExceptionresponse_タイプのアプリケーションでは奇妙なことに関係しているため、機能しません。

(単にコンテキストをセッションに格納しないことで)機能する実装を見つけることができたとしても、<http>構成によって構築されたフィルターにそれを挿入する方法の問題がまだあります。docsのSECURITY_CONTEXT_FILTERように、その位置でフィルターを単純に交換することはできません。カバーの下で作成されたにフックすることがわかった唯一の方法は、醜いBean を書くことでした:SecurityContextPersistenceFilterApplicationContextAware

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>

9年後、これはまだ正しい答えです。これで、XMLの代わりにJava構成を使用できます。WebSecurityConfigurerAdapterhttp.addFilterBefore(new SpringSecuritySessionDeletingFilter(), SecurityContextPersistenceFilter.class)」でカスタムフィルターを追加しました
workerjoe

3

簡単なメモ:「create-sessions」ではなく「create-session」です

作成セッション

HTTPセッションが作成される熱意を制御します。

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

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

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

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


これらはすべて素晴らしい答えですが、私は<http>構成要素を使用するときにこれを達成する方法を理解しようとして壁に頭をぶつけてきました。を使用してもauto-config=false、そのSECURITY_CONTEXT_FILTER位置にあるものを自分のものに置き換えることはできないようです。私はいくつかのApplicationContextAwareBeanで無効化しようとハッキングしてきました(リフレクションを使用securityContextRepositoryしてをnull実装に強制しますSessionManagementFilter)が、ダイスはありません。そして残念なことに、私は春のセキュリティを提供する3 create-session=stateless. 1年に切り替えることはできません。
Jeff Evans

常に有益なこのサイトをご覧ください。これがあなたと他の人にも役立つことを願っています " baeldung.com/spring-security-session"•always –セッションが存在しない場合は常に作成されます•ifRequired –必要な場合にのみセッションが作成されます(デフォルト)• never –フレームワークはセッション自体を作成しませんが、すでに存在する場合はセッションを使用します•ステートレス– Spring Securityによってセッションが作成または使用されません
Java_Fire_Within
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.