Spring Security基本原理和自定義認(rèn)證邏輯

image

概述

Web系統(tǒng)中的認(rèn)證和權(quán)限服務(wù)是現(xiàn)在互聯(lián)網(wǎng)服務(wù)中不可或缺的一部分存炮,目前主流的安全框架無(wú)非就是Apache Shiro和Spring Security。Apache Shiro簡(jiǎn)單易用是一大優(yōu)勢(shì),而Spring Security則功能更為強(qiáng)大且能夠配合Spring使用久窟。


Spring Security的hello world

先聲明本文意在闡明Spring Security的基本原理尉尾,所以代碼沒(méi)有持久層部分(數(shù)據(jù)庫(kù)操作)。

Web項(xiàng)目搭建

先利用SpringBoot搭建一個(gè)簡(jiǎn)單的Web服務(wù)并且加入Spring Security依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

寫一個(gè)簡(jiǎn)單的controller

@RestController
public class HelloController {
    @RequestMapping("hello")
    public String hello(){
        return "hello spring security";
    }
}

咋們嘗試訪問(wèn)這個(gè)接口localhost:8080/hello萌庆,不能直接訪問(wèn)了溶褪,而需要表單登錄


登錄頁(yè)面

在加入Spring Security后就有一個(gè)默認(rèn)的安全配置,所有服務(wù)器資源的訪問(wèn)都需要經(jīng)過(guò)表單登錄認(rèn)證践险。并且會(huì)生成一個(gè)用戶名user的憑證猿妈,密碼是程序啟動(dòng)時(shí)控制臺(tái)輸出的一串字符


控制臺(tái)輸出
加入自定義安全配置

自定義安全配置類,繼承WebSecurityConfigurerAdapter

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .formLogin()
                .and()
                .authorizeRequests()
                .anyRequest()
                .authenticated();
    }

}

http.formLogin().and().authorizeRequests().anyRequest().authenticated()巍虫,指定了默認(rèn)的驗(yàn)證方式是表單登錄并且任何請(qǐng)求都需要經(jīng)過(guò)認(rèn)證彭则。Spring Security早期的版本默認(rèn)指定的認(rèn)證方式是HttpBasic的認(rèn)證方式,現(xiàn)在的默認(rèn)就是表單登錄驗(yàn)證占遥。所以如果使用了最新版本的Spring Security俯抖,根據(jù)上面的配置和之前不加入配置并沒(méi)有變化

Spring Security工作原理

Spring Security的核心就是過(guò)濾器鏈,在訪問(wèn)服務(wù)器資源時(shí)請(qǐng)求需要經(jīng)過(guò)過(guò)濾器鏈才能獲取資源


Spring Security過(guò)濾器鏈

圖中綠色的過(guò)濾器是負(fù)責(zé)認(rèn)證的過(guò)濾器瓦胎,比如HttpBasic和表單認(rèn)證蚌成;橙色過(guò)濾器過(guò)濾器鏈的最后一關(guān),它判斷請(qǐng)求用戶是否認(rèn)證和擁有權(quán)限凛捏,沒(méi)通過(guò)會(huì)拋出異常担忧;藍(lán)色是異常處理過(guò)濾器,它對(duì)橙色過(guò)濾器拋出的異常進(jìn)行處理坯癣。其中綠色的過(guò)濾器可以根據(jù)配置決定是否生效和順序瓶盛,藍(lán)色和橙色是固定順序和一定生效的


Spring Security自定義認(rèn)證邏輯

處理用戶信息獲取邏輯和密碼加密解密處理

上面提到Spring Security默認(rèn)會(huì)生成一個(gè)用戶名user的憑證并且提供密碼,但是我們的實(shí)際需求肯定不是這樣的示罗,所以我們自定義處理用戶信息獲取邏輯惩猫。我們通過(guò)自定義用戶信息獲取類,它實(shí)現(xiàn)UserDetailsService接口

@Component
public class MyUserDetailService implements UserDetailsService {

    @Autowired
    private BCryptPasswordEncoder bCryptPasswordEncoder;

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        /**
         * 這里實(shí)際情況應(yīng)該是根據(jù)參數(shù)s查詢數(shù)據(jù)庫(kù)用戶數(shù)據(jù)
         */
        return new User("admin",bCryptPasswordEncoder.encode("123"), AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
    }
}

上面并沒(méi)有對(duì)數(shù)據(jù)庫(kù)的真實(shí)查詢操作蚜点,只是模擬而已轧房。loadUserByUsername方法返回一個(gè)Spring Security提供的User類(這個(gè)類可以是一個(gè)自定義的User類,后面會(huì)說(shuō))绍绘。然后Spring Security配置類中重寫另一個(gè)configure方法對(duì)userDetailsService進(jìn)行配置奶镶,配置成我們自定義的用戶信息獲取類(auth.userDetailsService(myUserDetailService)指定了自定義處理用戶信息獲取的類)迟赃,并且初始化一個(gè)BCryptPasswordEncoder的編碼器

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{

    @Autowired
    private MyUserDetailService myUserDetailService;

    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .formLogin()
                .and()
                .authorizeRequests()
                .anyRequest()
                .authenticated();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(myUserDetailService);
        super.configure(auth);
    }
}

重新啟動(dòng)服務(wù)嘗試訪問(wèn)http://localhost:8080/hello,輸入用戶名admin厂镇,密碼123

訪問(wèn)成功

處理用戶檢驗(yàn)邏輯

在實(shí)際需求中纤壁,我們可能需要判斷用戶過(guò)期、用戶鎖定捺信、憑證過(guò)期酌媒、用戶可用等問(wèn)題。Spring Security提供的User類可能并不能解決我們的需求迄靠,自定義一個(gè)User實(shí)現(xiàn)UserDetails

public class User implements UserDetails{
    private Integer id;
    private String username;
    private String password;
    private Integer age;
    private Boolean isAccountNonExpired;
    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Boolean getAccountNonExpired() {
        return isAccountNonExpired;
    }

    public void setAccountNonExpired(Boolean accountNonExpired) {
        isAccountNonExpired = accountNonExpired;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }

    @Override
    public String getPassword() {
        return this.username;
    }

    @Override
    public String getUsername() {
        return this.password;
    }

    @Override
    public boolean isAccountNonExpired() {
        return isAccountNonExpired;
    }

    @Override
    public boolean isAccountNonLocked() {
        return false;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return false;
    }

    @Override
    public boolean isEnabled() {
        return false;
    }
}

其中包括一個(gè)用戶的一些基本字段秒咨,通過(guò)重寫isAccountNonExpired、isAccountNonLocked掌挚、isCredentialsNonExpired雨席、isEnabled自定義用戶校驗(yàn)邏輯。比如重寫isAccountNonExpired根據(jù)isAccountNonExpired字段的布爾值判斷是否用戶過(guò)期疫诽。修改MyUserDetailService的loadUserByUsername

@Component
public class MyUserDetailService implements UserDetailsService {

    @Autowired
    private BCryptPasswordEncoder bCryptPasswordEncoder;

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        /**
         * 這里實(shí)際情況應(yīng)該是根據(jù)參數(shù)s查詢數(shù)據(jù)庫(kù)用戶數(shù)據(jù)
         */
        User user = new User();
        user.setId(1);
        user.setUsername("admin");
        user.setPassword("123");
        user.setAge(21);
        user.setAccountNonExpired(false);
        return user;
        //return new User("admin",bCryptPasswordEncoder.encode("123"), AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
    }
}

重新啟動(dòng)服務(wù),嘗試訪問(wèn)http://localhost:8080/hello旦委,輸入admin奇徒、123,校驗(yàn)結(jié)果是用戶被鎖定

用戶鎖定


總結(jié)

  • Spring Security的Hello World
  • Spring Security的工作原理
  • Spring Security自定義認(rèn)證邏輯

Github倉(cāng)庫(kù)地址:https://github.com/iemi/spring-security-hello-world

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末缨硝,一起剝皮案震驚了整個(gè)濱河市摩钙,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌查辩,老刑警劉巖胖笛,帶你破解...
    沈念sama閱讀 218,386評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異宜岛,居然都是意外死亡长踊,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門萍倡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)身弊,“玉大人,你說(shuō)我怎么就攤上這事列敲≮宸穑” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 164,704評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵戴而,是天一觀的道長(zhǎng)凑术。 經(jīng)常有香客問(wèn)我,道長(zhǎng)所意,這世上最難降的妖魔是什么淮逊? 我笑而不...
    開(kāi)封第一講書人閱讀 58,702評(píng)論 1 294
  • 正文 為了忘掉前任催首,我火速辦了婚禮,結(jié)果婚禮上壮莹,老公的妹妹穿的比我還像新娘翅帜。我一直安慰自己,他們只是感情好命满,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,716評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布涝滴。 她就那樣靜靜地躺著,像睡著了一般胶台。 火紅的嫁衣襯著肌膚如雪器贩。 梳的紋絲不亂的頭發(fā)上损搬,一...
    開(kāi)封第一講書人閱讀 51,573評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼卧蜓。 笑死,一個(gè)胖子當(dāng)著我的面吹牛功炮,可吹牛的內(nèi)容都是我干的慕匠。 我是一名探鬼主播,決...
    沈念sama閱讀 40,314評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼阅仔,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼吹散!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起八酒,我...
    開(kāi)封第一講書人閱讀 39,230評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤空民,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后羞迷,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體界轩,經(jīng)...
    沈念sama閱讀 45,680評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,873評(píng)論 3 336
  • 正文 我和宋清朗相戀三年衔瓮,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了浊猾。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,991評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡热鞍,死狀恐怖与殃,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情碍现,我是刑警寧澤幅疼,帶...
    沈念sama閱讀 35,706評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站昼接,受9級(jí)特大地震影響爽篷,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜慢睡,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,329評(píng)論 3 330
  • 文/蒙蒙 一逐工、第九天 我趴在偏房一處隱蔽的房頂上張望铡溪。 院中可真熱鬧,春花似錦泪喊、人聲如沸棕硫。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,910評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)哈扮。三九已至,卻和暖如春蚓再,著一層夾襖步出監(jiān)牢的瞬間滑肉,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,038評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工摘仅, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留靶庙,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,158評(píng)論 3 370
  • 正文 我出身青樓娃属,卻偏偏與公主長(zhǎng)得像六荒,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子矾端,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,941評(píng)論 2 355