복붙노트

[SPRING] SecurityContext의 Authentication 개체가 여러 스레드에서 공유되지 않는 이유는 무엇입니까?

SPRING

SecurityContext의 Authentication 개체가 여러 스레드에서 공유되지 않는 이유는 무엇입니까?

가끔 AuthenticationCredentialsNotFoundException을받는 문제가 있습니다. 지금은 이것이 스레딩 문제라고 생각합니다. 다른 질문 (링크)에 따르면 SecurityContext는 다른 스레드 사이의 HttpSession 객체를 통해 전달됩니다. 그러나 어떤 이유로 이것이 나를 위해 작동하지 않습니다.

이것은 현재 로그인을 처리하는 방법입니다.

public ShopAdminDTO login(String userEmail, String password) throws EmailAddressNotFoundException {

    LOGGER.debug("Login request for " + userEmail);

    // Create and initialize user details object for Spring Security authentication mechanism.
    ShopAdminUserDetails userDetails = new ShopAdminUserDetails(userEmail, password, true, true, true, true, new ArrayList<GrantedAuthority>());

    // Create authentication object for the Spring SecurityContext
    Authentication auth = new UsernamePasswordAuthenticationToken(userDetails, password, new ArrayList<GrantedAuthority>());

    boolean requiresEmailActivation = this.shopAdminValidationTokenRepository.getRequiresEmailValidation(userEmail);

    if(requiresEmailActivation == true) {

        LOGGER.info("Login denied: Email is not validated yet.");

        // IMPORTANT NOTE: We throw an EmailNotFoundException instead of a
        // PleaseValidateYourEmailFirstException in order to NOT reveal
        // that this email exists. So: Do not "FIX" this!
        throw new EmailAddressNotFoundException();
    }

    LOGGER.debug("Email appears validated.");

    try {
        // Execute authentication chain to try user authentication
        auth = this.adminAuthenticationProvider.authenticate(auth);
    } catch(BadCredentialsException e) {
        // FIXME Login: We could/should count and limit login attempts here?
        LOGGER.info("Bad credentials found for: " + userEmail);
        throw e;
    }

    LOGGER.info("User successfully authenticated [userEmail="+userEmail+"]");

    // Set the authentication to the SecurityContext, the user is now logged in
    SecurityContext securityContext = SecurityContextHolder.getContext();
    securityContext.setAuthentication(auth);


    // Finally load the user data
    ShopAdminDTO shopAdminDto = this.shopAdminRepository.findByUserEmail(userEmail);
    return shopAdminDto;
}

이것은 applicationContext-security.xml 파일입니다.

<!-- //////////////////////////////////////////////////////////////////////////////// -->
<!-- // BEGIN Spring Security -->

<sec:http pattern="/**" auto-config="true" use-expressions="true"/>

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

<bean id="securityContextPersistenceFilter" class="org.springframework.security.web.context.SecurityContextPersistenceFilter">
    <constructor-arg ref="httpSessionSecurityContextRepository" />
</bean>

<bean id="filterChainProxy" class="org.springframework.security.web.FilterChainProxy">
    <constructor-arg>
        <list>
            <sec:filter-chain pattern="/**" filters="securityContextPersistenceFilter" />
        </list>
    </constructor-arg>
</bean>

<bean id="authenticationListener" class="com.mz.server.web.auth.CustomAuthenticationListener"/>

<bean id="authenticationProvider" class="com.mz.server.web.auth.CustomAuthenticationProvider"/>

<bean id="userDetailsService" class="com.mz.server.web.service.CustomUserDetailsService"/>

<sec:authentication-manager alias="authenticationManager">
    <sec:authentication-provider ref="authenticationProvider"/>
</sec:authentication-manager>

<bean id="permissionEvaluator"
      class="com.mz.server.web.auth.permission.CustomPermissionEvaluator">
    <constructor-arg index="0">
        <map key-type="java.lang.String"
             value-type="com.mz.server.web.auth.permission.Permission">
            <entry key="isTest" value-ref="testPermission"/>
        </map>
    </constructor-arg>
</bean>

<bean id="testPermission" class="com.mz.server.web.auth.permission.TestPermission">
</bean>

<bean id="expressionHandler" class="org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler">
    <property name="permissionEvaluator" ref="permissionEvaluator"/>
</bean>

<sec:global-method-security 
    authentication-manager-ref="authenticationManager"
    pre-post-annotations="enabled">     
    <sec:expression-handler ref="expressionHandler"/>       
</sec:global-method-security>

<!-- // END Spring Security -->
<!-- //////////////////////////////////////////////////////////////////////////////// -->

실패한 것은 AbstractSecurityInterceptor # beforeInvocation 함수의이 부분입니다.

if (debug) {
    logger.debug("Secure object: " + object + "; Attributes: " + attributes);
}

if (SecurityContextHolder.getContext().getAuthentication() == null) {
    credentialsNotFound(messages.getMessage(
            "AbstractSecurityInterceptor.authenticationNotFound",
            "An Authentication object was not found in the SecurityContext"),
            object, attributes);
}

Authentication authenticated = authenticateIfRequired();

SecurityContextHolder.getContext (). getAuthentication ()이 null이기 때문에 credentialsNotFound를 호출하는 곳.

서버를 부팅 한 후 첫 번째 로그인에서 실패한 스택 추적 비교 :

[http-bio-8080-exec-4] DEBUG com.mz.server.servlet.LoginServletImpl - Login request by userId: user@gmx.at
[http-bio-8080-exec-4] DEBUG com.mz.server.service.LoginService - Login request for user@gmx.at
[http-bio-8080-exec-4] DEBUG com.mz.server.service.LoginService - Email appears validated.. authenticating..
[http-bio-8080-exec-4] INFO  com.mz.server.spring.auth.AdminAuthenticationProvider - authenticate(), User email: user@gmx.at
[http-bio-8080-exec-4] DEBUG com.mz.server.repository.jooq.shop.ShopAdminRepository - findByUserEmail(): user@gmx.at
[http-bio-8080-exec-4] DEBUG com.mz.server.repository.jooq.shop.ShopAdminRepository - User found.
[http-bio-8080-exec-4] DEBUG com.mz.server.repository.jooq.shop.ShopAdminRepository - Loading password salt for user@gmx.at
[http-bio-8080-exec-4] INFO  com.mz.server.repository.jooq.shop.ShopAdminRepository - Checking password for user@gmx.at
[http-bio-8080-exec-4] DEBUG com.mz.server.repository.jooq.shop.ShopAdminRepository - Password valid.
[http-bio-8080-exec-4] DEBUG com.mz.server.spring.auth.CustomUserAuthentication - getPrincipal()
[http-bio-8080-exec-4] DEBUG com.mz.server.spring.auth.CustomUserAuthentication - Setting user com.mz.server.spring.auth.ShopAdminUserDetails@8ac733b2: Username: user@gmx.at; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Not granted any authorities to 'authenticated'.
[http-bio-8080-exec-4] INFO  com.mz.server.service.LoginService - User successfully authenticated [userEmail=user@gmx.at]
[http-bio-8080-exec-4] DEBUG com.mz.server.repository.jooq.shop.ShopAdminRepository - findByUserEmail(): user@gmx.at
[http-bio-8080-exec-4] DEBUG com.mz.server.repository.jooq.shop.ShopAdminRepository - User found.
[http-bio-8080-exec-6] DEBUG com.mz.server.servlet.shop.ShopServletImpl - Requested available shops.
[http-bio-8080-exec-6] INFO  com.mz.server.servlet.shop.ShopServletImpl - 
[http-bio-8080-exec-6] INFO  com.mz.server.servlet.shop.ShopServletImpl - 
[http-bio-8080-exec-6] INFO  com.mz.server.servlet.shop.ShopServletImpl - SPRING_SECURITY_CONTEXT: org.springframework.security.core.context.SecurityContextImpl@259bee56: Authentication: com.mz.server.spring.auth.CustomUserAuthentication@259bee56
[http-bio-8080-exec-6] INFO  com.mz.server.servlet.shop.ShopServletImpl - 
[http-bio-8080-exec-6] INFO  com.mz.server.servlet.shop.ShopServletImpl - 
[http-bio-8080-exec-6] DEBUG org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor - Secure object: ReflectiveMethodInvocation: public java.util.List com.mz.server.service.ShopService.getAvailableShops(); target is of class [com.mz.server.service.ShopService]; Attributes: [[authorize: 'isAuthenticated()', filter: 'null', filterTarget: 'null']]
[http-bio-8080-exec-6] DEBUG com.mz.server.spring.auth.CustomHttpSessionListener - AuthenticationCredentialsNotFoundEvent
Jun 09, 2016 8:06:42 PM org.apache.catalina.core.ApplicationContext log
SEVERE: Exception while dispatching incoming RPC call
com.google.gwt.user.server.rpc.UnexpectedException: Service method 'public abstract java.util.List com.mz.shared.web.service.shop.ShopServlet.getAvailableShops()' threw an unexpected exception: org.springframework.security.authentication.AuthenticationCredentialsNotFoundException: An Authentication object was not found in the SecurityContext
    at com.google.gwt.user.server.rpc.RPC.encodeResponseForFailure(RPC.java:416)
    at com.google.gwt.user.server.rpc.RPC.invokeAndEncodeResponse(RPC.java:605)
....

작동중인 스택 추적 :

[http-bio-8080-exec-7] DEBUG com.mz.server.servlet.LoginServletImpl - Login request by userId: user@gmx.at
[http-bio-8080-exec-7] DEBUG com.mz.server.service.LoginService - Login request for user@gmx.at
[http-bio-8080-exec-7] DEBUG com.mz.server.service.LoginService - Email appears validated.. authenticating..
[http-bio-8080-exec-7] INFO  com.mz.server.spring.auth.AdminAuthenticationProvider - authenticate(), User email: user@gmx.at
[http-bio-8080-exec-7] DEBUG com.mz.server.repository.jooq.shop.ShopAdminRepository - findByUserEmail(): user@gmx.at
[http-bio-8080-exec-7] DEBUG com.mz.server.repository.jooq.shop.ShopAdminRepository - User found.
[http-bio-8080-exec-7] DEBUG com.mz.server.repository.jooq.shop.ShopAdminRepository - Loading password salt for user@gmx.at
[http-bio-8080-exec-7] INFO  com.mz.server.repository.jooq.shop.ShopAdminRepository - Checking password for user@gmx.at
[http-bio-8080-exec-7] DEBUG com.mz.server.repository.jooq.shop.ShopAdminRepository - Password valid.
[http-bio-8080-exec-7] DEBUG com.mz.server.spring.auth.CustomUserAuthentication - getPrincipal()
[http-bio-8080-exec-7] DEBUG com.mz.server.spring.auth.CustomUserAuthentication - Setting user com.mz.server.spring.auth.ShopAdminUserDetails@8ac733b2: Username: user@gmx.at; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Not granted any authorities to 'authenticated'.
[http-bio-8080-exec-7] INFO  com.mz.server.service.LoginService - User successfully authenticated [userEmail=user@gmx.at]
[http-bio-8080-exec-7] DEBUG com.mz.server.repository.jooq.shop.ShopAdminRepository - findByUserEmail(): user@gmx.at
[http-bio-8080-exec-7] DEBUG com.mz.server.repository.jooq.shop.ShopAdminRepository - User found.
[http-bio-8080-exec-7] DEBUG com.mz.server.servlet.shop.ShopServletImpl - Requested available shops.
[http-bio-8080-exec-7] INFO  com.mz.server.servlet.shop.ShopServletImpl - 
[http-bio-8080-exec-7] INFO  com.mz.server.servlet.shop.ShopServletImpl - 
[http-bio-8080-exec-7] INFO  com.mz.server.servlet.shop.ShopServletImpl - SPRING_SECURITY_CONTEXT: org.springframework.security.core.context.SecurityContextImpl@1ea22883: Authentication: com.mz.server.spring.auth.CustomUserAuthentication@1ea22883
[http-bio-8080-exec-7] INFO  com.mz.server.servlet.shop.ShopServletImpl - 
[http-bio-8080-exec-7] INFO  com.mz.server.servlet.shop.ShopServletImpl - 
[http-bio-8080-exec-7] DEBUG org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor - Secure object: ReflectiveMethodInvocation: public java.util.List com.mz.server.service.ShopService.getAvailableShops(); target is of class [com.mz.server.service.ShopService]; Attributes: [[authorize: 'isAuthenticated()', filter: 'null', filterTarget: 'null']]
[http-bio-8080-exec-7] DEBUG com.mz.server.spring.auth.CustomUserAuthentication - isAuthenticate(): true
[http-bio-8080-exec-7] DEBUG org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor - Previously Authenticated: com.mz.server.spring.auth.CustomUserAuthentication@1ea22883
[http-bio-8080-exec-7] DEBUG org.springframework.security.access.vote.AffirmativeBased - Voter: org.springframework.security.access.prepost.PreInvocationAuthorizationAdviceVoter@653fccd, returned: 1
[http-bio-8080-exec-7] DEBUG org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor - Authorization successful
[http-bio-8080-exec-7] DEBUG org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor - RunAsManager did not change Authentication object
[http-bio-8080-exec-7] DEBUG com.mz.server.service.ShopService - Getting available shops for ..
[http-bio-8080-exec-7] DEBUG com.mz.server.spring.auth.CustomUserAuthentication - getPrincipal()
[http-bio-8080-exec-7] DEBUG com.mz.server.service.ShopService - user@gmx.at
[http-bio-8080-exec-7] DEBUG com.mz.server.repository.jooq.shop.ShopAdminRepository - Fetching shops for shop_admin_id 1

차이점은 첫 번째 스택 추적은 [http-bio-8080-exec-4]와 [http-bio-8080-exec-6] 두 개의 스레드에 의해 생성된다는 것입니다. 스레드 이름이 바뀌고이 예외가 발생한다는 사실을 꽤 자주 봅니다. 이것이 멀티 스레딩 문제 인 것 같습니다.

이것은 전체 web.xml입니다.

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    version="3.0">

    <display-name>mz | life</display-name>

    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
    </welcome-file-list>

    <filter>
        <filter-name>springSecurityFilterChain</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>springSecurityFilterChain</filter-name>
        <url-pattern>/**</url-pattern>
    </filter-mapping>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <listener>
        <listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class>
    </listener>

    <listener>
        <listener-class>com.mz.server.BootstrappingServerConfig</listener-class>
    </listener>

    <!-- -->

    <servlet>
        <servlet-name>application</servlet-name>
        <servlet-class>com.mz.server.servlet.app.ApplicationDataServletImpl</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>application</servlet-name>
        <url-pattern>/app/application</url-pattern>
    </servlet-mapping>

    <!-- -->

    <servlet>
        <servlet-name>login</servlet-name>
        <servlet-class>com.mz.server.servlet.LoginServletImpl</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>login</servlet-name>
        <url-pattern>/app/login</url-pattern>
    </servlet-mapping>

    <!-- -->

    <servlet>
        <servlet-name>shop</servlet-name>
        <servlet-class>com.mz.server.servlet.shop.ShopServletImpl</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>shop</servlet-name>
        <url-pattern>/app/shop</url-pattern>
    </servlet-mapping>

    <!-- -->

    <servlet>
        <servlet-name>shopadmin</servlet-name>
        <servlet-class>com.mz.server.servlet.shop.ShopAdminServletImpl</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>shopadmin</servlet-name>
        <url-pattern>/app/shopadmin</url-pattern>
    </servlet-mapping>

    <!-- 
        XSRF-Token Servlet 
    -->

    <servlet>
        <servlet-name>xsrf</servlet-name>
        <servlet-class>com.google.gwt.user.server.rpc.XsrfTokenServiceServlet</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>xsrf</servlet-name>
        <url-pattern>/app/xsrf</url-pattern>
    </servlet-mapping>

    <!-- 
        This is the name of the session cookie set by the Servlet container (e.g. Tomcat or Jetty) 
    -->
    <context-param>
        <param-name>gwt.xsrf.session_cookie_name</param-name>
        <param-value>JSESSIONID</param-value>
    </context-param>

    <!-- -->

    <servlet>
        <servlet-name>mobile-restapi</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>mobile-restapi</servlet-name>
        <url-pattern>/app/restapi/*</url-pattern>
    </servlet-mapping>

    <!-- -->

    <servlet>
        <servlet-name>web-restapi</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/classes/context/applicationContext-restapi.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>web-restapi</servlet-name>
        <url-pattern>/rest/*</url-pattern>
    </servlet-mapping>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            /WEB-INF/classes/context/root-context.xml
        </param-value>
    </context-param>


</web-app>

해결법

  1. ==============================

    1.한 스레드에서 다른 스레드로 SecurityContext를 전송할 수 있습니다.

    한 스레드에서 다른 스레드로 SecurityContext를 전송할 수 있습니다.

    Runnable originalRunnable = new Runnable() {
    public void run() {
        // invoke secured service
    }
    };
    SecurityContext context = SecurityContextHolder.getContext();
    DelegatingSecurityContextRunnable wrappedRunnable =
        new DelegatingSecurityContextRunnable(originalRunnable, context);
    
    new Thread(wrappedRunnable).start();
    

    동시성 지원을 참조하십시오.

    http://docs.spring.io/spring-security/site/docs/current/reference/html/concurrency.html

  2. ==============================

    2.allowSessionCreation을 false로 설정했습니다.

    allowSessionCreation을 false로 설정했습니다.

    이 플래그를 false로 설정해도,이 클래스는 시큐리티 문맥을 포함 할 수 없습니다. 응용 프로그램 (또는 다른 필터)이 세션을 만드는 경우 보안 컨텍스트는 여전히 인증 된 사용자에 대해 저장됩니다.

    세션을 올바르게 만들고 있습니까? 정말로 보안을 통해 세션을 만들지 않겠습니까?

  3. ==============================

    3.글쎄, 나는 이것을 가지고있다.

    글쎄, 나는 이것을 가지고있다.

    <filter>
        <filter-name>springSecurityFilterChain</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>
    
    <filter-mapping>
        <filter-name>springSecurityFilterChain</filter-name>
        <url-pattern>/**</url-pattern>
    </filter-mapping>
    

    단순히 다음과 같이 변경했습니다.

    <filter>
        <filter-name>springSecurityFilterChain</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>
    
    <filter-mapping>
        <filter-name>springSecurityFilterChain</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    

    나 또한 제거했다.

    <sec:http pattern="/**" auto-config="true" use-expressions="true" />
    

    내 applicationContext-spring.xml에이 별칭을 추가했습니다.

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

    마지막 applicationContext-spring.xml은 다음과 같습니다.

    <beans xmlns="http://www.springframework.org/schema/beans"
    
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    
        xmlns:sec="http://www.springframework.org/schema/security"
    
        xsi:schemaLocation="http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
        http://www.springframework.org/schema/security 
        http://www.springframework.org/schema/security/spring-security-4.0.xsd"    
        >
    
        <!-- Imports -->
        <import resource="applicationContext-spring-acl.xml"/>
    
        <bean id="authenticationListener" class="com.mahlzeit.server.spring.auth.CustomAuthenticationListener"/>
    
        <bean id="httpSessionListener" class="com.mahlzeit.server.spring.auth.CustomHttpSessionListener"/>
    
        <bean id="adminAuthenticationProvider" class="com.mahlzeit.server.spring.auth.AdminAuthenticationProvider">
            <constructor-arg ref="dslContext" />
        </bean>
    
        <bean id="userDetailsService" class="com.mahlzeit.server.service.CustomUserDetailsService"/>
    
        <sec:authentication-manager alias="authenticationManager">
            <sec:authentication-provider ref="adminAuthenticationProvider"/>
        </sec:authentication-manager>
    
        <!-- Filter Chain -->
    
        <bean id="httpSessionSecurityContextRepository" class='org.springframework.security.web.context.HttpSessionSecurityContextRepository'/>
    
        <bean id="securityContextPersistenceFilter" class="org.springframework.security.web.context.SecurityContextPersistenceFilter">
            <constructor-arg ref="httpSessionSecurityContextRepository" />
        </bean>
    
        <alias name="filterChainProxy" alias="springSecurityFilterChain"/>
    
        <bean id="filterChainProxy" class="org.springframework.security.web.FilterChainProxy">
            <constructor-arg>
                <list>
                    <sec:filter-chain pattern="/**" filters="securityContextPersistenceFilter" />
                </list>
            </constructor-arg>
        </bean>
    
    </beans>
    

    나는 이것을 참고로 삼았다.

  4. from https://stackoverflow.com/questions/34273755/why-is-the-authentication-object-of-the-securitycontext-not-shared-across-thread by cc-by-sa and MIT license