세션없이 Spring Security를 ​​어떻게 사용할 수 있습니까?


99

Amazon EC2에 상주하고 Amazon의 Elastic Load Balancer를 사용할 Spring Security로 웹 애플리케이션을 구축하고 있습니다. 안타깝게도 ELB는 고정 세션을 지원하지 않으므로 내 응용 프로그램이 세션없이 제대로 작동하는지 확인해야합니다.

지금까지 쿠키를 통해 토큰을 할당하도록 RememberMeServices를 설정했는데 제대로 작동하지만 브라우저 세션과 함께 쿠키가 만료되기를 원합니다 (예 : 브라우저가 닫힐 때).

세션없이 Spring Security를 ​​사용하고 싶은 첫 번째 사람이 아니라고 상상해야합니다 ... 어떤 제안이라도?

답변:


124

Java Config가있는 Spring Security 3 에서는 HttpSecurity.sessionManagement () 사용할 수 있습니다 .

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

2
이것은 Java 구성에 대한 정답이며, 수락 된 답변에 대한 주석에서 @sappenin이 xml 구성에 대해 올바르게 언급 한 내용을 미러링합니다. 우리는이 방법을 사용하고 실제로 우리의 응용 프로그램은 세션이 없습니다.
Paul

이것은 부작용이 있습니다. Tomcat 컨테이너는 이미지, 스타일 시트 등에 대한 요청에 "; jsessionid = ..."를 추가합니다. Tomcat은 상태 비 저장을 원하지 않기 때문에 Spring Security는 "URL에 잠재적으로 악의적 인 문자열 ';' ".
workerjoe

@workerjoe 그래서,이 자바 구성에서 말하려는 것은 세션이 봄 보안에 의해 생성되지 않고 바람둥이로 만들어지는 것입니까?
Vishwas Atrey

@VishwasAtrey 내 이해에 따라 (잘못되었을 수 있음) Tomcat은 세션을 만들고 유지 관리합니다. Spring은이를 활용하여 자체 데이터를 추가합니다. 상태 비 저장 웹 애플리케이션을 만들려고했지만 위에서 언급했듯이 작동하지 않았습니다. 자세한 내용 은 내 질문에 대한 답변을 참조하십시오 .
workerjoe

28

Spring Securitiy 3.0에서는 훨씬 더 쉬운 것 같습니다. 네임 스페이스 구성을 사용하는 경우 다음과 같이 간단히 수행 할 수 있습니다.

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

또는 당신은 널 (null)로 SecurityContextRepository을 구성 할 수 있습니다, 아무것도 이제까지 그런 식으로 저장되지 얻을 것 뿐만 아니라 .


5
이것은 내가 생각했던 것처럼 작동하지 않았습니다. 대신 "never"와 "stateless"를 구분하는 주석이 아래에 있습니다. "never"를 사용하여 내 앱은 여전히 ​​세션을 생성하고있었습니다. "상태 비 저장"을 사용하여 내 앱은 실제로 상태 비 저장이되었으며 다른 답변에서 언급 한 재정의를 구현할 필요가 없었습니다. 여기에서 JIRA 문제를 참조하십시오. jira.springsource.org/browse/SEC-1424
sappenin

27

우리는 오늘 4-5 시간 동안 동일한 문제 (사용자 지정 SecurityContextRepository를 SecurityContextPersistenceFilter에 삽입)에 대해 작업했습니다. 마침내 우리는 그것을 알아 냈습니다. 먼저 Spring Security ref의 8.3 섹션에서. doc, SecurityContextPersistenceFilter 빈 정의가 있습니다.

<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에 삽입해야했습니다. 그래서 우리는 사용자 정의 impl을 사용하여 위의 bean 정의를 변경하고 보안 컨텍스트에 넣었습니다.

애플리케이션을 실행할 때 로그를 추적하고 SecurityContextPersistenceFilter가 사용자 지정 impl을 사용하지 않고 HttpSessionSecurityContextRepository를 사용하고 있음을 확인했습니다.

몇 가지 다른 작업을 시도한 후 "http"네임 스페이스의 "security-context-repository-ref"속성을 사용하여 사용자 정의 SecurityContextRepository impl을 제공해야한다는 것을 알아 냈습니다. "http"네임 스페이스를 사용하고 자신의 SecurityContextRepository impl을 삽입하려면 "security-context-repository-ref"속성을 사용해보십시오.

"http"네임 스페이스가 사용되면 별도의 SecurityContextPersistenceFilter 정의가 무시됩니다. 위에서 복사했듯이 참조 문서. 그것을 언급하지 않습니다.

제가 잘못 이해했다면 수정 해주세요.


감사합니다. 귀중한 정보입니다. 내 응용 프로그램에서 시험해 보겠습니다.
Jeff Evans

나는 스프링 3.0 필요한 것을 감사합니다, 그의
저스틴 루드비히

1
당신이 네임 스페이스 정의 SecurityContextPersistenceFilter 허용하지 않습니다, 그것은 나에게 디버깅 몇 시간을했다 HTTP를 그것을 파악하는 것을 말할 때 당신은 아주 정확하다
제이미 Hablutzel

이것을 게시 해 주셔서 대단히 감사합니다! 나는 내가 가진 작은 머리카락을 찢어 버리려고했습니다. SecurityContextPersistenceFilter의 setSecurityContextRepository 메서드가 더 이상 사용되지 않는 이유가 궁금합니다 (문서에서 생성자 주입을 사용하라는 문서도 옳지 않습니다).
fool4jesus

10

한 번 봐 가지고 SecurityContextPersistenceFilter클래스를. SecurityContextHolder이 채워지 는 방법을 정의합니다 . 기본적 HttpSessionSecurityContextRepository으로 http 세션에 보안 컨텍스트를 저장 하는 데 사용 합니다.

이 메커니즘은 custom 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
Lukas 님, 보안 컨텍스트 저장소 구현에 대해 더 자세히 설명해 주시겠습니까?
Jim Downing

1
TokenSecurityContextRepository 클래스는 HashMap <String, SecurityContext> contextMap을 포함합니다. loadContext () 메서드에서 requestParameter sid, 쿠키 또는 사용자 정의 requestHeader 또는 위의 조합에 의해 전달 된 세션 해시 코드에 대한 SecurityContext가 있는지 확인합니다. 컨텍스트를 확인할 수없는 경우 SecurityContextHolder.createEmptyContext ()를 반환합니다. saveContext 메소드는 해결 된 컨텍스트를 contextMap에 넣습니다.
Lukas Herman

8

실제로 create-session="never"완전히 무국적자가되는 것은 아닙니다. 거기에 문제가 봄 보안 문제를 관리하는 대한이.


3

이 답변에 게시 된 수많은 솔루션으로 어려움을 겪은 후 <http>네임 스페이스 구성을 사용할 때 작동하는 것을 시도한 후 마침내 실제로 사용 사례에 맞는 접근 방식을 찾았습니다. 나는 실제로 Spring Security가 세션을 시작하지 않을 것을 요구하지 않는다 (왜냐하면 나는 애플리케이션의 다른 부분에서 세션을 사용하기 때문이다). 단지 세션에서 인증을 전혀 "기억"하지 않는다는 것 (다시 확인되어야한다) 모든 요청).

우선 위에서 설명한 "null 구현"기법을 수행하는 방법을 알아낼 수 없었습니다. securityContextRepository를 설정해야하는지 null또는 no-op 구현 으로 설정해야하는지 명확하지 않았습니다 . 전자 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;
    }

}

이것은 유형 ClassCastException과 관련된 이상한 점 때문에 내 응용 프로그램에서 작동하지 않습니다 response_.

(단순히 세션에 컨텍스트를 저장하지 않음으로써) 작동하는 구현을 찾았다 고 가정하더라도 <http>구성에 의해 구축 된 필터에이를 주입하는 방법에 대한 문제가 여전히 있습니다. 문서 에 따라 단순히 SECURITY_CONTEXT_FILTER위치 에서 필터를 교체 할 수는 없습니다 . 내가 커버 아래에 만들어진 것에 연결하는 유일한 방법 은 못생긴 콩 을 작성하는 것입니다 .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());
                        }
                    }
                }

            }
        }
    }
}

어쨌든 실제로 작동하는 솔루션에 대해 매우 hackish하지만. 일을 할 때 찾는 Filter세션 항목을 삭제하는 a 를 사용하십시오 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 구성을 사용할 수 있습니다. WebSecurityConfigurerAdapter" http.addFilterBefore(new SpringSecuritySessionDeletingFilter(), SecurityContextPersistenceFilter.class)"로 맞춤 필터를 추가했습니다
workerjoe

3

참고 : "세션 만들기"가 아니라 "세션 만들기"입니다.

세션 생성

HTTP 세션이 생성되는 열의를 제어합니다.

설정하지 않으면 기본값은 "ifRequired"입니다. 다른 옵션은 "항상"및 "사용 안 함"입니다.

이 속성의 설정은 HttpSessionContextIntegrationFilter의 allowSessionCreation 및 forceEagerSessionCreation 속성에 영향을줍니다. allowSessionCreation은이 속성이 "never"로 설정되지 않는 한 항상 true입니다. forceEagerSessionCreation은 "always"로 설정되지 않은 경우 "false"입니다.

따라서 기본 구성은 세션 생성을 허용하지만 강제하지는 않습니다. 예외는 동시 세션 제어가 활성화 된 경우, 여기에있는 설정에 관계없이 forceEagerSessionCreation이 true로 설정 될 때입니다. "never"를 사용하면 HttpSessionContextIntegrationFilter를 초기화하는 동안 예외가 발생합니다.

세션 사용에 대한 자세한 내용은 HttpSessionSecurityContextRepository javadoc에 좋은 문서가 있습니다.


이것들은 모두 훌륭한 대답이지만 <http> 구성 요소를 사용할 때 이것을 달성하는 방법을 알아 내려고 노력하고 있습니다. 을 사용하더라도 auto-config=falseSECURITY_CONTEXT_FILTER위치 에있는 것을 자신의 것으로 대체 할 수는 없습니다 . 나는 일부 ApplicationContextAwarebean (반영을 사용하여 securityContextRepositorynull 구현 을 강제로 사용) 으로 비활성화하려고 시도 SessionManagementFilter했지만 주사위는 없습니다. 그리고 슬프게도 봄 보안 3.1 년으로 전환 할 수 없습니다 create-session=stateless.
Jeff Evans

항상 유익한이 사이트를 방문하십시오. " baeldung.com/spring-security-session"• 항상 – 세션이 아직 존재하지 않으면 항상 생성됩니다. • ifRequired – 필요한 경우에만 세션이 생성됩니다 (기본값). never – 프레임 워크는 세션 자체를 생성하지 않지만 이미 존재하는 경우 세션을 사용합니다. • stateless – 세션이 생성되거나 사용되지 않습니다. Spring Security
Java_Fire_Within
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.