spring security 最簡單的登錄莫過於form表單登錄,但是要做到極致的交互式體驗,還是需要用到ajax登錄
首先我們藉助於(五)Spring Security基於數據庫的權限授權的登錄頁面
一:修改form 表單
- 原表單:
<form th:action="@{/login}" method="post"> <div><label> User Name : <input type="text" name="username"/> </label></div> <div><label> Password: <input type="password" name="password"/> </label></div> <div><label> code: <input type="text" name="captcha"/> </label> <img src="/getCode" id="code" alt="驗證碼" onclick="refresh(this)"/></div> <label> <input type="hidden" name="uuid" id="uuid"/> </label> <div><input type="submit" value="Sign In"/></div> </form>
- 修改後
<form > <div><label> User Name : <input type="text" name="username"/> </label></div> <div><label> Password: <input type="password" name="password"/> </label></div> <div><label> code: <input type="text" name="captcha"/> </label> <img src="/getCode" id="code" alt="驗證碼" onclick="refresh(this)"/></div> <label> <input type="hidden" name="uuid" id="uuid"/> </label> <div><a href="javascript:login();" >登錄</a></div> </form> ********************************************************* let login=()=>{ let username = $("input[name = 'username']"); let password = $("input[name = 'password']"); let captcha = $("input[name = 'captcha']"); let uuid = $("input[name = 'uuid']"); $.ajax({ type : "POST", url : "/login", data : { "username" : username.val(), "password" : password.val(), "captcha" : captcha.val(), "uuid" : uuid.val() }, success: function(result){ if(result.code === 1){ window.location.href="/"; }else { alert('登錄失敗'); } }, error : function(data) { alert("錯誤的用戶或密碼"); }, beforeSend : function() { if (username.val() === "") { alert("請輸入用戶名!"); username.focus(); return false; } if (password.val() === "") { alert("請輸入密碼!"); password.focus(); return false; } } }); };
二:創建一個登錄接口
/**
* 這個接口用來代替spring security的登錄界面
*/
@GetMapping("/login_page")
@ResponseBody
public Object loginPage() {
return new BaseResult(ReturnCode.FAILED.getCode(), "尚未登錄,請登錄!");
}
在WebSecurityConfig 裏配置
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/login","/login_page","/home","/getCode").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login_page") /* 未登錄的自動跳轉到這個接口 */
.loginProcessingUrl("/login") /* 指定登錄邏輯路徑 ,由過濾器攔截*/
.successHandler(successHandler)
.failureHandler(failureHandler)
.and()
.logout()
.logoutSuccessUrl("/home");
http.addFilterBefore(verifyCodeFilter, UsernamePasswordAuthenticationFilter.class);
三:自定義認證入口點(AuthenticationEntryPoint)
由於配置的loginPage("/login_page")跳轉的頁面是json數據,不是一個頁面,所以問哦們需要自定義一個AuthenticationEntryPoint,
- AuthenticationEntryPoint的實現類有多種
AuthenticationEntryPoint在ExceptionTranslationFilter使用,
ExceptionTranslationFilter會截獲AuthenticationException
或者AccessDeniedException異常,AuthenticationEntryPoint. Commence(…)就會被調用。
如下:源代碼public class ExceptionTranslationFilter extends GenericFilterBean { private AuthenticationEntryPoint authenticationEntryPoint; ...... public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest)req; HttpServletResponse response = (HttpServletResponse)res; try { chain.doFilter(request, response); this.logger.debug("Chain processed normally"); } catch (IOException var9) { throw var9; } catch (Exception var10) { ...... this.handleSpringSecurityException(request, response, chain, (RuntimeException)ase); } } private void handleSpringSecurityException(HttpServletRequest request, HttpServletResponse response, FilterChain chain, RuntimeException exception) throws IOException, ServletException { ...... this.sendStartAuthentication(request, response, chain, (AuthenticationException)exception); ...... } protected void sendStartAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, AuthenticationException reason) throws ServletException, IOException { SecurityContextHolder.getContext().setAuthentication((Authentication)null); this.requestCache.saveRequest(request, response); this.logger.debug("Calling Authentication entry point."); /* 調用 */ this.authenticationEntryPoint.commence(request, response, reason); } ......
- 當用戶訪問受限的資源時跳轉登錄頁面
@Component public class UnauthorizedEntryPoint implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { if(isAjaxRequest(request)){ response.sendError(HttpServletResponse.SC_UNAUTHORIZED,authException.getMessage()); }else{ response.sendRedirect("/login"); } } private boolean isAjaxRequest(HttpServletRequest request) { return request.getHeader("X-Requested-With") != null&&request.getHeader("X-Requested-With").equals("XMLHttpRequest"); } }
四:最終配置
- 關閉crsf
@Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/login","/login_page","/home","/getCode").permitAll() .anyRequest().authenticated() .and() .formLogin() .loginPage("/login_page") .loginProcessingUrl("/login") .successHandler(successHandler) .failureHandler(failureHandler) .and() .logout() .logoutSuccessUrl("/home"); //http.csrf().disable(); /* 關閉csrf,否則會攔截ajax請求 */ kuhttp.exceptionHandling().authenticationEntryPoint(unauthorizedEntryPoint); /* 用來解決匿名用戶訪問無權限資源時的異常 */ http.addFilterBefore(verifyCodeFilter, UsernamePasswordAuthenticationFilter.class); }
- 開啓crsf
crsf開啓,會攔截ajax請求所以需要在請求中加入crsf參數
注意:需要在頭部加入這些,否則無法創建session
示例