簡介
Spring Security 是一個擁有高度可定制或的認證和授權(quán)機制的安全框架,同時更是守護基于 Spring 應用「事實上的」安全標準裙戏。
它支持開箱即用,只需簡單配置即可保護應用遠離黑客攻擊厕诡,支持以下功能:
- 防護會話固定攻擊(Session Fixation)累榜、點擊劫持(Clickjacking)以及跨站請求偽造(CSRF)等常見攻擊
- 支持表單登錄、OAuth 2.0 登錄
- 認證方式多種多樣灵嫌,支持基于內(nèi)存(In-Memory)認證壹罚、基于JDBC 數(shù)據(jù)庫認證、基于 LDAP 認證或者自定義的認證方式
- 集成 Servlet API寿羞,支持 HttpServletRequest.logout() login() 等方法
- 記住我認證
完整介紹可以參考Spring Security 白皮書猖凛。
認證與授權(quán)
上述功能可能仍然不滿足實際需要,因此有必要了解 Spring Security 認證和授權(quán)大體流程以便實現(xiàn)定制化绪穆。
有的讀者可能認為認證與授權(quán)是一回事辨泳,實際上兩者的界限非常清晰。認證是指驗證登錄用戶身份是否合法玖院,而授權(quán)則是驗證用戶能否訪問的指定資源菠红。
Spring Security 認證流程如下:
- 用戶輸入用戶名和密碼,形成用戶名和密碼令牌
UsernamePasswordAuthenticationToken
难菌; - 將令牌傳遞給認證管理器(
AuthenticationManager
)驗證是否有效试溯; - 認證成功后,認證管理器返回一個包含必需信息的認證令牌郊酒;
- 此時調(diào)用
SecurityContextHolder.getContext().setAuthentication(…?)
方法為用戶建立安全上下文遇绞,放入剛剛得到的認證令牌。
Spring Security 授權(quán)流程如下:
- 從安全上下文容器(
SecurityContextHolder
)獲取認證令牌 (Authentication
)燎窘; - 通過在安全元信息源(
SecurityMetadataSource
)中查找對比用戶的認證令牌 (Authentication
)來判斷當前請求申請公開資源還是安全資源摹闽; - 如果是安全資源,則詢問訪問控制器(
AccessDecisionManager
)是否允許訪問荠耽; - 如果是開放資源钩骇,則直接允許訪問。
Spring Security 通過 Web 安全配置適配器 WebSecurityConfigurerAdapter
來暴露配置接口铝量,可配置項極其豐富倘屹。具體如何配置以及每項配置用途可參看剛剛提到的白皮書或 Javadoc。
Ajax 登錄
Spring Security 默認支持表單登錄慢叨,如果你的項目使用 Ajax 來實現(xiàn)用戶登錄則需要按以下步驟進行配置纽匙。
- 首先,繼承 Web 安全配置適配器
WebSecurityConfigurerAdapter
來配置HttpSecurity
對象拍谐,這個對象主要用來配置 HTTP 認證和授權(quán)相關(guān)選項烛缔。
我們要配置的地方比較多比較雜馏段,因此提供如下參考代碼:
package me.jerrychin.spring.security.demo;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
/**
* 配置應用安全相關(guān)功能
*/
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private static final Logger logger = LogManager.getLogger(SecurityConfig.class);
@Override
protected void configure(HttpSecurity http) throws Exception {
// 暫時禁用 csrf
http.csrf().disable().authorizeRequests()
// 允許訪問 /public 下方所有路徑
.antMatchers("/public/**").permitAll()
.and()
.formLogin() // 開啟表單登錄(我們實際上是通過 Ajax 來登錄)
.loginPage("/login/page") // 登錄主頁,由于我們是 Ajax 登錄践瓷,這里的主頁實際上一串 JSON
.failureUrl("/login/fail") // 失敗時院喜,重定向客戶端用戶到此地址。failureForwardUrl 和 failureUrl 只能設(shè)置一個
.successForwardUrl("/login/success") // 認證成功對于的處理頁面(注意這里的 FORWARD 時內(nèi)部轉(zhuǎn)跳)
.failureForwardUrl("/login/fail") // 認證失敗對于的處理頁面(注意這里的 FORWARD 時內(nèi)部轉(zhuǎn)跳)
.loginProcessingUrl("/login") // 處理登錄 POST 請求的接口名晕翠,Spring Security 已經(jīng)幫我們實現(xiàn)
.passwordParameter("password") // 密碼對應的參數(shù)名
.usernameParameter("username") // 用戶名對應的參數(shù)名
// 允許訪問所有登錄相關(guān)的URL
.permitAll();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 你可以使用這里配置的用戶憑據(jù)登錄
auth.inMemoryAuthentication().withUser("user").password("password").roles("USER")
.and().withUser("admin").password("password").roles("USER", "ADMIN");
}
}
上述配置出現(xiàn)了多個 URL喷舀,這些 URL對應的接口都是需要我們自己來實現(xiàn)。比如認證失敗淋肾,我們肯定想向客戶端返回相應的錯誤代碼和原因硫麻。
下面的代碼實現(xiàn)了上述 URL以便 Spring Security 適時調(diào)用,主要目的是向客戶端響應合適的 JSON 認證結(jié)果樊卓。
package me.jerrychin.spring.security.demo;
import com.alibaba.fastjson.JSONObject;
import com.eques.eqhome.commons.ErrorCode;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.WebAttributes;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Ajax 形式的登錄控制器帅掘,主要用于響應認證成功或失敗
*
*
* @author Jerry Chin
*/
@Controller
@ResponseBody
@RequestMapping(path = "/login")
public class LoginController {
private static final Logger logger = LogManager.getLogger(LoginController.class);
// 登錄主頁
@RequestMapping("/page")
public JSONObject handleLoginPage() {
JSONObject object = new JSONObject();
object.put("reason", "未登錄");
object.put("code", 100);
return object;
}
// 登錄成功
@RequestMapping("/success")
public JSONObject handleSuccessLogin(HttpServletRequest request, HttpServletResponse response) {
JSONObject object = new JSONObject();
object.put("reason", "登錄成功");
object.put("code", 0);
return object;
}
// 登錄失敗
@RequestMapping("/fail")
public JSONObject handleFailLogin(HttpServletRequest request) {
AuthenticationException ex = (AuthenticationException) request.getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
JSONObject object = new JSONObject();
if (ex instanceof BadCredentialsException) {
object.put("reason", "賬號或密碼錯誤");
object.put("code", ErrorCode.UNAUTHORIZED.code);
} else {
object.put("reason", "未知錯誤");
object.put("code", ErrorCode.UNKNOWN.code);
}
return object;
}
}
這里其實省略了很多步驟癣朗,比如如何建立 Spring Security 項目環(huán)境郭毕,由于這些事情跟本文關(guān)系不大朋魔,所以你必須自己搞定。
動態(tài)授權(quán)
Spring Security 的授權(quán)配置十分靈活唾戚,但存在一個致命缺陷————無法動態(tài)更新授權(quán)配置奢赂,這代表什么意思呢?
很簡單颈走,一旦應用啟動則無法修改或重新加載新的授權(quán)配置,除非....重啟應用咱士!這顯然太沙雕了立由。
不過,Spring Security 提供了強大的定制化接口序厉,就看你動手能力強不強了锐膜。
...待續(xù)未完