2017-01-12 2 views
2

Я работаю над приложением Spring-MVC, в котором мы используем Spring-security для аутентификации и авторизации. Есть некоторые задачи, в которых мы хотели бы использовать Thread, но всякий раз, когда мы используем поток Java, и пытаемся получить информацию об аутентифицированном пользователе.Сессия Spring-security недоступна в java-потоке

Я также пробовал аннотацию @Async, но это были не то, что мы рассматриваем только как отдельные части кода, которые нам нужны в потоке.

Есть ли способ вставить весеннюю сессию в поток Java? Если нет, альтернативные способы запуска частей кода в отдельном потоке. Спасибо.

Пример кода:

public void sendNotification(Notification notification, int memberId) { 
      Thread thread = new Thread(() -> { 
      Person onlinePerson = this.personService.getCurrentlyAuthenticatedUser(); 
      GroupMembers groupMembers = this.groupMembersService.getMemberById(memberId); 
} 
thread.start(); 
} 

Теперь, в приведенном выше коде, если я пытаюсь получить какую-либо информацию о onlinePerson, такие как идентификатор с onlinePerson.getId() ;, я получаю NPE. Проверьте журнал ошибок:

Exception in thread "Thread-9" java.lang.NullPointerException 
    at com.project.spring.chat.ChatServiceImpl.lambda$sendNotification$3(ChatServiceImpl.java:532) 
    at java.lang.Thread.run(Thread.java:745) 

Любая идея, как я могу решить эту проблему. Спасибо.

Security-applicationContext.xml:

<security:http pattern="/resources/**" security="none"/> 
    <security:http create-session="ifRequired" use-expressions="true" auto-config="false" disable-url-rewriting="true"> 
     <security:form-login login-page="/login" username-parameter="j_username" password-parameter="j_password" 
          login-processing-url="/j_spring_security_check" default-target-url="/canvaslisting" 
          always-use-default-target="true" authentication-failure-url="/denied"/> 
     <security:remember-me key="_spring_security_remember_me" user-service-ref="userDetailsService" 
           token-validity-seconds="1209600" data-source-ref="dataSource"/> 
     <security:logout delete-cookies="JSESSIONID" invalidate-session="true" logout-url="/j_spring_security_logout"/> 
     <!--<security:intercept-url pattern="/**" requires-channel="https"/>--> 
     <security:port-mappings> 
      <security:port-mapping http="80" https="443"/> 
     </security:port-mappings> 
     <security:logout logout-url="/logout" logout-success-url="/" success-handler-ref="myLogoutHandler"/> 

     <security:session-management session-fixation-protection="newSession"> 
      <security:concurrency-control session-registry-ref="sessionReg" max-sessions="5" expired-url="/login"/> 
     </security:session-management> 
    </security:http> 

    <beans:bean id="sessionReg" class="org.springframework.security.core.session.SessionRegistryImpl"/> 

    <beans:bean id="rememberMeAuthenticationProvider" 
       class="org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices"> 
     <beans:constructor-arg index="0" value="_spring_security_remember_me"/> 
     <beans:constructor-arg index="1" ref="userDetailsService"/> 
     <beans:constructor-arg index="2" ref="jdbcTokenRepository"/> 
     <property name="alwaysRemember" value="true"/> 
    </beans:bean> 

    <beans:bean id="jdbcTokenRepository" 
       class="org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl"> 
     <beans:property name="createTableOnStartup" value="false"/> 
     <beans:property name="dataSource" ref="dataSource"/> 
    </beans:bean> 

    <!-- Remember me ends here --> 
    <security:authentication-manager alias="authenticationManager"> 
     <security:authentication-provider user-service-ref="LoginServiceImpl"> 
      <security:password-encoder ref="encoder"/> 
     </security:authentication-provider> 
    </security:authentication-manager> 

    <beans:bean id="encoder" 
       class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"> 
     <beans:constructor-arg name="strength" value="11"/> 
    </beans:bean> 

    <beans:bean id="daoAuthenticationProvider" 
       class="org.springframework.security.authentication.dao.DaoAuthenticationProvider"> 
     <beans:property name="userDetailsService" ref="LoginServiceImpl"/> 
     <beans:property name="passwordEncoder" ref="encoder"/> 
    </beans:bean> 
</beans> 
+1

Вы можете использовать 'DelegatingSecurityContextExecutor' для передачи SecurityContext в новые потоки. См. [Spring Security 3.2 M1 Highlights, поддержка сервлета 3 API] (http://spring.io/blog/2012/12/17/spring-security-3-2-m1-highlights-servlet-3-api-support/). Вы также можете настроить его в своей реализации «AsyncConfigurer», чтобы он работал с '@ Async'. – Ritesh

ответ

2

Это потому, что информация сеанса хранится в ThreadLocal против потока, который обрабатывается запрос HTTP. Он по умолчанию не доступен для новых потоков (создаете ли вы их вручную или через @Async).

К счастью, это легко настроить:

SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL) //in a context loader listener for example

Или с переменной окружения:

spring.security.strategy=MODE_INHERITABLETHREADLOCAL

+0

Спасибо. Я обновил свой вопрос с помощью Security-applicationContext.xml. Вы знаете, где находится вышеуказанная конфигурация? –

+0

Я не думаю, что он может туда попасть. Вам нужно либо «ContextLoaderListener» для первого способа установить его программно, либо как параметр JVM при запуске: '-Dspring.security.strategy = MODE_INHERITABLETHREADLOCAL' – StuPointerException

+0

Это сработало хорошо. Как вы можете видеть, я также обсуждаю это с другим пользователем. Меня немного путают с Runnable, который часто используется для предоставления кода, который должен выполнять поток, но Runnable сам по себе не имеет ничего общего с потоками. Это всего лишь объект с методом run(). Означает ли это, что новый Thread(), который я создаю, не является нитью. Спасибо. –

0
public class HelloThread extends Thread { 

    public void run() { 
     System.out.println("Hello from a thread!"); 
     sendNotification(notification, memberId); 
    } 

    public static void main(String args[]) { 
     (new HelloThread()).start(); 
    } 

} 

Runnable часто используется, чтобы обеспечить код, что поток должен run, но Runnable сам не имеет ничего общего с потоками. Это всего лишь объект с методом run().

+0

Я не понимаю вашу точку зрения. Не могли бы вы рассказать? Спасибо. –

0
public void sendNotification(Notification notification, int memberId) { 
     Thread thread = new Thread(new Runnable(){ 
      public void run() 
      { 
       Person onlinePerson = this.personService.getCurrentlyAuthenticatedUser(); 
       GroupMembers groupMembers = this.groupMembersService.getMemberById(memberId); 
      } 
     } 
     thread.start(); 
    } 
+0

Итак, вы имеете в виду нить, которую я создаю новой Thread(), не acutally нить? –

+0

да, у вас нет темы создания и для ваших запросов синхронизироваться на новом объекте() и отправить – gygy19

-1

Да вы не должны создать поток и для ваших запросов синхронизации на static final new Object() и отправить

+0

Пожалуйста, отредактируйте ответ, чтобы я мог удалить downvote –

1

Ваш класс нитей не управляется spring, потому что в вашем примере кода вы вручную создаете экземпляр класса, вызывая его конструктор. Вам нужно получить экземпляр вашего потока, управляемый весной, и извлечь его из бобов, используя весну ApplicationContext. Я думаю, вы хотите, чтобы несколько экземпляров потоков запускались одновременно? Если это так, для каждого выполнения потока может потребоваться новый поток-экземпляр (bean). Вот пример того, как вы можете это сделать:

@Component 
@Scope("prototype") // the scope “prototype" will return a new instance each time when the bean will be retrieved. 
public class YourThreadClass implements Runnable { 
    private String anyString; 

    @Autowired 
    AnyOtherBean anyOtherBean; // since YourThreadClass is now managed by spring, you can also inject services/components into YourThreadClass 
} 

@Component 
public class YourClassThatStartsTheThread{ 
    @Autowired 
    ApplicationContext ctx; 

    public void sendNotification(Notification notification, int memberId){ 
     // ... your code .... 
     yourThreadClass aNewInstanceOfYourThreadClass = (YourThreadClass) ctx.getBean(yourThreadClass); 
     // ... your code ... 
    } 
}