springboot 中使用JWT保护资源安全

  • 什么是JWT

Json Web Token(JWT)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于在各方之间以JSON对象的形式安全地传输信息。此信息可以验证和信任,因为它是数字签名的。jwt可以使用密钥(使用HMAC算法)或使用RSA或ECDSA的公钥/私钥对进行签名。

尽管jwt可以被加密以提供双方之间的保密性,但我们将重点关注签名令牌。签名令牌可以验证其中包含的声明的完整性,而加密令牌则向其他方隐藏这些声明。当使用公钥/私钥对对令牌进行签名时,签名还证明只有持有私钥的一方才是签名方。

  • 什么时候使用JWT

以下是JSON Web Tokens使用的一些场景:

授权:这是使用JWT最常见的场景。一旦用户登录,每个后续请求将包括JWT,允许用户访问该令牌允许的路由、服务和资源。单点登录(Single-Sign-On)是目前广泛使用JWT的一个特性,因为它的开销很小,而且能够轻松地跨不同的域使用。

信息交换:JSON Web Tokens 是一种在各方之间安全传输信息的好方法。因为jwt可以使用公钥/私钥对进行签名,因此可以确保发送者是他们所说的人。另外,由于签名是使用报头和有效负载计算的,所以您还可以验证内容没有被篡改。

  • JWT结构

JSON Web Tokens 由三个部分组成,用点(.)隔开,分别是:

1、Header

2、Payload

3、Signature

如下形式:

xxxx.yyyy.zzzz

Header(头)

头通常由两部分组成:Token的类型(JWT)和 签名算法,如HMAC、SHA256或RSA。

示例:

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

这个JSON会被Base64Url编码,形成JWT的第一部分。

Payload(负载)

Token的第二部分是有效负载,它包含声明。声明是关于实体(通常是用户)和附加数据。有三种类型的声明:注册声明、公共声明和私有声明。

注册声明:这些是一组预定义的声明,这些声明不是强制性的,而是推荐的,以提供一组有用的、可互操作的声明。其中包括:iss(发行者)、exp(到期时间)、sub(主题)、aud(接受jwt的一方)和其他。

公共声明:这些可以由使用jwt的用户随意定义。一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密。

私有声明:这些是为在同意使用它们的各方之间共享信息而创建的自定义声明,这些声明既不是注册的,也不是公开的。

示例:

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

然后对有效负载进行Base64Url编码,以形成JSON Web Tokens的第二部分。

Signature(签名)

要创建签名部分,您必须获取编码的头、负载、密钥、算法,并对其进行签名。

示例:

HMACSHA256(   base64UrlEncode(header) + "." +   base64UrlEncode(payload),   secret )

签名用于验证消息在整个过程中没有被更改,如果是使用私钥签名的Token的情况下,它还可以验证JWT的发送者是它所说的人。

JWT示例:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiYWRtaW4iLCJpZCI6IjEiLCJleHAiOjE2MDY5NzA0ODd9.uMB243IGMnms3KtYPqZR4JJQoXePdzdBg8X8uaOkISI

接下来在springboot中使用jwt

加入依赖:

<dependency> 			<groupId>com.auth0</groupId> 			<artifactId>java-jwt</artifactId> 			<version>3.11.0</version> </dependency>

实体,DAO,Service类

@Entity @Table(name = "T_USERS") public class Users implements Serializable {  	private static final long serialVersionUID = 1L;  	@Id 	private String id ; 	private String username ; 	private String password ; }  public interface UsersRepository extends JpaRepository<Users, String>, JpaSpecificationExecutor<Users> {  	Users findByUsernameAndPassword(String username, String password) ; 	 }  @Service public class UsersService { 	 	@Resource 	private UsersRepository usersRepository ; 	 	public Users login(String username, String password) { 		return usersRepository.findByUsernameAndPassword(username, password) ; 	} 	 }

两个API接口,一个登录,一个作为测试资源接口

@RestController @RequestMapping("/users") public class UsersController { 	 	@Resource 	private UsersService usersService ; 	 	@GetMapping("/login") 	public String login(String username, String password) { 		Users users = usersService.login(username, password) ; 		if (users != null) { 			return JwtUtils.genToken(users) ; 		} 		return null ; 	} 	 }  @RestController @RequestMapping("/res") public class ResourceController { 	 	@GetMapping("/info") 	public Object infos() { 		return "resource" ; 	} 	 }

生成JWT 的工具类:

public class JwtUtils { 	public static String genToken(Users users) { 		Builder builder = JWT.create() ; 		builder.withClaim("id", users.getId()) ; 		builder.withClaim("name", users.getUsername()) ; 		LocalDateTime ldt = LocalDateTime.now().plusMinutes(30) ; 		// 过期时间 		builder.withExpiresAt(Date.from(ldt.atZone(ZoneId.systemDefault()).toInstant())) ; 		// 算法 		Algorithm algorithm = Algorithm.HMAC256("abc") ; 		return builder.sign(algorithm) ; 	} }

为了简单,密码直接写死“abc”

这里可以看看是怎么生成的token。sign方法:


springboot 中使用JWT保护资源安全

进入JWTCreator的sign方法


springboot 中使用JWT保护资源安全

这里接看出来JWT 3部分是怎么生成的了。

拦截器

public class SecurityInterceptor implements HandlerInterceptor {  	@Override 	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) 			throws Exception { 		String token = extractToken(request) ; 		if (token == null) { 			throw new RuntimeException("缺失有效token信息") ; 		} 		JWTVerifier verify = JWT.require(Algorithm.HMAC256("abc")).build() ; 		try { 			verify.verify(token) ; 		} catch (Exception e) { 			throw new RuntimeException(e) ; 		} 		return HandlerInterceptor.super.preHandle(request, response, handler); 	}  	private String extractToken(HttpServletRequest request) { 		String token = request.getHeader("access-token") ; 		if (token == null) { 			token = request.getParameter("access-token") ; 		} 		return token ; 	} 	 }

拦截器配置

@Configuration public class WebConfig implements WebMvcConfigurer {  	@Override 	public void addInterceptors(InterceptorRegistry registry) { 		registry.addInterceptor(new SecurityInterceptor()).addPathPatterns("/res/**") ; 	} 	 } 

到此所有的类完成

测试:

直接访问资源接口/res/info


springboot 中使用JWT保护资源安全

这里没有写全局异常拦截,看到效果即可。

通过登录获取token


springboot 中使用JWT保护资源安全

将token信息加入到其他接口(资源接口)的请求header中


springboot 中使用JWT保护资源安全

您可能还会对下面的文章感兴趣: