Spring Security 入門原理及實戰

參考博客: http://www.javashuo.com/article/p-tnxxjbfy-mr.htmlhtml

Spring Security 是spring項目之中的一個安全模塊,能夠很是方便與spring項目無縫集成。web

特別是在spring boot項目中加入spring security更是十分簡單, 這裏就簡單介紹一下入門案例和使用原理吧!spring

寫一個簡單的springboot測試例子:數據庫

@Controller
public class AppController {

    @RequestMapping("/hello")
    @ResponseBody
    String home() {
        return "Hello ,spring security!";
    }
}

在springboot中的pom.xml中引入springsecurity, 加入以下代碼:緩存

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

而後在配置文件中寫入 安全

security.basic.enabled=false

spring security 默認提供了表單登陸的功能。咱們新建一個類SecurityConfiguration,並加入一些代碼,以下所示:springboot

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

上面的代碼其實就是 一種配置,authorizeRequests() 定義哪些URL須要被保護、哪些不須要被保護。 formLogin() 定義當須要用戶登陸時候,轉到的登陸頁面。服務器

此時,咱們並無寫登陸頁面,可是spring security默認提供了一個登陸頁面,以及登陸控制器。咱們這裏簡單寫一個自定義的登陸頁面:app

<html><head><title>Login Page</title></head><body onload='document.f.username.focus();'>
<h3>Login with Username and Password</h3><form name='f' action='/login' method='POST'>
<table>
    <tr><td>User:</td><td><input type='text' name='username' value=''></td></tr>
    <tr><td>Password:</td><td><input type='password' name='password'/></td></tr>
    <tr><td colspan='2'><input name="submit" type="submit" value="Login"/></td></tr>
    <input name="_csrf" type="hidden" value="635780a5-6853-4fcd-ba14-77db85dbd8bd" />
</table>
</form></body></html>

咱們能夠發現,這裏有個form 。action="/login",這個/login依然是spring security提供的。form表單提交了三個數據:框架

  • username 用戶名
  • password 密碼
  • _csrf CSRF保護方面的內容,暫時先不展開解釋

再配置文件加入以下:

security.user.name=admin
security.user.password=admin

重啓項目,訪問被保護的/hello頁面。自動跳轉到了spring security 默認的登陸頁面,咱們輸入用戶名admin密碼admin。點擊Login按鈕。會發現登陸成功並跳轉到了/hello。

除了登陸,spring security還提供了rememberMe功能,這裏不作過多解釋

一般狀況下,咱們須要實現「特定資源只能由特定角色訪問」的功能。假設咱們的系統有以下兩個角色:

  • ADMIN 能夠訪問全部資源
  • USER 只能訪問特定資源

如今咱們給系統增長「/product」 表明商品信息方面的資源(USER能夠訪問);增長"/admin"代碼管理員方面的資源(USER不能訪問)。代碼以下:

@Controller
@RequestMapping("/product")
public class ProductTestController {

    @RequestMapping("/info")
    @ResponseBody
    public String productInfo(){
        return " some product info ";
    }
}
-------------------------------------------
@Controller
@RequestMapping("/admin")
public class AdminTestController {

    @RequestMapping("/home")
    @ResponseBody
    public String productInfo(){
        return " admin home page ";
    }
}
@Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
                .inMemoryAuthentication()
                .withUser("admin1") // 管理員,同事具備 ADMIN,USER權限,能夠訪問全部資源
                    .password("admin1")
                    .roles("ADMIN", "USER")
                    .and()
                .withUser("user1").password("user1") // 普通用戶,只能訪問 /product/**
                    .roles("USER");
    }

 @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                    .antMatchers("/product/**").hasRole("USER")
                    .antMatchers("/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()
                .and()
                .formLogin().and()
                .httpBasic();
    }

經過上面的配置, 能夠簡單實現一個springsecurity的小例子. 下面說一下springsecurity的原理:

Spring Security的核心組件

spring security核心組件有:SecurityContext、SecurityContextHolder、Authentication、Userdetails 和 AuthenticationManager,下面分別介紹。

SecurityContext

安全上下文,用戶經過Spring Security 的校驗以後,驗證信息存儲在SecurityContext中,SecurityContext的接口定義以下:

public interface SecurityContext extends Serializable {
    /**
     * Obtains the currently authenticated principal, or an authentication request token.
     *
     * @return the <code>Authentication</code> or <code>null</code> if no authentication
     * information is available
     */
    Authentication getAuthentication();

    /**
     * Changes the currently authenticated principal, or removes the authentication
     * information.
     *
     * @param authentication the new <code>Authentication</code> token, or
     * <code>null</code> if no further authentication information should be stored
     */
    void setAuthentication(Authentication authentication);
}

SecurityContextHolder

SecurityContextHolder看名知義,是一個holder,用來hold住SecurityContext實例的。其做用就是存儲當前認證信息。
在典型的web應用程序中,用戶登陸一次,而後由其會話ID標識。服務器緩存持續時間會話的主體信息。
在Spring Security中,在請求之間存儲SecurityContext的責任落在SecurityContextPersistenceFilter上,
默認狀況下,該上下文將上下文存儲爲HTTP請求之間的HttpSession屬性。
它會爲每一個請求恢復上下文SecurityContextHolder,而且最重要的是,在請求完成時清除SecurityContextHolder。
SecurityContextHolder是一個類,他的功能方法都是靜態的(static)。 SecurityContextHolder能夠設置指定JVM策略(SecurityContext的存儲策略),這個策略有三種: 1. MODE_THREADLOCAL:SecurityContext 存儲在線程中。 2. MODE_INHERITABLETHREADLOCAL:SecurityContext 存儲在線程中,但子線程能夠獲取到父線程中的 SecurityContext。 3. MODE_GLOBAL:SecurityContext 在全部線程中都相同。 SecurityContextHolder默認使用MODE_THREADLOCAL模式,即存儲在當前線程中。在spring security應用中,咱們一般能看到相似以下的代碼: SecurityContextHolder.getContext().setAuthentication(token);

Authentication

authentication 直譯過來是「認證」的意思,在Spring Security 中Authentication用來表示當前用戶是誰,通常來說你能夠理解爲authentication就是一組用戶名密碼信息。Authentication也是一個接口,其定義以下:

public interface Authentication extends Principal, Serializable {
 
    Collection<? extends GrantedAuthority> getAuthorities();
    Object getCredentials();
    Object getDetails();
    Object getPrincipal();
    boolean isAuthenticated();
    void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}

接口有4個get方法,分別獲取

  • Authorities, 填充的是用戶角色信息。
  • Credentials,直譯,證書。填充的是密碼。
  • Details ,用戶信息。
  • Principal 直譯,形容詞是「主要的,最重要的」,名詞是「負責人,資本,本金」。感受很彆扭,因此,仍是不翻譯了,直接用原詞principal來表示這個概念,其填充的是用戶名。

所以能夠推斷其實現類有這4個屬性。這幾個方法做用以下:

  • getAuthorities: 獲取用戶權限,通常狀況下獲取到的是用戶的角色信息。
  • getCredentials: 獲取證實用戶認證的信息,一般狀況下獲取到的是密碼等信息。
  • getDetails: 獲取用戶的額外信息,(這部分信息能夠是咱們的用戶表中的信息)
  • getPrincipal: 獲取用戶身份信息,在未認證的狀況下獲取到的是用戶名,在已認證的狀況下獲取到的是 UserDetails (UserDetails也是一個接口,裏邊的方法有getUsername,getPassword等)。
  • isAuthenticated: 獲取當前 Authentication 是否已認證。
  • setAuthenticated: 設置當前 Authentication 是否已認證(true or false)。

UserDetails

UserDetails,看命知義,是用戶信息的意思。其存儲的就是用戶信息,其定義以下:

public interface UserDetails extends Serializable {

    Collection<? extends GrantedAuthority> getAuthorities();
    String getPassword();
    String getUsername();
    boolean isAccountNonExpired();
    boolean isAccountNonLocked();
    boolean isCredentialsNonExpired();
    boolean isEnabled();
}

方法含義以下:

  • getAuthorites:獲取用戶權限,本質上是用戶的角色信息。
  • getPassword: 獲取密碼。
  • getUserName: 獲取用戶名。
  • isAccountNonExpired: 帳戶是否過時。
  • isAccountNonLocked: 帳戶是否被鎖定。
  • isCredentialsNonExpired: 密碼是否過時。
  • isEnabled: 帳戶是否可用。

UserDetailsService

提到了UserDetails就必須得提到UserDetailsService, UserDetailsService也是一個接口,且只有一個方法loadUserByUsername,他能夠用來獲取UserDetails。

一般在spring security應用中,咱們會自定義一個CustomUserDetailsService來實現UserDetailsService接口,並實現其public UserDetails loadUserByUsername(final String login);方法。

咱們在實現loadUserByUsername方法的時候,就能夠經過查詢數據庫(或者是緩存、或者是其餘的存儲形式)來獲取用戶信息,而後組裝成一個UserDetails,

(一般是一個org.springframework.security.core.userdetails.User,它繼承自UserDetails) 並返回。

在實現loadUserByUsername方法的時候,若是咱們經過查庫沒有查到相關記錄,須要拋出一個異常來告訴spring security來「善後」。

這個異常是org.springframework.security.core.userdetails.UsernameNotFoundException

AuthenticationManager

AuthenticationManager 是一個接口,它只有一個方法,接收參數爲Authentication,其定義以下:

public interface AuthenticationManager {
    Authentication authenticate(Authentication authentication)
            throws AuthenticationException;
}

AuthenticationManager 的做用就是校驗Authentication,若是驗證失敗會拋出AuthenticationException異常。AuthenticationException是一個抽象類,

所以代碼邏輯並不能實例化一個AuthenticationException異常並拋出,實際上拋出的異常一般是其實現類,如DisabledException,LockedException,BadCredentialsException等。

BadCredentialsException可能會比較常見,即密碼錯誤的時候。

總結:

SpringSecurity是個很是不錯的框架,是個顆粒級別的驗證框架,和springboot融合的很是完美, 值得學習。

http://www.javashuo.com/article/p-tnxxjbfy-mr.html  這篇博客寫的很是清楚, 把原理分析的比較透徹, 能夠接着學習底層的東西多一點。

相關文章
相關標籤/搜索