1、理论阐述
前两节我们使用redis+cookie完成了单点登录,但这种方式有缺陷,那就是用户数据登录信息需要存储到redis,如果redis固化失败,则整个单点登录方案失效。同时,这一种方案比较重,数据集中化存储在服务端,能不能轻装上阵,数据不存储于服务端,倘若服务器不保存任何登录数据,即服务器变为无状态,则其更容易扩展。我们马上要看的JSON Web Token(JWT)就是将登录数据存储在客户端。JWT的原则是在服务器身份验证之后,将生成一个JSON对象并将其发送回用户。之后,当用户与服务器通信时,客户在请求中发回JSON对象,服务器再重新校验。
一般JWT格式如下:
# JWT头
eyJhbGciOiJIUzI1NiJ9.
# 有效载荷
eyJ1aWQiOiJjMzU4MzBkNC1kMzZkLTQyNDAtODM3NS0yMmE1YTY0ZGUyZjkiLCJzdWIiOiJ7aWQ6MTAwLG5hbWU6YWxpfSIsInVzZXJfbmFtZSI6ImFkbWluIiwibmlja19uYW1lIjoiemhhbmdsaSIsImV4cCI6MTU4MjcwNTQzOCwiaWF0IjoxNTgyNzA1NDMyLCJqdGkiOiI4OTZlNDkxOC02NTlhLTQzYjMtYjIxYy04MmIyY2U2MDI1ZWEifQ.
# 签名
ulld0DfmDPfQqmV4t7elwmBkhRyOi0O8VPeAuVHup04
JWT格式
- JWT头:包含签名算法和令牌类型;
- 有效载荷:一般包括如下数据:
"ss":发行人
"exp":到期时间
"sub":主题
"aud":用户
"nbf":在此之前不可用
"iat":发布时间
"jti":JWT ID用于标识该JWT
# 以上是7个默认字段,也可以自定义字段,如下所示
"name": "zhangli",
"root": false
- 签名:是对上面两部分数据签名,通过指定的算法生成哈希,以确保数据不会被篡改
理论阐述完毕,接下来我们将完成一个demo。
2、操作步骤
- 基于springboot创建一个web项目 portal,端口设置为8080
https://www.jianshu.com/p/de979f53ad80 - 加入以下依赖:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.7.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-codec/commons-codec -->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.10</version>
</dependency>
- 在项目/src/main/resources/static文件夹下导入jquery-1.10.2.js
- 在项目/src/main/resources/static文件夹下创建index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>sso</title>
</head>
<body>
<h2>Hello World!</h2>
账号:<input type="text" name="name" id="name" value="ali"/>
密码:<input type="password" name="password" id="password" value="123456" />
<input type="button" value="登录" id="sub"/>
</body>
<script type="text/javascript" src="jquery-1.10.2.js"></script>
<script type="text/javascript">
$(function(){
$("#sub").click(function(){
$.ajax({
data:{name:$("#name").val(),password:$("#password").val()},
type:"post",
dataType:"json",
url:"/login",
success:function (data) {
if(data.status=='200')
{
window.location.href="main.html";
}
else {
alert(data.msg)
}
}
})
})
})
</script>
</html>
- 在项目/src/main/resources/static文件夹下创建main.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
</head>
<body>
<div id="welcome"></div>欢迎你!
<h2>Hello World!</h2>
</body>
<script type="text/javascript" src="jquery-1.10.2.js"></script>
<script type="text/javascript">
$(function(){
$.ajax({
data:{},
type:"get",
dataType:"json",
url:"/getuser",
success:function (data) {
if(data.status=='200')
{
$("#welcome").html(data.data);
}
else
{
window.location.href="index.html";
}
}
})
})
</script>
</html>
- 在主启动类同级目录下创建JwtUtil.java
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.apache.commons.codec.binary.Base64;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
public class JwtUtil {
public static String createJWT(String id, String username, long ttlMillis) throws Exception {
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
//生成JWT的时间
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
Map<String, Object> claims = new HashMap<String, Object>();
SecretKey key = generalKey();//签名秘钥,和生成的签名的秘钥一模一样
JwtBuilder builder = Jwts.builder()
.setClaims(claims)
.setId(id)
.setIssuedAt(now)
.setSubject(username)
.signWith(signatureAlgorithm, key);
if (ttlMillis >= 0) {
long expMillis = nowMillis + ttlMillis;
Date exp = new Date(expMillis);
builder.setExpiration(exp);
}
return builder.compact();
}
public static Claims parseJWT(String jwt) throws Exception {
SecretKey key = generalKey(); //签名秘钥,和生成的签名的秘钥一模一样
Claims claims = Jwts.parser()
.setSigningKey(key)
.parseClaimsJws(jwt).getBody();
return claims;
}
public static SecretKey generalKey() {
String stringKey = "zhangli123";//来自数据库,不同的用户key值不同
byte[] encodedKey = Base64.decodeBase64(stringKey);
SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
return key;
}
}
- 在主启动类同级目录下创建UserController.java
import com.alibaba.fastjson.JSONObject;
import io.jsonwebtoken.Claims;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.UUID;
@RestController
public class UserController {
@PostMapping("/login")
public JSONObject login(@RequestParam("name") String name, @RequestParam("password") String password, HttpServletResponse resp) throws Exception {
JSONObject jsonObject = new JSONObject();
if ("ali".equals(name) && "123456".equals(password)) {
String jwt = JwtUtil.createJWT(UUID.randomUUID().toString(), name, 6000);
Cookie cookie = new Cookie("USER_JWT", jwt);
cookie.setPath("/");
cookie.setMaxAge(30 * 60);
resp.addCookie(cookie);
jsonObject.put("status", 200);
} else {
jsonObject.put("status", 100);
jsonObject.put("msg", "account or password wrong");
}
return jsonObject;
}
@GetMapping("/getuser")
public JSONObject doGet(HttpServletRequest req) throws Exception {
JSONObject jsonObject = new JSONObject();
String token = "";
Cookie[] cookies = req.getCookies();
for (Cookie cookie : cookies) {
if ("USER_JWT".equals(cookie.getName())) {
token = cookie.getValue();
break;
}
}
if ("".equals(token)) {
jsonObject.put("status", 303);
jsonObject.put("data", "cookie当中并未携带token");
return jsonObject;
}
Claims c = JwtUtil.parseJWT(token);
jsonObject.put("status", 200);
jsonObject.put("data", c.getSubject());
return jsonObject;
}
}
- 测试
启动主启动类,在浏览器中输入 http://localhost:8080/index.html,点击“登录”,就会进到main页面,同时也会看到登录者的名称。
以上就是使用jwt完成登录验证。