SpringBoot集成Spring Security

一、Spring Security介紹

Spring security,是一個強大的和高度可定製的身份驗證和訪問控制框架。它是確保基於Spring的應用程序的標準 ——來自官方參考手冊java

Spring securityshiro 同樣,具備認證、受權、加密等用於權限管理的功能。和 shiro 不一樣的是,Spring security擁有比shiro更豐富的功能,而且,對於Springboot而言,Spring SecurityShiro更合適一些,由於都是Spring家族成員。今天,咱們來爲SpringBoot項目集成Spring Security程序員

本文所使用的版本:web

SpringBoot : 2.2.6.RELEASESpring Security : 5.2.2.RELEASE面試

二、配置Spring Security

SpringBoot中集成Spring Security很簡單,只須要在pom.xml中添加下面代碼就行:spring

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>複製代碼

這裏能夠不指定Spring Security的版本號,它會根據SpringBoot的版原本匹配對應的版本,SpringBoot版本是 2.2.6.RELEASE,對應Spring Security的版本是5.2.2.RELEASEsql

而後,咱們就能夠將springboot啓動了。數據庫

當咱們嘗試訪問項目時,它會跳轉到這個界面來:安全

image-20200415201525535

​ 對!在此以前,你什麼也不用作。這就是Spring Security的優雅之處。你只須要引入Spring Security的包,它就能在你的項目中工做。由於它已經幫你實現了一個簡單的登錄界面。根據官方介紹,登陸使用的帳號是user,密碼是隨機密碼,這個隨機密碼能夠在控制檯中找到,相似這樣的一句話:springboot

Using generated security password: 1cb77bc5-8d74-4846-9b6c-4813389ce096複製代碼

​ Using generated security password後面的的就是系統給的隨機密碼,咱們可使用這個密碼進行登陸。隨機密碼在每一次啓動服務後生成(若是你配置了熱部署devtools,你得隨時留意控制檯了,由於每當你修改了代碼,系統會自動重啓,那時隨機密碼就會從新生成)。服務器

​ 固然,這樣的功能必定不是你想要的,也必定不會就這樣拿給你的用戶使用。那麼,接下來,讓咱們把它配置成咱們想要的樣子。

​ 要實現自定義配置,首先要建立一個繼承於WebSecurityConfigurerAdapter的配置類:

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

}
複製代碼

​ 這裏使用了@EnableWebSecurity註解,這個註解是Spring Security用於啓用web安全的註解。具體實現,這裏就不深刻了。

​ 要實現自定義攔截配置,首先得告訴Spring Security,用戶信息從哪裏獲取,以及用戶對應的角色等信息。這裏就須要重寫WebSecurityConfigurerAdapterconfigure(AuthenticationManagerBuilder auth)方法了。這個方法將指使Spring Security去找到用戶列表,而後再與想要經過攔截器的用戶進行比對,再進行下面的步驟。

Spring Security的用戶存儲配置有多個方案能夠選擇,包括:

  • 內存用戶存儲
  • 數據庫用戶存儲
  • LDAP用戶存儲
  • 自定義用戶存儲

​ 咱們分別來看看這幾種用戶存儲的配置方法:

1.內存用戶存儲

​ 此配置方式是直接將用戶信息存儲在內存中,這種方式在速度上無疑是最快的。但只適用於有限個用戶數量,且這些用戶幾乎不會發生改變。咱們來看看配置方法:

@Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().passwordEncoder(passwordEncoder())
            .withUser("zhangsan").password(passwordEncoder().encode("123456")).authorities("ADMIN")
            .and()
            .withUser("lisi").password(passwordEncoder().encode("123456")).authorities("ORDINARY");
    }

    private PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }複製代碼

​ 能夠看到,AuthenticationManagerBuilder使用構造者方式來構建的。在上面方法中,先調用了inMemoryAuthentication()方法,它來指定用戶存儲在內存中。接下來又調用了passwordEncoder()方法,這個方法的做用是告訴Spring Security認證密碼的加密方式。由於在Spring security5事後,必須指定某種加密方式,否則程序會報錯。接下來調用的withUser()、password()、authorities()方法,分別是在指定用戶的帳號、密碼以及權限名。在添加完一個用戶後,要使用and()方法來鏈接下一個用戶的添加。

​ 若是使用這種配置方法,你會發現,在修改用戶時,就必須修改代碼。對於絕大多數項目來講,這種方式是知足不了需求的,至少咱們須要一個註冊功能。

2.數據庫用戶存儲

​ 將用戶信息存儲在數據庫中,讓咱們能夠很方便地對用戶信息進行增刪改查。而且還能夠爲用戶添加除認證信息外的附加信息,這樣的設計也是咱們不少當心應用所採起的方式。讓咱們來實現如下:

@Autowired
    private DataSource dataSource;


    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.jdbcAuthentication().dataSource(dataSource).passwordEncoder(passwordEncoder())
            .usersByUsernameQuery(
                    "select username, password, status from Users where username = ?")
            .authoritiesByUsernameQuery(
                    "select username, authority from Authority where username = ?");

    }

    private PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }複製代碼

​ 調用jdbcAuthentication()來告訴Spring Security使用jdbc的方式來查詢用戶和權限,dataSource()方法指定數據庫鏈接信息,passwordEncoder()指定密碼加密規則,用戶的密碼數據應該以一樣的方式進行加密存儲,否則,兩個加密方式不一樣的密碼,匹配補上。usersByUsernameQuery()authoritiesByUsernameQuery()方法分別定義了查詢用戶和權限信息的sql語句。其實,Spring security爲咱們默認了查詢用戶、權限甚至還有羣組用戶受權的sql,這三條默認的sql存放在org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl中,有興趣的小夥伴能夠點進去看看。若是你要使用默認的,那你的表中關鍵性的字段必須和語句中的一致。

​ 使用數據庫來存儲用戶和權限等信息已經能夠知足大部分的需求。可是Spring security還爲咱們提供了另一種配置方式,讓咱們來看一下。

3.LDAP用戶存儲

LDAP:輕型目錄訪問協議,是一個開放的,中立的,工業標準的應用協議,經過IP協議提供訪問控制和維護分佈式信息的目錄信息。簡單來講,就是將用戶信息存放在另一臺服務器中(固然,也能夠在同一臺服務器,但咱們通常不這麼作),經過網絡來進行訪問的技術。

​ 咱們來簡單配置一下:

@Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        LdapAuthenticationProviderConfigurer<AuthenticationManagerBuilder> configurer =                     auth.ldapAuthentication()
            .userSearchBase("ou=people")
            .userSearchFilter("(uid={0})")
            .groupSearchBase("ou=groups")
            .groupSearchFilter("member={0}");

        configurer.passwordCompare()
            .passwordEncoder(passwordEncoder())
            .passwordAttribute("passcode");
        configurer.contextSource().url("ldap://xxxxx.com:33389/dc=xxxxxx,dc=com");
    }

    private PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }複製代碼

userSearchFilter()groupSearchFilter()設置的是用戶和羣組的過濾條件,而userSearchBase()groupSearchBase()設置了搜索起始位置,contextSource().url()設置LDAP服務器的地址。若是沒有遠程的服務器可使用contextSource().root()來使用嵌入式LDAP服務器,此方式將使用項目中的用戶數據文件來提供認證服務。

​ 若是以上幾種方式還不能知足咱們的需求,咱們能夠用自定義的方式來配置。

4.自定義用戶存儲

​ 自定義用戶存儲,就是自行使用認證名稱來查找對應的用戶數據,而後交給Spring Security使用。咱們須要定義一個實現UserDetailsServiceservice類:

@Service
public class MyUserDetailsService implements UserDetailsService{

    @Autowired
    private UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userMapper.getUserByUsername(username);
        return user == null ? new User() : user;
    }
}

public class User implements UserDetails {
    ...
}複製代碼

​ 該類只須要實現一個方法:loadUserByUsername()。該方法須要作的是使用傳過來的username來匹配一個帶有密碼等信息的用戶實體。須要注意的是這裏的User類須要實現UserDetails,也就是說,查到的信息裏,必須得有Spring Security所須要的信息。

​ 下面,讓咱們來繼續配置:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private MyUserDetailsService userDetailsService;

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

    @Bean
    private PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}複製代碼

​ 這樣的配置方法就很簡單了,只須要告訴Spring Security你的UserDetailsService實現類是哪一個就能夠了,它會去調用loadUserByUsername()來查找用戶。

​ 以上就是Spring Security所提供的4種用戶存儲方式,接下來,須要考慮的是,怎麼攔截請求。

三、請求攔截

1.安全規則

Spring Security的請求攔截配置方法是用戶存儲配置方法的重載方法,咱們先來簡單配置一下:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/user", "/menu")
            .hasRole("ADMIN")
            .antMatchers("/", "/**").permitAll();
    }
}複製代碼

​ 調用authorizeRequests()方法後,就能夠添加自定義攔截路徑了。antMatchers()方法配置了請求路徑,hasRole()permitAll()指定了訪問規則,分別表示擁有「ADMIN」權限的用戶才能訪問、全部用戶能夠訪問。

​ 須要注意的是:這裏的配置須要成對出現,而且配置的順序也很重要。聲明在前面的規則擁有更高的優先級。也就是說,若是咱們將.antMatchers("/", "/").permitAll()**放到了最前面,像這樣:

@Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/", "/**").permitAll()
             .antMatchers("/user", "/menu")
            .hasRole("ADMIN");
    }複製代碼

​ 那麼,下面的"/user"和 "/menu"的配置是徒勞,由於前面的規則已經指明全部路徑能被全部人訪問。固然權限的規則方法還有不少,我這裏只列舉了兩個。如下爲常見的內置表達式:

表達 描述
hasRole(String role) 返回true當前委託人是否具備指定角色。例如, hasRole('admin')默認狀況下,若是提供的角色不是以「 ROLE_」開頭,則會添加該角色。能夠經過修改defaultRolePrefixon來自定義DefaultWebSecurityExpressionHandler
hasAnyRole(String… roles) 返回true當前委託人是否具備提供的任何角色(以逗號分隔的字符串列表形式)。例如, hasAnyRole('admin', 'user')默認狀況下,若是提供的角色不是以「 ROLE_」開頭,則會添加該角色。能夠經過修改defaultRolePrefixon來自定義DefaultWebSecurityExpressionHandler
hasAuthority(String authority) 返回true當前委託人是否具備指定權限。例如, hasAuthority('read')
hasAnyAuthority(String… authorities) 返回true若是當前主體具備任何所提供的當局的(給定爲逗號分隔的字符串列表)例如, hasAnyAuthority('read', 'write')
principal 容許直接訪問表明當前用戶的主體對象
authentication 容許直接訪問AuthenticationSecurityContext
permitAll 始終評估爲 true
denyAll 始終評估爲 false
isAnonymous() 返回true當前委託人是否爲匿名用戶
isRememberMe() 返回true當前主體是不是「記住我」的用戶
isAuthenticated() true若是用戶不是匿名的,則返回
isFullyAuthenticated() 返回true若是用戶不是匿名或記得,個人用戶
hasPermission(Object target, Object permission) 返回true用戶是否能夠訪問給定權限的給定目標。例如,hasPermission(domainObject, 'read')
hasPermission(Object targetId, String targetType, Object permission) 返回true用戶是否能夠訪問給定權限的給定目標。例如,hasPermission(1, 'com.example.domain.Message', 'read')

除此以外,還有一個支持SpEL表達式計算的方法,它的使用方法以下:

@Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/user", "/menu")
            .access("hasRole('ADMIN')")
            .antMatchers("/", "/**").permitAll();
    }複製代碼

​ 它所實現的規則和上面的方法同樣。Spring Security還提供了其餘豐富的SpEL表達式,如:

表達 描述
hasRole(String role) 返回true當前委託人是否具備指定角色。例如, hasRole('admin')默認狀況下,若是提供的角色不是以「 ROLE_」開頭,則會添加該角色。能夠經過修改defaultRolePrefixon來自定義DefaultWebSecurityExpressionHandler
hasAnyRole(String… roles) 返回true當前委託人是否具備提供的任何角色(以逗號分隔的字符串列表形式)。例如, hasAnyRole('admin', 'user')默認狀況下,若是提供的角色不是以「 ROLE_」開頭,則會添加該角色。能夠經過修改defaultRolePrefixon來自定義DefaultWebSecurityExpressionHandler
hasAuthority(String authority) 返回true當前委託人是否具備指定權限。例如, hasAuthority('read')
hasAnyAuthority(String… authorities) 返回true若是當前主體具備任何所提供的當局的(給定爲逗號分隔的字符串列表)例如, hasAnyAuthority('read', 'write')
principal 容許直接訪問表明當前用戶的主體對象
authentication 容許直接訪問AuthenticationSecurityContext
permitAll 始終評估爲 true
denyAll 始終評估爲 false
isAnonymous() 返回true當前委託人是否爲匿名用戶
isRememberMe() 返回true當前主體是不是「記住我」的用戶
isAuthenticated() true若是用戶不是匿名的,則返回
isFullyAuthenticated() 返回true若是用戶不是匿名或記得,個人用戶
hasPermission(Object target, Object permission) 返回true用戶是否能夠訪問給定權限的給定目標。例如,hasPermission(domainObject, 'read')
hasPermission(Object targetId, String targetType, Object permission) 返回true用戶是否能夠訪問給定權限的給定目標。例如,hasPermission(1, 'com.example.domain.Message', 'read')
2.登陸

​ 若是此時,咱們有本身的登陸界面,須要替換掉Spring Security所提供的默認的界面,這時能夠用fromLogin()loginPage()方法來實現:

@Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/user", "/menu")
            .access("hasRole('ADMIN')")
            .antMatchers("/", "/**").permitAll()
            .and()
            .formLogin()
            .loginPage("/login");
    }複製代碼

​ 這便將登陸地址指向了「/login」。若是須要指定登陸成功時,跳轉的地址,可使用defaultSuccessUrl()方法:

.and()
            .formLogin()
            .loginPage("/login")
            .defaultSuccessUrl("/home")複製代碼

​ 此時用戶登陸事後,將跳轉到主頁來。

​ 下面,咱們來看看登出。

3.登出

​ 和登陸相似的,可使用logout()logoutSuccessUrl()方法來實現:

.and()
            .logout()
            .logoutSuccessUrl("/login")複製代碼

​ 上面例子中,用戶登出後將跳轉到登陸界面。

四、小結

至此,咱們已基本瞭解了Spring Security配置,能夠將它配置成咱們想要的樣子(基本)。其實Spring Security能作的事還有不少,光看我這篇文章是不夠的。學習它最有效的方法就是閱讀官方文檔。裏面有關於Spring Security最全最新的知識!(官網地址:spring.io/projects/sp…


最後,最近不少小夥伴找我要Linux學習路線圖,因而我根據本身的經驗,利用業餘時間熬夜肝了一個月,整理了一份電子書。不管你是面試仍是自我提高,相信都會對你有幫助!目錄以下:

免費送給你們,只求你們金指給我點個贊!

電子書 | Linux開發學習路線圖

也但願有小夥伴能加入我,把這份電子書作得更完美!

有收穫?但願老鐵們來個三連擊,給更多的人看到這篇文章

推薦閱讀:

相關文章
相關標籤/搜索