hello 各位小夥伴,國慶節終於過完啦,鬆哥也回來啦,今天開始我們繼續發乾貨!前端
關於 Spring Security,鬆哥以前發過多篇文章和你們聊聊這個安全框架的使用:java
不過,今天要和小夥伴們聊一聊 Spring Security 中的另一個問題,那就是在 Spring Security 中未獲認證的請求默認會重定向到登陸頁,可是在先後端分離的登陸中,這個默認行爲則顯得很是不合適,今天咱們主要來看看如何實現未獲認證的請求直接返回 JSON ,而不是重定向到登陸頁面。json
這裏關於 Spring Security 的基本用法我就再也不贅述了,若是小夥伴們不瞭解,能夠參考上面的 6 篇文章。後端
你們知道,在自定義 Spring Security 配置的時候,有這樣幾個屬性:跨域
@Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .anyRequest().authenticated() .formLogin() .loginProcessingUrl("/doLogin") .loginPage("/login") //其餘配置 .permitAll() .and() .csrf().disable(); }
這裏有兩個比較重要的屬性:瀏覽器
這種配置在先後端不分的登陸中是沒有問題的,在先後端分離的登陸中,這種配置就有問題了。我舉個簡單的例子,例如我想訪問 /hello
接口,可是這個接口須要登陸以後才能訪問,我如今沒有登陸就直接去訪問這個接口了,那麼系統會給我返回 302,讓我去登陸頁面,在先後端分離中,個人後端通常是沒有登陸頁面的,就是一個提示 JSON,例以下面這樣:安全
@GetMapping("/login") public RespBean login() { return RespBean.error("還沒有登陸,請登陸!"); }
完整代碼你們能夠參考個人微人事項目。app
也就是說,當我沒有登陸直接去訪問 /hello
這個接口的時候,我會看到上面這段 JSON 字符串。在先後端分離開發中,這個看起來沒問題(後端再也不作頁面跳轉,不管發生什麼都是返回 JSON)。可是問題就出在這裏,系統默認的跳轉是一個重定向,就是說當你訪問 /hello
的時候,服務端會給瀏覽器返回 302,同時響應頭中有一個 Location 字段,它的值爲 http://localhost:8081/login
,也就是告訴瀏覽器你去訪問 http://localhost:8081/login
地址吧。瀏覽器收到指令以後,就會直接去訪問 http://localhost:8081/login
地址,若是此時是開發環境而且請求仍是 Ajax 請求,就會發生跨域。由於先後端分離開發中,前端咱們通常在 NodeJS 上啓動,而後前端的全部請求經過 NodeJS 作請求轉發,如今服務端直接把請求地址告訴瀏覽器了,瀏覽器就會直接去訪問 http://localhost:8081/login
了,而不會作請求轉發了,所以就發生了跨域問題。框架
很明顯,上面的問題咱們不能用跨域的思路來解決,雖然這種方式看起來也能解決問題,但不是最佳方案。前後端分離
若是咱們的 Spring Security 在用戶未獲認證的時候去請求一個須要認證後才能請求的數據,此時不給用戶重定向,而是直接就返回一個 JSON,告訴用戶這個請求須要認證以後才能發起,就不會有上面的事情了。
這裏就涉及到 Spring Security 中的一個接口 AuthenticationEntryPoint
,該接口有一個實現類:LoginUrlAuthenticationEntryPoint
,該類中有一個方法 commence
,以下:
/** * Performs the redirect (or forward) to the login form URL. */ public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) { String redirectUrl = null; if (useForward) { if (forceHttps && "http".equals(request.getScheme())) { redirectUrl = buildHttpsRedirectUrlForRequest(request); } if (redirectUrl == null) { String loginForm = determineUrlToUseForThisRequest(request, response, authException); if (logger.isDebugEnabled()) { logger.debug("Server side forward to: " + loginForm); } RequestDispatcher dispatcher = request.getRequestDispatcher(loginForm); dispatcher.forward(request, response); return; } } else { redirectUrl = buildRedirectUrlToLoginPage(request, response, authException); } redirectStrategy.sendRedirect(request, response, redirectUrl); }
首先咱們從這個方法的註釋中就能夠看出,這個方法是用來決定究竟是要重定向仍是要 forward,經過 Debug 追蹤,咱們發現默認狀況下 useForward 的值爲 false,因此請求走進了重定向。
那麼咱們解決問題的思路很簡單,直接重寫這個方法,在方法中返回 JSON 便可,再也不作重定向操做,具體配置以下:
@Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .anyRequest().authenticated() .formLogin() .loginProcessingUrl("/doLogin") .loginPage("/login") //其餘配置 .permitAll() .and() .csrf().disable().exceptionHandling() .authenticationEntryPoint(new AuthenticationEntryPoint() { @Override public void commence(HttpServletRequest req, HttpServletResponse resp, AuthenticationException authException) throws IOException, ServletException { resp.setContentType("application/json;charset=utf-8"); PrintWriter out = resp.getWriter(); RespBean respBean = RespBean.error("訪問失敗!"); if (authException instanceof InsufficientAuthenticationException) { respBean.setMsg("請求失敗,請聯繫管理員!"); } out.write(new ObjectMapper().writeValueAsString(respBean)); out.flush(); out.close(); } }); }
在 Spring Security 的配置中加上自定義的 AuthenticationEntryPoint
處理方法,該方法中直接返回相應的 JSON 提示便可。這樣,若是用戶再去直接訪問一個須要認證以後才能夠訪問的請求,就不會發生重定向操做了,服務端會直接給瀏覽器一個 JSON 提示,瀏覽器收到 JSON 以後,該幹嗎幹嗎。
好了,一個小小的重定向問題和小夥伴們分享下,不知道你們有沒有看懂呢?這也是我最近在重構微人事的時候遇到的問題。預計 11 月份,微人事的 Spring Boot 版本會升級到目前最新版,請小夥伴們留意哦。
關注公衆號【江南一點雨】,專一於 Spring Boot+微服務以及先後端分離等全棧技術,按期視頻教程分享,關注後回覆 Java ,領取鬆哥爲你精心準備的 Java 乾貨!