Spring security自定義使用者認證流程詳解
1.自定義登入頁面
(1)首先在static目錄下面建立login.html
注意:springboot專案預設可以訪問resources/resources,resources/staic,resources/public目錄下面的靜態檔案
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>登入頁面</title> </head> <body> <form action="/auth/login" method="post"> 使用者名稱:<input type="text" name="username"> <br/> 密 碼:<input type="password" name="password"> <br/> <input type="submit" value="登入"> </form> </body> </html>
(2)在spring securiy配置類中做如下配置
@Override protected void configure(HttpSecurity http) throws Exception { http.formLogin() // 指定自定義登入頁面 .loginPage("/login.html") // 登入url .loginProcessingUrl("/auth/login") .and() .authorizeRequests() // 新增一個url匹配器,如果匹配到login.html,就授權 .antMatchers("/login.html").permitAll() .anyRequest() .authenticated() .and() // 關閉spring security預設的防csrf攻擊 .csrf().disable(); }
(3)測試
略
(4)存在的問題
<1>作為可以複用的登入模組,我們應該提供個性化的登入頁面,也就是說不能寫死只跳轉到login.html。
此問題比較好解決,使用可配置的登入頁面,預設使用login.html即可。
<2> 請求跳轉到login.html登入頁面,貌似沒有什麼問題,但作為restful風格的介面,一般響應的都是json資料格式,尤其是app請求。
解決思想:使用者發起資料請求 --> security判斷是否需要身份認證 ----->跳轉到一個自定義的controller方法 ------>在該方法內判斷是否是html發起的請求,如果是,就跳轉到login.html,如果不是,響應一個json格式的資料,說明錯誤資訊。
自定義Controller
@Slf4j @RestController public class LoginController { /** * 請求快取 */ private RequestCache requestCache = new HttpSessionRequestCache(); /** * 重定向工具類 */ private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); /** * 如果配置的登入頁就使用配置的登入面,否則使用預設的登入頁面 */ // @Value("${xxxx:defaultLoginPage}") // private String standardLoginPage; private String standardLoginPage = "/login.html"; // 登入頁 /** * 使用者身份認證方法 */ @GetMapping("/user/auth") @ResponseStatus(code = HttpStatus.UNAUTHORIZED) // 返回狀態 public ResponseData login(HttpServletRequest request,HttpServletResponse response) throws IOException { SavedRequest savedRequest = requestCache.getRequest(request,response); if (savedRequest != null) { String targetUrl = savedRequest.getRedirectUrl(); log.info("請求是:" + targetUrl); // 如果請求是以html結尾 if (StringUtils.endsWithIgnoreCase(targetUrl,".html")) { redirectStrategy.sendRedirect(request,response,standardLoginPage); } } return new ResponseData("該請求需要登入,js拿到我的響應資料後,是否需要跳轉到登入頁面你自己看著辦吧?"); } }
spring security給該controller的login方法授權
@Override protected void configure(HttpSecurity http) throws Exception { http.formLogin() // 先進controller中去 .loginPage("/user/auth") // 指定自定義登入頁面 .loginPage("/login.html") // 登入url .loginProcessingUrl("/auth/login") .and() .authorizeRequests() // 該controller需要授權 .antMatchers("/user/auth").permitAll() // 新增一個url匹配器,如果匹配到login.html,就授權 .antMatchers("/login.html").permitAll() .anyRequest() .authenticated() .and() // 關閉spring security預設的防csrf攻擊 .csrf().disable(); }
這樣子就行了!!!
2. 自定義登入成功處理(返回json)
(1)實現AuthenticationSuccessHandler.java
import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; 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; @Slf4j @Component public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler { @Autowired private ObjectMapper objectMapper; /** * Called when a user has been successfully authenticated. * @param request * @param response * @param authentication * @throws IOException * @throws ServletException */ @Override public void onAuthenticationSuccess(HttpServletRequest request,HttpServletResponse response,Authentication authentication) throws IOException,ServletException { log.info("登入成功!!!"); // 將登入成功的資訊寫到前端 response.setContentType(MediaType.APPLICATION_JSON_VALUE); response.getWriter().write(objectMapper.writeValueAsString(authentication)); } }
(2)修改security配置類
@Autowired private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler; @Override protected void configure(HttpSecurity http) throws Exception { http.formLogin() // 先進controller中去 .loginPage("/user/auth") // 指定自定義登入頁面 .loginPage("/login.html") // 登入url .loginProcessingUrl("/auth/login") .successHandler(myAuthenticationSuccessHandler) .and() .authorizeRequests() // 該controller需要授權 .antMatchers("/user/auth").permitAll() // 新增一個url匹配器,如果匹配到login.html,就授權 .antMatchers("/login.html").permitAll() .anyRequest() .authenticated() .and() // 關閉spring security預設的防csrf攻擊 .csrf().disable(); }
(3)測試
說明:authentication物件中包含的資訊,會因為登入方式的不同而發生改變
3.自定義登入失敗處理(返回json)
實現AuthenticationFailureHandler.java介面即可,跟登入成敗處理配置一樣。
4.自定義登入成功處理邏輯
以上的登入成功或失敗的返回的都是json,但是在某些情況下,就是存在著登入成功或者失敗進行頁面跳轉(spring security預設的處理方式),那麼這種返回json的方式就不合適了。所以,我們應該做得更靈活,做成可配置的。
對於登入成功邏輯而言只需要對MyAuthenticationSuccessHandler.java稍做修改就行,程式碼如下所示:
/** * SavedRequestAwareAuthenticationSuccessHandler spring security 預設的成功處理器 */ @Slf4j @Component public class MyAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler { @Autowired private ObjectMapper objectMapper; /** * 配置的登入方式 */ // @Value("${xxx:預設方式}") private String loginType = "JSON"; /** * Called when a user has been successfully authenticated. */ @Override public void onAuthenticationSuccess(HttpServletRequest request,ServletException { log.info("登入成功!!!"); // 如果配置的登入方式是JSON,就返回json資料 if ("JSON".equals(loginType)) { // 將登入成功的資訊寫到前端 response.setContentType(MediaType.APPLICATION_JSON_VALUE); response.getWriter().write(objectMapper.writeValueAsString(authentication)); } else { // 否則就使用預設的跳轉方式 super.onAuthenticationSuccess(request,authentication); } } }
5.自定義登入失敗處理邏輯
同登入成功類似,具體程式碼如下:
import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; import org.springframework.stereotype.Component; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @Slf4j @Component public class MySimpleUrlAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler { @Autowired private ObjectMapper objectMapper; /** * 配置的登入方式 */ // @Value("${xxx:預設方式}") private String loginType = "JSON"; @Override public void onAuthenticationFailure(HttpServletRequest request,AuthenticationException exception) throws IOException,ServletException { log.info("登入失敗!!!"); // 如果配置的登入方式是JSON,就返回json資料 if ("JSON".equals(loginType)) { // 將登入成功的資訊寫到前端 response.setStatus(HttpStatus.UNAUTHORIZED.value()); response.setContentType(MediaType.APPLICATION_JSON_VALUE); response.getWriter().write(objectMapper.writeValueAsString(exception)); } else { // 否則就使用預設的跳轉方式,跳轉到一個錯誤頁面 super.onAuthenticationFailure(request,exception); } } }
@Autowired private MySimpleUrlAuthenticationFailureHandler mySimpleUrlAuthenticationFailureHandler; @Override protected void configure(HttpSecurity http) throws Exception { http.formLogin() // 先進controller中去 .loginPage("/user/auth") // 指定自定義登入頁面 .loginPage("/login.html") // 登入url .loginProcessingUrl("/auth/login") .successHandler(myAuthenticationSuccessHandler) .failureHandler(mySimpleUrlAuthenticationFailureHandler) .and() .authorizeRequests() // 該controller需要授權 .antMatchers("/user/auth").permitAll() // 新增一個url匹配器,如果匹配到login.html,就授權 .antMatchers("/login.html").permitAll() .anyRequest() .authenticated() .and() // 關閉spring security預設的防csrf攻擊 .csrf().disable(); }
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。