# 相關代碼
https://github.com/mofadeyunduo/money
0.1.3-SNAPSHOT
security 模塊中
# 原因
最近在做一款管理金錢的網站進行自娛自樂,發現沒有安全控制豈不是大家都知道我的工資了(一臉黑線)?
最近公司也在搞 Spring OAuth2,當時我沒有時間(其實那時候不想搞)就沒做,現在回頭來學習學習。
Spring OAuth2 官方的教程寫的比較少,實用性比較差。
# 教程內容
- Spring OAuth2 Github SSO
- 替換認證過的 Spring Security Authentication 對象
- 后端設置 Cookie 實現 Remember-Me 功能
# Github 登錄
Spring OAuth2 Github,官網寫的已經比較詳細了。
要注意的是必須要配置兩個 Filter:
- OAuth2ClientAuthenticationProcessingFilter ,用於處理 OAuth2 流程。
- OAuth2ClientContextFilter,用於觸發跳轉到 OAuth2 請求。
P.S. 這很詭異,我不知道 Spring OAuth2 為什么要這么做。
# 替換認證過的 Spring Security Authentication
步驟:
- 重寫方法 getPrincipal,把 Authentication 中的 Principal 換成用戶信息對象。
- 最好設置 AuthenticationSuccessHandler,默認的 AuthenticationSuccessHandler 會跳轉到主頁,並將 Cookie 清空,影響 RememberMeServices 認證。
@Override protected Object getPrincipal(Map<String, Object> map) { String principal = String.class.cast(map.get("name")); // 從數據庫讀取用戶 UserWithAccount userWithAccount = userAndAccountService.getByPrincipalAndType(principal, AccountType.GITHUB); // 未獲取到用戶信息,保存 if (Objects.isNull(userWithAccount)) { Account newAccount = new Account(null, AccountType.GITHUB, principal, new Gson().toJson(map)); userWithAccount = userAndAccountService.accountSignup(newAccount); } // 替換原來的 OAuth2Authentication return userWithAccount; }
githubFilter.setAuthenticationSuccessHandler(new GithubAuthenticationSuccessHandler());
# 設置 Cookie
- 實現 UserDetailServices。
- 用 UserDetailServices 構造 RememberMeServices。這里采用的 RememberMeServices 的具體實現是 TokenBasedRememberMeServices。TokenBasedRememberMeServices 會用 UserDetailServices 構造 Authentication 的 Principal 對象。還有一個構造參數(key)是指定鹽,指定一個值,該值在應用反復重啟的時要保持不變。順便提一下,TokenBasedRememberMeServices 加密是根據 UserDetailServices 構造出的 UserDetails 中的 username、password、時間戳、構造參數 key 進行 MD5 和 Base64 加密和解密。
- 在 OAuth2ClientAuthenticationProcessingFilter 和繼承類 WebSecurityConfigurerAdapter 的方法 configure 注冊 RememberMeServices。注冊到 OAuth2ClientAuthenticationProcessingFilter 是為了在 OAuth2 認證成功或者失敗之后設置 Cookie。在 configure 注冊 RememberMeServices 是為了利用 Cookie 自動登錄。
public class CachingUserDetailsService implements UserDetailsService { private static final String USER_KEY = "user:%s"; private StringRedisTemplate stringRedisTemplate; private UserAndAccountService userAndAccountService; public CachingUserDetailsService(StringRedisTemplate stringRedisTemplate, UserAndAccountService userAndAccountService) { this.stringRedisTemplate = stringRedisTemplate; this.userAndAccountService = userAndAccountService; } public UserDetails loadUserByUsername(String username) { String actualKey = String.format(USER_KEY, username); UserWithAccount userWithAccount; String userWithAccountJson = stringRedisTemplate.opsForValue().get(actualKey); if (StringUtils.isEmpty(userWithAccountJson)) { userWithAccount = userAndAccountService.getByUserId(Integer.parseInt(username)); stringRedisTemplate.opsForValue().set(actualKey, new Gson().toJson(userWithAccount)); } else { userWithAccount = new Gson().fromJson(userWithAccountJson, UserWithAccount.class); } return userWithAccount; } }
@Bean public RememberMeServices rememberMeServices() { TokenBasedRememberMeServices tokenBasedRememberMeServices = new TokenBasedRememberMeServices(REMEMBER_ME_KEY, userDetailsService()); tokenBasedRememberMeServices.setAlwaysRemember(true); tokenBasedRememberMeServices.setTokenValiditySeconds(ONE_DAY_IN_SECONDS); return tokenBasedRememberMeServices; }
@Override protected void configure(HttpSecurity http) throws Exception { http .rememberMe() .key(REMEMBER_ME_KEY) .rememberMeServices(rememberMeServices()); http .addFilterBefore(oAuth2ClientAuthenticationProcessingFilter(), BasicAuthenticationFilter.class); // Github OAuth2 登錄的 Filter } @Bean public OAuth2ClientAuthenticationProcessingFilter oAuth2ClientAuthenticationProcessingFilter() { OAuth2ClientAuthenticationProcessingFilter githubFilter = new OAuth2ClientAuthenticationProcessingFilter("/security/oauth2/github"); OAuth2RestTemplate facebookTemplate = new OAuth2RestTemplate(githubClient(), oauth2ClientContext); githubFilter.setAllowSessionCreation(false); githubFilter.setRestTemplate(facebookTemplate); githubFilter.setTokenServices(new GithubUserInfoTokenServices(githubResource().getUserInfoUri(), githubClient().getClientId(), userAndAccountService)); githubFilter.setRememberMeServices(rememberMeServices()); githubFilter.setAuthenticationSuccessHandler(new GithubAuthenticationSuccessHandler()); return githubFilter; }
# 后記
我本來預估一周時間就把我項目的安全控制模塊給開發完,結果被 Spring Oauth2 拖了一周。由於對 Spring 核心概念也不是特別熟悉,看代碼也比較費事(雖然大部分看得懂)。最近一段時間准備寫一套 IoC 方面的代碼。
我現在所在的公司之前有個人挺厲害,自己寫了一套 IoC 容器用到了系統中。不過他也很坑,他寫的 IoC 容器出了問題,根本找不到任何解決資料,只能看源碼。自己寫的只能做玩具玩玩吧。