單點登入效能測試方案
專案登入系統升級,改為單點登入:英文全稱Single Sign On。SSO是在多個應用系統中,使用者只需要登入一次就可以訪問所有相互信任的應用系統。 之前有的統一登入方式被廢棄,由於單點登入比較之前的登入系統複雜很多。之前的方案請求一個介面即可獲得使用者校驗令牌。 先分享一下單點登入的技術方案的時序圖:
然後發一下我梳理的前端呼叫介面的時序圖:
效能測試分成了兩個場景: 效能壓測場景分析: 跳過不必要的302響應狀態請求,只測試業務邏輯相關介面,不處理頁面相關介面(資原始檔等),登入完成請求額外介面完成登入驗證。
-
場景一:單個使用者登入單個系統。 第一步:請求cas服務login頁面,解析頁面獲取祕鑰串(lt/execution) 第二步:請求cas服務登入介面,獲取TGC令牌和ST令牌 第三步:請求svr服務校驗ST令牌,獲取admin_jsessionid資訊 第四步:請求額外介面完成登入狀態驗證
-
場景二:單個使用者登入兩個系統 第一步:請求cas服務login頁面,解析頁面獲取祕鑰串(lt/execution) 第二步:請求cas服務登入介面,獲取TGC令牌和ST1令牌 第三步:請求svr1服務校驗ST1令牌,獲取admin_jsessionid資訊 第四步:請求額外介面完成登svr1錄狀態驗證 第五步:請求cas服務登入介面(攜帶TGC令牌),獲取svr2對應的ST2令牌 第六步:請求svr2服務校驗校驗ST2令牌,獲取admin_jsessionid資訊 第七步:請求額外介面完成svr2登入狀態校驗
針對這兩個場景,測試指令碼如下:
import com.fun.base.constaint.ThreadBase import com.fun.config.SqlConstant import com.fun.frame.excute.Concurrent import com.fun.utils.Time import com.okayqa.teacherweb.base.OkayBase import org.slf4j.Logger import org.slf4j.LoggerFactory class Tss extends OkayBase { private static Logger logger = LoggerFactory.getLogger(Tss.class) public static void main(String[] args) { def threadNum = changeStringToInt(args[0]) def times = changeStringToInt(args[1]) SqlConstant.flag = false // def threadNum = 3 // def times = 2 def arrayList = new ArrayList<ThreadBase>() for (int i = 0; i < threadNum; i++) { def thread = new ThreadBase<Integer>(new Integer(i)) { @Override protected void before() { } @Override protected void doing() throws Exception { def mark = Time.getTimeStamp() def base = getBase(changeStringToInt(getT())) // def cookies = base.getCookies() // def base1 = new com.okayqa.publicweb.base.OkayBase() //建立public-web專案的使用者物件 // base1.init(cookies)//初始化使用者物件 // def common = new SchoolCommon(base1)//建立學校公共介面請求物件 // def years = common.getYears()//請求學校學年介面 def mark0 = Time.getTimeStamp() def i1 = mark0 - mark logger.error("----------------" + i1 + EMPTY) } @Override protected void after() { } } thread.setTimes(times) arrayList << thread } new Concurrent(arrayList).start() // allOver() } }
首先各個專案使用者物件程式碼如下:
package com.okayqa.teacherweb.base; import com.fun.base.bean.BeanUtil; import com.fun.base.bean.RequestInfo; import com.fun.base.interfaces.IBase; import com.fun.config.HttpClientConstant; import com.fun.config.SqlConstant; import com.fun.config.SysInit; import com.fun.frame.SourceCode; import com.fun.frame.httpclient.FanLibrary; import com.okayqa.common.CasCredential; import com.okayqa.common.Common; import com.okayqa.common.Users; import com.okayqa.teacherweb.bean.UserInfoBean; import com.okayqa.teacherweb.function.UserCenter; import com.okayqa.teacherweb.profile.Profile; import com.okayqa.teacherweb.profile.UserApi; import net.sf.json.JSONObject; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpRequestBase; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; /** * 教師空間專案 * qa專案base類 */ public class OkayBase extends SourceCode implements IBase { private static Logger logger = LoggerFactory.getLogger(OkayBase.class); private static OkayBase base; static { SqlConstant.REQUEST_TABLE = Common.SQL_REQUEST; SqlConstant.flag = Common.SQL_KEY; SqlConstant.PERFORMANCE_TABLE = Common.SQL_PERFORMANCE; if (FanLibrary.getiBase() == null) FanLibrary.setiBase(new OkayBase()); } public final static String HOST = Profile.HOST; /** * 登入響應 */ JSONObject loginResponse; private UserInfoBean userInfoBean = new UserInfoBean(); /** * 獲取物件方法 * <p> * 暫未進行使用者管理,同意使用單例 * </p> * * @return */ public static OkayBase getBase() { if (base == null) base = new OkayBase(0); return base; } public static OkayBase getBase(int i) { return new OkayBase(i); } public static OkayBase getBase(String name) { return new OkayBase(name); } long uid; String token; String username; public JSONObject getCookies() { return cookies; } public void setCookies(JSONObject cookies) { this.cookies = cookies; } public void addCookie(JSONObject cookies) { this.cookies.putAll(cookies); } JSONObject cookies = new JSONObject(); @Override public void login() { // /** // * 單點登入方式 String url = UserApi.LOGIN; JSONObject params = new JSONObject(); params.put("loginType", "1"); params.put("platformType", "teacher"); params.put("username", username); params.put("password", getPassword()); params.put("pictureVerifyCode", ""); params.put("phone", ""); params.put("traceno", ""); params.put("phoneVerifyCode", ""); JSONObject tgc = CasCredential.getTGC(HOST, params); this.cookies = tgc.getJSONObject("cookie"); String location = tgc.containsKey("location") ? tgc.getString("location") : EMPTY; if (!location.contains("ticket=ST-")) logger.error("登入失敗!"); JSONObject getResponse = this.getGetResponse(location.replace(HOST, EMPTY)); UserCenter userCenter = new UserCenter(this.cookies); userInfoBean = userCenter.getUserinfo(); logger.info("賬號:{},暱稱:{},學科名稱:{},登入成功!", username,userInfoBean.getName(),userInfoBean.getSubjectName()); } /** * 獲取到明文的預設密碼 * * @return */ public String getPassword() { return Profile.PWD; } public OkayBase(String username) { this.username = username; login(); } public OkayBase(int i) { this.username = Users.getTeaUser(i); login(); } protected OkayBase() { } public OkayBase(OkayBase okayBase) { this.uid = okayBase.uid; this.username = okayBase.username; this.token = okayBase.token; this.userInfoBean = okayBase.userInfoBean; this.cookies = okayBase.cookies; } public JSONObject getParams() { return getJson("_=" + getMark()); } @Override public void init(JSONObject jsonObject) { addCookie(jsonObject); HttpGet get = FanLibrary.getHttpGet(Profile.LOGIN_REDIRECT); get.addHeader(FanLibrary.getCookies(jsonObject)); JSONObject response = FanLibrary.getHttpResponse(get); JSONObject credential = CasCredential.verifyST(response.getString("location")); addCookie(credential); } public JSONObject getLoginResponse() { return loginResponse; } public long getUid() { return uid; } public String getToken() { return token; } public String getUname() { return username; } public UserInfoBean getUserInfoBean() { return userInfoBean; } @Override public HttpGet getGet(String s) { return FanLibrary.getHttpGet(HOST + s); } @Override public HttpGet getGet(String s, JSONObject jsonObject) { return FanLibrary.getHttpGet(HOST + s, jsonObject); } @Override public HttpPost getPost(String s) { return FanLibrary.getHttpPost(HOST + s); } @Override public HttpPost getPost(String s, JSONObject jsonObject) { return FanLibrary.getHttpPost(HOST + s, jsonObject); } @Override public HttpPost getPost(String s, JSONObject jsonObject, File file) { return FanLibrary.getHttpPost(HOST + s, jsonObject, file); } @Override public JSONObject getResponse(HttpRequestBase httpRequestBase) { setHeaders(httpRequestBase); JSONObject response = FanLibrary.getHttpResponse(httpRequestBase); handleResponseHeader(response); return response; } @Override public void setHeaders(HttpRequestBase httpRequestBase) { httpRequestBase.addHeader(Common.REQUEST_ID); this.addCookie(getJson("user_phone_check_" + this.username + "=true")); if (!cookies.isEmpty()) httpRequestBase.addHeader(FanLibrary.getCookies(cookies)); } @Override public void handleResponseHeader(JSONObject response) { if (!response.containsKey(HttpClientConstant.COOKIE)) return; cookies.putAll(response.getJSONObject(HttpClientConstant.COOKIE)); response.remove(HttpClientConstant.COOKIE); } @Override public JSONObject getGetResponse(String s) { return getResponse(getGet(s)); } @Override public JSONObject getGetResponse(String s, JSONObject jsonObject) { return getResponse(getGet(s, jsonObject)); } @Override public JSONObject getPostResponse(String s) { return getResponse(getPost(s)); } @Override public JSONObject getPostResponse(String s, JSONObject jsonObject) { return getResponse(getPost(s, jsonObject)); } @Override public JSONObject getPostResponse(String s, JSONObject jsonObject, File file) { return getResponse(getPost(s, jsonObject, file)); } @Override public boolean isRight(JSONObject jsonObject) { if (jsonObject.containsKey("success")) return jsonObject.getBoolean("success"); int code = checkCode(jsonObject, new RequestInfo(getGet(HOST))); try { JSONObject data = jsonObject.getJSONObject("data"); return code == 0 && !data.isEmpty(); } catch (Exception e) { output(jsonObject); return false; } } /** * 獲取並檢查code * * @param jsonObject * @return */ public int checkCode(JSONObject jsonObject, RequestInfo requestInfo) { int code = TEST_ERROR_CODE; if (SysInit.isBlack(requestInfo.getHost())) return code; try { code = jsonObject.getInt("code"); } catch (Exception e) { logger.warn("非標準響應:{}", jsonObject.toString()); } return code; } /** * 測試結束,資源釋放 */ public static void allOver() { FanLibrary.testOver(); } }
統一驗證類的程式碼如下:
package com.okayqa.common
import com.fun.config.HttpClientConstant
import com.fun.frame.httpclient.FanLibrary
import com.fun.utils.Regex
import net.sf.json.JSONObject
import org.apache.http.client.methods.HttpGet
import org.slf4j.Logger
import org.slf4j.LoggerFactory
/**
* cas服務驗證類,主要解決web端登入驗證功能
*/
class CasCredential extends FanLibrary {
static final String OR="/"
private static Logger logger = LoggerFactory.getLogger(CasCredential.class)
/**
* 校驗值,隨機一次性,從login返回頁面中獲取
*/
String lt
/**
* 校驗值,隨機一次性,從login返回頁面中獲取,正常值長度在4000+,低於4000請檢查請求連線是否傳入了回撥服務的地址
*/
String execution
/**
* 從cas服務的login頁面獲取到令牌對,此處正則暫時可用,二期會修改表單提交
*/
CasCredential(String host) {
def get = getHttpGet(Common.CAS_LOGIN + (host.endsWith(OR) ? host : host + OR))
get.addHeader(Common.REQUEST_ID)
def response = getHttpResponse(get)
def string = response.getString("content")
this.lt = Regex.getRegex(string, "<input type=\"hidden\" name=\"lt\" value=\".*?\" />")
this.execution = Regex.getRegex(string, " <input type=\"hidden\" name=\"execution\" value=\".*?\" />")
// logger.info("cas服務登入host:{},lt:{},execution:{}", host, lt, execution)
}
/**
* 各個服務端引數一致,由各個服務自己把引數拼好之後傳過來,之後在去cas服務拿到令牌對
* @param host 服務的host地址,回撥由各個服務自己完成,二次驗證也是,此處的host不做相容,有cascredential做處理
* @param params 拼好的引數
* @return
*/
static JSONObject getTGC(String host, JSONObject params) {
def credential = new CasCredential(host)
params.put("lt", credential.getLt());
params.put("execution", credential.getExecution())
params.put("_eventId", "submit");
def post = FanLibrary.getHttpPost(Common.CAS_LOGIN + (host.endsWith(OR) ? host : host + OR), params)
post.addHeader(Common.REQUEST_ID);
FanLibrary.getHttpResponse(post)
}
/**
* 通過使用者
* @param url
* @return
*/
public static JSONObject verifyST(String url) {
HttpGet location = FanLibrary.getHttpGet(url);
location.addHeader(Common.REQUEST_ID);
JSONObject httpResponse = FanLibrary.getHttpResponse(location);
httpResponse.getJSONObject(HttpClientConstant.COOKIE) as JSONObject
}
}
然後順利完工。因為之前效能測試方案都是使用jmeter作為解決方案,這次架構變更的測試用例難以實現,故才用了指令碼。效能框架才用了之前發過的效能測試框架有興趣的可以點選檢視一下,語言以Java為主,指令碼使用Groovy寫的。
技術類文章精選
- java一行程式碼列印心形
- Linux效能監控軟體netdata中文漢化版
- 介面測試程式碼覆蓋率(jacoco)方案分享
- 效能測試框架
- 如何在Linux命令列介面愉快進行效能測試
- 圖解HTTP腦圖
- 如何測試概率型業務介面
- httpclient處理多使用者同時線上
- 將swagger文件自動變成測試程式碼
- 五行程式碼構建靜態部落格
- httpclient如何處理302重定向
- 基於java的直線型介面測試框架初探
- Tcloud 雲測平臺--集大成者
- 如何測試概率型業務介面
- python plotly處理介面效能測試資料方法封裝
- 單點登入效能測試方案
非技術文章精選
- 為什麼選擇軟體測試作為職業道路?
- 成為傑出Java開發人員的10個步驟
- 寫給所有人的程式設計思維
- 自動化測試的障礙
- 自動化測試的問題所在
- 測試之《程式碼不朽》腦圖
- 成為優秀自動化測試工程師的7個步驟
- 優秀軟體開發人員的態度
- 如何正確執行功能API測試
- 未來10年軟體測試的新趨勢-上
- 未來10年軟體測試的新趨勢-上
- 自動化測試解決了什麼問題
- 17種軟體測試人員常用的高效技能-上
- 17種軟體測試人員常用的高效技能-下