单点登录性能测试方案
项目登录系统升级,改为单点登录:英文全称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种软件测试人员常用的高效技能-下
大咖风采
点击查看公众号地图
- 网页前端页面加载性能测试各工具可行性方案分析
- 性能测试方案之性能测试术语解释
- 基于spark排序的一种更廉价的实现方案-附基于spark的性能测试
- 自动化测试与持续集成方案--Jmeter 测试接口及性能
- 性能测试方案
- tair ldb存储引擎性能测试方案
- 基于spark排序的一种更廉价的实现方案-附基于spark的性能测试
- 后台性能测试--性能测试方案设计
- 转:jmeter性能测试---登录百度进行搜索
- 全面测试嵌套多层For循环的性能和优化方案
- 性能测试方案之性能测试方法
- 基于spark排序的一种更廉价的实现方案-附基于spark的性能测试
- 性能测试方案
- 使用cookie绕过登录进行性能测试
- 数据库性能测试方案示例
- 源码解读腾讯 GT 的性能测试方案
- QQ登录的测试点(功能性测试、登录界面、性能、安全性、可用性、兼容性)...
- 基于spark排序的一种更廉价的实现方案-附基于spark的性能测试
- web前端性能意义、关注重点、测试方案、优化技巧
- 银行中间业务平台的几种性能测试方案