JSON WEB TOKEN(JWT)详解以及JAVA项目实战

1 你听没听说过Selenium?

1.1 自动化测试

提到Selenium,便离不开自动化测试。

自动化测试,就是把手工进行的测试过程,转变成机器自动执行的测试过程。

自动化测试有如下优点

  • 对程序的回归测试更方便。 这可能是自动化测试最主要的任务,特别是在程序修改比较频繁时,效果是非常明显的。 ...
  • 可以运行更多更繁琐的测试。 ...
  • 可以执行一些手工测试困难或不可能进行的测试。 ...
  • 更好地利用资源。 ...
  • 测试具有一致性和可重复性。 ...
  • 测试的复用性。 ...
  • 增加软件信任度。

1.2 Selenium

因为对自动化测试卓越体验的追求,众多自动化测试工具应运而生,Selenium就是其中最出色的一款。

Selenium 是一个用于Web应用程序测试的工具。他是一款浏览器仿真程序 可以像真正的用户在操作一样操作浏览器。

Selenium支持全部主流的浏览器,支持主流的编程语言,包括:Java、Python、C#、PHP、Ruby、JavaScript等,基于标准的 WebDriver 语法规范,
同时支持所有基于web 的管理任务自动化。

Selenium由多个软件工具组成。每个工具都有一个特定的角色。主要包含以下工具:

  • Selenium IDE Selenium IDE(集成开发环境)是一个构建测试脚本的原型工具
  • Selenium RC 是Selenium的远程控制(又称Selenium1.0)
  • Selenium Grid 可以测试集分布在多个环境中并行运行测试用例。

2 java中集成Selenium

Selenium支持主流的编程语言,包括:Java、Python、C#、PHP、Ruby、JavaScript;

Q:为什么选择java 而不是 python?

A:Python是简洁高效的脚本语言,有时间我再出一篇Python版本的。

2.1 maven添加依赖

在java中使用Selenium很简单,你只需要添加如下依赖:

<dependency>
    <groupId>org.seleniumhq.selenium</groupId>
    <artifactId>selenium-java</artifactId>
    <version>3.141.59</version>
</dependency>
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>23.0</version>
</dependency>
<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>2.8.2</version>
</dependency>

2.2 添加浏览器驱动

当selenium升级到3.0之后,对不同的浏览器驱动进行了规范。如果想使用selenium驱动不同的浏览器,必须单独下载并设置不同的浏览器驱动。

在java中使用不同浏览器:
首先配置驱动属性,指定驱动文件路径

System.setProperty("webdriver.chrome.driver", "Q:\\chromedriver.exe");

获取WebDriver并打开一个新的浏览器窗口

WebDriver driver = new ChromeDriver();    //Chrome浏览器
WebDriver driver = new FirefoxDriver();   //Firefox浏览器
WebDriver driver = new EdgeDriver();      //Edge浏览器
WebDriver driver = new InternetExplorerDriver();  // Internet Explorer浏览器
WebDriver driver = new OperaDriver();     //Opera浏览器
WebDriver driver = new PhantomJSDriver();   //PhantomJS

注:可以在linux中使用无窗口模式,后续会讲到

简单样例

public class Itest {
    public static void main(String[] args) {
        System.setProperty("webdriver.chrome.driver", "Q:\\chromedriver.exe");
        WebDriver driver = new ChromeDriver();
        driver.get("http://www.ytooo.org");
  
        Thread.sleep(10000);
        driver.close();
    }
}

2.2 selenium元素定位

2.2.1 定位元素
  • findElement(By.id()) driver.findElement(By.id("kw"))

  • findElement(By.name()) driver.findElement(By.name("wd"))

  • findElement(By.className()) driver.findElement(By.className("s_ipt"))

  • findElement(By.tagName()) driver.findElement(By.tagName("input"))

  • findElement(By.linkText())

<a class="mnav" href="http://news.baidu.com" name="tj_trnews">新闻</a>
<a class="mnav" href="http://www.hao123.com" name="tj_trhao123">hao123</a>
driver.findElement(By.linkText("新闻")

driver.findElement(By.linkText("hao123")
  • findElement(By.partialLinkText())

driver.findElement(By.partialLinkText("新")

  • findElement(By.xpath()) driver.findElement(By.xpath("//*[@id='kw']"))
  • findElement(By.cssSelector()) driver.findElement(By.cssSelector("html > body > form > span > input")
2.2.2 获取元素列表
    driver.findElements(By.cssSelector(".for.list td"));

获取到的元素列表为 List<WebElement> 对象,不建议直接循环来获取 元素对象,而是从根中重新获取,以避免获取元素失败

List<WebElement> heads = driver.findElements(By.cssSelector(".for.list td"));

for (int i = 0; i < heads.size(); i++) {
    String href = driver.findElements(By.cssSelector(".for.list td")).get(i).getText();
}
2.2.3 下拉框选择
WebElement el = driver.findElement(By.xpath("//select"));
Select sel = new Select(el);
sel.selectByValue("20");

2.3 设置元素等待

WebDriver提供了两种类型的等待:显式等待和隐式等待。

2.3.1 显式等待

显式等待, 针对某个元素等待

WebDriverWait wait = new WebDriverWait(driver,10,1);
wait.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector(".for.list")));
2.3.1 显式等待

隐式等待, 针对某个元素等待

driver.manage().timeouts().pageLoadTimeout(5, TimeUnit.SECONDS);

2.4 WebElement常用方法* clear() 清除文本。

  • sendKeys(*value) 模拟按键输入。
  • click() 单击元素
driver.findElement(By.id("username")).sendKeys("用户名");
driver.findElement(By.id("password"))sendKeys("密码");
driver.findElement(By.id("commit")).click;

2.5 键鼠操作

2.5.1 Actions 键鼠操作
  • contextClick() 右击
  • clickAndHold() 鼠标点击并控制
  • doubleClick() 双击
  • dragAndDrop() 拖动
  • release() 释放鼠标
  • perform() 执行所有Actions中存储的行为
// 新建一个action   
Actions action = new Actions(driver);   
// 鼠标左键单击
action.click().perform();
// 鼠标左键双击
action.doubleClick(WebElement).perform();
// 鼠标左键按下
action.clickAndHold(WebElement).perform();
// 鼠标移动到元素
action.moveToElement(WebElement).perform();
// 元素右键点击
action.contextClick(WebElement).perform();
// 将目标元素拖拽到指定的元素上
action.dragAndDrop(webElement1,webElement2);
action.dragAndDrop(webElement, xOffset, yOffset);

Actions action = new Actions(driver);
action.keyDown(Keys.CONTROL);//按下control键
action.keyUp(Keys.CONTROL);//松开control键
action.keyDown(Keys.CONTROL).keyDown(Keys.ALT).keyDown("A").keyUp(Keys.CONTROL).keyUp(Keys.ALT).keyUp("A").perform();
action.sendKeys(Keys.CONTROL+"a").perform();
action.sendKeys(Keys.CONTROL, Keys.ALT, "A").perform();
2.5.2 元素sendKeys()
sendKeys(Keys.BACK_SPACE) 回格键(BackSpace)
sendKeys(Keys.SPACE) 空格键(Space)
sendKeys(Keys.TAB) 制表键(Tab)

2.6 窗口控制

2.6.1 窗口切换

使用 driver.getWindowHandles() 方法获取所有窗口

使用 driver.switchTo().window(hand) 切换窗口

Set<String> handles = driver.getWindowHandles();
for (String hand : handles) {
    if (!StringUtils.equals(mainHand, hand)) {
        driver.switchTo().window(hand);
    }
}

3 linux无窗口模式

3.1 linux安装chrome浏览器

wget https://dl.google.com/linux/direct/google-chrome-stable_current_x86_64.rpm 
yum install -y google-chrome-stable_current_x86_64.rpm

3.2 下载对应版本的driver

查询当前浏览器版本

google-chrome --version
avatar

3.3 设置Selenium无头模式

  1. 设置无头模式
options.setHeadless(Boolean.TRUE);
  1. 配置头信息
options.addArguments("--no-sandbox");
options.addArguments("--disable-dev-shm-usage");
  1. 需要配置浏览器窗口大小,来确保元素可以检索
WebDriverWait wait = new WebDriverWait(driver, 60);
Dimension dimension = new Dimension(1920, 1080);
driver.manage().window().setSize(dimension);
  1. 若遇到如下提示
The driver is not executable: /opt/code/news/chromedriver

运行如下命令即可:

chmod 775 ./chromedriver

3.4 设置成功,启动运行





更多好玩好看的内容,欢迎到我的博客交流,共同进步        WaterMin

1.我们为什么要是用token

Token, 令牌,代表执行某些操作的权利,也就是我们进行某些操作的通行证。

1.1 在很久很久以前,我们使用什么做身份认证?

我们都知道 HTTP 是无状态(stateless)的协议:HTTP 对于事务处理没有记忆能力,不对请求和响应之间的通信状态进行保存。
使用 HTTP 协议,每当有新的请求发送时,就会有对应的新响应产生。协议本身并不保留之前一切的请求或响应报文的信息。这是为了更快地处理大量事务,确保协议的可伸缩性,而特意把 HTTP 协议设计成如此简单的。

可是,随着 Web 的发展,早期这种无状态的特性却带来了很多不方便性,比如说用户登录新浪微博,在登录页输入用户名、密码之后进入首页,但是由于 HTTP 是无状态的,HTTP 并不知道上一次的 HTTP 请求是否通过了验证,更无法得知当前用户的具体信息。

最简单的解决方案就是在所有的请求里面都带上用户名和密码,这样虽然可行,但是大大加重了服务器的负担(对于每个 request 都需要到数据库验证),而且用户也要每进入一个页面输入一次密码,毫无用户体验可言。
为此,引入了各种身份认证机制

1.1.1 Cookie & session

Cookie 是由 HTTP 服务器设置的,保存在浏览器中的小型文本文件,其内容为一系列的键值对

相对于保存在浏览器中的 Cookie,Session 是存储在服务器端的

Cookie 传递过程:

  • 浏览器向某个 URL 发送请求
  • 对应的服务器收到该 HTTP 请求,生成要发给浏览器的 HTTP 响应
  • 在响应头中加入 Set-Cookie 字段,值为要设置的的Cookie
  • 浏览器收到来自服务器的 HTTP 响应
  • 浏览器在响应头中发现了 Set-Cookie 字段,就会将该字段的值保存在内存或者是硬盘中。
  • 当下一次向该服务器发送 HTTP 请求时,会将服务器设置的 Cookie 附加在 HTTP 请求的字段 Cookie 中。
  • 服务器收到这个 HTTP 请求之后,发现请求头中有 Cookie 字段,就知道了已经处理过这个用户的请求了。
  • 过期的 Cookie 会被删除

基于Cookie-session的认证过程

  • 用户输入登录信息
  • 服务端验证登录信息是否正确,如果正确就在服务器端为这个用户创建一个 Session,并把 Session 存入数据库
  • 服务器端会向客户端返回带有 sessionID 的 Cookie
  • 客户端接收到服务器端发来的请求之后,看见响应头中的 Set-Cookie 字段,将 Cookie 保存起来
  • 接下来的请求中都会带上这个 Cookie,服务器将 sessionID 和 数据库中的相匹配,如果有效则处理该请求
  • 如果用户登出,Session 会在客户端和服务器端都被销毁
1.1.2 Cookie & session 的弊端
  1. 由于Cookie 以及 session 需要存储在浏览器内存以及服务器内存中,请求增大时,资源消耗会很大
  2. 基于cookie做身份认证,若浏览器屏蔽cookie,则造成很麻烦,需要另辟蹊径,使用url重写等方式
  3. 对于分布式系统支持不好
  4. cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。

1.2 基于token的鉴权方式

token在服务器时可以不用存储用户信息的,token传递的方式也不限于cookie传递。

当用户第一次访问服务器时,服务端通过算法,加密钥,生成一个token。通过BASE64编码后将token发送给客户端。

客户端将token保存起来,下次请求带着token,服务器收到请求会用相同的算法取验证toekn,如果通过就继续执行。

此时服务器变成无状态了,从而比较容易实现扩展

session为会话,token为令牌。

token解决了session扩展性差的问题

2. JWT

2.1 JWT结构

一个JWT实际上就是一个字符串,它由三部分组成,头部、载荷与签名依顺序用点号(".")链接而成:1.header,2.payload,3.signature。

  • Header(头部)
  • Payload(负载)
  • Signature(签名)

如下为一个 JWT字符串:

eyJhbGciOiJIUzI1NiJ9.eyJleHBpcmVUaW1lIjoxNTg5MDE0MzA2OTU0LCJkZXB0Ijoi5Yas6KW_55Oc5ZywIiwidXNlcm5hbWUiOiLlpKfopb_nk5wifQ.QSv0FcvNheiA3FW6OEah7jJKG4SG0ver3q67F0980rY
2.1.1 Header(头部)
eyJhbGciOiJIUzI1NiJ9

使用base64解密后得到:

{"alg":"HS256"}
2.1.2 Payload(负载)

Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据。JWT 规定了7个官方字段,供选用:、

  • iss (issuer):签发人
  • exp (expiration time):过期时间
  • sub (subject):主题
  • aud (audience):受众
  • nbf (Not Before):生效时间
  • iat (Issued At):签发时间
  • jti (JWT ID):编号

也可以自定义 :

  • username:大西瓜

JWT 默认是不加密的,任何人都可以读到,所以不要把秘密信息放在这个部分。

2.1.3 Signature(签名)

Signature 部分是对前两部分的签名,防止数据篡改。
使用指定加密算法以及仅服务器可知的 密钥(secret)对前两部分进行加密。

2.1.4 拼接

算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用"点"(.)分隔,就可以生成完整的JWT token了

2.2 JWT 的几个特点

  • JWT 默认是不加密,但也是可以加密的。生成原始 Token 以后,可以用密钥再加密一次。
  • JWT 不仅可以用于认证,也可以用于交换信息。有效使用 JWT,可以降低服务器查询数据库的次数。
  • JWT 的最大缺点是,由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或者更改 token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。
  • JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT 的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。
  • 为了减少盗用,JWT 不应该使用 HTTP 协议明码传输,要使用 HTTPS 协议传输。

3. java中使用 JWT

本文使用 JJWT 来实现java环境 JWT 生成,解密操作

3.1 引入 JJWT maven依赖

目前最新版本 0.9.1

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

3.2 配置 key

private static final String SALT = "0142add7c2664198863943f24bf4b8b9";

private static Key getKeyInstance() {
    SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
    String apiKey = DatatypeConverter.printBase64Binary(SALT.getBytes());
    byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(apiKey);
    Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());
    return signingKey;
}

3.3 生成token

public static String createJavaWebToken4JwtAuth(Map<String, Object> claims) {
    logger.info("生成的token为开始");
    String toekn = Jwts.builder().setClaims(claims).setExpiration(DateUtil.addSeconds(new Date(), 50))
            .signWith(SignatureAlgorithm.HS256, getKeyInstance()).compact();
    logger.info("生成的token为:" + toekn);
    return toekn;
}

其中 官方Payload 中的属性都有对应api来配置,🌰中配置了 .setExpiration(DateUtil.addSeconds(new Date(), 50))

3.4 获取body部分

public static Map<String, Object> verifyJavaWebToken(String jwt) {
    try {
        Map<String, Object> jwtClaims =
                Jwts.parser().setSigningKey(getKeyInstance()).parseClaimsJws(jwt).getBody();
        return jwtClaims;
    } catch (Exception e) {
        logger.info(e.getMessage());
        return null;
    }
}

当当前时间超过配置的过期时间时,会后抛出异常

3.5 校验有效性方法

public static boolean isTokenEffect(String jwt) {
    if (StringUtils.isEmpty(jwt)) {
        return false;
    }
    Map<String, Object> claims = verifyJavaWebToken(jwt);
    if (null == claims) {
        logger.info("转换jwt失败!");
        return false;
    }
    return true;
}

3.6 使用

public static void main(String[] args)  {

    Map<String, Object> paramMap = new HashMap<>();
    paramMap.put("username", "大西瓜");
    paramMap.put("dept", "冬西瓜地");
    String tokens = JwtUtil.createJavaWebToken4JwtAuth(paramMap);
    System.out.println(tokens);
    System.out.println(isTokenEffect(tokens));

}

本文JwtUtil 已提交github到我的工具集 ytooo-util

我的工具集包含 常用字符串处理、日期处理、http请求封装、文件处理、请求体封装等常用工具

欢迎使用

<dependency>
  <groupId>ml.ytooo</groupId>
  <artifactId>ytooo-util</artifactId>
  <version>3.6.5</version>
</dependency>





更多好玩好看的内容,欢迎到我的博客交流,共同进步 WaterMin


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,188评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,464评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,562评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,893评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,917评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,708评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,430评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,342评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,801评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,976评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,115评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,804评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,458评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,008评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,135评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,365评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,055评论 2 355