項目登陸系統升級,改成單點登陸:英文全稱Single Sign On。SSO是在多個應用系統中,用戶只須要登陸一次就能夠訪問全部相互信任的應用系統。 以前有的統一登陸方式被廢棄,因爲單點登陸比較以前的登陸系統複雜不少。以前的方案請求一個接口便可得到用戶校驗令牌。 先分享一下單點登陸的技術方案的時序圖:前端
而後發一下我梳理的前端調用接口的時序圖:java
性能測試分紅了兩個場景: 性能壓測場景分析: 跳過沒必要要的302響應狀態請求,只測試業務邏輯相關接口,不處理頁面相關接口(資源文件等),登陸完成請求額外接口完成登陸驗證。python
場景一:單個用戶登陸單個系統。 第一步:請求cas服務login頁面,解析頁面獲取祕鑰串(lt/execution) 第二步:請求cas服務登陸接口,獲取TGC令牌和ST令牌 第三步:請求svr服務校驗ST令牌,獲取admin_jsessionid信息 第四步:請求額外接口完成登陸狀態驗證web
場景二:單個用戶登陸兩個系統 第一步:請求cas服務login頁面,解析頁面獲取祕鑰串(lt/execution) 第二步:請求cas服務登陸接口,獲取TGC令牌和ST1令牌 第三步:請求svr1服務校驗ST1令牌,獲取admin_jsessionid信息 第四步:請求額外接口完成登svr1錄狀態驗證 第五步:請求cas服務登陸接口(攜帶TGC令牌),獲取svr2對應的ST2令牌 第六步:請求svr2服務校驗校驗ST2令牌,獲取admin_jsessionid信息 第七步:請求額外接口完成svr2登陸狀態校驗apache
針對這兩個場景,測試腳本以下:編程
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() } }
首先各個項目用戶對象代碼以下:json
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(); } }
統一驗證類的代碼以下:cookie
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寫的。session