项目登录系统升级,改为单点登录:英文全称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写的。