1. 程式人生 > 實用技巧 >軟體測試實踐乾貨 | 測試登入功能的思路與原理解析(基於 Spring Security)

軟體測試實踐乾貨 | 測試登入功能的思路與原理解析(基於 Spring Security)

本文為霍格沃茲測試學院優秀學員測試開發學習筆記,進階學習文末加群。

登入功能對軟體測試工程師可能是最常見卻是最重要,也是最容易被忽視的測試場景。本文整理一些經驗豐富的測試工程師總結的測試用例,並結合 Java Spring Security 框架來簡單說下登入的測試方向思路和部分原理,供大家交流探討。

登入測試方向

功能測試(基礎)

  1. 輸入已註冊的使用者名稱和正確的密碼,驗證是否登入成功;
  2. 輸入已註冊的使用者名稱和不正確的密碼,驗證是否登入失敗,並且提示資訊正確;
  3. 輸入未註冊的使用者名稱和任意密碼,驗證是否登入失敗,並且提示資訊正確;
  4. 使用者名稱和密碼兩者都為空,驗證是否登入失敗,並且提示資訊正確;
  5. 使用者名稱和密碼兩者之一為空,驗證是否登入失敗,並且提示資訊正確;
  6. 如果登入功能啟用了驗證碼功能,在使用者名稱和密碼正確的前提下,輸入正確的驗證碼,驗證是否登入成功;
  7. 如果登入功能啟用了驗證碼功能,在使用者名稱和密碼正確的前提下,輸入錯誤的驗證碼,驗證是否登入失敗,並且提示資訊正確。

功能測試(深入)

1.使用者名稱和密碼是否大小寫敏感;
2.頁面上的密碼框是否加密顯示;
3.後臺系統建立的使用者第一次登入成功時,是否提示修改密碼;
4.忘記使用者名稱和忘記密碼的功能是否可用;
5.前端頁面是否根據設計要求限制使用者名稱和密碼長度;
6.如果登入功能需要驗證碼,點選驗證碼圖片是否可以更換驗證碼,更換後的驗證碼是否可用;
7.重新整理頁面是否會重新整理驗證碼;
8.如果驗證碼具有時效性,需要分別驗證時效內和時效外驗證碼的有效性;
9.使用者登入成功但是會話超時後,繼續操作是否會重定向到使用者登入介面;
10.不同級別的使用者,比如管理員使用者和普通使用者,登入系統後的許可權是否正確;
11.頁面預設焦點是否定位在使用者名稱的輸入框中;
12.快捷鍵 Tab 和 Enter 等,是否可以正常使用。

安全測試

1.使用者密碼後臺儲存是否加密;
2.使用者密碼在網路傳輸過程中是否加密;
3.密碼是否具有有效期,密碼有效期到期後,是否提示需要修改密碼;
4.不登入的情況下,在瀏覽器中直接輸入登入後的 URL 地址,驗證是否會重新定向到使用者登入介面;
5.密碼輸入框是否不支援複製和貼上;
6.密碼輸入框內輸入的密碼是否都可以在頁面原始碼模式下被檢視;
7.使用者名稱和密碼的輸入框中分別輸入典型的“SQL 注入攻擊”字串,驗證系統的返回頁面;
8.使用者名稱和密碼的輸入框中分別輸入典型的“XSS 跨站指令碼攻擊”字串,驗證系統行為是否被篡改;
9.連續多次登入失敗情況下,系統是否會阻止後續的嘗試以應對暴力破解;
10.同一使用者在同一終端的多種瀏覽器上登入,驗證登入功能的互斥性是否符合設計預期;
11.同一使用者先後在多臺終端的瀏覽器上登入,驗證登入是否具有互斥性。

效能壓力測試

1.單使用者登入的響應時間是否小於 3 秒;
2.單使用者登入時,後臺請求數量是否過多;
3.高併發場景下使用者登入的響應時間是否小於 5 秒;
4.高併發場景下服務端的監控指標是否符合預期;
5.高集合點併發場景下,是否存在資源死鎖和不合理的資源等待;
6.長時間大量使用者連續登入和登出,伺服器端是否存在記憶體洩漏。

相容性測試

1.不同瀏覽器下,驗證登入頁面的顯示以及功能正確性;
2.相同瀏覽器的不同版本下,驗證登入頁面的顯示以及功能正確性;
3.不同移動裝置終端的不同瀏覽器下,驗證登入頁面的顯示以及功能正確性;
4.不同解析度的介面下,驗證登入頁面的顯示以及功能正確性。

Spring Security簡介

Spring Security 是一個能夠為基於 Spring 的企業應用系統提供宣告式的安全訪問控制解決方案的安全框架。它提供了一組可以在 Spring 應用上下文中配置的 Bean,充分利用了 Spring IoC,DI(控制反轉 Inversion of Control ,DI:Dependency Injection 依賴注入)和 AOP(面向切面程式設計)功能,為應用系統提供宣告式的安全訪問控制功能,減少了為企業系統安全控制編寫大量重複程式碼的工作。

Java Web工程——登入

配置檔案

1、在 Maven 工程的 Pom.xml 檔案中新增 Spring Security 的依賴

   <dependency>
      <groupId>org.springframework.security</groupId>
      <artifactId>spring-security-web</artifactId>
      <version>4.1.0.RELEASE</version>
  </dependency>
  <dependency>
      <groupId>org.springframework.security</groupId>
      <artifactId>spring-security-config</artifactId>
      <version>4.1.0.RELEASE</version>
  </dependency>

2、配置 web.xml 檔案

     <context-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:spring-security.xml</param-value>
   </context-param>
   <listener>
      <listener-class>
          org.springframework.web.context.ContextLoaderListener
      </listener-class>
   </listener>    
   <filter>  
      <filter-name>springSecurityFilterChain</filter-name>         
      <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>  
   </filter>  
   <filter-mapping>  
      <filter-name>springSecurityFilterChain</filter-name>  
      <url-pattern>/*</url-pattern>  
   </filter-mapping>

3、建立 index.html
4、建立 spring 配置檔案 spring-security.xml

    <!-- 以下頁面不被攔截 -->
    <http pattern="/login.html" security="none"></http>
    <http pattern="/css/**" security="none"></http>
    <http pattern="/img/**" security="none"></http>
    <http pattern="/js/**" security="none"></http>
    <http pattern="/plugins/**" security="none"></http>

    <!-- 頁面攔截規則 -->
    <http use-expressions="false">
        <intercept-url pattern="/*" access="ROLE_ADMIN" />
        <form-login login-page="/login.html"  default-target-url="/admin/index.html" authentication-failure-url="/login.html" always-use-default-target="true"/>    
        <csrf disabled="true"/>
        <headers>
            <frame-options policy="SAMEORIGIN"/>
        </headers>
    </http>

    <beans:bean id="bcryptEncoder"  
    class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" />

    <!-- 認證管理器 -->
<authentication-manager>
    <authentication-provider user-service-ref="userDetailService">          
    </authentication-provider>  
</authentication-manager>
<beans:bean id="userDetailService"
   class="com.pinyougou.service.UserDetailServiceImpl">
 </beans:bean>     

測試點提取:

測試點(1)

這裡設定了不被攔截的頁面,就表示在所有這些頁面的訪問過程中使不需要攜帶登入資訊的,直接輸入URL即可;所以在測試的過程中要注意頁面的區分,分別測試。

參考測試用例:不登入的情況下,在瀏覽器中直接輸入登入後的 URL 地址,驗證是否會重新定向到使用者登入介面;

測試點(2)

這隻設定了使用者登入的許可權攔截規則,規定了登入後跳轉的頁面

參考測試用例:不同級別的使用者,比如管理員使用者和普通使用者,登入系統後的許可權是否正確

測試點(3)

這裡使用了 Spring Security 的一個認證類,使用認證類呼叫服務UserDetails,對登入的使用者進行認證校驗,判斷使用者是否為合法登入使用者;結合後端程式碼來看:

  public class UserDetailsServiceImpl implements UserDetailsService {
      @Override
      public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
          List<GrantedAuthority> grantedAuths = new ArrayList<GrantedAuthority>();  
          grantedAuths.add(new SimpleGrantedAuthority("ROLE_SELLER"));          
          return new User(username,"123456", grantedAuths);
      }
  }

如果按照上述的寫法和配置,則使用者在輸入密碼123456時就會通過,無論使用者名稱為什麼,在開發階段可能為了某些功能的方便測試驗證而使用這種寫法,為防止測試環境或生產環境的程式碼忘記修改,此場景也需要測試。(具體的通用密碼、賬號或驗證碼之類的可諮詢相關開發人員)

參考測試用例:參考上述功能測試用例

若後端程式碼和配置這樣寫:

/**
 * 認證類
 * @author Administrator
 *
 */
public class UserDetailsServiceImpl implements UserDetailsService {
    private SellerService sellerService;
    public void setSellerService(SellerService sellerService) {
        this.sellerService = sellerService;
    }
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        System.out.println("經過了UserDetailsServiceImpl");
        //構建角色列表
        List<GrantedAuthority> grantAuths=new ArrayList();
        grantAuths.add(new SimpleGrantedAuthority("ROLE_SELLER"));
        //得到商家物件
        TbSeller seller = sellerService.findOne(username);
        if(seller!=null){
            if(seller.getStatus().equals("1")){
                return new User(username,seller.getPassword(),grantAuths);
            }else{
                return null;
            }           
        }else{
            return null;
        }
    }
}

修改spring-security.xml ,新增如下配置

  <!-- 引用dubbo 服務 -->    
    <dubbo:application name="shop-web" />
    <dubbo:registry address="zookeeper://192.168.25.129:2181"/>
    <dubbo:reference id="sellerService"  interface="com.pinyougou.sellergoods.service.SellerService" >
    </dubbo:reference>
    <beans:bean id="userDetailService" class="com.pinyougou.service.UserDetailsServiceImpl">
        <beans:property name="sellerService" ref="sellerService"></bean:property>
    </beans:bean>

經過上述修改後,在登陸頁輸入使用者名稱和密碼與資料庫一致即可登陸

參考測試用例:參考上述功能測試用例

密碼加密

使用者表的密碼通常使用 MD5 等不可逆演算法加密後儲存,為防止彩虹表破解更會先使用一個特定的字串(如域名)加密,然後再使用一個隨機的 salt(鹽值)加密。特定字串是程式程式碼中固定的,salt 是每個密碼單獨隨機,一般給使用者表加一個欄位單獨儲存,比較麻煩。BCrypt 演算法將 salt 隨機並混入最終加密後的密碼,驗證時也無需單獨提供之前的 salt,從而無需單獨處理 salt 問題。

我們在日常測試中除了要關注功能外,還要關注軟體的安全性,可能我們很多人並不是專業的安全測試工程師,但是一般的測試點還是要保證覆蓋的

後端部分程式碼和配置檔案:

@RequestMapping("/add")
public Result add(@RequestBody TbSeller seller){
    //密碼加密
    BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
    String password = passwordEncoder.encode(seller.getPassword());
    seller.setPassword(password);

spring-security.xml ,新增如下配置

  <beans:bean id="bcryptEncoder"  
            class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" />

修改認證管理器的配置

<!-- 認證管理器 -->
<authentication-manager alias="authenticationManager">  
    <authentication-provider user-service-ref='userDetailService'>   
          <password-encoder ref="bcryptEncoder"></password-encoder>           
    </authentication-provider>  
</authentication-manager> 

這裡我們可以看一下密碼在資料庫的顯示結果:

我們可以看到很明顯的區別,未加密的密碼直接暴露,會帶來賬戶安全隱患;而使用MD5和BCrypt加密的密碼要更為安全;理論上MD5也是不可逆的密碼,無法被破解,但是因為MD5在相同的密碼下生成的加密字串是固定的,所以在大資料技術下可以建立資料庫將常用密碼進行一一對應儲存的方法來進行破解;相對比BCrypt加鹽的方式,BCrypt加密就更為安全的多了。

參考測試用例:參考上述安全測試

最後,準備好登入介面 login.html。

以上,本文主要用作自己的工作總結,有不對和不足的地方請大家多指正。

更多技術文章分享及測試資料點此獲取