從零開始的Spring Session(三)

我們使用Redis集成了Spring Session。大多數的配置都是Spring Boot幫我們自動配置的,這一節我們介紹一點Spring Session較爲高級的特性。

集成Spring Security

之所以把Spring Session和Spring Security放在一起討論,是因爲我們的應用在集成Spring Security之後,用戶相關的認證與Session密不可分,如果不注意一些細節,會引發意想不到的問題。

與Spring Session相關的依賴可以參考上一篇文章,這裏給出增量的依賴:

  • <dependency>
  •     <groupId>org.springframework.boot</groupId>
  •     <artifactId>spring-boot-starter-security</artifactId>
  • </dependency>

我們引入依賴後,就已經自動配置了Spring Security,我們在application.yml添加一個內存中的用戶:

  • security:
  •   user:
  •     name: admin
  •     password: admin

測試登錄點沿用上一篇文章的端點,訪問 http://localhost:8080/test/cookie?browser=chrome端點後會出現http basic的認證框,我們輸入admin/admin,即可獲得結果,也遇到了第一個坑點,我們會發現每次請求,sessionId都會被刷新,這顯然不是我們想要的結果。

 

這個現象筆者研究了不少源碼,但並沒有得到非常滿意的解釋,只能理解爲SecurityAutoConfiguration提供的默認配置,沒有觸發到響應的配置,導致了session的不斷刷新(如果讀者有合理的解釋可以和我溝通)。Spring Session之所以能夠替換默認的tomcat httpSession是因爲配置了 springSessionRepositoryFilter這個過濾器,且提供了非常高的優先級,這歸功於 AbstractSecurityWebApplicationInitializer , AbstractHttpSessionApplicationInitializer 這兩個初始化器,當然,也保證了Spring Session會在Spring Security之前起作用。

而解決上述的詭異現象也比較容易(但原理不清),我們使用@EnableWebSecurity對Spring Security進行一些配置,即可解決這個問題。

  • @EnableWebSecurity
  • public class SecurityConfig extends WebSecurityConfigurerAdapter {
  •     // @formatter:off
  •     @Override
  •     protected void configure(HttpSecurity http) throws Exception {
  •         http
  •             .authorizeRequests()
  •             .antMatchers("/resources/**").permitAll()
  •             .anyRequest().authenticated()
  •             .and()
  •                 .httpBasic()//<1>
  •             .and()
  •             .logout().permitAll();
  •     }
  •     // @formatter:on
  •     // @formatter:off
  •     @Autowired
  •     public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
  •         auth
  •                 .inMemoryAuthentication()
  •                 .withUser("admin").password("admin").roles("USER");//<2>
  •     }
  •     // @formatter:on
  • }

<1> 不想大費周章寫一個登錄頁面,於是開啓了http basic認證

<2> 配置了security config之後,springboot的autoConfig就會失效,於是需要手動配置用戶。

再次請求,可以發現SessionId返回正常,@EnableWebSecurity似乎觸發了相關的配置,當然了,我們在使用Spring Security時不可能使用autoconfig,但是這個現象的確是一個疑點。

使用自定義CookieSerializer

  • @Bean
  • public CookieSerializer cookieSerializer() {
  •     DefaultCookieSerializer serializer = new DefaultCookieSerializer();
  •     serializer.setCookieName("JSESSIONID");
  •     serializer.setCookiePath("/");
  •     serializer.setDomainNamePattern("^.+?\\.(\\w+\\.[a-z]+)$");
  •     return serializer;
  • }

使用上述配置後,我們可以將Spring Session默認的Cookie Key從SESSION替換爲原生的JSESSIONID。而CookiePath設置爲根路徑且配置了相關的正則表達式,可以達到同父域下的單點登錄的效果,在未涉及跨域的單點登錄系統中,這是一個非常優雅的解決方案。如果我們的當前域名是 moe.cnkirito.moe,該正則會將Cookie設置在父域 cnkirito.moe中,如果有另一個相同父域的子域名 blog.cnkirito.moe也會識別這個Cookie,便可以很方便的實現同父域下的單點登錄。

根據用戶名查找用戶歸屬的SESSION

這個特性聽起來非常有意思,你可以在一些有趣的場景下使用它,如知道用戶名後即可刪除其SESSION。一直以來我們都是通過線程綁定的方式,讓用戶操作自己的SESSION,包括獲取用戶名等操作。但如今它提供了一個反向的操作,根據用戶名獲取SESSION,恰巧,在一些項目中真的可以使用到這個特性,最起碼,當別人問起你,或者討論到和SESSION相關的知識時,你可以明晰一點,這是可以做到的。

我們使用Redis作爲Session Store還有一個好處,就是其實現了 FindByIndexNameSessionRepository接口,下面讓我們來見證這一點。

  • @Controller
  • public class CookieController {
  •     @Autowired
  •     FindByIndexNameSessionRepository<? extends ExpiringSession> sessionRepository;
  •     @RequestMapping("/test/findByUsername")
  •     @ResponseBody
  •     public Map findByUsername(@RequestParam String username) {
  •         Map<String, ? extends ExpiringSession> usersSessions = sessionRepository.findByIndexNameAndIndexValue(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, username);
  •         return usersSessions;
  •     }
  • }

由於一個用戶可能擁有多個Session,所以返回的是一個Map信息,而這裏的username,則就是與Spring Security集成之後的用戶名,最令人感動Spring厲害的地方,是這一切都是自動配置好的。我們在內存中配置的用戶的username是admin,於是我們訪問這個端點,可以看到如下的結果

 

連同我們存入session中的browser=chrome,browser=360都可以看見(只有鍵名)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章