优秀的编程知识分享平台

网站首页 > 技术文章 正文

springboot 中使用JWT保护资源安全

nanyue 2024-08-17 19:15:48 技术文章 20 ℃
  • 什么是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方法:


进入JWTCreator的sign方法


这里接看出来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


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

通过登录获取token


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


都是很基础的东西。

完毕!!!

给个转发,关注吧,谢谢

Spring MVC 异步请求方式

Spring MVC 异常处理方式

Spring Cloud Sentinel 熔断降级

SpringCloud Nacos 整合feign

SpringMVC参数统一验证方法

SpringBoot多数据源配置详解

SpringCloud Hystrix实现资源隔离应用

SpringBoot开发自己的Starter

SpringBoot RabbitMQ消息可靠发送与接收

Springboot Security 基础应用 (1)

SpringBoot开发自己的@Enable功能

Springboot接口幂等性基于token实现方案

Tags:

最近发表
标签列表