上一次进行了Linux下一键部署区块链网络WeBASE(FISCO BCOS),并做了一下简单的使用测试,现需要通过登录进行token获取的测试,通过测试后就可以提供成接口给第三方应用使用了。
如图,需要通过程序进行登录并获取登录后的token.
这里的验证码被我设置成固定的8888了,因此不需要进行图片识别。降低了难度
系统先通过http://192.168.56.101:5000/mgr/WeBASE-Node-Manager/account/pictureCheckCode进行验证码的获取,结果截图如下
响应结果
{"code":0,"message":"success","data":{"base64Image":"iVBORw0KGgoAAAANSUhEUgAAAJsAAAA8CAIAAAD+Gl+NAAADd0lEQVR42u3csXHdMAwGYE7hBXKXIk02yATpM0kKe51M4QU8T8p0SuE7n08kwR8/AJLSg05NHD1KfN8jCYKSyt9/R+532suR2722FE3R3FI0t/uL/nl7xfdE2leUQ0rdTUW9SJJ2vWiEQbouFr1o4Smaoimaot4Th+tFRjma1oor56MZ8UZXfH2GYcKM9k6cW4yj355/N/deHwKmkH79/NrcjVf78vTc3I3F/vjyvblfTLRn2XTFt56l0bVnaXTtWXKuQ9SylpNARTgJVISTQEU4CVTBtczhlKVpTlma5pSlaU5Z2hjufhiTosPQBjEjXBEzwhUxI1wRM6Nr7VK4jw179hSttZqBHnKManmxGDl7f3xYUUHLq43K0sXO2fwvZLC0izbB7KJNsPqAYQNCBkvHXpfJAlpE5fjIEugaOXuBrnAAmOUYxrS+g6i6jQ7roEL1CndPcl7h7gnVK9w9yd1BNOejLskjnSgY+3ihZs7IkTNcdOhquXRfTsQ1Iq9Lc/Yi3vVtlHCN6HXxjjeijQqu2llpoCg+jtojIzsqPo7aI6MhquWG1nmiXj1w08yO2jSzozbNBFT76m+UqKxFi8patKisRYvK/WpQiBQiSqQAEVQiBYigEilABBXpUSNQl4ke+mU10ClC9NAvq9VOzR71VqLazBHuFCGqzRzVAyTXOW+RM7q0qFBNl153F1GaE+91tZOW3kTNwilk4S2cgtOMyMi48AKKagNdcHTUBrq90fHkquJshjxzOENED/1SGiJaNz7kViNkqi7Ese8fVMVEHyfSLqXtIqpN5GotP2NYskUCqle2aLjcvSxTj1/0+z+bjcAl81cbGDN/FlQ5OdcsebLlwd05dqrV55oM7zZSpYeEftKS8NN2v712iafOJ1j6PJsG/k5dSvatfESBE14LAp6i+H4v9jpMeErJ/YsmGIi9LtNhHAXrsDPnhboTOU70iYwO/d1G2341F+JUdeDFvQ5EPZc8D3qVAUJ7lvKYnPuL0oUX93OoLmXh09o7i1pKLu7neBzRoOv3vyslOfecCO0uuvztGBuiugwEJTnnh6MRl6R42tDdaZN310Sk6JZzpuguZbr8GqBMPfEGqUcWpRur5S3Sime8k3MaqirdL38kRWNdwWW13v0hvc8y7zNKziBd4ZKQXwC/9pKic9quankcOeY/IMqe2xLtt4oAAAAASUVORK5CYII=","token":"86cb45a78f3f001b814891728628c2d1534c666943273d319380f494d39d8f33"},"attachment":null}
然后通过http://192.168.56.101:5000/mgr/WeBASE-Node-Manager/account/login?checkCode=8888进行登陆的调用
可以看到在登陆成功的响应包里有对应的token信息
通过以上分析发现,主要是通过2个url的http请求获取并执行,是否可以执行采用http请求方式直接拿到,理论上是可行的,试了很多次,没有成功,可能2次登陆用的session不一致导致。这个后续有时间继续完成,没理由直接采用http请求做不到
,因时间较紧,想到了采用Java使用selenium模拟登录
的方式,先解决问题。验证了下是可行的,记录下来备用,后续再找时间研究http的直接调用方式,以下为Java使用selenium模拟登录并获取token的详细过程
。
####http直接调用的方式目前也已解决,见文章最后
因为写出来的程序大部分时间是要在linux下进行的,因此我直接采用linux系统centos7下的方式。
selenium需要模拟打开浏览器进行操作,因此先要安装浏览器
如果有外网访问,直接采用以下的方式,在/etc/yum.repos.d/下增加镜像源
cat > /etc/yum.repos.d/google-chrome.repo << EOF
[google-chrome]
name=google-chrome
baseurl=http://dl.google.com/linux/chrome/rpm/stable/$basearch
enabled=1
gpgcheck=1
gpgkey=https://dl-ssl.google.com/linux/linux_signing_key.pub
EOF
如果是采用nexus3做私库,则先建一个私库的proxy,然后修改google-chrome.repo
cat > /etc/yum.repos.d/google-chrome.repo << EOF
[google-chrome]
name=google-chrome
baseurl=http://192.168.56.1:8081//nexus3/repository/google-chrome/linux/chrome/rpm/stable/x86_64
enabled=1
gpgcheck=0
EOF
yum -y install google-chrome-stable --nogpgcheck
安装完查看版本
[root@localhost ~]# google-chrome --version
Google Chrome 92.0.4515.107
因为selenium是依赖chrome浏览器的环境,通过程序打开chrome,需要GNOME 界面的支持才能完成,因此如果centos7是最小化安装的,也需要安装相应的GNOME组件。
安装GNOME较简单,执行yum安装即可,需要耐心等待,最小安装的centos7要更新更安装1000多个rpm包。
yum groupinstall -y "GNOME Desktop"
下载地址:https://chromedriver.storage.googleapis.com/index.html
找到与Google Chrome 92.0.4515.107版本相对应的chromedriver进行下载
下载后放置在linux的目录下,我放在/usr/local/bin/,因此驱动的绝对路径为/usr/local/bin/chromedriver
/usr/local/bin/chromedriver需要有可执行权限,默认放上去的没有,因此执行以下命令进行赋权
chmod +x /usr/local/bin/chromedriver
##selenium
<!-- selenium start -->
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>3.141.59</version>
</dependency>
<!--selenium end-->
##日志相关
<!-- log start -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<!-- log end -->
package cn.gzsendi;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.html5.LocalStorage;
import org.openqa.selenium.html5.WebStorage;
import org.openqa.selenium.remote.Augmenter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SeleniumLoginTest {
private static final Logger logger = LoggerFactory.getLogger(SeleniumLoginTest.class);
public static void main(String[] args) {
WebDriver webDriver = null;
try {
// 设置 chromedirver 的存放位置
System.getProperties().setProperty("webdriver.chrome.driver", "/usr/local/bin/chromedriver");
ChromeOptions chromeOptions = new ChromeOptions();
chromeOptions.addArguments("--no-sandbox");
chromeOptions.addArguments("--disable-dev-shm-usage");
chromeOptions.addArguments("blink-settings=imagesEnabled=false");
chromeOptions.addArguments("--disable-gpu");
//使用后台打开chrome的方式
chromeOptions.addArguments("--headless");
webDriver = new ChromeDriver(chromeOptions);
//1.模拟打开登陆页面
String url = "http://192.168.56.101:5000/#/login";
logger.info("open login page,url is {}",url);
webDriver.get(url);
//2.等3秒钟响应后再操作,不然内容可能还没有返回
Thread.sleep(3000l);
logger.info("login page title is : {}", webDriver.getTitle());
//3.找到账号的输入框,并模拟输入账号
WebElement accountInput = webDriver.findElement(By.xpath("/html/body/div/div/div[2]/div[3]/form/div[1]/div/div[1]/input"));
accountInput.sendKeys("admin");
logger.info("now sendKeys to accountInput");
Thread.sleep(1000l);
//4.找到密码的输入框,并模拟输入密码
WebElement passwordInput = webDriver.findElement(By.xpath("/html/body/div/div/div[2]/div[3]/form/div[2]/div/div[1]/input"));
passwordInput.sendKeys("Sd123456");
logger.info("now sendKeys to passwordInput");
Thread.sleep(1000l);
//5.找到验证码的输入框,并模拟验证码
WebElement codeInput = webDriver.findElement(By.xpath("/html/body/div/div/div[2]/div[3]/form/div[3]/div/div[1]/div/input"));
codeInput.sendKeys("8888");
logger.info("now sendKeys to checkCodeInput");
Thread.sleep(1000l);
//6.找到登陆的按钮,并模拟点击登陆
WebElement loginButton = webDriver.findElement(By.xpath("/html/body/div/div/div[2]/div[4]/button"));
loginButton.click();
logger.info("now click the loginButton");
Thread.sleep(3000l);
//7.登陆后,通过localStorage获取token信息
WebStorage webStorage = (WebStorage) new Augmenter().augment(webDriver);
LocalStorage localStorage = webStorage.getLocalStorage();
logger.info("login success, get login result , token is : " + localStorage.getItem("token"));
} catch (Exception e) {
logger.error("",e);
} finally {
if(webDriver != null) webDriver.close();
}
}
}
备注说明:上面代码中的xpath如何获取?如果你不熟悉语法,可以直接通过浏览器的右键查看功能获取,以账号框为例,先选中账号的输入框,然后右键检查,在弹出的窗口里面copy full xpath即可
cd /root/seleniumlogintest
java -cp "./lib/*:./seleniumlogintest.jar" cn.gzsendi.SeleniumLoginTest
可看到能正常执行登陆并拿到相应的token,代码运行正常
拿到token后,可以进行相应的接口分析,然后进行接口调用,比如,通过手工登陆页面后,进行用户管理的访问,并使用F12分析,可得知此接口的url为http://192.168.56.101:5000/mgr/WeBASE-Node-Manager/account/accountList/1/10?account=,并且为Get请求
同时分析到发请求的同时需要带上AuthorizationToken的参数,如图如示
:
AuthorizationToken
尝试(值通过前面的Java程序执行获取),可看到接口调用成功,我较习惯使用curl工具,安装了git后自带的,不用再装postman等插件了,你如果使用postman,jmeter,httpclient等,按这些工具的使用方式进行即可。
curl -X GET -H 'AuthorizationToken:Token 02f2f63b045e5f88d80fa9c6b6f273dc74d6c18dfa8b739665e7e47c36372605' -H 'Content-Type:application/json;charset=UTF-8' "http://192.168.56.101:5001/WeBASE-Node-Manager/account/accountList/1/10?account="
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 290 0 290 0 0 6590 0 --:--:-- --:--:-- --:--:-- 6744{"code":0,"message":"success","data":[{"account":"admin","accountPwd":null,"roleId":100000,"roleName":"admin","roleNameZh":"管理员","loginFailTime":0,"accountStatus":2,"description":null,"createTime":"2021-07-27 11:46:16","modifyTime":"2021-07-27 11:49:35","email":null}],"totalCount":1}
如下为http直接调用的方式测试用的Java类代码
,里面用了一个JsonUtil的工具类,你可以改成你自己常用的Json解决工具类进行解析即可。
package liujhtest;
import cn.gzsendi.system.utils.JsonUtil;
import org.apache.http.Consts;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class LoginTest {
public static void main(String[] args) throws IOException {
// 直接创建client
CloseableHttpClient client = HttpClients.createDefault();
HttpGet httpGet = new HttpGet("http://192.168.56.101:5000/mgr/WeBASE-Node-Manager/account/pictureCheckCode");
HttpResponse httpResponse1 = client.execute(httpGet);
//执行验证码的http请求访问,并解析出来返回的登陆用的token
HttpEntity entity = httpResponse1.getEntity();
String result = entity != null ? EntityUtils.toString(entity, Consts.UTF_8) : null;
String token = ((Map<String,Object>) JsonUtil.castToObject(result).get("data")).get("token").toString();
System.out.println("获取登陆接口需要用的token: " + token);
// login地址
String loginUrl = "http://192.168.56.101:5000/mgr/WeBASE-Node-Manager/account/login?checkCode=8888";
HttpPost httppost = new HttpPost(loginUrl);
//拿验证码接口中的token进行登陆(此token仅用于登陆)
httppost.addHeader("token",token);
List<NameValuePair> formparams = new ArrayList<NameValuePair>();
formparams.add(new BasicNameValuePair("account", "admin"));
formparams.add(new BasicNameValuePair("accountPwd", "df67246b2ea4aa0e5bf169d7102961096cc7ff70d7e905075d0c2d6847ac6b99"));
UrlEncodedFormEntity entity2 = new UrlEncodedFormEntity(formparams, Consts.UTF_8);
httppost.setEntity(entity2);
// Create a custom response handler
ResponseHandler<String> responseHandler = new ResponseHandler<String>() {
@Override
public String handleResponse(
final HttpResponse response) throws ClientProtocolException, IOException {
int status = response.getStatusLine().getStatusCode();
if (status >= 200 && status < 300) {
HttpEntity entity = response.getEntity();
return entity != null ? EntityUtils.toString(entity, Consts.UTF_8) : null;
} else {
throw new ClientProtocolException("Unexpected response status: " + status);
}
}
};
//进行登陆,然后获取登陆成功响应结果,里面有token的信息。
String responseBody = client.execute(httppost, responseHandler);
System.out.println("登陆成功,结果为: " + responseBody);
}
}
JsonUtil的工具类代码如下
package cn.gzsendi.system.utils;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
public class JsonUtil {
private final static Logger logger = LoggerFactory.getLogger(JsonUtil.class);
//日期格式化
private static final String STANDARD_FORMAT = "yyyy-MM-dd HH:mm:ss";
private static ObjectMapper objectMapper;
static{
/**
* ObjectobjectMapper是JSON操作的核心,Jackson的所有JSON操作都是在ObjectobjectMapper中实现。
* ObjectobjectMapper有多个JSON序列化的方法,可以把JSON字符串保存File、OutputStream等不同的介质中。
* writeValue(File arg0, Object arg1)把arg1转成json序列,并保存到arg0文件中。
* writeValue(OutputStream arg0, Object arg1)把arg1转成json序列,并保存到arg0输出流中。
* writeValueAsBytes(Object arg0)把arg0转成json序列,并把结果输出成字节数组。
* writeValueAsString(Object arg0)把arg0转成json序列,并把结果输出成字符串。
*/
objectMapper = new ObjectMapper();
//对象的所有字段全部列入
objectMapper.setSerializationInclusion(JsonInclude.Include.ALWAYS);
//取消默认转换timestamps形式
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS,false);
//忽略空Bean转json的错误
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS,false);
//所有的日期格式都统一为以下的样式,即yyyy-MM-dd HH:mm:ss
objectMapper.setDateFormat(new SimpleDateFormat(STANDARD_FORMAT));
//忽略 在json字符串中存在,但是在java对象中不存在对应属性的情况。防止错误
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,false);
objectMapper.setTimeZone(TimeZone.getTimeZone("GMT+8"));
objectMapper.setVisibility(PropertyAccessor.ALL, Visibility.ANY);
}
/**
* 对象转Json格式字符串
* @param obj 对象
* @return Json格式字符串
*/
public static String toJSONString(Object o) {
if (o == null) {
return null;
}
if (o instanceof String)
return (String) o;
String jsonValue = null;
try {
jsonValue = objectMapper.writeValueAsString(o);
} catch (JsonProcessingException e) {
logger.error("Parse Object to String error",e);
}
return jsonValue;
}
@SuppressWarnings("unchecked")
public static Map<String,Object> castToObject(String str){
if(str == null || "".equals(str) ){
return null;
}
try {
return objectMapper.readValue(str, Map.class);
} catch (Exception e) {
logger.error("Parse String to Object error:", e);
return null;
}
}
/**
* 字符串转换为自定义对象
* @param str 要转换的字符串
* @param clazz 自定义对象的class对象
* @return 自定义对象
*/
@SuppressWarnings("unchecked")
public static <T> T castToObject(String str, Class<T> clazz){
if(str == null || "".equals(str) || clazz == null){
return null;
}
try {
return clazz.equals(String.class) ? (T) str : objectMapper.readValue(str, clazz);
} catch (Exception e) {
logger.error("Parse String to Object error:", e);
return null;
}
}
@SuppressWarnings("unchecked")
public static <T> T castToObject(String str, TypeReference<T> typeReference) {
if (str == null || "".equals(str) || typeReference == null) {
return null;
}
try {
return (T) (typeReference.getType().equals(String.class) ? str : objectMapper.readValue(str, typeReference));
} catch (IOException e) {
logger.error("Parse String to Object error:", e);
return null;
}
}
public static <T> T castToObject(String str, Class<?> collectionClazz, Class<?>... elementClazzes) {
JavaType javaType = objectMapper.getTypeFactory().constructParametricType(collectionClazz, elementClazzes);
try {
return objectMapper.readValue(str, javaType);
} catch (IOException e) {
logger.error("Parse String to Object error : ", e.getMessage());
return null;
}
}
public static String getString(Map<String,Object> jsonObject, String fieldName){
return jsonObject.get(fieldName)==null ? null : (String)jsonObject.get(fieldName);
}
public static Integer getInteger(Map<String,Object> jsonObject, String fieldName){
return jsonObject.get(fieldName)==null ? null : (Integer)jsonObject.get(fieldName);
}
@SuppressWarnings("unchecked")
public static <T> List<T> getList(Map<String,Object> jsonObject, String fieldName,Class<T> clazz){
return jsonObject.get(fieldName)==null ? null : (List<T>)jsonObject.get(fieldName);
}
@SuppressWarnings("unchecked")
public static <T> T[] getArray(Map<String,Object> jsonObject, String fieldName,Class<T> clazz){
return jsonObject.get(fieldName)==null ? null : (T[])jsonObject.get(fieldName);
}
}
已将源码demo例子放在github,地址为:
https://github.com/jxlhljh/seleniumlogintest.git