spring security是spring家族的一個安全框架,入門簡單。對比shiro,它自帶登陸頁面,自動完成登陸操做。權限過濾時支持http方法過濾。html
在新手入門使用時,只須要簡單的配置,便可實現登陸以及權限的管理,無需本身寫功能邏輯代碼。前端
可是對於如今大部分先後端分離的web程序,尤爲是前端廣泛使用ajax請求時,spring security自帶的登陸系統就有一些不知足需求了。java
由於spring security有本身默認的登陸頁,本身默認的登陸控制器。而登陸成功或失敗,都會返回一個302跳轉。登陸成功跳轉到主頁,失敗跳轉到登陸頁。若是未認證直接訪問也會跳轉到登陸頁。可是若是前端使用ajax請求,ajax是沒法處理302請求的。先後端分離web中,規範是使用json交互。咱們但願登陸成功或者失敗都會返回一個json。何況spring security自帶的登陸頁太醜了,咱們仍是須要使用本身的。git
spring security通常簡單使用:github
web的安全控制通常分爲兩個部分,一個是認證,一個是受權。web
認證管理:ajax
就是認證是否爲合法用戶,簡單的說是登陸。通常爲匹對用戶名和密碼,即認證成功。spring
在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