JWT 和 JJWT,別再傻傻分不清了!

來源 | https://blog.csdn.net/change_on/article/details/76279441
jwt是什么?
JWTs是JSON對象的編碼表示。JSON對象由零或多個名稱/值對組成,其中名稱為字符串,值為任意JSON值。
JWT有助于在clear(例如在URL中)發(fā)送這樣的信息,可以被信任為不可讀(即加密的)、不可修改的(即簽名)和URL - safe(即Base64編碼的)。
jwt的組成
Header: 標題包含了令牌的元數(shù)據(jù),并且在最小包含簽名和/或加密算法的類型 Claims: Claims包含您想要簽署的任何信息 JSON Web Signature (JWS): 在header中指定的使用該算法的數(shù)字簽名和聲明
例如:
Header:
{
"alg": "HS256",
"typ": "JWT"
}
Claims:
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
Signature:
base64UrlEncode(Header) + "." + base64UrlEncode(Claims),
加密生成的token:

如何保證 JWT 安全
有很多庫可以幫助您創(chuàng)建和驗證JWT,但是當使用JWT時,仍然可以做一些事情來限制您的安全風險。在您信任JWT中的任何信息之前,請始終驗證簽名。這應該是給定的。
換句話說,如果您正在傳遞一個秘密簽名密鑰到驗證簽名的方法,并且簽名算法被設(shè)置為“none”,那么它應該失敗驗證。
確保簽名的秘密簽名,用于計算和驗證簽名。秘密簽名密鑰只能由發(fā)行者和消費者訪問,不能在這兩方之外訪問。
不要在JWT中包含任何敏感數(shù)據(jù)。這些令牌通常是用來防止操作(未加密)的,因此索賠中的數(shù)據(jù)可以很容易地解碼和讀取。
如果您擔心重播攻擊,包括一個nonce(jti索賠)、過期時間(exp索賠)和創(chuàng)建時間(iat索賠)。這些在JWT規(guī)范中定義得很好。

jwt的框架:JJWT
JJWT是一個提供端到端的JWT創(chuàng)建和驗證的Java庫。永遠免費和開源(Apache License,版本2.0),JJWT很容易使用和理解。它被設(shè)計成一個以建筑為中心的流暢界面,隱藏了它的大部分復雜性。
JJWT的目標是最容易使用和理解用于在JVM上創(chuàng)建和驗證JSON Web令牌(JWTs)的庫。 JJWT是基于JWT、JWS、JWE、JWK和JWA RFC規(guī)范的Java實現(xiàn)。 JJWT還添加了一些不屬于規(guī)范的便利擴展,比如JWT壓縮和索賠強制。
規(guī)范兼容:
創(chuàng)建和解析明文壓縮JWTs 創(chuàng)建、解析和驗證所有標準JWS算法的數(shù)字簽名緊湊JWTs(又稱JWSs): HS256: HMAC using SHA-256 HS384: HMAC using SHA-384 HS512: HMAC using SHA-512 RS256: RSASSA-PKCS-v1_5 using SHA-256 RS384: RSASSA-PKCS-v1_5 using SHA-384 RS512: RSASSA-PKCS-v1_5 using SHA-512 PS256: RSASSA-PSS using SHA-256 and MGF1 with SHA-256 PS384: RSASSA-PSS using SHA-384 and MGF1 with SHA-384 PS512: RSASSA-PSS using SHA-512 and MGF1 with SHA-512 ES256: ECDSA using P-256 and SHA-256 ES384: ECDSA using P-384 and SHA-384 ES512: ECDSA using P-521 and SHA-512
這里以github上的demo演示,理解原理,集成到自己項目中即可。如果您正在學習Spring Boot,推薦一個連載多年還在繼續(xù)更新的免費教程:http://blog.didispace.com/spring-boot-learning-2x/
應用采用 spring boot + angular + jwt
結(jié)構(gòu)圖

Maven 引進 : pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.nibado.example</groupId>
<artifactId>jwt-angular-spring</artifactId>
<version>0.0.2-SNAPSHOT</version>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<commons.io.version>2.4</commons.io.version>
<jjwt.version>0.6.0</jjwt.version>
<junit.version>4.12</junit.version>
<spring.boot.version>1.5.3.RELEASE</spring.boot.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring.boot.version}</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>${commons.io.version}</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>${jjwt.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
</dependency>
</dependencies>
</project>
WebApplication.java
package com.nibado.example.jwtangspr;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@EnableAutoConfiguration
@ComponentScan
@Configuration
public class WebApplication {
//過濾器
@Bean
public FilterRegistrationBean jwtFilter() {
final FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(new JwtFilter());
registrationBean.addUrlPatterns("/api/*");
return registrationBean;
}
public static void main(final String[] args) throws Exception {
SpringApplication.run(WebApplication.class, args);
}
}JwtFilter.java
package com.nibado.example.jwtangspr;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import org.springframework.web.filter.GenericFilterBean;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureException;
public class JwtFilter extends GenericFilterBean {
@Override
public void doFilter(final ServletRequest req,
final ServletResponse res,
final FilterChain chain) throws IOException, ServletException {
final HttpServletRequest request = (HttpServletRequest) req;
//客戶端將token封裝在請求頭中,格式為(Bearer后加空格):Authorization:Bearer +token
final String authHeader = request.getHeader("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
throw new ServletException("Missing or invalid Authorization header.");
}
//去除Bearer 后部分
final String token = authHeader.substring(7);
try {
//解密token,拿到里面的對象claims
final Claims claims = Jwts.parser().setSigningKey("secretkey")
.parseClaimsJws(token).getBody();
//將對象傳遞給下一個請求
request.setAttribute("claims", claims);
}
catch (final SignatureException e) {
throw new ServletException("Invalid token.");
}
chain.doFilter(req, res);
}
}
UserController.java
package com.nibado.example.jwtangspr;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletException;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
@RestController
@RequestMapping("/user")
public class UserController {
//這里模擬數(shù)據(jù)庫
private final Map<String, List<String>> userDb = new HashMap<>();
@SuppressWarnings("unused")
private static class UserLogin {
public String name;
public String password;
}
public UserController() {
userDb.put("tom", Arrays.asList("user"));
userDb.put("wen", Arrays.asList("user", "admin"));
}
/*以上是模擬數(shù)據(jù)庫,并往數(shù)據(jù)庫插入tom和sally兩條記錄*/
@RequestMapping(value = "login", method = RequestMethod.POST)
public LoginResponse login(@RequestBody final UserLogin login)
throws ServletException {
if (login.name == null || !userDb.containsKey(login.name)) {
throw new ServletException("Invalid login");
}
//加密生成token
return new LoginResponse(Jwts.builder().setSubject(login.name)
.claim("roles", userDb.get(login.name)).setIssuedAt(new Date())
.signWith(SignatureAlgorithm.HS256, "secretkey").compact());
}
@SuppressWarnings("unused")
private static class LoginResponse {
public String token;
public LoginResponse(final String token) {
this.token = token;
}
}
}
ApiController.java
package com.nibado.example.jwtangspr;
import io.jsonwebtoken.Claims;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api")
public class ApiController {
@SuppressWarnings("unchecked")
@RequestMapping(value = "role/{role}", method = RequestMethod.GET)
public Boolean login(@PathVariable final String role,
final HttpServletRequest request) throws ServletException {
final Claims claims = (Claims) request.getAttribute("claims");
return ((List<String>) claims.get("roles")).contains(role);
}
}
index.html
<!doctype html>
<html ng-app="myApp">
<head>
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
<title>JSON Web Token / AngularJS / Spring Boot example</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width">
<link rel="stylesheet" href="libs/bootstrap/css/bootstrap.css">
<script src="libs/jquery/jquery.js"></script>
<script src="libs/bootstrap/js/bootstrap.js"></script>
<script src="libs/angular/angular.js"></script>
<script src="app.js"></script>
</head>
<body>
<div class="container" ng-controller='MainCtrl'>
<h1>{{greeting}}</h1>
<div ng-show="!loggedIn()">
Please log in (tom and sally are valid names)</br>
<form ng-submit="login()">
Username: <input type="text" ng-model="userName"/><span><input type="submit" value="Login"/>
</form>
</div>
<div class="alert alert-danger" role="alert" ng-show="error.data.message">
<span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
<span class="sr-only">Error:</span>
{{error.data.message}}
</div>
<div ng-show="loggedIn()">
<div class="row">
<div class="col-md-6">
<h3><span class="label label-success">Success!</span> Welcome {{userName}}</h3>
<a href ng-click="logout()">(logout)</a>
</div>
<div class="col-md-4">
<div class="row header">
<div class="col-sm-4">{{userName}} is a</div>
</div>
<div class="row">
<div class="col-sm-2">User</div>
<div class="col-sm-2"><span class="glyphicon glyphicon-ok" aria-hidden="true" ng-show="roleUser"></span></div>
</div>
<div class="row">
<div class="col-sm-2">Admin</div>
<div class="col-sm-2"><span class="glyphicon glyphicon-ok" aria-hidden="true" ng-show="roleAdmin"></span></div>
</div>
<div class="row">
<div class="col-sm-2">Foo</div>
<div class="col-sm-2"><span class="glyphicon glyphicon-ok" aria-hidden="true" ng-show="roleFoo"></span></div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
