Java使用selenium模拟登录并获取token

Java使用selenium模拟登录并获取token

  • 需求背景
  • 登陆流程分析
  • 第一次尝试与思考
  • 采用Java使用selenium模拟登录
    • centos7安装Chrome浏览器
    • centos7安装GNOME 界面
    • 准备linux下的chromedriver驱动
    • 编写Java程序完成登陆和获取token的逻辑
    • 执行Java程序进行测试获取token
    • 拿到token后,就可以进行后续的一些接口调用测试
  • http直接调用的方式也解决,如下
  • 本文selenium测试demo源码下载


需求背景

上一次进行了Linux下一键部署区块链网络WeBASE(FISCO BCOS),并做了一下简单的使用测试,现需要通过登录进行token获取的测试,通过测试后就可以提供成接口给第三方应用使用了。

如图,需要通过程序进行登录并获取登录后的token.Java使用selenium模拟登录并获取token_第1张图片
这里的验证码被我设置成固定的8888了,因此不需要进行图片识别。降低了难度

登陆流程分析

  1. ##先通过手工输入账号密码和验证码,然后登陆,按浏览器f12查看请求数据包,得出以下结论:

系统先通过http://192.168.56.101:5000/mgr/WeBASE-Node-Manager/account/pictureCheckCode进行验证码的获取,结果截图如下
Java使用selenium模拟登录并获取token_第2张图片

响应结果

{"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信息
Java使用selenium模拟登录并获取token_第3张图片
Java使用selenium模拟登录并获取token_第4张图片

对应浏览器里面的localStorage里面也存储了相应的token
Java使用selenium模拟登录并获取token_第5张图片

第一次尝试与思考

通过以上分析发现,主要是通过2个url的http请求获取并执行,是否可以执行采用http请求方式直接拿到,理论上是可行的,试了很多次,没有成功,可能2次登陆用的session不一致导致。这个后续有时间继续完成,没理由直接采用http请求做不到,因时间较紧,想到了采用Java使用selenium模拟登录的方式,先解决问题。验证了下是可行的,记录下来备用,后续再找时间研究http的直接调用方式,以下为Java使用selenium模拟登录并获取token的详细过程

####http直接调用的方式目前也已解决,见文章最后

采用Java使用selenium模拟登录

因为写出来的程序大部分时间是要在linux下进行的,因此我直接采用linux系统centos7下的方式。

centos7安装Chrome浏览器

selenium需要模拟打开浏览器进行操作,因此先要安装浏览器

  1. ##镜像yum准备

如果有外网访问,直接采用以下的方式,在/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

Java使用selenium模拟登录并获取token_第6张图片
Java使用selenium模拟登录并获取token_第7张图片

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
  1. ##安装google chrome浏览器
yum -y install google-chrome-stable --nogpgcheck

安装完查看版本

[root@localhost ~]# google-chrome --version
Google Chrome 92.0.4515.107 

centos7安装GNOME 界面

因为selenium是依赖chrome浏览器的环境,通过程序打开chrome,需要GNOME 界面的支持才能完成,因此如果centos7是最小化安装的,也需要安装相应的GNOME组件。
安装GNOME较简单,执行yum安装即可,需要耐心等待,最小安装的centos7要更新更安装1000多个rpm包。

yum groupinstall -y "GNOME Desktop"

准备linux下的chromedriver驱动

下载地址:https://chromedriver.storage.googleapis.com/index.html
找到与Google Chrome 92.0.4515.107版本相对应的chromedriver进行下载
下载后放置在linux的目录下,我放在/usr/local/bin/,因此驱动的绝对路径为/usr/local/bin/chromedriver
Java使用selenium模拟登录并获取token_第8张图片
Java使用selenium模拟登录并获取token_第9张图片

/usr/local/bin/chromedriver需要有可执行权限,默认放上去的没有,因此执行以下命令进行赋权

chmod +x /usr/local/bin/chromedriver

编写Java程序完成登陆和获取token的逻辑

  1. ##引入maven相关

##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 -->
  1. ##编写程序测试,完整的Java类如下,整个工程源码附文章最后:
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即可
Java使用selenium模拟登录并获取token_第10张图片
Java使用selenium模拟登录并获取token_第11张图片

执行Java程序进行测试获取token

代码编写完后进行相应的编绎部署,后再运行,以下为执行命令后程序的运行结果
Java使用selenium模拟登录并获取token_第12张图片

cd /root/seleniumlogintest
java -cp "./lib/*:./seleniumlogintest.jar" cn.gzsendi.SeleniumLoginTest

##结果日志如下:Java使用selenium模拟登录并获取token_第13张图片

可看到能正常执行登陆并拿到相应的token,代码运行正常

拿到token后,就可以进行后续的一些接口调用测试

拿到token后,可以进行相应的接口分析,然后进行接口调用,比如,通过手工登陆页面后,进行用户管理的访问,并使用F12分析,可得知此接口的url为http://192.168.56.101:5000/mgr/WeBASE-Node-Manager/account/accountList/1/10?account=,并且为Get请求
Java使用selenium模拟登录并获取token_第14张图片
同时分析到发请求的同时需要带上AuthorizationToken的参数,如图如示
Java使用selenium模拟登录并获取token_第15张图片

  1. ##直接发Get请求先试一下,因为没有token,肯定会提示错误
    在这里插入图片描述
  2. ##接下来采用curl工具带上header参数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直接调用的方式也解决,如下

如下为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);
    }

}

本文selenium测试demo源码下载

已将源码demo例子放在github,地址为:
https://github.com/jxlhljh/seleniumlogintest.git

你可能感兴趣的:(【Java】,java,selenium,linux,centos)