整合營(yíng)銷(xiāo)服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          “用戶登錄”測(cè)試你真的會(huì)嗎?

          “用戶登錄”測(cè)試你真的會(huì)嗎?

          后,進(jìn)入金三銀四招聘旺季,相信不少參加測(cè)試相關(guān)崗位面試的時(shí)候,會(huì)遇到設(shè)計(jì)測(cè)試的題目。用戶登錄測(cè)試屬于老生常談的問(wèn)題了,如果面試時(shí)能先概述一下你的設(shè)計(jì)思路、考慮的維度,再開(kāi)始逐個(gè)寫(xiě)出用例的設(shè)計(jì),應(yīng)該會(huì)更有條理。

          給你一個(gè)“用戶登錄”功能你會(huì)如何測(cè)試它?

          設(shè)計(jì)測(cè)試用例,通常主要有 2 個(gè)大類(lèi):功能性需求和非功能性需求。

          一、功能需求

          功能需求其實(shí)就是軟件本身需要實(shí)現(xiàn)的具體功能,通常它們是這個(gè)功能的最直接體現(xiàn)。

          在設(shè)計(jì)這種用例的時(shí)候,我們基本會(huì)用【等價(jià)類(lèi)】和【邊界值】這兩種方法。

          1. 用戶名和密碼都為空,驗(yàn)證是否登錄失敗,并提示信息(“用戶名為空”)

          2. 用戶名為空或密碼為空,驗(yàn)證是否登錄失敗,并提示信息(“用戶名/密碼為空”)

          3. 輸入已注冊(cè)的用戶名和正確的密碼,驗(yàn)證是否登錄成功

          4. 登錄成功后,鏈接能否跳轉(zhuǎn)至正確的頁(yè)面

          5. 輸入已注冊(cè)的用戶名和錯(cuò)誤的密碼,驗(yàn)證是否登錄失敗,并提示信息(“密碼錯(cuò)誤”)

          6. 輸入未注冊(cè)的用戶名和隨意密碼,驗(yàn)證是否登錄失敗,并提示信息(“用戶名錯(cuò)誤”)

          7. 如果登錄功能啟用驗(yàn)證碼功能,在用戶名和密碼正確的前提下,輸入正確的驗(yàn)證碼,驗(yàn)證是否登錄成功

          8. 如果登錄功能啟用驗(yàn)證碼功能,在用戶名和密碼正確的前提下,輸入錯(cuò)誤的驗(yàn)證碼,驗(yàn)證是否登錄失敗,并提示信息正確(“驗(yàn)證碼錯(cuò)誤”)

          9. 如果登錄功能啟用驗(yàn)證碼功能,考慮驗(yàn)證碼的辨認(rèn)難易程度,是否可以點(diǎn)擊驗(yàn)證碼圖片(“換一個(gè)”),更換驗(yàn)證碼

          10. 記住用戶名和密碼的功能是否正確,且登錄失敗后不記錄密碼

          11. 必填項(xiàng)為空時(shí),功能是否正確

          不合法的用戶名:空白的用戶名,不正確的用戶名,使用了字符大于用戶名的限制;正常用戶名不允許的特殊字符;系統(tǒng)的保留字段;

          不合法的密碼:空密碼;錯(cuò)誤的密碼;字符大于密碼的限制;正常密碼不允許的特殊字符;系統(tǒng)的保留字段。

          二、非功能性需求

          很多時(shí)候,僅做了功能性需求的測(cè)試覆蓋還是不夠的,因?yàn)檫€存在一些其他"隱藏"的需求,比如:界面UI、安全性、性能、兼容性。這些往往是決定軟件質(zhì)量的關(guān)鍵因素。

          這些需求往往不容易優(yōu)先想到,需要仔細(xì)深入場(chǎng)景、設(shè)身處地的考慮才能很好地構(gòu)思出來(lái)。

          1、界面測(cè)試(UI Test):

          1. 布局是否美觀、合理

          2. 控件是否美觀、對(duì)齊

          3. 界面設(shè)計(jì)風(fēng)格是否統(tǒng)一

          4. 界面文字無(wú)錯(cuò)別字

          2、安全性測(cè)試

          考慮登錄的安全性,可能需要用到以下測(cè)試用例:

          1. 用戶名和密碼的后臺(tái)存儲(chǔ)是否加密

          2. 用戶名和密碼是否加密后,發(fā)給web服務(wù)器

          3. 用戶名和密碼的驗(yàn)證,應(yīng)該通過(guò)服務(wù)器驗(yàn)證,而不單單在客戶端用javascript驗(yàn)證

          4. 登錄成功后生成的cookie,是否是httponly(否則容易被腳本盜取)

          5. 密碼框內(nèi)輸入的密碼是否可以在頁(yè)面源碼模式下被查看

          6. 密碼是否具有有效期,有效期到期后,是否提示修改密碼

          7. 未登錄的情況下,在瀏覽器中直接輸入登錄后的URL地址,是否會(huì)重定向到登錄界面

          8. 用戶名和密碼輸入框中分別輸入典型的“SQL注入攻擊”字符串,驗(yàn)證系統(tǒng)的返回頁(yè)面(防止SQL注入攻擊)

          9. 用戶名和密碼輸入框中分別輸入典型的“XSS跨站腳本攻擊”字符串,驗(yàn)證系統(tǒng)行為是否被篡改(防止XSS攻擊)

          10. 連續(xù)多次登錄失敗后,系統(tǒng)是否會(huì)阻止后續(xù)的嘗試,錯(cuò)誤登錄的次數(shù)限制,以應(yīng)對(duì)暴力破解(防止暴力破解)

          11. 考慮很多用戶在同一終端上的登陸

          12. 考慮同一用戶在不同終端登錄的互斥性

          13. 同一用戶在同一終端的多種瀏覽器上登錄,驗(yàn)證登錄功能的互斥性

          3、 性能壓力測(cè)試

          考慮到性能,可能還需要增加以下的測(cè)試用例:

          1. 單用戶登錄的響應(yīng)時(shí)間是否小于 3 秒

          2. 單用戶登錄時(shí),后臺(tái)請(qǐng)求數(shù)量是否過(guò)多

          3. 高并發(fā)場(chǎng)景下用戶登錄的響應(yīng)時(shí)間是否小于 5 秒

          4. 高并發(fā)場(chǎng)景下服務(wù)端的監(jiān)控指標(biāo)是否符合預(yù)期

          5. 高集合點(diǎn)并發(fā)場(chǎng)景下,是否存在資源死鎖和不合理的資源等待

          6. 長(zhǎng)時(shí)間大量用戶連續(xù)登錄和登出,服務(wù)器端是否存在內(nèi)存泄漏

          4、兼容性測(cè)試

          1. 不同瀏覽器下,驗(yàn)證登錄頁(yè)面的顯示以及功能正確性

          2. 相同瀏覽器的不同版本下,驗(yàn)證登錄頁(yè)面的顯示以及功能正確性

          3. 不同移動(dòng)終端的不同瀏覽器下,驗(yàn)證登錄頁(yè)面的顯示以及功能正確性

          4. 不同分辨率的界面下,驗(yàn)證登錄頁(yè)面的顯示以及功能正確性

          5. 不同平臺(tái)(Windows、Mac)、不同移動(dòng)設(shè)備(iPhone、Andriod),登錄界面的顯示及功能是否正確

          6. 不同語(yǔ)言環(huán)境下,登錄界面的顯示及功能是否正確 (本地化測(cè)試 Localization Test)

          5、弱網(wǎng)測(cè)試

          1、網(wǎng)絡(luò)延遲或者弱網(wǎng)或者切換網(wǎng)絡(luò)、斷網(wǎng)是否登錄正確

          2、未激活或者凍結(jié)的用戶登陸

          3、登錄的日志是否記錄正確

          4、密碼強(qiáng)弱進(jìn)行校驗(yàn)

          5、有沒(méi)有對(duì)登陸設(shè)備和地區(qū)進(jìn)行檢測(cè)

          三、測(cè)試的不可窮盡性

          通常在實(shí)際工作中,測(cè)試由于受限于時(shí)間成本和經(jīng)濟(jì)成本,是不可能去窮盡所有可能的組合的,而是采用基于風(fēng)險(xiǎn)驅(qū)動(dòng)的模式,有所側(cè)重地選擇測(cè)試范圍和設(shè)計(jì)測(cè)試用例,以尋求缺陷風(fēng)險(xiǎn)和研發(fā)成本之間的平衡。

          對(duì)于高質(zhì)量的軟件測(cè)試,用例設(shè)計(jì)不僅需要考慮明確的功能性需求,還要涉及兼容性、安全性和性能等一系列的非功能性需求,這些非功能性需求對(duì)軟件系統(tǒng)的質(zhì)量有著舉足輕重的作用。

          載自: java大師
          博客系統(tǒng)訪問(wèn):

          登錄功能

          1、前端頁(yè)面

          采用的是layui-admin框架,文中的驗(yàn)證碼內(nèi)容,請(qǐng)參考作者之前的驗(yàn)證碼功能

          <!DOCTYPE html>
          <html lang="zh" xmlns:th="http://www.thymeleaf.org">
          <head>
              <title>ds博客</title>
              <div th:replace="common/link::header"></div>
              <link rel="stylesheet" th:href="@{/static/layuiadmin/style/login.css}" media="all">
          </head>
          <body>
          <div class="layadmin-user-login layadmin-user-display-show" id="LAY-user-login" style="display: none;">
              <div class="layadmin-user-login-main">
                  <div class="layadmin-user-login-box layadmin-user-login-header">
                      <h2>ds博客</h2>
                      <p>后臺(tái)登錄</p>
                  </div>
                  <div class="layadmin-user-login-box layadmin-user-login-body layui-form">
                      <div class="layui-form-item">
                          <label class="layadmin-user-login-icon layui-icon layui-icon-username" for="LAY-user-login-username"></label>
                          <input type="text" name="userName" value="test" id="LAY-user-login-username" lay-verify="required" placeholder="用戶名" class="layui-input">
                      </div>
                      <div class="layui-form-item">
                          <label class="layadmin-user-login-icon layui-icon layui-icon-password" for="LAY-user-login-password"></label>
                          <input type="password" name="passWord" value="test" id="LAY-user-login-password" lay-verify="required" placeholder="密碼" class="layui-input">
                      </div>
                      <div class="layui-form-item">
                          <div class="layui-row">
                              <div class="layui-col-xs7">
                                  <label class="layadmin-user-login-icon layui-icon layui-icon-vercode"></label>
                                  <input type="text" name="code"  lay-verify="required" placeholder="圖形驗(yàn)證碼" class="layui-input">
                              </div>
                              <div class="layui-col-xs5">
                                  <div style="margin-left: 10px;">
                                      <img id="codeImg" class="layadmin-user-login-codeimg">
                                  </div>
                              </div>
                          </div>
                      </div>
                      <div class="layui-form-item" style="margin-bottom: 20px;">
                          <input type="checkbox" name="remember-me" lay-skin="primary" title="記住密碼">
                      </div>
                      <div class="layui-form-item">
                          <button class="layui-btn layui-btn-fluid layui-bg-blue"  lay-submit lay-filter="login">登 錄</button>
                      </div>
                  </div>
              </div>
          
          <!--    <div class="layui-trans layadmin-user-login-footer">-->
          <!--        <p>版權(quán)所有 ? 2022 <a href="#" target="_blank">濟(jì)南高新開(kāi)發(fā)區(qū)微本地軟件開(kāi)發(fā)工作室</a> 魯ICP備20002306號(hào)-1</p>-->
          <!--    </div>-->
          </div>
          <div th:replace="common/script::footer"></div>
          <script th:inline="javascript">
              layui.config({
                  base: '/static/layuiadmin/' //靜態(tài)資源所在路徑
              }).extend({
                  index: 'lib/index' //主入口模塊
              }).use(['index', 'user'], function(){
                  let $=layui.$,
                      form=layui.form;
                  // 初始化
                  getImgCode();
                  form.render();
                  //提交
                  form.on('submit(login)', function(obj) {
                      // 打開(kāi)loading
                      let loading=layer.load(0, {
                          shade: false,
                          time: 2 * 1000
                      });
                      // 禁止重復(fù)點(diǎn)擊按鈕
                      $('.layui-btn').attr("disabled",true);
                      //請(qǐng)求登入接口
                      $.ajax({
                          type: 'POST',
                          url:  ctx + '/login',
                          data: obj.field,
                          dataType: 'json',
                          success: function(result) {
                              if (result.code===200) {
                                  layer.msg('登入成功', {
                                       icon: 1
                                      ,time: 1000
                                  }, function(){
                                      window.location.href='/';
                                  });
                              } else {
                                  layer.msg(result.message);
                                  // 刷新驗(yàn)證碼
                                  getImgCode();
                                  // 關(guān)閉loading
                                  layer.close(loading);
                                  // 開(kāi)啟點(diǎn)擊事件
                                  $('.layui-btn').attr("disabled", false);
                              }
                          }
                      });
                  });
                  $("#codeImg").on('click', function() {
                      // 添加驗(yàn)證碼
                      getImgCode();
                  });
                  $(document).keydown(function (e) {
                      if (e.keyCode===13) {
                          $('.layui-btn').click();
                      }
                  });
                  // 解決session過(guò)期跳轉(zhuǎn)到登錄頁(yè)并跳出iframe框架
                  $(document).ready(function () {
                      if (window !=top) {
                          top.location.href=location.href;
                      }
                  });
              });
              /**
               * 獲取驗(yàn)證碼
               */
              function getImgCode() {
                  let url=ctx + '/getImgCode';
                  let xhr=new XMLHttpRequest();
                  xhr.open('GET', url, true);
                  xhr.responseType="blob";
                  xhr.onload=function() {
                      if (this.status===200) {
                          let blob=this.response;
                          document.getElementById("codeImg").src=window.URL.createObjectURL(blob);
                      }
                  }
                  xhr.send();
              }
          </script>
          </body>
          </html>
          

          2、后端處理/login請(qǐng)求

          通過(guò)springsecurity的.loginProcessingUrl(“/login”)處理,處理邏輯如下:

          .loginProcessingUrl("/login") 用于指定處理登錄操作的URL地址,而具體的驗(yàn)證邏輯是由 Spring Security 提供的認(rèn)證過(guò)濾器鏈負(fù)責(zé)的。在Spring Security中,主要的認(rèn)證過(guò)程是由UsernamePasswordAuthenticationFilter來(lái)完成的。

          當(dāng)用戶提交登錄表單,請(qǐng)求到達(dá).loginProcessingUrl("/login")配置的URL時(shí),UsernamePasswordAuthenticationFilter會(huì)攔截這個(gè)請(qǐng)求,然后進(jìn)行以下主要步驟:

          1. 獲取用戶名和密碼:從請(qǐng)求中獲取用戶輸入的用戶名和密碼。
          2. 創(chuàng)建認(rèn)證令牌:使用獲取到的用戶名和密碼創(chuàng)建一個(gè)認(rèn)證令牌(UsernamePasswordAuthenticationToken)。
          3. 將認(rèn)證令牌傳遞給認(rèn)證管理器:將認(rèn)證令牌傳遞給配置的認(rèn)證管理器(AuthenticationManager)進(jìn)行認(rèn)證。
          4. 執(zhí)行認(rèn)證邏輯:認(rèn)證管理器會(huì)調(diào)用已配置的身份驗(yàn)證提供者(AuthenticationProvider)來(lái)執(zhí)行實(shí)際的身份驗(yàn)證邏輯。通常,會(huì)使用用戶提供的用戶名和密碼與系統(tǒng)中存儲(chǔ)的用戶信息進(jìn)行比對(duì)。
          5. 處理認(rèn)證結(jié)果:認(rèn)證提供者返回認(rèn)證結(jié)果,如果認(rèn)證成功,則將認(rèn)證令牌標(biāo)記為已認(rèn)證,并設(shè)置用戶權(quán)限等信息。如果認(rèn)證失敗,會(huì)拋出相應(yīng)的異常。
          6. 處理認(rèn)證成功或失敗:根據(jù)認(rèn)證的結(jié)果,UsernamePasswordAuthenticationFilter將請(qǐng)求重定向到成功或失敗的處理器,執(zhí)行相應(yīng)的操作,比如跳轉(zhuǎn)頁(yè)面或返回錯(cuò)誤信息。

          這個(gè)整個(gè)過(guò)程是由 Spring Security 提供的默認(rèn)配置完成的,通常情況下,開(kāi)發(fā)者只需要配置好認(rèn)證管理器、用戶信息服務(wù)(UserDetailsService),以及成功和失敗的處理器,Spring Security 就會(huì)負(fù)責(zé)處理登錄驗(yàn)證的整個(gè)流程。

          package com.ds.core.config;
          
          import com.ds.blog.system.service.SysUserService;
          import com.ds.core.security.CustomAccessDeniedHandler;
          import com.ds.core.security.DefaultAuthenticationFailureHandler;
          import com.ds.core.security.DefaultAuthenticationSuccessHandler;
          import com.ds.core.security.filter.ValidateCodeFilter;
          import net.bytebuddy.asm.Advice;
          import org.springframework.beans.factory.annotation.Autowired;
          import org.springframework.context.annotation.Bean;
          import org.springframework.context.annotation.Configuration;
          import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
          import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
          import org.springframework.security.config.annotation.web.builders.HttpSecurity;
          import org.springframework.security.config.annotation.web.builders.WebSecurity;
          import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
          import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
          import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
          import org.springframework.security.web.access.AccessDeniedHandler;
          import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
          
          @Configuration
          @EnableWebSecurity
          @EnableGlobalMethodSecurity(prePostEnabled=true)
          public class MySecurityConfig extends WebSecurityConfigurerAdapter {
          
              @Override
              protected void configure(HttpSecurity http) throws Exception {
                  http.authorizeRequests()
                          // 放過(guò)
                          .antMatchers("/loginPage", "/getImgCode").permitAll()
                          .antMatchers("/**/*.jpg", "/**/*.png", "/**/*.gif", "/**/*.jpeg").permitAll()
                          // 剩下的所有的地址都是需要在認(rèn)證狀態(tài)下才可以訪問(wèn)
                          .anyRequest().authenticated()
                          .and()
                          // 過(guò)濾登錄驗(yàn)證碼
                          .addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)
                          // 配置登錄功能
                          .formLogin()
                          .usernameParameter("userName")
                          .passwordParameter("passWord")
                          // 指定指定要的登錄頁(yè)面
                          .loginPage("/loginPage")
                          // 處理認(rèn)證路徑的請(qǐng)求
                          .loginProcessingUrl("/login")
                          .successHandler(defaultAuthenticationSuccessHandler)
                          .failureHandler(defaultAuthenticationFailureHandler)
                          .and()
                          .exceptionHandling()
                          .accessDeniedHandler(accessDeniedHandler)
                          .and()
                          // 登出
                          .logout()
                          .invalidateHttpSession(true)
                          .deleteCookies("remember-me")
                          .logoutUrl("/logout")
                          .logoutSuccessUrl("/loginPage")
                          .and()
                          .rememberMe()
                          // 有效期7天
                          .tokenValiditySeconds(3600 * 24 * 7)
                          // 開(kāi)啟記住我功能
                          .rememberMeParameter("remember-me")
                          .and()
                          //禁用csrf
                          .csrf().disable()
                          // header response的X-Frame-Options屬性設(shè)置為SAMEORIGIN
                          .headers().frameOptions().sameOrigin()
                          .and()
                          // 配置session管理
                          .sessionManagement()
                          //session失效默認(rèn)的跳轉(zhuǎn)地址
                          .invalidSessionUrl("/loginPage");
              }
          }
          

          3、登錄成功監(jiān)聽(tīng)器(記錄登錄日志)

          創(chuàng)建監(jiān)聽(tīng)器,在登錄成功的時(shí)候記錄登錄日志。

          1. @Slf4j
          2. @Slf4j 是 Lombok 提供的注解,用于自動(dòng)生成日志對(duì)象,這里是為了方便使用日志。
          3. @Component
          4. @Component 注解將類(lèi)標(biāo)識(shí)為一個(gè) Spring 組件,使得 Spring 能夠自動(dòng)掃描并將其納入容器管理。
          5. AuthenticationSuccessListener 實(shí)現(xiàn) ApplicationListener 接口
          6. AuthenticationSuccessListener 類(lèi)實(shí)現(xiàn)了 ApplicationListener<AuthenticationSuccessEvent> 接口,表明它是一個(gè)事件監(jiān)聽(tīng)器,監(jiān)聽(tīng)的是用戶認(rèn)證成功的事件。
          7. SysLoginLogService 注入
          8. SysLoginLogService 是一個(gè)服務(wù)類(lèi),通過(guò) @Autowired 注解注入到當(dāng)前類(lèi)中。該服務(wù)類(lèi)用于對(duì)登錄日志的持久化操作。
          9. onApplicationEvent 方法
          10. onApplicationEvent 方法是實(shí)現(xiàn) ApplicationListener 接口的回調(diào)方法,在用戶認(rèn)證成功的時(shí)候會(huì)被觸發(fā)。
          11. 通過(guò) authenticationSuccessEvent.getAuthentication().getPrincipal() 獲取登錄的用戶信息,這里假設(shè)用戶信息是 User 類(lèi)型。
          12. 通過(guò) ServletUtil.getClientIP 獲取客戶端的IP地址,這里使用了 ServletUtil 工具類(lèi),可以通過(guò)請(qǐng)求上下文獲取IP地址。
          13. 創(chuàng)建一個(gè) SysLoginLog 對(duì)象,將登錄成功的相關(guān)信息設(shè)置進(jìn)去,包括賬號(hào)、登錄IP、備注等。
          14. 調(diào)用 sysLoginLogService.save(sysLoginLog) 將登錄日志持久化存儲(chǔ)。

          總的來(lái)說(shuō),這段代碼的作用是在用戶成功登錄后,通過(guò)監(jiān)聽(tīng) Spring Security 的認(rèn)證成功事件,記錄用戶的登錄日志信息,包括登錄賬號(hào)、登錄IP和登錄成功的備注。這樣可以實(shí)現(xiàn)登錄成功后的自定義操作,例如記錄登錄日志等。

          @Slf4j
          @Component
          public class AuthenticationSuccessListener implements ApplicationListener<AuthenticationSuccessEvent> {
              @Autowired
              private SysLoginLogService sysLoginLogService;
          
              @Override
              public void onApplicationEvent(AuthenticationSuccessEvent authenticationSuccessEvent) {
                  // 登錄賬號(hào)
                  User user=(User) authenticationSuccessEvent.getAuthentication().getPrincipal();
                  // 請(qǐng)求IP
                  String ip=ServletUtil.getClientIP(((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest(), "");
                  SysLoginLog sysLoginLog=new SysLoginLog();
                  sysLoginLog.setAccount(user.getUsername());
                  sysLoginLog.setLoginIp(ip);
                  sysLoginLog.setRemark("登錄成功");
                  sysLoginLogService.save(sysLoginLog);
              }
          }
          

          4、登錄失敗監(jiān)聽(tīng)器(記錄登錄日志)

          創(chuàng)建監(jiān)聽(tīng)器,在登錄失敗的時(shí)候記錄異常登錄日志。

          @Slf4j
          @Component
          public class AuthenticationFailureListener implements ApplicationListener<AbstractAuthenticationFailureEvent>  {
          
              @Autowired
              private SysLoginLogService sysLoginLogService;
          
              @Override
              public void onApplicationEvent(AbstractAuthenticationFailureEvent abstractAuthenticationFailureEvent) {
                  // 登錄賬號(hào)
                  String username=abstractAuthenticationFailureEvent.getAuthentication().getPrincipal().toString();
                  // 登錄失敗原因
                  String message ;
                  if (abstractAuthenticationFailureEvent instanceof AuthenticationFailureBadCredentialsEvent) {
                      //提供的憑據(jù)是錯(cuò)誤的,用戶名或者密碼錯(cuò)誤
                      message="提供的憑據(jù)是錯(cuò)誤的,用戶名或者密碼錯(cuò)誤";
                  } else if (abstractAuthenticationFailureEvent instanceof AuthenticationFailureCredentialsExpiredEvent) {
                      //驗(yàn)證通過(guò),但是密碼過(guò)期
                      message="驗(yàn)證通過(guò),但是密碼過(guò)期";
                  } else if (abstractAuthenticationFailureEvent instanceof AuthenticationFailureDisabledEvent) {
                      //驗(yàn)證過(guò)了但是賬戶被禁用
                      message="驗(yàn)證過(guò)了但是賬戶被禁用";
                  } else if (abstractAuthenticationFailureEvent instanceof AuthenticationFailureExpiredEvent) {
                      //驗(yàn)證通過(guò)了,但是賬號(hào)已經(jīng)過(guò)期
                      message="驗(yàn)證通過(guò)了,但是賬號(hào)已經(jīng)過(guò)期";
                  } else if (abstractAuthenticationFailureEvent instanceof AuthenticationFailureLockedEvent) {
                      //賬戶被鎖定
                      message="賬戶被鎖定";
                  } else if (abstractAuthenticationFailureEvent instanceof AuthenticationFailureProviderNotFoundEvent) {
                      //配置錯(cuò)誤,沒(méi)有合適的AuthenticationProvider來(lái)處理登錄驗(yàn)證
                      message="配置錯(cuò)誤";
                  } else if (abstractAuthenticationFailureEvent instanceof AuthenticationFailureProxyUntrustedEvent) {
                      // 代理不受信任,用于Oauth、CAS這類(lèi)三方驗(yàn)證的情形,多屬于配置錯(cuò)誤
                      message="代理不受信任";
                  } else if (abstractAuthenticationFailureEvent instanceof AuthenticationFailureServiceExceptionEvent) {
                      // 其他任何在AuthenticationManager中內(nèi)部發(fā)生的異常都會(huì)被封裝成此類(lèi)
                      message="內(nèi)部發(fā)生的異常";
                  } else {
                      message="其他未知錯(cuò)誤";
                  }
                  // 請(qǐng)求IP
                  String ip=ServletUtil.getClientIP(((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest(), "");
                  SysLoginLog sysLoginLog=new SysLoginLog();
                  sysLoginLog.setAccount(username);
                  sysLoginLog.setLoginIp(ip);
                  sysLoginLog.setRemark(message);
                  sysLoginLogService.save(sysLoginLog);
              }
          }
          

          5、認(rèn)證成功處理器

          下面是一個(gè)認(rèn)證成功處理器,登錄成功后,會(huì)返回響應(yīng)的信息給前端

          @Component
          @Slf4j
          public class DefaultAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
          
              @Override
              public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException {
                  log.info("-----login in success----");
                  response.setCharacterEncoding("utf-8");
                  response.setContentType("application/json;charset=utf-8");
                  PrintWriter writer=response.getWriter();
                  writer.write(JSON.toJSONString(Result.success()));
                  writer.flush();
              }
          }
          
          .successHandler(defaultAuthenticationSuccessHandler)
          .failureHandler(defaultAuthenticationFailureHandler)
          

          6、認(rèn)證失敗處理器

          下面是一個(gè)認(rèn)證成功處理器,登錄成功后,會(huì)返回響應(yīng)的信息給前端

          @Component
          @Slf4j
          public class DefaultAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
              @Override
              public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
                  log.info("login in failure : " +  exception.getMessage());
          
                  response.setContentType("application/json;charset=utf-8");
                  response.setCharacterEncoding("utf-8");
                  PrintWriter writer=response.getWriter();
                  String message;
                  if (exception instanceof BadCredentialsException) {
                      message="用戶名或密碼錯(cuò)誤,請(qǐng)重試。";
                      writer.write(JSON.toJSONString(Result.failure(message)));
                  }else{
                      writer.write(JSON.toJSONString(Result.failure(exception.getMessage())));
                  }
                  writer.flush();
              }
          
          .successHandler(defaultAuthenticationSuccessHandler)
          .failureHandler(defaultAuthenticationFailureHandler)
          

          7、前端頁(yè)面

          返回200,就代表成功,跳轉(zhuǎn)到/請(qǐng)求,進(jìn)去index或者main頁(yè)面

          if (result.code===200) {
              layer.msg('登入成功', {
                   icon: 1
                  ,time: 1000
              }, function(){
                  window.location.href='/';
              });
          } else {
          

          總結(jié)

          AuthenticationSuccessEvent 是 Spring Security 中用于表示用戶認(rèn)證成功的事件。判斷登錄成功的主要依據(jù)是在認(rèn)證過(guò)程中,用戶提供的憑據(jù)(通常是用戶名和密碼)與系統(tǒng)中存儲(chǔ)的憑據(jù)匹配。以下是判斷登錄成功的基本流程:

          1. 用戶提交登錄表單:用戶在瀏覽器中輸入用戶名和密碼,然后點(diǎn)擊登錄按鈕,提交登錄表單。
          2. Spring Security 攔截登錄請(qǐng)求:配置的 .loginProcessingUrl("/login") 指定了登錄請(qǐng)求的URL,Spring Security會(huì)攔截這個(gè)URL的請(qǐng)求。
          3. UsernamePasswordAuthenticationFilter處理登錄請(qǐng)求UsernamePasswordAuthenticationFilter 是 Spring Security 內(nèi)置的過(guò)濾器之一,用于處理用戶名密碼登錄認(rèn)證。當(dāng)用戶提交登錄表單時(shí),UsernamePasswordAuthenticationFilter會(huì)攔截該請(qǐng)求,嘗試進(jìn)行身份驗(yàn)證。
          4. AuthenticationManager執(zhí)行身份驗(yàn)證UsernamePasswordAuthenticationFilter將用戶名密碼等信息封裝成一個(gè) UsernamePasswordAuthenticationToken。通過(guò) AuthenticationManager 進(jìn)行身份驗(yàn)證,AuthenticationManager 是一個(gè)接口,實(shí)際的實(shí)現(xiàn)為 ProviderManagerProviderManager通過(guò)配置的 AuthenticationProvider 來(lái)執(zhí)行實(shí)際的身份驗(yàn)證邏輯。
          5. AuthenticationProvider處理身份驗(yàn)證DaoAuthenticationProviderAuthenticationProvider 的默認(rèn)實(shí)現(xiàn)之一,用于處理基于數(shù)據(jù)庫(kù)的身份驗(yàn)證。DaoAuthenticationProvider會(huì)從配置的 UserDetailsService 中獲取用戶信息,然后與用戶提交的信息進(jìn)行比對(duì)。
          6. 認(rèn)證成功:如果認(rèn)證成功,AuthenticationProvider 會(huì)返回一個(gè)已認(rèn)證的 Authentication 對(duì)象。這個(gè)已認(rèn)證的 Authentication 對(duì)象包含了用戶的信息,通常是 UserDetails 的實(shí)現(xiàn)。
          7. AuthenticationSuccessHandler處理認(rèn)證成功:在配置中,通過(guò) .successHandler() 方法指定了處理認(rèn)證成功的 AuthenticationSuccessHandler。在這個(gè)處理器中,可以執(zhí)行一些額外的邏輯,例如記錄登錄日志等。
          8. AuthenticationSuccessEvent被發(fā)布:在處理成功的階段,Spring Security 發(fā)布了 AuthenticationSuccessEvent 事件,表示認(rèn)證成功。

          在上述流程中,認(rèn)證成功的判斷主要是在 AuthenticationProvider 中完成的。DaoAuthenticationProvider 會(huì)檢查用戶提供的密碼與數(shù)據(jù)庫(kù)中存儲(chǔ)的密碼是否匹配。如果匹配,就認(rèn)為認(rèn)證成功。當(dāng)認(rèn)證成功后,后續(xù)的處理流程包括 AuthenticationSuccessHandler 的執(zhí)行和 AuthenticationSuccessEvent 的發(fā)布。你可以通過(guò)監(jiān)聽(tīng) AuthenticationSuccessEvent 事件來(lái)執(zhí)行一些額外的自定義邏輯,例如記錄登錄日志。

          解Spring Security的formLogin登錄認(rèn)證模式

          一、formLogin的應(yīng)用場(chǎng)景

          在本專(zhuān)欄之前的文章中,已經(jīng)給大家介紹過(guò)Spring Security的HttpBasic模式,該模式比較簡(jiǎn)單,只是進(jìn)行了通過(guò)攜帶Http的Header進(jìn)行簡(jiǎn)單的登錄驗(yàn)證,而且沒(méi)有定制的登錄頁(yè)面,所以使用場(chǎng)景比較窄。
          對(duì)于一個(gè)完整的應(yīng)用系統(tǒng),與登錄驗(yàn)證相關(guān)的頁(yè)面都是高度定制化的,非常美觀而且提供多種登錄方式。這就需要Spring Security支持我們自己定制登錄頁(yè)面,也就是本文給大家介紹的formLogin模式登錄認(rèn)證模式。

          準(zhǔn)備工作

          • 新建一個(gè)Spring Boot 的web應(yīng)用
          • 準(zhǔn)備一個(gè)login.html登錄頁(yè)面,頁(yè)面內(nèi)容非常簡(jiǎn)單,一個(gè)from表單、用戶名和密碼輸入框,一個(gè)提交按鈕
          • 準(zhǔn)備一個(gè)首頁(yè)index.html,在登錄成功之后需要進(jìn)入index.html首頁(yè)
          • 首頁(yè)可以看到syslog(日志管理)、sysuer(用戶管理)、biz1(業(yè)務(wù)一)、biz2(業(yè)務(wù)二)四個(gè)頁(yè)面超文本鏈接選項(xiàng)。通過(guò)controller控制層跳轉(zhuǎn)頁(yè)面,并在對(duì)應(yīng)頁(yè)面寫(xiě)一些標(biāo)志性文字即可,不需寫(xiě)具體業(yè)務(wù)。

          需求

          • 我們希望biz1(業(yè)務(wù)一)、biz2(業(yè)務(wù)二)普通的操作用戶user就可以訪問(wèn)
          • 我們希望管理員可以訪問(wèn)包括syslog(日志管理)和sysuser(用戶管理)在內(nèi)的所有資源

          以上就是本文介紹formLogin模式需要進(jìn)行的準(zhǔn)備工作及需求,下面我們就來(lái)實(shí)現(xiàn)其中的核心的登錄驗(yàn)證邏輯,準(zhǔn)備工作非常簡(jiǎn)單請(qǐng)自行實(shí)現(xiàn)。(新建spring boot應(yīng)用,登錄頁(yè)面、首頁(yè)、四個(gè)業(yè)務(wù)頁(yè)面都寫(xiě)成非常簡(jiǎn)單的html即可,不用寫(xiě)實(shí)際業(yè)務(wù)和樣式。)

          二、說(shuō)明

          formLogin模式的三要素:

          • 登錄驗(yàn)證邏輯
          • 資源訪問(wèn)控制規(guī)則,如:資源權(quán)限、角色權(quán)限
          • 用戶信息

          一般來(lái)說(shuō),使用權(quán)限認(rèn)證框架的的業(yè)務(wù)系統(tǒng)登錄驗(yàn)證邏輯是固定的,而資源訪問(wèn)控制規(guī)則和用戶信息是從數(shù)據(jù)庫(kù)或其他存儲(chǔ)介質(zhì)靈活加載的。但本文所有的用戶、資源、權(quán)限信息都是代碼配置寫(xiě)死的,旨在為大家介紹formLogin認(rèn)證模式,如何從數(shù)據(jù)庫(kù)加載權(quán)限認(rèn)證相關(guān)信息我還會(huì)結(jié)合RBAC權(quán)限模型再寫(xiě)文章的。

          三、實(shí)現(xiàn)formLogin模式基礎(chǔ)配置

          首先,我們要繼承WebSecurityConfigurerAdapter ,重寫(xiě)configure(HttpSecurity http) 方法,該方法用來(lái)配置登錄驗(yàn)證邏輯。請(qǐng)注意看下文代碼中的注釋信息。

          實(shí)現(xiàn)formLogin模式基礎(chǔ)配置(點(diǎn)擊可放大)

          上面的代碼分為兩部分:

          • 第一部分是formLogin配置段,用于配置登錄驗(yàn)證邏輯相關(guān)的信息。如:登錄頁(yè)面、登錄成功頁(yè)面、登錄請(qǐng)求處理路徑等。
          • 第二部分是authorizeRequests配置端,用于配置資源的訪問(wèn)權(quán)限。如:開(kāi)發(fā)登錄頁(yè)面的permitAll開(kāi)放訪問(wèn),“/biz1”(業(yè)務(wù)一頁(yè)面資源)需要有資源ID為"biz1"的用戶才可以訪問(wèn)。

          這時(shí)候,我們通過(guò)瀏覽器訪問(wèn),隨便測(cè)試一個(gè)沒(méi)有訪問(wèn)權(quán)限的資源,都會(huì)跳轉(zhuǎn)到login.html頁(yè)面。

          四、實(shí)現(xiàn)資源訪問(wèn)限制的需求

          在上文中,我們配置了登錄驗(yàn)證及資源訪問(wèn)的權(quán)限規(guī)則,我們還沒(méi)有具體的用戶,下面我們就來(lái)配置具體的用戶。重寫(xiě)WebSecurityConfigurerAdapter的 configure(AuthenticationManagerBuilder auth)方法

          實(shí)現(xiàn)資源訪問(wèn)限制的需求(點(diǎn)擊可放大)

          • inMemoryAuthentication指的是在內(nèi)存里面存儲(chǔ)用戶的身份認(rèn)證和授權(quán)信息。
          • withUser("user")用戶名是user
          • password(passwordEncoder().encode("123456"))密碼是加密之后的123456
          • authorities("biz1","biz2")指的是user用戶擁有資源ID為biz1(業(yè)務(wù)一)和biz2(業(yè)務(wù)二)資源的權(quán)限

          這樣,我們就實(shí)現(xiàn)了文首提出的普通用戶只能訪問(wèn)biz1(業(yè)務(wù)一)和biz2(業(yè)務(wù)二)資源的需求。那么管理員用戶可以訪問(wèn)所有的資源的配置方式,你會(huì)不會(huì)呢?同樣的配方、同樣的方式、自己可以嘗試一下哦!

          五、靜態(tài)資源訪問(wèn)

          在我們的實(shí)際開(kāi)發(fā)中,登錄頁(yè)面login.html和控制層Controller登錄驗(yàn)證'/login'都必須無(wú)條件的開(kāi)放。除此之外,一些靜態(tài)資源如css、js文件通常也都不需要驗(yàn)證權(quán)限,我們需要將它們的訪問(wèn)權(quán)限也開(kāi)放出來(lái)。下面就是實(shí)現(xiàn)的方法:重寫(xiě)WebSecurityConfigurerAdapter類(lèi)的configure(WebSecurity web) 方法

          靜態(tài)資源訪問(wèn)(點(diǎn)擊可放大)


          主站蜘蛛池模板: 日韩AV片无码一区二区不卡| 亚洲av无码成人影院一区 | 日韩精品一区二区三区影院| 麻豆果冻传媒2021精品传媒一区下载| 日本不卡免费新一区二区三区| 久久国产免费一区| 2022年亚洲午夜一区二区福利| 怡红院美国分院一区二区 | 亚洲综合无码AV一区二区| 亚洲av午夜福利精品一区人妖| 2022年亚洲午夜一区二区福利| 精品国产日韩一区三区| 在线观看免费视频一区| 激情内射亚洲一区二区三区| 国产伦精品一区二区| 久久久综合亚洲色一区二区三区 | 国产精品一区二区久久国产| 亚洲午夜在线一区| 精品久久久久一区二区三区| 国产精品一区视频| 国产一区二区三区精品视频| 日韩人妻一区二区三区免费 | 鲁丝丝国产一区二区| 风间由美性色一区二区三区| 亚洲AV无码一区二区三区网址 | 蜜桃视频一区二区三区| 亚洲一区二区三区在线观看蜜桃| 人妻无码一区二区三区四区| 亚洲Av高清一区二区三区| 熟女精品视频一区二区三区| 国产一区三区二区中文在线| 久久久国产一区二区三区 | 一区二区三区在线播放| 麻豆果冻传媒2021精品传媒一区下载| 日韩人妻无码一区二区三区久久99 | 国产在线一区观看| 国产精品成人99一区无码| 国内精品视频一区二区八戒| 老熟妇高潮一区二区三区| 国产精品成人免费一区二区| 日韩一区二区超清视频|