spring security簡單教程以及實現完全前后端分離


spring security是spring家族的一個安全框架,入門簡單。對比shiro,它自帶登錄頁面,自動完成登錄操作。權限過濾時支持http方法過濾。

在新手入門使用時,只需要簡單的配置,即可實現登錄以及權限的管理,無需自己寫功能邏輯代碼。

但是對於現在大部分前后端分離的web程序,尤其是前端普遍使用ajax請求時,spring security自帶的登錄系統就有一些不滿足需求了。

因為spring security有自己默認的登錄頁,自己默認的登錄控制器。而登錄成功或失敗,都會返回一個302跳轉。登錄成功跳轉到主頁,失敗跳轉到登錄頁。如果未認證直接訪問也會跳轉到登錄頁。但是如果前端使用ajax請求,ajax是無法處理302請求的。前后端分離web中,規范是使用json交互。我們希望登錄成功或者失敗都會返回一個json。況且spring security自帶的登錄頁太丑了,我們還是需要使用自己的。

 

spring security一般簡單使用:

web的安全控制一般分為兩個部分,一個是認證,一個是授權。

認證管理:

就是認證是否為合法用戶,簡單的說是登錄。一般為匹對用戶名和密碼,即認證成功。

在spring security認證中,我們需要注意的是:哪個類表示用戶?哪個屬性表示用戶名?哪個屬性表示密碼?怎么通過用戶名取到對應的用戶?密碼的驗證方式是什么?

只要告訴spring security這幾個東西,基本上就可以了。

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 {

}

事實上只要繼承WebSecurityConfigurerAdapter ,spring security就已經啟用了,當你訪問資源時,它就會跳轉到它自己默認的登錄頁。但是這還不行,

當用戶點擊登錄時,

1.它會拿到用戶輸入的用戶名密碼;

2.根據用戶名通過UserDetailsService 的 loadUserByUsername(username)方法獲得一個用戶對象;

3.獲得一個UserDetails 對象,獲得內部的成員屬性password;

4.通過PasswordEncoder 的 matchs(s1, s2) 方法對比用戶的輸入的密碼和第3步的密碼;

5.匹配成功;

 

所以我們要實現這三個接口的三個方法:

1.實現UserDetailsService ,可以選擇同時實現用戶的正常業務方法和UserDetailsService ;

例如:UserServiceImpl implement IUserService,UserDetailsService {}

2.實現UserDetails ,一般使用用戶的實體類實現此接口。

其中有getUsername(), getPassword(), getAuthorities()為獲取用戶名,密碼,權限。可根據個人情況實現。

3.實現PasswordEncoder ,spring security 提供了多個該接口的實現類,可百度和查看源碼理解,也可以自己寫。

三個實現類的配置如下:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;

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

其中Userdetails 為UserDetailsService 中 loadUserByUsername() 方法的返回值類型。

 

到目前為止,就可以完成簡單認證了。而授權管理,到現在,是默認的:所有資源都只有‘認證’權限,所有用戶也只有‘認證’權限。即,經過認證就可以訪問所有資源。

以上,就是spring security的簡易應用。可以實現一個稍微完整的安全控制。非常簡單。

 

授權管理:

授權管理,是在已認證的前提下。用戶在認證后,根據用戶的不同權限,開放不同的資源。

根據RBAC設計,用戶有多個角色,角色有多個權限。(真正控制資源的是權限,角色只是一個權限列表,方便使用。)

每個用戶都有一個權限列表,授權管理,就是權限和資源的映射。在編程中,寫好對應關系。然后當用戶請求資源時,查詢用戶是否有資源對應的權限決定是否通過。

權限寫在數據庫,配置文件或其他任何地方。只要調用loadUserByUsername()時返回的UserDetails對象中的getAuthorities()方法能獲取到。

所以無論用戶的權限寫在哪里,只要getAuthorities()能得到就可以了。

舉例:

授權管理映射:add==/api/add,query==/api/query;

數據庫中存儲了用戶權限:query;

那么該用戶就只能訪問/api/query,而不能訪問/api/add。

授權管理配置如下:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;

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

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers(HttpMethod.POST, "/api/data").hasAuthority("add")
                .antMatchers(HttpMethod.GET, "/api/data").hasAuthority("query")
                .antMatchers("/home").hasAuthority("base");
    }
}

以上就是spring security的基本應用。下面是解決前后端分離下的無法302跳轉的情況。

需求是:前后端分離,需要自己的登錄頁面,使用ajax請求。

出現問題:自己的登錄頁面請求登錄后,后端返回302跳轉主頁,ajax無法處理;未認證請求資源時,后端返回302跳轉登錄頁,也無法處理。

解決思想:修改302狀態碼,修改為401,403或者200和json數據。

 

HttpSecurity 有很多方法,可以看一看

比如 設置登錄頁(formLogin().loginPage("/login.html")) 可以設置自己的登錄頁(該設置主要是針對使用302跳轉,且有自己的登錄頁,如果不使用302跳轉,前后端完全分離,無需設置)。

比如 設置認證成功處理

比如 設置認證失敗處理

比如 設置異常處理

比如 設置退出成功處理

可以繼承重寫其中的主要方法(里面有httpResponse對象,可以隨便返回任何東西)

例如:

import org.springframework.http.HttpStatus;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class LoginSuccessHandler implements AuthenticationSuccessHandler {

    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        httpServletResponse.setStatus(HttpStatus.OK.value());
    }
}

設置完成登錄成功和失敗處理后,還是不夠滿足需求,當用戶未通過登錄頁進入網站,我們需要在用戶直接訪問資源時,告訴前端此用戶未認證。(默認是302跳轉到登錄頁)。我們可以改成返回403狀態碼。

這里就需要實現一個特殊的方法:AuthenticationEntryPoint 接口的 commence()方法。

這個方法主要是,用戶未認證訪問資源時,所做的處理。

spring security給我們提供了很多現成的AuthenticationEntryPoint 實現類,

比如默認的302跳轉登錄頁,比如返回403狀態碼,還比如返回json數據等等。當然也可以自己寫。和上面的登錄處理一樣,實現接口方法,將實現類實例傳到配置方法(推薦spring注入)。

如下:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.web.authentication.Http403ForbiddenEntryPoint;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Qualifier("userService")
    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private LoginSuccessHandler loginSuccessHandler;

    @Autowired
    private LoginFailureHandler loginFailureHandler;

    @Autowired
    private MyLogoutHandler logoutHandler;

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

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .formLogin()
                .loginProcessingUrl("/login")
                // 登錄成功
                .successHandler(loginSuccessHandler)
                // 登錄失敗
                .failureHandler(loginFailureHandler).permitAll()
                .and()
                // 注銷成功
                .logout().logoutSuccessHandler(logoutHandler)
                .and()
                // 未登錄請求資源
                .exceptionHandling().authenticationEntryPoint(new Http403ForbiddenEntryPoint())
                .and()
                .authorizeRequests()
                .antMatchers(HttpMethod.POST, "/api/data").hasAuthority("add")
                .antMatchers(HttpMethod.GET, "/api/data").hasAuthority("query")
                .antMatchers("/home").hasAuthority("base");
    }
}

以上就算是完了,前端發起ajax請求時,后端會返回200,401,403狀態碼,前端可根據狀態碼做相應的處理。

以下是我的全部代碼(后端,安全管理demo)

https://github.com/Question7/spring-security-demo


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM