1. 程式人生 > 程式設計 >Spring security自定義使用者認證流程詳解

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/>
  密&emsp;碼:<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)測試

Spring security自定義使用者認證流程詳解

說明: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();
  }

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。