⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 blog.csdn.net/change_on/article/details/76279441/ 「J_小浩子」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注**微信公众号:【芋道源码】**有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

jwt是什么?

JWTs是JSON对象的编码表示。JSON对象由零或多个名称/值对组成,其中名称为字符串,值为任意JSON值。JWT有助于在clear(例如在URL中)发送这样的信息,可以被信任为不可读(即加密的)、不可修改的(即签名)和URL - safe(即Base64编码的)。

jwt的组成

  • Header: 标题包含了令牌的元数据,并且在最小包含签名和/或加密算法的类型
  • Claims: Claims包含您想要签署的任何信息
  • JSON Web Signature (JWS): 在header中指定的使用该算法的数字签名和声明

例如:

Header:

{
"alg": "HS256",
"typ": "JWT"
}

Claims:

{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}

Signature:

base64UrlEncode(Header) + "." + base64UrlEncode(Claims),

加密生成的token:

如何保证 JWT 安全

有很多库可以帮助您创建和验证JWT,但是当使用JWT时,仍然可以做一些事情来限制您的安全风险。在您信任JWT中的任何信息之前,请始终验证签名。这应该是给定的。

换句话说,如果您正在传递一个秘密签名密钥到验证签名的方法,并且签名算法被设置为“none”,那么它应该失败验证。

确保签名的秘密签名,用于计算和验证签名。秘密签名密钥只能由发行者和消费者访问,不能在这两方之外访问。

不要在JWT中包含任何敏感数据。这些令牌通常是用来防止操作(未加密)的,因此索赔中的数据可以很容易地解码和读取。

如果您担心重播攻击,包括一个nonce(jti索赔)、过期时间(exp索赔)和创建时间(iat索赔)。这些在JWT规范中定义得很好。

jwt的框架:JJWT

JJWT是一个提供端到端的JWT创建和验证的Java库。永远免费和开源(Apache License,版本2.0),JJWT很容易使用和理解。它被设计成一个以建筑为中心的流畅界面,隐藏了它的大部分复杂性。

  • JJWT的目标是最容易使用和理解用于在JVM上创建和验证JSON Web令牌(JWTs)的库。
  • JJWT是基于JWT、JWS、JWE、JWK和JWA RFC规范的Java实现。
  • JJWT还添加了一些不属于规范的便利扩展,比如JWT压缩和索赔强制。

规范兼容:

  • 创建和解析明文压缩JWTs
  • 创建、解析和验证所有标准JWS算法的数字签名紧凑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 + angular + jwt

结构图

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 {

//这里模拟数据库
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"));
}
/*以上是模拟数据库,并往数据库插入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>

app.js

var appModule = angular.module('myApp', []);

appModule.controller('MainCtrl', ['mainService','$scope','$http',
function(mainService, $scope, $http) {
$scope.greeting = 'Welcome to the JSON Web Token / AngularJR / Spring example!';
$scope.token = null;
$scope.error = null;
$scope.roleUser = false;
$scope.roleAdmin = false;
$scope.roleFoo = false;

$scope.login = function() {
$scope.error = null;
mainService.login($scope.userName).then(function(token) {
$scope.token = token;
$http.defaults.headers.common.Authorization = 'Bearer ' + token;
$scope.checkRoles();
},
function(error){
$scope.error = error
$scope.userName = '';
});
}

$scope.checkRoles = function() {
mainService.hasRole('user').then(function(user) {$scope.roleUser = user});
mainService.hasRole('admin').then(function(admin) {$scope.roleAdmin = admin});
mainService.hasRole('foo').then(function(foo) {$scope.roleFoo = foo});
}

$scope.logout = function() {
$scope.userName = '';
$scope.token = null;
$http.defaults.headers.common.Authorization = '';
}

$scope.loggedIn = function() {
return $scope.token !== null;
}
} ]);



appModule.service('mainService', function($http) {
return {
login : function(username) {
return $http.post('/user/login', {name: username}).then(function(response) {
return response.data.token;
});
},

hasRole : function(role) {
return $http.get('/api/role/' + role).then(function(response){
console.log(response);
return response.data;
});
}
};
});

运行应用

效果

文章目录
  1. 1. jwt是什么?
  2. 2. jwt的组成
  3. 3. 如何保证 JWT 安全
  4. 4. jwt的框架:JJWT
    1. 4.1. 规范兼容:
  5. 5. 这里以github上的demo演示,理解原理,集成到自己项目中即可。
    1. 5.0.1. 应用采用 spring boot + angular + jwt