项目登录系统升级,改为单点登录:英文全称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登录状态校验
针对这两个场景,测试脚本如下:
1import com.fun.base.constaint.ThreadBase
2import com.fun.config.SqlConstant
3import com.fun.frame.excute.Concurrent
4import com.fun.utils.Time
5import com.okayqa.teacherweb.base.OkayBase
6import org.slf4j.Logger
7import org.slf4j.LoggerFactory
8
9class Tss extends OkayBase {
10 private static Logger logger = LoggerFactory.getLogger(Tss.class)
11
12
13 public static void main(String[] args) {
14 def threadNum = changeStringToInt(args[0])
15 def times = changeStringToInt(args[1])
16 SqlConstant.flag = false
17
18
19// def threadNum = 3
20// def times = 2
21 def arrayList = new ArrayList<ThreadBase>()
22 for (int i = 0; i < threadNum; i++) {
23 def thread = new ThreadBase<Integer>(new Integer(i)) {
24 @Override
25 protected void before() {
26
27 }
28
29 @Override
30 protected void doing() throws Exception {
31 def mark = Time.getTimeStamp()
32 def base = getBase(changeStringToInt(getT()))
33// def cookies = base.getCookies()
34// def base1 = new com.okayqa.publicweb.base.OkayBase() //创建public-web项目的用户对象
35// base1.init(cookies)//初始化用户对象
36// def common = new SchoolCommon(base1)//创建学校公共接口请求对象
37// def years = common.getYears()//请求学校学年接口
38 def mark0 = Time.getTimeStamp()
39 def i1 = mark0 - mark
40 logger.error("----------------" + i1 + EMPTY)
41 }
42
43 @Override
44 protected void after() {
45
46 }
47 }
48
49 thread.setTimes(times)
50 arrayList << thread
51 }
52 new Concurrent(arrayList).start()
53//
54 allOver()
55
56 }
57}
首先各个项目用户对象代码如下:
1package com.okayqa.teacherweb.base;
2
3import com.fun.base.bean.BeanUtil;
4import com.fun.base.bean.RequestInfo;
5import com.fun.base.interfaces.IBase;
6import com.fun.config.HttpClientConstant;
7import com.fun.config.SqlConstant;
8import com.fun.config.SysInit;
9import com.fun.frame.SourceCode;
10import com.fun.frame.httpclient.FanLibrary;
11import com.okayqa.common.CasCredential;
12import com.okayqa.common.Common;
13import com.okayqa.common.Users;
14import com.okayqa.teacherweb.bean.UserInfoBean;
15import com.okayqa.teacherweb.function.UserCenter;
16import com.okayqa.teacherweb.profile.Profile;
17import com.okayqa.teacherweb.profile.UserApi;
18import net.sf.json.JSONObject;
19import org.apache.http.client.methods.HttpGet;
20import org.apache.http.client.methods.HttpPost;
21import org.apache.http.client.methods.HttpRequestBase;
22import org.slf4j.Logger;
23import org.slf4j.LoggerFactory;
24
25import java.io.File;
26
27/**
28 * 教师空间项目
29 * qa项目base类
30 */
31public class OkayBase extends SourceCode implements IBase {
32
33 private static Logger logger = LoggerFactory.getLogger(OkayBase.class);
34
35 private static OkayBase base;
36
37 static {
38 SqlConstant.REQUEST_TABLE = Common.SQL_REQUEST;
39 SqlConstant.flag = Common.SQL_KEY;
40 SqlConstant.PERFORMANCE_TABLE = Common.SQL_PERFORMANCE;
41 if (FanLibrary.getiBase() == null) FanLibrary.setiBase(new OkayBase());
42 }
43
44 public final static String HOST = Profile.HOST;
45
46
47 /**
48 * 登录响应
49 */
50 JSONObject loginResponse;
51
52 private UserInfoBean userInfoBean = new UserInfoBean();
53
54 /**
55 * 获取对象方法
56 * <p>
57 * 暂未进行用户管理,同意使用单例
58 * </p>
59 *
60 * @return
61 */
62 public static OkayBase getBase() {
63 if (base == null) base = new OkayBase(0);
64 return base;
65 }
66
67 public static OkayBase getBase(int i) {
68 return new OkayBase(i);
69 }
70
71 public static OkayBase getBase(String name) {
72 return new OkayBase(name);
73 }
74
75 long uid;
76
77 String token;
78
79 String username;
80
81 public JSONObject getCookies() {
82 return cookies;
83 }
84
85 public void setCookies(JSONObject cookies) {
86 this.cookies = cookies;
87 }
88
89 public void addCookie(JSONObject cookies) {
90 this.cookies.putAll(cookies);
91 }
92
93 JSONObject cookies = new JSONObject();
94
95
96 @Override
97 public void login() {
98// /**
99// * 单点登录方式
100 String url = UserApi.LOGIN;
101 JSONObject params = new JSONObject();
102 params.put("loginType", "1");
103 params.put("platformType", "teacher");
104 params.put("username", username);
105 params.put("password", getPassword());
106 params.put("pictureVerifyCode", "");
107 params.put("phone", "");
108 params.put("traceno", "");
109 params.put("phoneVerifyCode", "");
110 JSONObject tgc = CasCredential.getTGC(HOST, params);
111 this.cookies = tgc.getJSONObject("cookie");
112 String location = tgc.containsKey("location") ? tgc.getString("location") : EMPTY;
113 if (!location.contains("ticket=ST-")) logger.error("登录失败!");
114 JSONObject getResponse = this.getGetResponse(location.replace(HOST, EMPTY));
115 UserCenter userCenter = new UserCenter(this.cookies);
116 userInfoBean = userCenter.getUserinfo();
117 logger.info("账号:{},昵称:{},学科名称:{},登录成功!", username,userInfoBean.getName(),userInfoBean.getSubjectName());
118 }
119
120 /**
121 * 获取到明文的默认密码
122 *
123 * @return
124 */
125 public String getPassword() {
126 return Profile.PWD;
127 }
128
129 public OkayBase(String username) {
130 this.username = username;
131 login();
132 }
133
134 public OkayBase(int i) {
135 this.username = Users.getTeaUser(i);
136 login();
137 }
138
139 protected OkayBase() {
140 }
141
142 public OkayBase(OkayBase okayBase) {
143 this.uid = okayBase.uid;
144 this.username = okayBase.username;
145 this.token = okayBase.token;
146 this.userInfoBean = okayBase.userInfoBean;
147 this.cookies = okayBase.cookies;
148 }
149
150 public JSONObject getParams() {
151 return getJson("_=" + getMark());
152 }
153
154
155 @Override
156 public void init(JSONObject jsonObject) {
157 addCookie(jsonObject);
158 HttpGet get = FanLibrary.getHttpGet(Profile.LOGIN_REDIRECT);
159 get.addHeader(FanLibrary.getCookies(jsonObject));
160 JSONObject response = FanLibrary.getHttpResponse(get);
161 JSONObject credential = CasCredential.verifyST(response.getString("location"));
162 addCookie(credential);
163 }
164
165 public JSONObject getLoginResponse() {
166 return loginResponse;
167 }
168
169 public long getUid() {
170 return uid;
171 }
172
173 public String getToken() {
174 return token;
175 }
176
177 public String getUname() {
178 return username;
179 }
180
181 public UserInfoBean getUserInfoBean() {
182 return userInfoBean;
183 }
184
185 @Override
186 public HttpGet getGet(String s) {
187 return FanLibrary.getHttpGet(HOST + s);
188 }
189
190 @Override
191 public HttpGet getGet(String s, JSONObject jsonObject) {
192 return FanLibrary.getHttpGet(HOST + s, jsonObject);
193 }
194
195 @Override
196 public HttpPost getPost(String s) {
197 return FanLibrary.getHttpPost(HOST + s);
198 }
199
200 @Override
201 public HttpPost getPost(String s, JSONObject jsonObject) {
202 return FanLibrary.getHttpPost(HOST + s, jsonObject);
203 }
204
205 @Override
206 public HttpPost getPost(String s, JSONObject jsonObject, File file) {
207 return FanLibrary.getHttpPost(HOST + s, jsonObject, file);
208 }
209
210 @Override
211 public JSONObject getResponse(HttpRequestBase httpRequestBase) {
212 setHeaders(httpRequestBase);
213 JSONObject response = FanLibrary.getHttpResponse(httpRequestBase);
214 handleResponseHeader(response);
215 return response;
216 }
217
218 @Override
219 public void setHeaders(HttpRequestBase httpRequestBase) {
220 httpRequestBase.addHeader(Common.REQUEST_ID);
221 this.addCookie(getJson("user_phone_check_" + this.username + "=true"));
222 if (!cookies.isEmpty()) httpRequestBase.addHeader(FanLibrary.getCookies(cookies));
223 }
224
225 @Override
226 public void handleResponseHeader(JSONObject response) {
227 if (!response.containsKey(HttpClientConstant.COOKIE)) return;
228 cookies.putAll(response.getJSONObject(HttpClientConstant.COOKIE));
229 response.remove(HttpClientConstant.COOKIE);
230 }
231
232 @Override
233 public JSONObject getGetResponse(String s) {
234 return getResponse(getGet(s));
235 }
236
237 @Override
238 public JSONObject getGetResponse(String s, JSONObject jsonObject) {
239 return getResponse(getGet(s, jsonObject));
240 }
241
242 @Override
243 public JSONObject getPostResponse(String s) {
244 return getResponse(getPost(s));
245 }
246
247 @Override
248 public JSONObject getPostResponse(String s, JSONObject jsonObject) {
249 return getResponse(getPost(s, jsonObject));
250 }
251
252 @Override
253 public JSONObject getPostResponse(String s, JSONObject jsonObject, File file) {
254 return getResponse(getPost(s, jsonObject, file));
255 }
256
257 @Override
258 public boolean isRight(JSONObject jsonObject) {
259 if (jsonObject.containsKey("success")) return jsonObject.getBoolean("success");
260 int code = checkCode(jsonObject, new RequestInfo(getGet(HOST)));
261 try {
262 JSONObject data = jsonObject.getJSONObject("data");
263 return code == 0 && !data.isEmpty();
264 } catch (Exception e) {
265 output(jsonObject);
266 return false;
267 }
268
269 }
270
271 /**
272 * 获取并检查code
273 *
274 * @param jsonObject
275 * @return
276 */
277 public int checkCode(JSONObject jsonObject, RequestInfo requestInfo) {
278 int code = TEST_ERROR_CODE;
279 if (SysInit.isBlack(requestInfo.getHost())) return code;
280 try {
281 code = jsonObject.getInt("code");
282 } catch (Exception e) {
283 logger.warn("非标准响应:{}", jsonObject.toString());
284 }
285 return code;
286 }
287
288 /**
289 * 测试结束,资源释放
290 */
291 public static void allOver() {
292 FanLibrary.testOver();
293 }
294
295}
统一验证类的代码如下:
1package com.okayqa.common
2
3import com.fun.config.HttpClientConstant
4import com.fun.frame.httpclient.FanLibrary
5import com.fun.utils.Regex
6import net.sf.json.JSONObject
7import org.apache.http.client.methods.HttpGet
8import org.slf4j.Logger
9import org.slf4j.LoggerFactory
10
11/**
12 * cas服务验证类,主要解决web端登录验证功能
13 */
14class CasCredential extends FanLibrary {
15 static final String OR="/"
16 private static Logger logger = LoggerFactory.getLogger(CasCredential.class)
17 /**
18 * 校验值,随机一次性,从login返回页面中获取
19 */
20 String lt
21 /**
22 * 校验值,随机一次性,从login返回页面中获取,正常值长度在4000+,低于4000请检查请求连接是否传入了回调服务的地址
23 */
24 String execution
25
26/**
27 * 从cas服务的login页面获取到令牌对,此处正则暂时可用,二期会修改表单提交
28 */
29 CasCredential(String host) {
30 def get = getHttpGet(Common.CAS_LOGIN + (host.endsWith(OR) ? host : host + OR))
31 get.addHeader(Common.REQUEST_ID)
32 def response = getHttpResponse(get)
33 def string = response.getString("content")
34 this.lt = Regex.getRegex(string, "<input type=\"hidden\" name=\"lt\" value=\".*?\" />")
35 this.execution = Regex.getRegex(string, " <input type=\"hidden\" name=\"execution\" value=\".*?\" />")
36// logger.info("cas服务登录host:{},lt:{},execution:{}", host, lt, execution)
37 }
38
39/**
40 * 各个服务端参数一致,由各个服务自己把参数拼好之后传过来,之后在去cas服务拿到令牌对
41 * @param host 服务的host地址,回调由各个服务自己完成,二次验证也是,此处的host不做兼容,有cascredential做处理
42 * @param params 拼好的参数
43 * @return
44 */
45 static JSONObject getTGC(String host, JSONObject params) {
46 def credential = new CasCredential(host)
47 params.put("lt", credential.getLt());
48 params.put("execution", credential.getExecution())
49 params.put("_eventId", "submit");
50 def post = FanLibrary.getHttpPost(Common.CAS_LOGIN + (host.endsWith(OR) ? host : host + OR), params)
51 post.addHeader(Common.REQUEST_ID);
52 FanLibrary.getHttpResponse(post)
53 }
54
55/**
56 * 通过用户
57 * @param url
58 * @return
59 */
60 public static JSONObject verifyST(String url) {
61 HttpGet location = FanLibrary.getHttpGet(url);
62 location.addHeader(Common.REQUEST_ID);
63 JSONObject httpResponse = FanLibrary.getHttpResponse(location);
64 httpResponse.getJSONObject(HttpClientConstant.COOKIE) as JSONObject
65 }
66}
然后顺利完工。因为之前性能测试方案都是使用jmeter作为解决方案,这次架构变更的测试用例难以实现,故才用了脚本。性能框架才用了之前发过的性能测试框架有兴趣的可以点击查看一下,语言以Java为主,脚本使用Groovy写的。