spring-security 個性化用戶認證流程——自定義登錄頁面(可配置)
我們需要根據自己的業務系統構建自己的登錄頁面以及登錄成功、失敗處理
在spring security提供給我的登錄頁面中,只有用戶名、密碼框,而自帶的登錄成功頁面是空白頁面(可以重定向之前請求的路徑中),而登錄失敗時也只是提示用戶被鎖定、過期等信息。
在實際的開發中,則需要更精細力度的登錄控制,記錄錯誤的日誌(錯誤的次數等)
2.自定義登錄頁面
- 配置登錄頁面的路徑
在BrowserSecurityConfig類中配置登錄頁面的路徑http.formLogin() .loginPage("/sign.html") //位於resources/resources/sign.html .and() .authorizeRequests() .anyRequest().authenticated();
頁面內容如下:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>sign.html</title> </head> <body> <h2>標準登錄頁面</h2> <h3>表單登錄</h3> <form action="/authentication/form" method="post"> <table> <tr> <td>用戶名:</td> <td><input type="text" name="username"></td> </tr> <tr> <td>密碼:</td> <td><input type="password" name="password"></td> </tr> <tr> <td colspan="2"><button type="submit">登錄</button></td> </tr> </table> </form> </body> </html>
啟動之後,訪問我們的路徑:http://localhost:8080/sign.html
這個時候瀏覽器會報錯:重定向的次數過多,這是為什麽呢?
因為在配置中,我們只是配置了loginPage("/sign.html"),但是又沒有給該請求做授權,又因為沒有做授權,又被跳轉給sign.html頁面,所以會是:重定向的次數過多。
此時:需要再配置授權路徑,改造之後
http.formLogin() .loginPage("/sign.html") .and() .authorizeRequests() .antMatchers("/sign.html").permitAll() //當訪問sign.html這個頁面的時候不需要進行身份認證 .anyRequest().authenticated();
在sign.html中,自己配置了一個post路徑,在spring security原理中,表單登錄實際上是由UsernamePasswordAuthenticationFilter這個過濾器來處理的,在這個過濾器中
處理的是/login 請求,為了讓UsernamePasswordAuthenticationFilter這個過濾器知道處理我們自定義的登錄路徑/authentication/form,還需要再配置登錄的處理請求
http.formLogin()
.loginPage("/sign.html")
.loginProcessingUrl("/authentication/form") //登錄請求
.and()
.authorizeRequests()
.antMatchers("/sign.html").permitAll()
.anyRequest().authenticated();
啟動之後,繼續訪問我們的路徑:http://localhost:8080/sign.html
輸入賬戶名和密碼,登錄之後會報錯,403
在默認情況下,spring security提供了跨站請求偽造的防護,用CSRF Token來完成的,在***和防護的時候再細講,目前先把跨站請求偽造的功能先disable掉。
http.formLogin()
.loginPage("/sign.html")
.loginProcessingUrl("/authentication/form") //登錄請求
.and()
.authorizeRequests()
.antMatchers("/sign.html").permitAll()
.anyRequest().authenticated()
.and()
.csrf().disable();
啟動之後,繼續訪問我們的路徑:http://localhost:8080/user/1 ,系統會幫我們重定向到sign.html頁面
此時我們輸入正確的用戶名和密碼就可以訪問我們的請求了
3.優化rest請求和html請求
完成了基本功能之後還需要繼續優化我們的代碼結構,是要面向可重用的一種。
目前有2個問題:
- 發送的請求,例如:localhost:8080/user/1 需要身份認證的話返回HTML是不合理的,rest服務應該返回json
想要做到的效果,如果是html請求則返回登錄頁上,如果不是則返回json數據 帶未授權(401狀態碼) - 我們寫了一個標準的登錄頁面,但是我們的目標是提供可重用的安全模塊,這個時候就需要提供可配置項
(因為會有多個項目使用該模塊,但是多個項目它有不同的登錄模塊)
流程如下:
當我們接收到html請求或者數據請求的時候,先判斷是否需要身份認證(spring security來做的)如果是否的話就直接返回了,如果是的話則需要跳轉到我們自定義的Controller上面去(目前我們的做法是跳轉到了sign.html頁面上)在該方法內判斷是html請求還是數據請求。
Controller
@RestController
public class BrowserSecurityController {
private Logger logger = LoggerFactory.getLogger(BrowserSecurityConfig.class);
//拿到引發跳轉的請求(HttpSessionRequestCache把當前的請求緩存到Session中)
private RequestCache requestCache = new HttpSessionRequestCache();
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
@Autowired
private SecurityProperties securityProperties;
//當需要身份認證時,跳轉到這裏
@RequestMapping("/authentication/require")
@ResponseStatus(code=HttpStatus.UNAUTHORIZED) //不是html請求時,返回401狀態碼
public SimpleResponse requireAuthentication(HttpServletRequest request,HttpServletResponse response) throws IOException {
//之前緩存的請求(可以拿到引發跳轉的請求)
SavedRequest savedRequest = requestCache.getRequest(request, response);
if (savedRequest != null) {
String targetUrl = savedRequest.getRedirectUrl();
logger.info("引發跳轉的請求是:"+targetUrl);
//是否以.html結尾,如果是則跳轉到登錄頁面
if (StringUtils.endsWithIgnoreCase(targetUrl, ".html")) {
//這個url,我們需要做成可配置的url(因為我們不可能每次都跳轉到我們自己的寫的固定登錄頁面,需要根據每個項目的不同)
//這個時候就需要用到**Properties 配置文件類來做靈活性配置
redirectStrategy.sendRedirect(request, response, securityProperties.getBrowser().getLoginPage());
}
//如果不是html請求,則返回401狀態碼以及錯誤信息
}
return new SimpleResponse("訪問的服務需要身份認證,請引導用戶到登錄頁");
}
}
SecurityProperties自定義的登錄頁配置屬性類,為了可配置化
#另外的項目的登錄請求的頁面
core.security.browser.loginPage = /demo-sign.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>demo-sign</title>
</head>
<body>
<h2>demo-sign自定義登錄頁</h2>
</body>
</html>
而我們不想單純的要一個簡單的配置是,而是可管理的配置類,因為後面會有其他的配置,例如驗證碼的配置,OAuth的配置,等 這個時候需要同一個的配置類入口(SecurityProperties)
//讀取配置文件內的信息
@ConfigurationProperties(prefix="core.security")
public class SecurityProperties {
private BrowserProperties browser = new BrowserProperties();
public BrowserProperties getBrowser() {
return browser;
}
public void setBrowser(BrowserProperties browser) {
this.browser = browser;
}
}
public class BrowserProperties {
//標準的登錄頁面,如果其他項目沒有配置則使用默認的登錄配置
private String loginPage = "/sign.html";
public String getLoginPage() {
return loginPage;
}
public void setLoginPage(String loginPage) {
this.loginPage = loginPage;
}
}
//為了使core.security生效則需要一個@Configuration類
@Configuration
@EnableConfigurationProperties(SecurityProperties.class)
public class SecurityCoreConfig {
}
//最後要在權限配置類**BrowserSecurityConfig**中 配置放行的url
private final static String loginPage = "/authentication/require";
@Autowired
private SecurityProperties securityProperties;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
.loginPage(loginPage)
.loginProcessingUrl("/authentication/form")
.and()
.authorizeRequests()
.antMatchers(loginPage).permitAll()
//自定義的登錄頁面權限放開
.antMatchers(securityProperties.getBrowser().getLoginPage()).permitAll()
.anyRequest().authenticated()
.and()
.csrf().disable();
}
第一種情況:訪問請求:http://localhost:8080/user/1
第二種情況:訪問請求:http://localhost:8080/index.html
第三種情況:關閉配置,繼續訪問請求:http://localhost:8080/index.html
#core.security.browser.loginPage = /demo-sign.html
這個功能就是我們目前想要的,可以針對不同的請求對於沒有權限時的攔截以及調整判斷。
spring-security 個性化用戶認證流程——自定義登錄頁面(可配置)