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很简单,你只需要添加如下依赖:


    org.seleniumhq.selenium
    selenium-java
    3.141.59


    com.google.guava
    guava
    23.0


    com.google.code.gson
    gson
    2.8.2

2.2 添加浏览器驱动

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

  • Firefox浏览器驱动:geckodriver
  • Chrome浏览器驱动:chromedriver
  • IE浏览器驱动:IEDriverServer
  • Edge浏览器驱动:MicrosoftWebDriver
  • Opera浏览器驱动:operadriver
  • PhantomJS浏览器驱动:phantomjs

在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())

新闻
hao123
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 对象,不建议直接循环来获取 元素对象,而是从根中重新获取,以避免获取元素失败

List 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 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


    io.jsonwebtoken
    jjwt
    0.9.1

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 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 verifyJavaWebToken(String jwt) {
    try {
        Map 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 claims = verifyJavaWebToken(jwt);
    if (null == claims) {
        logger.info("转换jwt失败!");
        return false;
    }
    return true;
}

3.6 使用

public static void main(String[] args)  {

    Map 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请求封装、文件处理、请求体封装等常用工具

欢迎使用


  ml.ytooo
  ytooo-util
  3.6.5





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


你可能感兴趣的:(JSON WEB TOKEN(JWT)详解以及JAVA项目实战)