Spring security自定義用戶認(rèn)證流程詳解
1.自定義登錄頁面
(1)首先在static目錄下面創(chuàng)建login.html
注意:springboot項(xiàng)目默認(rèn)可以訪問resources/resources,resources/staic,resources/public目錄下面的靜態(tài)文件
<!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()
// 添加一個(gè)url匹配器,如果匹配到login.html,就授權(quán)
.antMatchers("/login.html").permitAll()
.anyRequest()
.authenticated()
.and()
// 關(guān)閉spring security默認(rèn)的防csrf攻擊
.csrf().disable();
}
(3)測(cè)試
略
(4)存在的問題
<1>作為可以復(fù)用的登錄模塊,我們應(yīng)該提供個(gè)性化的登錄頁面,也就是說不能寫死只跳轉(zhuǎn)到login.html。
此問題比較好解決,使用可配置的登錄頁面,默認(rèn)使用login.html即可。
<2> 請(qǐng)求跳轉(zhuǎn)到login.html登錄頁面,貌似沒有什么問題,但作為restful風(fēng)格的接口,一般響應(yīng)的都是json數(shù)據(jù)格式,尤其是app請(qǐng)求。
解決思想:用戶發(fā)起數(shù)據(jù)請(qǐng)求 --> security判斷是否需要身份認(rèn)證 ----->跳轉(zhuǎn)到一個(gè)自定義的controller方法 ------>在該方法內(nèi)判斷是否是html發(fā)起的請(qǐng)求,如果是,就跳轉(zhuǎn)到login.html,如果不是,響應(yīng)一個(gè)json格式的數(shù)據(jù),說明錯(cuò)誤信息。
自定義Controller
@Slf4j
@RestController
public class LoginController {
/**
* 請(qǐng)求緩存
*/
private RequestCache requestCache = new HttpSessionRequestCache();
/**
* 重定向工具類
*/
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
/**
* 如果配置的登錄頁就使用配置的登錄面,否則使用默認(rèn)的登錄頁面
*/
// @Value("${xxxx:defaultLoginPage}")
// private String standardLoginPage;
private String standardLoginPage = "/login.html"; // 登錄頁
/**
* 用戶身份認(rèn)證方法
*/
@GetMapping("/user/auth")
@ResponseStatus(code = HttpStatus.UNAUTHORIZED) // 返回狀態(tài)
public ResponseData login(HttpServletRequest request, HttpServletResponse response) throws IOException {
SavedRequest savedRequest = requestCache.getRequest(request, response);
if (savedRequest != null) {
String targetUrl = savedRequest.getRedirectUrl();
log.info("請(qǐng)求是:" + targetUrl);
// 如果請(qǐng)求是以html結(jié)尾
if (StringUtils.endsWithIgnoreCase(targetUrl, ".html")) {
redirectStrategy.sendRedirect(request, response, standardLoginPage);
}
}
return new ResponseData("該請(qǐng)求需要登錄,js拿到我的響應(yīng)數(shù)據(jù)后,是否需要跳轉(zhuǎn)到登錄頁面你自己看著辦吧?");
}
}
spring security給該controller的login方法授權(quán)
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
// 先進(jìn)controller中去
.loginPage("/user/auth")
// 指定自定義登錄頁面
.loginPage("/login.html")
// 登錄url
.loginProcessingUrl("/auth/login")
.and()
.authorizeRequests()
// 該controller需要授權(quán)
.antMatchers("/user/auth").permitAll()
// 添加一個(gè)url匹配器,如果匹配到login.html,就授權(quán)
.antMatchers("/login.html").permitAll()
.anyRequest()
.authenticated()
.and()
// 關(guān)閉spring security默認(rèn)的防csrf攻擊
.csrf().disable();
}
這樣子就行了!??!
2. 自定義登錄成功處理(返回json)
(1)實(shí)現(xiàn)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()
// 先進(jìn)controller中去
.loginPage("/user/auth")
// 指定自定義登錄頁面
.loginPage("/login.html")
// 登錄url
.loginProcessingUrl("/auth/login")
.successHandler(myAuthenticationSuccessHandler)
.and()
.authorizeRequests()
// 該controller需要授權(quán)
.antMatchers("/user/auth").permitAll()
// 添加一個(gè)url匹配器,如果匹配到login.html,就授權(quán)
.antMatchers("/login.html").permitAll()
.anyRequest()
.authenticated()
.and()
// 關(guān)閉spring security默認(rèn)的防csrf攻擊
.csrf().disable();
}
(3)測(cè)試

說明:authentication對(duì)象中包含的信息,會(huì)因?yàn)榈卿浄绞降牟煌l(fā)生改變
3.自定義登錄失敗處理(返回json)
實(shí)現(xiàn)AuthenticationFailureHandler.java接口即可,跟登錄成敗處理配置一樣。
4.自定義登錄成功處理邏輯
以上的登錄成功或失敗的返回的都是json,但是在某些情況下,就是存在著登錄成功或者失敗進(jìn)行頁面跳轉(zhuǎn)(spring security默認(rèn)的處理方式),那么這種返回json的方式就不合適了。所以,我們應(yīng)該做得更靈活,做成可配置的。
對(duì)于登錄成功邏輯而言只需要對(duì)MyAuthenticationSuccessHandler.java稍做修改就行,代碼如下所示:
/**
* SavedRequestAwareAuthenticationSuccessHandler spring security 默認(rèn)的成功處理器
*/
@Slf4j
@Component
public class MyAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
@Autowired
private ObjectMapper objectMapper;
/**
* 配置的登錄方式
*/
// @Value("${xxx:默認(rèn)方式}")
private String loginType = "JSON";
/**
* Called when a user has been successfully authenticated.
*/
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
log.info("登錄成功?。?!");
// 如果配置的登錄方式是JSON,就返回json數(shù)據(jù)
if ("JSON".equals(loginType)) {
// 將登錄成功的信息寫到前端
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.getWriter().write(objectMapper.writeValueAsString(authentication));
} else { // 否則就使用默認(rèn)的跳轉(zhuǎn)方式
super.onAuthenticationSuccess(request,response,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:默認(rèn)方式}")
private String loginType = "JSON";
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
log.info("登錄失?。。。?);
// 如果配置的登錄方式是JSON,就返回json數(shù)據(jù)
if ("JSON".equals(loginType)) {
// 將登錄成功的信息寫到前端
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.getWriter().write(objectMapper.writeValueAsString(exception));
} else { // 否則就使用默認(rèn)的跳轉(zhuǎn)方式,跳轉(zhuǎn)到一個(gè)錯(cuò)誤頁面
super.onAuthenticationFailure(request,response,exception);
}
}
}
@Autowired
private MySimpleUrlAuthenticationFailureHandler mySimpleUrlAuthenticationFailureHandler;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
// 先進(jìn)controller中去
.loginPage("/user/auth")
// 指定自定義登錄頁面
.loginPage("/login.html")
// 登錄url
.loginProcessingUrl("/auth/login")
.successHandler(myAuthenticationSuccessHandler)
.failureHandler(mySimpleUrlAuthenticationFailureHandler)
.and()
.authorizeRequests()
// 該controller需要授權(quán)
.antMatchers("/user/auth").permitAll()
// 添加一個(gè)url匹配器,如果匹配到login.html,就授權(quán)
.antMatchers("/login.html").permitAll()
.anyRequest()
.authenticated()
.and()
// 關(guān)閉spring security默認(rèn)的防csrf攻擊
.csrf().disable();
}
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
SpringCloud feign微服務(wù)調(diào)用之間的異常處理方式
這篇文章主要介紹了SpringCloud feign微服務(wù)調(diào)用之間的異常處理方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06
Java 使用Axis調(diào)用WebService的示例代碼
這篇文章主要介紹了Java 使用Axis調(diào)用WebService的示例代碼,幫助大家更好的理解和使用Java,感興趣的朋友可以了解下2020-09-09
Spring中Bean對(duì)象的定義、注冊(cè)和獲取流程分析
這篇文章主要介紹了Spring中Bean對(duì)象的定義、注冊(cè)和獲取流程分析,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-06-06
詳解IDEA使用Maven項(xiàng)目不能加入本地Jar包的解決方法
這篇文章主要介紹了詳解IDEA使用Maven項(xiàng)目不能加入本地Jar包的解決方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08
java編譯后的文件出現(xiàn)xx$1.class的原因及解決方式
這篇文章主要介紹了java編譯后的文件出現(xiàn)xx$1.class的原因及解決方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-12-12
java實(shí)現(xiàn)簡(jiǎn)單的圖書借閱系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)簡(jiǎn)單的圖書借閱系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03
Mybatis動(dòng)態(tài)SQL foreach標(biāo)簽用法實(shí)例
這篇文章主要介紹了Mybatis動(dòng)態(tài)SQL foreach標(biāo)簽用法實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-10-10
使用Feign遠(yuǎn)程調(diào)用時(shí),序列化對(duì)象失敗的解決
這篇文章主要介紹了使用Feign遠(yuǎn)程調(diào)用時(shí),序列化對(duì)象失敗的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07

