簡述 Spring Security 框架及其不足

簡介

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 認證流程如下:

  1. 用戶輸入用戶名和密碼,形成用戶名和密碼令牌 UsernamePasswordAuthenticationToken难菌;
  2. 將令牌傳遞給認證管理器(AuthenticationManager)驗證是否有效试溯;
  3. 認證成功后,認證管理器返回一個包含必需信息的認證令牌郊酒;
  4. 此時調(diào)用 SecurityContextHolder.getContext().setAuthentication(…?) 方法為用戶建立安全上下文遇绞,放入剛剛得到的認證令牌。

Spring Security 授權(quán)流程如下:

  1. 從安全上下文容器(SecurityContextHolder)獲取認證令牌 (Authentication )燎窘;
  2. 通過在安全元信息源(SecurityMetadataSource)中查找對比用戶的認證令牌 (Authentication )來判斷當前請求申請公開資源還是安全資源摹闽;
  3. 如果是安全資源,則詢問訪問控制器(AccessDecisionManager)是否允許訪問荠耽;
  4. 如果是開放資源钩骇,則直接允許訪問。

Spring Security 通過 Web 安全配置適配器 WebSecurityConfigurerAdapter 來暴露配置接口铝量,可配置項極其豐富倘屹。具體如何配置以及每項配置用途可參看剛剛提到的白皮書或 Javadoc。

Ajax 登錄

Spring Security 默認支持表單登錄慢叨,如果你的項目使用 Ajax 來實現(xiàn)用戶登錄則需要按以下步驟進行配置纽匙。

  1. 首先,繼承 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ù)未完

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市弛房,隨后出現(xiàn)的幾起案子道盏,更是在濱河造成了極大的恐慌,老刑警劉巖文捶,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件荷逞,死亡現(xiàn)場離奇詭異,居然都是意外死亡粹排,警方通過查閱死者的電腦和手機种远,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來顽耳,“玉大人坠敷,你說我怎么就攤上這事妙同。” “怎么了膝迎?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵粥帚,是天一觀的道長。 經(jīng)常有香客問我限次,道長芒涡,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任掂恕,我火速辦了婚禮拖陆,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘懊亡。我一直安慰自己依啰,他們只是感情好,可當我...
    茶點故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布店枣。 她就那樣靜靜地躺著速警,像睡著了一般。 火紅的嫁衣襯著肌膚如雪鸯两。 梳的紋絲不亂的頭發(fā)上闷旧,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天,我揣著相機與錄音钧唐,去河邊找鬼忙灼。 笑死,一個胖子當著我的面吹牛钝侠,可吹牛的內(nèi)容都是我干的该园。 我是一名探鬼主播,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼帅韧,長吁一口氣:“原來是場噩夢啊……” “哼里初!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起忽舟,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤双妨,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后叮阅,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體刁品,經(jīng)...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年浩姥,在試婚紗的時候發(fā)現(xiàn)自己被綠了哑诊。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,577評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡及刻,死狀恐怖镀裤,靈堂內(nèi)的尸體忽然破棺而出竞阐,到底是詐尸還是另有隱情,我是刑警寧澤暑劝,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布骆莹,位于F島的核電站,受9級特大地震影響担猛,放射性物質(zhì)發(fā)生泄漏幕垦。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一傅联、第九天 我趴在偏房一處隱蔽的房頂上張望先改。 院中可真熱鬧,春花似錦蒸走、人聲如沸仇奶。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽该溯。三九已至,卻和暖如春别惦,著一層夾襖步出監(jiān)牢的瞬間狈茉,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工掸掸, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留氯庆,地道東北人。 一個月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓扰付,卻偏偏與公主長得像点晴,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子悯周,可洞房花燭夜當晚...
    茶點故事閱讀 43,452評論 2 348

推薦閱讀更多精彩內(nèi)容