JWT是什么?
使用JWT的好处
使用io.jsonwebtoken包的方式
pom.xml导入的jar包
User实体
JjwtUtil类
TestJjwt测试类
打印的结果
使用com.auth0包的方式
pom.xml文件
User实体使用上面的
JWTInterceptor拦截器
spring-context.xml配置文件
web.xml配置文件
JWTUtil类
UsersController层
Json web token(JWT)是一种用于双方之间传递安全信息的简洁的、URL安全的表述性声明规范。JWT作为一个开放的标准(RFC 7519),定义了一种简洁的,自包含的方法用于通信双方之间以Json对象的形式安全的传递信息,该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。
JWT的声明一般被用在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。
JWT其实就是一个字符串,由三个部分组成:头部、载荷以及签名,是一种基于JSON的令牌安全验证(在某些特定的场合可以替代Session或者Cookie)。
JWT经常被用来保护服务器的资源,客户端一般通过HTTP/header的Authorzation把JWT发送给服务端,服务端使用自己保存的Key进行计算,验证签名JWT是否合法。JWT可以使用HMAC算法或者是RSA的公私秘钥对进行签名。
jwt的三个组成部分,每部分通过点号来分割,即:header.payload.signature
头部(header)是一个JSON对象,一般使用Base64URL编码,承载两部分信息,存放一些声明信息,如:用什么加密,用什么编码【作用:指定JWT使用的签名】
① 声明类型typ,表示这个令牌(token)的类型(type),JWT令牌统一写为JWT
② 声明加密的算法alg,通常直接使用HMAC SHA256,也可以使用RSA,支持很多算法(HS256、HS384、HS512、RS256、RS384、RS512、ES256、ES384、ES512、PS256、PS384)
载荷也叫消息体(payload)是一个JSON对象,一般使用Base64URL编码,存放主要的有用信息,如:登陆的用户名,登录时间,登录的过期时间,还可以放一些自定义信息【作用:指定JWT的请求数据】
这些有效信息包含三个部分:标准中注册的声明、公共的声明和私有的声明
一、标准中注册的声明:
① iss(issuer): jwt签发者
② sub(subject): jwt所面向的用户,放登录的用户名等
③ aud(audience): jwt接收者
④ exp(expiration time): jwt的过期时间,这个过期时间必须要大于签发时间
⑤ nbf(Not Before): 生效时间,定义在什么时间之前
⑥ iat(issuedAt): jwt的签发时间
⑦ jti(JWT ID): jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击
二、公共的声明:公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息,但不建议添加敏感信息,因为该部分在客户端可解密
三、私有的声明:私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息
签证(signature)存放有效信息的地方,这些有效信息包含三个部分,三部分之间用"."号做分隔,对header和payload使用密钥进行签名,防止数据篡改
签证信息的算法:base64UrlEncode(header) + "." + base64UrlEncode(payload) + your-256-bit-secret
1.支持跨域访问:Cookie是不允许垮域访问的,这一点对Token机制是不存在的,前提是传输的用户认证信息通过HTTP头传输
2.无状态(也称:服务端可扩展行):Token机制在服务端不需要存储session信息,因为Token自身包含了所有登录用户的信息,只需要在客户端的cookie或本地介质存储状态信息
4.更适用CDN:可以通过内容分发网络请求你服务端的所有资料(如:javascript,HTML,图片等),而你的服务端只要提供API即可(居于前面两点得出这个更适用于CDN内容分发网络)
5.去耦:不需要绑定到一个特定的身份验证方案。Token可以在任何地方生成,只要在你的API被调用的时候,你可以进行Token生成调用即可
6.更适用于移动应用:当你的客户端是一个原生平台(iOS,Android,Windows等)时,Cookie是不被支持的(你需要通过Cookie容器进行处理),这时采用Token认证机制就会简单得多
7.CSRF:因为不再依赖于Cookie,所以你就不需要考虑对CSRF(跨站请求伪造)的防范(如果token是用cookie保存,CSRF还是需要考虑,一般建议使用①在HTTP请求中以参数的形式加入一个服务器端产生的token。或者②放入http请求头中也就是一次性给所有该类请求加上csrftoken这个HTTP头属性,并把token值放入其中)
8.性能:一次网络往返时间(通过数据库查询session信息)总比做一次HMACSHA256计算的Token验证和解析要费时得多
9.不需要为登录页面做特殊处理:如果你使用Protractor做功能测试的时候,不再需要为登录页面做特殊处理
10.基于标准化:你的API可以采用标准化的JSON Web Token(JWT)这个标准已经存在多个后端库(.NET,Ruby,Java,Python,PHP)和多家公司的支持(如:Firebase,Google,Microsoft)
io.jsonwebtoken
jjwt
0.6.0
com.alibaba
fastjson
1.2.47
package com.jjwt.entity;
/**
* @ClassName: User
* @Description: 用户实体
*/
public class User {
private int id;
private String username;
private String password;
public User() {
super();
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User [id=" + id + ", username=" + username + ", password=" + password + "]";
}
}
package com.jwt.utils;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import org.apache.tomcat.util.codec.binary.Base64;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
// 使用io.jsonwebtoken包
public class JjwtUtil {
// jti:jwt的唯一身份标识
public static final String JWT_ID = UUID.randomUUID().toString();
// 加密密文,私钥
public static final String JWT_SECRET = "jiamimiwen";
// 过期时间,单位毫秒
public static final int EXPIRE_TIME = 60 * 60 * 1000; // 一个小时
// public static final long EXPIRE_TIME = 7 * 24 * 3600 * 1000; // 一个星期
// 由字符串生成加密key
public static SecretKey generalKey() {
// 本地的密码解码
byte[] encodedKey = Base64.decodeBase64(JWT_SECRET);
// 根据给定的字节数组使用AES加密算法构造一个密钥
SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
return key;
}
// 创建jwt
public static String createJWT(String issuer, String audience, String subject) throws Exception {
// 设置头部信息
// Map header = new HashMap();
// header.put("typ", "JWT");
// header.put("alg", "HS256");
// 或
// 指定header那部分签名的时候使用的签名算法,jjwt已经将这部分内容封装好了,只有{"alg":"HS256"}
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
// 创建payload的私有声明(根据特定的业务需要添加,如果要拿这个做验证,一般是需要和jwt的接收方提前沟通好验证的方式)
Map claims = new HashMap<>();
claims.put("username", "admin");
claims.put("password", "010203");
// jti用户id,例如:20da39f8-b74e-4a9b-9a0f-a39f1f73fe64
String jwtId = JWT_ID;
// 生成JWT的时间
long nowTime = System.currentTimeMillis();
Date issuedAt = new Date(nowTime);
// 生成签名的时候使用的秘钥secret,切记这个秘钥不能外露,是你服务端的私钥,在任何场景都不应该流露出去,一旦客户端得知这个secret,那就意味着客户端是可以自我签发jwt的
SecretKey key = generalKey();
// 为payload添加各种标准声明和私有声明
JwtBuilder builder = Jwts.builder() // 表示new一个JwtBuilder,设置jwt的body
// .setHeader(header) // 设置头部信息
.setClaims(claims) // 如果有私有声明,一定要先设置自己创建的这个私有声明,这是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明
.setId(jwtId) // jti(JWT ID):jwt的唯一身份标识,根据业务需要,可以设置为一个不重复的值,主要用来作为一次性token,从而回避重放攻击
.setIssuedAt(issuedAt) // iat(issuedAt):jwt的签发时间
.setIssuer(issuer) // iss(issuer):jwt签发者
.setSubject(subject) // sub(subject):jwt所面向的用户,放登录的用户名,一个json格式的字符串,可存放userid,roldid之类,作为用户的唯一标志
.signWith(signatureAlgorithm, key); // 设置签名,使用的是签名算法和签名使用的秘钥
// 设置过期时间
long expTime = EXPIRE_TIME;
if (expTime >= 0) {
long exp = nowTime + expTime;
builder.setExpiration(new Date(exp));
}
// 设置jwt接收者
if (audience == null || "".equals(audience)) {
builder.setAudience("Tom");
} else {
builder.setAudience(audience);
}
return builder.compact();
}
// 解密jwt
public static Claims parseJWT(String jwt) throws Exception {
SecretKey key = generalKey(); // 签名秘钥,和生成的签名的秘钥一模一样
Claims claims = Jwts.parser() // 得到DefaultJwtParser
.setSigningKey(key) // 设置签名的秘钥
.parseClaimsJws(jwt).getBody(); // 设置需要解析的jwt
return claims;
}
}
package com.jwt.test;
import java.text.SimpleDateFormat;
import com.alibaba.fastjson.JSON;
import com.jwt.entity.User;
import com.jwt.utils.JjwtUtil;
import io.jsonwebtoken.Claims;
// 使用io.jsonwebtoken包
public class TestJjwt {
public static void main(String[] args) {
User user = new User();
user.setId(10);
user.setUsername("张三");
user.setPassword("123123");
// jwt所面向的用户,放登录的用户名等
String subject = JSON.toJSONString(user);
try {
// "Jack"是jwt签发者,"李四"是jwt接收者
String jwt = JjwtUtil.createJWT("Jack", "李四", subject);
System.out.println("JWT:" + jwt);
System.out.println("JWT长度:" + jwt.length());
System.out.println("\njwt三个组成部分中间payload部分的解密:");
Claims c = JjwtUtil.parseJWT(jwt);
System.out.println("jti用户id:" + c.getId());
System.out.println("iat登录时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(c.getIssuedAt()));
System.out.println("iss签发者:" + c.getIssuer());
System.out.println("sub用户信息列表:" + c.getSubject());
System.out.println("exp过期时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(c.getExpiration()));
System.out.println("aud接收者:" + c.getAudience());
System.out.println("登录的用户名:" + c.get("username"));
// 或
System.out.println("登录的用户名:" + c.get("username", String.class));
System.out.println("登录的密码:" + c.get("password", String.class));
} catch (Exception e) {
e.printStackTrace();
}
}
}
JWT:eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ7XCJpZFwiOjEwLFwicGFzc3dvcmRcIjpcIjEyMzEyM1wiLFwidXNlcm5hbWVcIjpcIuW8oOS4iVwifSIsImF1ZCI6IuadjuWbmyIsInBhc3N3b3JkIjoiMDEwMjAzIiwiaXNzIjoiSmFjayIsImV4cCI6MTU2NzUwOTYyMiwiaWF0IjoxNTY3NTA2MDIyLCJqdGkiOiJjYjkyMjlkMi1mMDRiLTQ2NmUtOGY4Ny1iMGM4OWU3YWQ5NDEiLCJ1c2VybmFtZSI6ImFkbWluIn0.UG7aVfmQO28bRTrCyD1u2C8pKYXONZ2FZ_R7aFYhJN0
JWT长度:352
jwt三个组成部分中间payload部分的解密:
jti用户id:cb9229d2-f04b-466e-8f87-b0c89e7ad941
iat登录时间:2019-09-03 18:20:22
iss签发者:Jack
sub用户信息列表:{"id":10,"password":"123123","username":"张三"}
exp过期时间:2019-09-03 19:20:22
aud接收者:李四
登录的用户名:admin
登录的用户名:admin
登录的密码:010203
5.1.5.RELEASE
junit
junit
4.12
test
org.springframework
spring-core
${spring.version}
org.springframework
spring-beans
${spring.version}
org.springframework
spring-context
${spring.version}
org.springframework
spring-context-support
${spring.version}
org.springframework
spring-web
${spring.version}
org.springframework
spring-webmvc
${spring.version}
org.springframework
spring-tx
${spring.version}
org.springframework
spring-jdbc
${spring.version}
org.springframework
spring-expression
${spring.version}
org.springframework
spring-test
${spring.version}
mysql
mysql-connector-java
5.1.47
com.auth0
java-jwt
3.5.0
io.jsonwebtoken
jjwt
0.6.0
com.alibaba
fastjson
1.2.47
package com.jwt.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import com.jwt.utils.JWTUtil;
import io.jsonwebtoken.Claims;
public class JWTInterceptor implements HandlerInterceptor {
@Autowired
private JWTUtil jWTUtil;
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object obj, Exception e)
throws Exception {
}
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object obj, ModelAndView mav)
throws Exception {
}
// 拦截每个请求
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object obj) {
System.out.println("开始进入拦截器检验jwt头部是否含有Authorization方法!");
// 通过url得到token请求头是否包含Authorization
String jwt = request.getHeader("Authorization");
System.out.println(jwt);
try {
// 检测请求头是否为空
if (jwt == null) {
System.out.println("用户未登录,验证失败");
} else {
Claims c = jWTUtil.parseJWT(jwt);
System.out.println("用户[ " + c.get("username") + " ]已是登录状态");
System.out.println("结束进入拦截器检验jwt头部是否含有Authorization方法!");
return true;
}
System.out.println("token解析错误,验证失败");
response.getWriter().write("未登录,请重新登录后操作");
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
}
jwt
org.springframework.web.servlet.DispatcherServlet
contextConfigLocation
classpath:spring-context.xml
1
jwt
/
characterEncodingFilter
org.springframework.web.filter.CharacterEncodingFilter
encoding
UTF-8
forceRequestEncoding
true
characterEncodingFilter
/*
package com.jwt.utils;
import java.util.Date;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import org.apache.tomcat.util.codec.binary.Base64;
import org.springframework.stereotype.Component;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.JWTVerifier;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
/**
*
* @ClassName: JWTUtil
* @Description: 实现对用户名和密码的加密处理,校验token是否正确,获取用户名等操作
* Algorithm algorithm = Algorithm.HMAC256(password) 是对密码进行加密后再与用户名混淆在一起
* 在签名时可以通过 .withExpiresAt(date) 指定token的过期时间
* @param:
*/
@Component
public class JWTUtil {
// 过期时间,单位毫秒
private static final long EXPIRE_TIME = 60 * 1000; // 1分钟
// private static final long EXPIRE_TIME = 15 * 60 * 1000; // 15分钟
// 加密密文,私钥
private static final String TOKEN_SECRET = "jiamimiwen";
// 由字符串生成加密key
public SecretKey generalKey() {
System.out.println("进入由字符串生成加密key方法!");
// 本地的密码解码
byte[] encodedKey = Base64.decodeBase64(TOKEN_SECRET);
// 根据给定的字节数组使用AES加密算法构造一个密钥
SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
return key;
}
// 生成签名
public String sign(int id, String username, String password) {
System.out.println("生成签名方法开始执行!");
try {
// 设置过期时间,单位毫秒
Date expTime = new Date(System.currentTimeMillis() + EXPIRE_TIME);
// 私钥和加密算法
Algorithm algorithm = Algorithm.HMAC256(password); //使用用户输入的密码
// Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
// 设置头部信息,也可以不用设置头部信息jwt会自动生成
// Map header = new HashMap();
// header.put("typ", "JWT");
// header.put("alg", "HS256");
// 或
// header.put("Type", "JWT");
// header.put("alg", "HS256");
// 生成JWT的时间
Date issuedAt = new Date(System.currentTimeMillis());
// 返回token字符串
System.out.println("生成签名方法结束执行!");
return JWT.create() // 表示new一个Jwt,设置jwt的body
// .withHeader(header) // 设置头部信息
.withClaim("id", id) // 数据库中用户的id
.withClaim("username", username) // 前端输入的用户名
.withIssuedAt(issuedAt) // jwt的签发时间
.withExpiresAt(expTime) // jwt过期时间
.sign(algorithm);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
*
* @Title: verify
* @Description: 检验token是否正确
* @param: @param token 密钥
* @param: @param username 登录名
* @param: @param password 密码
* @param: @return
* @return: boolean
* @throws
*/
public boolean verify(String token, String username, String password) {
System.out.println("进入检验token是否正确方法!");
try {
Algorithm algorithm = Algorithm.HMAC256(password); //使用用户输入的密码
// Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
JWTVerifier verifier = JWT.require(algorithm).withClaim("username", username).build();
// JWTVerifier verifier = JWT.require(algorithm).build();
verifier.verify(token);
return true;
} catch (Exception e) {
return false;
}
}
// 获取登录名
public String getUsername(String token) {
System.out.println("进入获取登录名方法!");
try {
DecodedJWT jwt = JWT.decode(token);
return jwt.getClaim("username").asString();
} catch (JWTDecodeException e) {
return null;
}
}
// 解密jwt
public Claims parseJWT(String jwt) throws Exception {
System.out.println("进入解密jwt方法!");
SecretKey key = generalKey(); // 签名秘钥,和生成的签名的秘钥一模一样
Claims claims = Jwts.parser() // 得到DefaultJwtParser
.setSigningKey(key) // 设置签名的秘钥
.parseClaimsJws(jwt).getBody(); // 设置需要解析的jwt
return claims;
}
}
package com.jwt.controller;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.jwt.entity.User;
import com.jwt.utils.JWTUtil;
@RestController
@RequestMapping("user")
public class UserController {
@Autowired
private JWTUtil jWTUtil;
@PostMapping("dologin")
public Map dologin(@RequestParam String username, @RequestParam String password) {
// public Map dologin(@RequestBody User user) {
System.out.println("dologin方法开始执行!");
int id = 10;
User user = new User();
user.setId(id);
user.setUsername(username);
user.setPassword(password);
// String username = user.getUsername();
// String password = user.getPassword();
Map map = new HashMap();
if ("admin".equals(username) && "123456".equals(password)) {
String token = jWTUtil.sign(id, username, password);
if (token != null) {
map.put("code", "200");
map.put("message", "认证成功!");
map.put("token", token);
map.put("data", user);
System.out.println("dologin方法结束执行----认证成功!");
return map;
}
} else {
map.put("code", "000");
map.put("message", "认证失败!");
System.out.println("dologin方法结束执行----认证失败!");
}
return map;
}
@GetMapping("list")
public Map list() {
System.out.println("list方法开始执行!");
Map map = new HashMap();
User user = new User();
user.setId(1001);
user.setUsername("张三");
user.setPassword("123123");
map.put("users", user);
System.out.println("list方法结束执行!");
return map;
}
}