天天看點

從零開始的Spring Session(三)

上一篇文章中,我們使用Redis內建了Spring Session。大多數的配置都是Spring Boot幫我們自動配置的,這一節我們介紹一點Spring Session較為進階的特性。

https://blog.didispace.com/spring-session-xjf-3/#%E9%9B%86%E6%88%90Spring-Security 內建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都會被重新整理,這顯然不是我們想要的結果。

從零開始的Spring Session(三)

這個現象筆者研究了不少源碼,但并沒有得到非常滿意的解釋,隻能了解為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,但是這個現象的确是一個疑點。

https://blog.didispace.com/spring-session-xjf-3/#%E4%BD%BF%E7%94%A8%E8%87%AA%E5%AE%9A%E4%B9%89CookieSerializer 使用自定義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,便可以很友善的實作同父域下的單點登入。

https://blog.didispace.com/spring-session-xjf-3/#%E6%A0%B9%E6%8D%AE%E7%94%A8%E6%88%B7%E5%90%8D%E6%9F%A5%E6%89%BE%E7%94%A8%E6%88%B7%E5%BD%92%E5%B1%9E%E7%9A%84SESSION 根據使用者名查找使用者歸屬的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,于是我們通路這個端點,可以看到如下的結果

從零開始的Spring Session(三)

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

總結

Spring Session對各種場景下的Session管理提供一套非常完善的實作。筆者所介紹的,僅僅是Spring Session常用的一些特性,更多的知識點可以在spring.io的文檔中一覽無餘,以及本文中作者存在的一個疑惑,如有興趣可與我溝通。