91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

SpringSecurity默認表單登錄頁展示流程源碼是怎樣的

發布時間:2021-10-15 17:13:59 來源:億速云 閱讀:156 作者:柒染 欄目:編程語言

這篇文章給大家介紹SpringSecurity默認表單登錄頁展示流程源碼是怎樣的,內容非常詳細,感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。

?1.準備工作(體驗SpringSecurity默認表單認證)

??1.1 創建SpringSecurity項目

??先通過IDEA 創建一個SpringBoot項目 并且依賴SpringSecurity,Web依賴

??此時pom.xml會自動添加

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId></dependency>

??1.2 提供一個接口

@RestControllerpublic class HelloController {@RequestMapping("/hello")public String hello() { return "Hello SpringSecurity"; }}

??1.3 啟動項目

??直接訪問 提供的接口

http://localhost:8080/hello

??會發現瀏覽器被直接重定向到了 /login 并且顯示如下默認的表單登錄頁

http://localhost:8080/login

??1.4 登錄

??在啟動項目的時候 控制臺會打印一個 seuciryt password : xxx

Using generated security password: f520875f-ea2b-4b5d-9b0c-f30c0c17b90b

??直接登錄

用戶名:user 密碼 :f520875f-ea2b-4b5d-9b0c-f30c0c17b90b

??登錄成功并且 瀏覽器又會重定向到 剛剛訪問的接口

?2.springSecurityFilterchain 過濾器鏈

?如果你看過我另一篇關于SpringSecurity初始化源碼的博客,那么你一定知道當SpringSecurity項目啟動完成后會初始化一個 springSecurityFilterchain 它內部 additionalFilters屬性初始化了很多Filter 如下所有的請求都會經過這一系列的過濾器 Spring Security就是通過這些過濾器 來進行認證授權等

?3.FilterSecurityInterceptor (它會判斷這次請求能否通過)

?FilterSecurityInterceptor是過濾器鏈中最后一個過濾器,主要用于判斷請求能否通過,內部通過AccessDecisionManager 進行投票判斷

?當我們未登錄訪問

http://localhost:8080/hello

?請求會被 FilterSecurityInterceptor 攔截

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { FilterInvocation fi = new FilterInvocation(request, response, chain); invoke(fi);}

?重點看invoke方法

public void invoke(FilterInvocation fi) throws IOException, ServletException { if ((fi.getRequest() != null)  && (fi.getRequest().getAttribute(FILTER_APPLIED) != null)  && observeOncePerRequest) { // filter already applied to this request and user wants us to observe // once-per-request handling, so don't re-do security checking fi.getChain().doFilter(fi.getRequest(), fi.getResponse()); } else { // first time this request being called, so perform security checking if (fi.getRequest() != null && observeOncePerRequest) {  fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE); } InterceptorStatusToken token = super.beforeInvocation(fi); try {  fi.getChain().doFilter(fi.getRequest(), fi.getResponse()); } finally {  super.finallyInvocation(token); } super.afterInvocation(token, null); }}

?源碼中有這樣一句,其實就是判斷當前用戶是否能夠訪問指定的接口,可以則執行 fi.getChain().doFilter 調用訪問的接口否則 內部會拋出異常

InterceptorStatusToken token = super.beforeInvocation(fi);

?beforeInvocation 方法內部是通過 accessDecisionManager 去做決定的?Spring Security已經內置了幾個基于投票的AccessDecisionManager包括(AffirmativeBased ,ConsensusBased ,UnanimousBased)當然如果需要你也可以實現自己的AccessDecisionManager

?使用這種方式,一系列的AccessDecisionVoter將會被AccessDecisionManager用來對Authentication是否有權訪問受保護對象進行投票,然后再根據投票結果來決定是否要拋出AccessDeniedException

this.accessDecisionManager.decide(authenticated, object, attributes);

?AffirmativeBased的 decide的實現如下

public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException { int deny = 0; Iterator var5 = this.getDecisionVoters().iterator(); while(var5.hasNext()) { AccessDecisionVoter voter = (AccessDecisionVoter)var5.next(); int result = voter.vote(authentication, object, configAttributes); if (this.logger.isDebugEnabled()) {  this.logger.debug("Voter: " + voter + ", returned: " + result); } switch(result) { case -1:  ++deny;  break; case 1:  return; } } if (deny > 0) { throw new AccessDeniedException(this.messages.getMessage("AbstractAccessDecisionManager.accessDenied", "Access is denied")); } else { this.checkAllowIfAllAbstainDecisions(); }}

?AffirmativeBased的邏輯是這樣的:

(1)只要有AccessDecisionVoter的投票為ACCESS_GRANTED則同意用戶進行訪問;(2)如果全部棄權也表示通過;(3)如果沒有一個人投贊成票,但是有人投反對票,則將拋出AccessDeniedException。

?當我們第一次訪問的時候

http://localhost:8080/hello的時候

?返回 result = -1 會拋出 AccessDeniedException 拒絕訪問異常

?4.ExceptionTranslationFilter (捕獲AccessDeniedException異常)

?該過濾器它會接收到FilterSecurityInterceptor拋出的 AccessDeniedException異常)并且進行捕獲,然后發送重定向到/login請求

?源碼如下:

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); logger.debug("Chain processed normally"); } catch (IOException ex) { throw ex; } catch (Exception ex) { // Try to extract a SpringSecurityException from the stacktrace Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex); RuntimeException ase = (AuthenticationException) throwableAnalyzer  .getFirstThrowableOfType(AuthenticationException.class, causeChain); if (ase == null) {  ase = (AccessDeniedException) throwableAnalyzer.getFirstThrowableOfType(   AccessDeniedException.class, causeChain); } if (ase != null) {  if (response.isCommitted()) {  throw new ServletException("Unable to handle the Spring Security Exception because the response is already committed.", ex);  }  handleSpringSecurityException(request, response, chain, ase); } else {  // Rethrow ServletExceptions and RuntimeExceptions as-is  if (ex instanceof ServletException) {  throw (ServletException) ex;  }  else if (ex instanceof RuntimeException) {  throw (RuntimeException) ex;  }  // Wrap other Exceptions. This shouldn't actually happen  // as we've already covered all the possibilities for doFilter  throw new RuntimeException(ex); } }}

?當獲取異常后 調用

handleSpringSecurityException(request, response, chain, ase);

?handleSpringSecurityException 源碼如下:

private void handleSpringSecurityException(HttpServletRequest request, HttpServletResponse response, FilterChain chain, RuntimeException exception) throws IOException, ServletException { if (exception instanceof AuthenticationException) { logger.debug(  "Authentication exception occurred; redirecting to authentication entry point",  exception); sendStartAuthentication(request, response, chain,  (AuthenticationException) exception); } else if (exception instanceof AccessDeniedException) { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authenticationTrustResolver.isAnonymous(authentication) || authenticationTrustResolver.isRememberMe(authentication)) {  logger.debug(   "Access is denied (user is " + (authenticationTrustResolver.isAnonymous(authentication) ? "anonymous" : "not fully authenticated") + "); redirecting to authentication entry point",   exception);  sendStartAuthentication(   request,   response,   chain,   new InsufficientAuthenticationException(   messages.getMessage(    "ExceptionTranslationFilter.insufficientAuthentication",    "Full authentication is required to access this resource"))); } else {  logger.debug(   "Access is denied (user is not anonymous); delegating to AccessDeniedHandler",   exception);  accessDeniedHandler.handle(request, response,   (AccessDeniedException) exception); } }}

?先判斷獲取的異常是否是AccessDeniedException 再判斷是否是匿名用戶,如果是則調用 sendStartAuthentication 重定向到登錄頁面

?重定向登錄頁面之前會保存當前訪問的路徑,這就是為什么我們訪問 /hello接口后 再登錄成功后又會跳轉到 /hello接口,因為在重定向到/login接口前 這里進行了保存 requestCache.saveRequest(request, response);

protected void sendStartAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, AuthenticationException reason) throws ServletException, IOException { // SEC-112: Clear the SecurityContextHolder's Authentication, as the // existing Authentication is no longer considered valid SecurityContextHolder.getContext().setAuthentication(null); requestCache.saveRequest(request, response); logger.debug("Calling Authentication entry point."); authenticationEntryPoint.commence(request, response, reason);}

?authenticationEntryPoint.commence(request, response, reason);方法內部

?調用LoginUrlAuthenticationEntryPoint 的 commence方法

?LoginUrlAuthenticationEntryPoint 的commence方法內部有 構造重定向URL的方法

redirectUrl = buildRedirectUrlToLoginPage(request, response, authException);protected String buildRedirectUrlToLoginPage(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) { String loginForm = determineUrlToUseForThisRequest(request, response,  authException);protected String determineUrlToUseForThisRequest(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) { return getLoginFormUrl();}

?最終會獲取到需要重定向的URL /login

?然后sendRedirect 既會重定向到 /login 請求

?5.DefaultLoginPageGeneratingFilter (會捕獲重定向的/login 請求)

?DefaultLoginPageGeneratingFilter是過濾器鏈中的一個用于捕獲/login請求,并且渲染出一個默認表單頁面

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; boolean loginError = isErrorPage(request); boolean logoutSuccess = isLogoutSuccess(request); if (isLoginUrlRequest(request) || loginError || logoutSuccess) { String loginPageHtml = generateLoginPageHtml(request, loginError,  logoutSuccess); response.setContentType("text/html;charset=UTF-8"); response.setContentLength(loginPageHtml.getBytes(StandardCharsets.UTF_8).length); response.getWriter().write(loginPageHtml); return; } chain.doFilter(request, response);}

?isLoginUrlRequest 判斷請求是否是 loginPageUrl

private boolean isLoginUrlRequest(HttpServletRequest request) { return matches(request, loginPageUrl);}

?因為我們沒有配置所以 默認的 loginPageUrl = /login

?驗證通過請求路徑 能匹配 loginPageUrl

String loginPageHtml = generateLoginPageHtml(request, loginError,  logoutSuccess);

?generateLoginPageHtml 繪制默認的HTML 頁面,到此我們默認的登錄頁面怎么來的就解釋清楚了

private String generateLoginPageHtml(HttpServletRequest request, boolean loginError,  boolean logoutSuccess) { String errorMsg = "Invalid credentials"; if (loginError) {  HttpSession session = request.getSession(false);  if (session != null) {   AuthenticationException ex = (AuthenticationException) session     .getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);   errorMsg = ex != null ? ex.getMessage() : "Invalid credentials";  } } StringBuilder sb = new StringBuilder(); sb.append("<!DOCTYPE html>\n"   + "<html lang=\"en\">\n"   + " <head>\n"   + " <meta charset=\"utf-8\">\n"   + " <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\">\n"   + " <meta name=\"description\" content=\"\">\n"   + " <meta name=\"author\" content=\"\">\n"   + " <title>Please sign in</title>\n"   + " <link href=\"https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css\" rel=\"stylesheet\" integrity=\"sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M\" crossorigin=\"anonymous\">\n"   + " <link href=\"https://getbootstrap.com/docs/4.0/examples/signin/signin.css\" rel=\"stylesheet\" crossorigin=\"anonymous\"/>\n"   + " </head>\n"   + " <body>\n"   + "  <p class=\"container\">\n"); String contextPath = request.getContextPath(); if (this.formLoginEnabled) {  sb.append("  <form class=\"form-signin\" method=\"post\" action=\"" + contextPath + this.authenticationUrl + "\">\n"    + "  <h3 class=\"form-signin-heading\">Please sign in</h3>\n"    + createError(loginError, errorMsg)    + createLogoutSuccess(logoutSuccess)    + "  <p>\n"    + "   <label for=\"username\" class=\"sr-only\">Username</label>\n"    + "   <input type=\"text\" id=\"username\" name=\"" + this.usernameParameter + "\" class=\"form-control\" placeholder=\"Username\" required autofocus>\n"    + "  </p>\n"    + "  <p>\n"    + "   <label for=\"password\" class=\"sr-only\">Password</label>\n"    + "   <input type=\"password\" id=\"password\" name=\"" + this.passwordParameter + "\" class=\"form-control\" placeholder=\"Password\" required>\n"    + "  </p>\n"    + createRememberMe(this.rememberMeParameter)    + renderHiddenInputs(request)    + "  <button class=\"btn btn-lg btn-primary btn-block\" type=\"submit\">Sign in</button>\n"    + "  </form>\n"); } if (openIdEnabled) {  sb.append("  <form name=\"oidf\" class=\"form-signin\" method=\"post\" action=\"" + contextPath + this.openIDauthenticationUrl + "\">\n"    + "  <h3 class=\"form-signin-heading\">Login with OpenID Identity</h3>\n"    + createError(loginError, errorMsg)    + createLogoutSuccess(logoutSuccess)    + "  <p>\n"    + "   <label for=\"username\" class=\"sr-only\">Identity</label>\n"    + "   <input type=\"text\" id=\"username\" name=\"" + this.openIDusernameParameter + "\" class=\"form-control\" placeholder=\"Username\" required autofocus>\n"    + "  </p>\n"    + createRememberMe(this.openIDrememberMeParameter)    + renderHiddenInputs(request)    + "  <button class=\"btn btn-lg btn-primary btn-block\" type=\"submit\">Sign in</button>\n"    + "  </form>\n"); } if (oauth3LoginEnabled) {  sb.append("<h3 class=\"form-signin-heading\">Login with OAuth 2.0</h3>");  sb.append(createError(loginError, errorMsg));  sb.append(createLogoutSuccess(logoutSuccess));  sb.append("<table class=\"table table-striped\">\n");  for (Map.Entry<String, String> clientAuthenticationUrlToClientName : oauth3AuthenticationUrlToClientName.entrySet()) {   sb.append(" <tr><td>");   String url = clientAuthenticationUrlToClientName.getKey();   sb.append("<a href=\"").append(contextPath).append(url).append("\">");   String clientName = HtmlUtils.htmlEscape(clientAuthenticationUrlToClientName.getValue());   sb.append(clientName);   sb.append("</a>");   sb.append("</td></tr>\n");  }  sb.append("</table>\n"); } if (this.saml2LoginEnabled) {  sb.append("<h3 class=\"form-signin-heading\">Login with SAML 2.0</h3>");  sb.append(createError(loginError, errorMsg));  sb.append(createLogoutSuccess(logoutSuccess));  sb.append("<table class=\"table table-striped\">\n");  for (Map.Entry<String, String> relyingPartyUrlToName : saml2AuthenticationUrlToProviderName.entrySet()) {   sb.append(" <tr><td>");   String url = relyingPartyUrlToName.getKey();   sb.append("<a href=\"").append(contextPath).append(url).append("\">");   String partyName = HtmlUtils.htmlEscape(relyingPartyUrlToName.getValue());   sb.append(partyName);   sb.append("</a>");   sb.append("</td></tr>\n");  }  sb.append("</table>\n"); } sb.append("</p>\n"); sb.append("</body></html>"); return sb.toString();}

至此 SpringSecurity 默認表單登錄頁展示流程源碼部分已經全部講解完畢,會渲染出下面的頁面,但是一定要有網的情況,否則樣式可能會變化

6.總結

本篇主要講解 SpringSecurity提供的默認表單登錄頁 它是如何展示的的流程,包括涉及這一流程中相關的 3個過濾器

1.FilterSecurityInterceptor,2.ExceptionTranslationFilter ,3.DefaultLoginPageGeneratingFilter 過濾器,并且簡單介紹了一下 AccessDecisionManager 它主要進行投票來判斷該用戶是否能夠訪問相應的 資源AccessDecisionManager 投票機制我也沒有深究 后續我會詳細深入一下再展開

關于SpringSecurity默認表單登錄頁展示流程源碼是怎樣的就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

佛学| 临武县| 桦南县| 尼玛县| 灵武市| 平塘县| 揭东县| 桓台县| 樟树市| 长泰县| 堆龙德庆县| 宜宾县| 通化县| 琼海市| 行唐县| 龙州县| 东乡族自治县| 白朗县| 综艺| 沂源县| 临桂县| 天镇县| 连州市| 凭祥市| 开阳县| 沙洋县| 洛南县| 平乐县| 始兴县| 双峰县| 台州市| 凉山| 洪江市| 邹城市| 泌阳县| 松溪县| 塘沽区| 兴义市| 柳林县| 横山县| 井冈山市|