如果想要提供自動登入的功能,可以在登入頁面上提供「記住我」的功能,若使用者決定自動登入,使用 Cookie 來存放自動登入憑據,下次使用者再度造訪時,看看該 Cookie 是否失效,基本上就是自動登入的實作原理。
問題在於,使用簡單的 Cookie 憑據會有容易被偽造的問題,必須對 Cookie 的憑據進行加工使其不易被偽造來避免風險,可以使用 Spring Security 的 Remember Me 功能來簡單達到這項任務。
最簡單的方式,就是在設定頁面防護時,呼叫 rememberMe
方法就可以了:
package cc.openhome.web;
... 略
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
... 略
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin").hasRole("ADMIN")
.antMatchers("/member").hasAnyRole("ADMIN", "MEMBER")
.antMatchers("/user").authenticated()
.anyRequest().permitAll()
.and()
.rememberMe() // 記住我
.and()
.formLogin()
.loginPage("/login_page")
.loginProcessingUrl("/perform_login")
.failureUrl("/login_page?error")
.and()
.logout()
.logoutUrl("/perform_logout")
.logoutSuccessUrl("/login_page?logout");
}
}
如此用來作為自動登入的 Cookie 憑據預設是兩星期有效期,可以使用 tokenValiditySeconds
自訂有效期,Cookie 的名稱預設是 remember-me
,可以使用 rememberMeCookieName
自訂名稱。
登入的頁面中,可以使用 checkbox
類型的輸入欄位,例如:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>登入表單</title>
</head>
<body>
<form method="post" action="perform_login">
<span th:unless="${param.error == null}" th:text="登入失敗">登入失敗</span>
<span th:unless="${param.logout == null}" th:text="你已經登出">你已經登出</span>
<h2>請登入</h2>
<p>名稱 <input type="text" name="username" required autofocus/></p>
<p>密碼 <input type="password" name="password" required/></p>
<p>記住我 <input name="remember-me" type="checkbox"/></p>
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
<button type="submit">登入</button>
</form>
</body>
</html>
預設的請求參數名稱為 remember-me
,可以使用 rememberMeParameter
方法來自訂請求參數名稱。如果選擇「記住我」的話,發送的 Cooke 會像是:
Spring Security 會以使用者名稱、密碼、有效期以及一個防止被修改的鍵來編碼 Cooke 值:
BASE64 編碼(名稱 + ":" + 有效期 + MD5 雜湊(名稱 + ":" + 有效期 + ":" + 密碼 + ":" + 鍵))
防止被修改的鍵預設是隨機產生,也可以使用 key
方法來自行設定,在使用者造訪網站時若有這樣的 Cookie 發送過來,Spring Security 可以根據使用者名稱從來取得 Web 應用程式中存放的密碼,經相同運算後,與 Cookie 值比對,若符合就自動登入。
當然,雖然避免了容易被偽造的問題,不過相同有效期間下,產生的 Cookie 值是固定的,還是有 Cookie 被擷取並於有效期內惡意運用的風險。
如果想在每次自動登入後,產生不同的 Cookie 值,可以在每次自動登入時,隨機產生 Token 值加入 Cookie 值的運算,當然,這個隨機產生的 Token,必須存放在 Web 應用程式之中,若想要實作此功能,可以使用 Token Repository。
例如,若想使用資料庫,基於 JDBC,可以建立以下的表格:
CREATE TABLE persistent_logins (
username VARCHAR(64) NOT NULL,
series VARCHAR(64) NOT NULL,
token VARCHAR(64) NOT NULL,
last_used TIMESTAMP NOT NULL,
PRIMARY KEY (series)
);
Spring Security 提供了 JdbcTokenRepositoryImpl
,它實作了 PersistentTokenRepository
介面,實現了 Token Repository 的增刪查找,使用的 SQL 語句,可以在 JdbcTokenRepositoryImpl.java 找到。
基本上,使用 JdbcTokenRepositoryImpl
只需要設定 DataSource
就可以了,為了簡化範例,在這邊使用 H2 的嵌入式資料庫:
package cc.openhome.web;
...略
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
...略
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin").hasRole("ADMIN")
.antMatchers("/member").hasAnyRole("ADMIN", "MEMBER")
.antMatchers("/user").authenticated()
.anyRequest().permitAll()
.and()
.rememberMe().tokenRepository(persistentTokenRepository())
.and()
.formLogin()
.loginPage("/login_page")
.loginProcessingUrl("/perform_login")
.failureUrl("/login_page?error")
.and()
.logout()
.logoutUrl("/perform_logout")
.logoutSuccessUrl("/login_page?logout");
}
@Bean(destroyMethod="shutdown")
public DataSource dataSource(){
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript("classpath:persistent_logins.sql")
.build();
}
public PersistentTokenRepository persistentTokenRepository() {
JdbcTokenRepositoryImpl repo = new JdbcTokenRepositoryImpl();
repo.setDataSource(dataSource());
return repo;
}
}
可以看到,在呼叫 rememberMe
之後,可以使用 tokenRepository
方法來設定 PersistentTokenRepository
實例,如此就可以使用資料表格來儲存 Token 值了。
你可以在 RememberMe 找到以上的範例專案。