2015-07-14 2 views
1

Я пишу приложение весеннего ботинка (весенний ботинок 1.2.2, весна 4.1.5), что по существу является REST API. Я настроил весеннюю безопасность для автоматической загрузки пользователя, проверки авторизации и т. Д. При каждом HTTP-запросе. Пользователи хранятся в базе данных, я использую hibernate для доступа ко всем данным внутри этой базы данных.Избегайте разрушения сеанса гибернации между методами

Короче говоря, проблема заключается в следующем: Я загружаю объект user из своей базы данных, используя спящий режим для каждого запроса HTTP во время PreFilter. Позже я получаю этот самый объект в параметрах моего RestController, но он больше не привязан к сеансу спящего режима. Из-за этого я не могу получить доступ к ленивым коллекциям внутри пользовательского объекта, не перечитывая его сначала из базы данных.

Вопрос: Есть ли способ начать сеанс гибернации во время PreFilter и сохранить его до тех пор, пока HTTP-запрос не будет завершен?

Длинная версия, с кодом: Прежде всего, я установка пружина безопасность поэтому она будет загружать мой пользовательский объект как часть авторизации: конфигурации
безопасности: Детали

@Configuration 
@Component 
@EnableWebMvcSecurity 
@EnableGlobalMethodSecurity(prePostEnabled = true) 
public class SecurityConfig extends WebSecurityConfigurerAdapter { 
    @Autowired 
    UserDetailsService userDetailsService; 

    @Override 
    protected void configure(AuthenticationManagerBuilder auth) throws Exception { 
     auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder()); 
    } 

    ... 
} 

пользователя служба, который загружает пользователей из базы данных:

@Service 
@Transactional 
public class DomainUserDetailsService implements UserDetailsService { 
    @Autowired 
    private UserRepository userRepository; 

    @Override 
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { 
     User user = userRepository.findByUsername(username); 
     if (user == null) 
      throw new UsernameNotFoundException("User '" + username + "' not found"); 
     return user; 
    } 
} 

Я также хочу, чтобы ввести объект пользователя в методы контроллера, так что я делаю это так:

@PreAuthorize("hasAuthority('VIEW_ACCOUNT_INFO')") 
@Transactional(readOnly = true) 
@RequestMapping(value = "/user/api-keys/list", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) 
public KeysResponse getApiKeys(
     @AuthenticationPrincipal User user 
) { 
    return new KeysResponse(user); 
} 

Это прекрасно работает. Однако, если я пытаюсь ленивой нагрузки коллекцию, связанную с пользователем, я получаю исключение:

@Table(name = "users") 
public class User implements UserDetails { 
    ... 
    @Column 
    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY) 
    private Set<ApiKey> keys; 
    ... 
} 

user.getKeys(); <-- Exception here 

failed to lazily initialize a collection of role: com.sebbia.pushwit.core.domain.User.keys, could not initialize proxy - no Session 

Это происходит потому, что hiberante сессия была закрыта после того, как объект пользователя был загружен во время ПРЕДФИЛЬТРА, и теперь переменная user отсоединяется. Для того, чтобы присоединить его снова, я должен повторно загрузить его из базы данных:

@PreAuthorize("hasAuthority('MANAGE_ACCOUNT_INFO')") 
@Transactional(readOnly = false) 
@RequestMapping(value = "/user/api-keys/add", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) 
public KeysResponse addApiKey(
     @RequestParam(required = true) String name, 
     @RequestParam(required = true) Role role, 
     @AuthenticationPrincipal User user 
) throws ApiKeyAlreadyExistsException { 
    // I have to re-attach user object to new session 
    Session session = (Session) entityManager.getDelegate(); 
    user = (User) session.merge(user); 

    // Now I can change user or load any of it's collections 
    ... 
} 

Это работает, но это, кажется, подвержены ошибкам и ненужные. Я по существу загружаю user из базы данных дважды. Это кажется пустой тратой, учитывая, что я только что загрузил ее в предыдущем методе, DomainUserDetailsService.loadUserByUsername.

Есть ли способ начать сеанс гибернации во время PreFilter и сохранить его до тех пор, пока HTTP-запрос не будет завершен?

+1

Почему вы не рассматриваете явную загрузку коллекции ключей api при первой загрузке пользователя? Или другой вариант - загрузить только коллекцию ключей api для данного пользователя, когда это необходимо. В любом случае вам не нужно перезагружать пользовательский объект. Если вам нужно больше объяснений, дайте мне знать. –

+0

@ PavlaNováková Спасибо за ваше предложение, это достойное решение проблемы. Тем не менее, я по-прежнему предпочитаю решение, которое позволяет мне обращаться к переменным обычным способом, например user.getKeys(). – Alexey

+0

С решением, которое я предложил, вы можете получить доступ к наборам ключей api для данного пользователя как user.getKeys() - в первом случае вы можете инициализировать вызов коллекции user.getKeys(). Size() в методе loadUserByUsername, например а затем получить доступ к коллекции как обычно - user.getKeys(), не получив LazyInitializationException, а во втором случае (явная загрузка набора ключей позже) логика метода будет: загрузить api-ключи с помощью данного идентификатора пользователя, а затем просто вызвать user.setKeys (загруженныеKeys) и получить к ним доступ user.getKeys(). Открытая сессия - это плохая практика, на мой взгляд. –

ответ

0

После некоторых раздумий, я пошел с ответом, user1675642: Я решил использовать один сеанс спящего режима (с возможными вложенными сеансов) для каждого запроса HTTP. Я не вижу никаких проблем с этим подходом для моего конкретного случая, потому что я всегда открываю соединение с базой данных для каждого запроса, поэтому открытие нескольких методов в трассировке стека на самом деле не имеет никакого значения (на самом деле это имеет значение, потому что я не нужно открывать его дважды).

Однако я не мог использовать предоставленную рамку OpenSessionInViewFilter.Я получаю исключение «java.lang.IllegalArgumentException: ServletContext не должно быть null». Я не был в состоянии выяснить, какая часть моей конфигурации была причина этой проблемы, поэтому я пошел вперед и написал свою собственную реализацию:

Во-первых, вы должны объявить следующий фильтр:

@Component 
public class OpenSessionFilter extends OncePerRequestFilter { 
    @Autowired 
    OpenSessionService sessionService; 

    @Override 
    protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response, final FilterChain filterChain) throws ServletException, IOException { 
     sessionService.doFilterInTransaction(request, response, filterChain); 
    } 
} 

Тогда , вы должны объявить следующие услуги:

@Service 
public class OpenSessionService { 
    @Transactional 
    public void doFilterInTransaction(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { 
     filterChain.doFilter(request, response); 
    } 
} 

Однако, это отвечает на мой вопрос, но это не решает мою проблему. Дело в том, что Spring Security кэширует принципы аутентификации. Это означает, что фильтр аутентификации будет загружать пользователя из базы данных только время от времени. Это означает, что объект User, исходящий из Spring Security, все еще может быть не привязан к спящему контексту (потому что он был создан во время одного из предыдущих запросов). Это заставляет меня создать обертку вокруг объекта User, которая будет вручную повторно прикреплять объект пользователя к новому сеансу гибернации, когда это необходимо.

Смежные вопросы