项目登陆系统升级,改成单点登陆:英文全称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