十、SpringBoot整合Token

十、SpringBoot整合Token

一日一步,小步? 大步?
聚沙成塔,集腋成裘。

JWT
1、什么是JSON Web Token?
JSON Web Token(JWT)是一个开放标准(RFC 7519),它定义了一种紧凑且独立的方式,用于在各方之间作为JSON对象安全地传输信息。此信息可以通过数字签名进行验证和信任。JWT可以使用秘密(使用HMAC算法)或使用RSA或ECDSA的公钥/私钥对进行签名。

虽然JWT可以加密以在各方之间提供保密,但我们将专注于签名令牌。签名令牌可以验证其中包含的声明的完整性,而加密令牌则隐藏其他方的声明。当使用公钥/私钥对签名令牌时,签名还证明只有持有私钥的一方是签署它的一方。

2、什么时候应该使用JSON Web令牌?
以下是JSON Web令牌有用的一些场景:

授权:这是使用JWT的最常见方案。一旦用户登录,每个后续请求将包括JWT,允许用户访问该令牌允许的路由,服务和资源。Single Sign On是一种现在广泛使用JWT的功能,因为它的开销很小,并且能够在不同的域中轻松使用。
信息交换:JSON Web令牌是在各方之间安全传输信息的好方法。因为JWT可以签名 - 例如,使用公钥/私钥对 - 您可以确定发件人是他们所说的人。此外,由于使用标头和有效负载计算签名,您还可以验证内容是否未被篡改。
什么是JSON Web令牌结构?
在紧凑的形式中,JSON Web Tokens由dot(.)分隔的三个部分组成,它们是:

Header
Payload
Signature
因此,JWT通常如下所示。

 xxxxx.yyyyy.zzzzz

Header
标头通常由两部分组成:令牌的类型,即JWT,以及正在使用的签名算法,例如HMAC SHA256或RSA。

例如:

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

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

Payload
令牌的第二部分是payload,其中包含声明。声明是关于实体(通常是用户)和其他数据的声明。声明有三种类型:注册,公开和私有声明。

已注册的声明:这些是一组预定义声明,不是强制性的,但建议使用,以提供一组有用的,可互操作的声明。其中一些是:iss (issuer), exp (expiration time), sub (subject), aud(audience)等。

请注意,声明名称只有三个字符,因为JWT意味着紧凑。

公开声明:这些可以由使用JWT的人随意定义。但为避免冲突,应在 IANA JSON Web令牌注册表中定义它们,或者将其定义为包含防冲突命名空间的URI。

私有声明:私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。

示例payload可以是:

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

然后,payload经过Base64Url编码,形成JSON Web令牌的第二部分。

请注意,对于签名令牌,此信息虽然可以防止被篡改,但任何人都可以读取。除非加密,否则不要将秘密信息放在JWT的payload或header中。

Signature
要创建签名部分,必须采用 header, payload, secret,标头中指定的算法,并对其进行签名。

例如,如果要使用HMAC SHA256算法,将按以下方式创建签名:

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

签名用于验证消息在此过程中未被更改,并且,在使用私钥签名的令牌的情况下,它还可以验证JWT的发件人是否是它所声称的人。

全部放在一起
输出是三个由点分隔的Base64-URL字符串,可以在HTML和HTTP环境中轻松传递,而与基于XML的标准(如SAML)相比更加紧凑。

下面显示了一个JWT,它具有先前的头和有效负载编码,并使用机密签名。

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiIxODcxMTM0Njc1NiIsImV4cCI6MTU3MDM1OTA0MywiaWF0IjoxNTcwMzU1NDQzfQ.vul-EMRIhkW0l2kZwPmZOTbgfwOjVQJSus_Gwt39CSE

JWT工作原理
在这里插入图片描述
1)用户登陆的时候使用用户名和密码发送POST请求。

2)服务器使用私钥创建一个jwt。

3)服务器返回这个jwt到浏览器。

4)浏览器将该jwt串加入请求头中向服务器发送请求。

5)服务器验证jwt。

6)返回相应的资源给客户端。

SpringBoot集成Token

1、引入依赖

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

2、定义注解
PassToken:跳过验证。

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PassToken {
     boolean required() default true;
}

3、UserLoginToken:需要验证。

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface UserLoginToken {
	 boolean required() default true;
}

4、定义实体类

public class UserVO implements Serializable {
	private int userId;
	private String userAccount;
	private String userPassword;

public int getUserId() {
    return userId;
}

public void setUserId(int userId) {
    this.userId = userId;
}

public String getUserAccount() {
    return userAccount;
}

public void setUserAccount(String userAccount) {
    this.userAccount = userAccount;
}

public String getUserPassword() {
    return userPassword;
}

public void setUserPassword(String userPassword) {
    this.userPassword = userPassword;
}
}

5、编写TokenService

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.hu.libraryManagement.VO.UserVO;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.HashMap;

/***
 * token 下发
 */
@Service
public class TokenService {

  public String getToken(UserVO userVo) {
  	  Date start = new Date();
  	  long currentTime = System.currentTimeMillis() + 60* 60 * 1000;//一小时有效时间
      Date end = new Date(currentTime);
   	 //设置头信息
   	 HashMap<String, Object> header = new HashMap<>(2);
   	 header.put("typ", "JWT");
   	 header.put("alg", "HS256");
     String token = "";

   	 /**
     * withHeader http头部信息
     * withAudience 用户登录(user)信息   在Token拦截器(AuthenticationInterceptor)中通过	JWT.decode(token).getAudience().get(0)提取
     * withIssuedAt(start) withExpiresAt(end) 有效时间
     * sign Algorithm.私钥及加密算法
     * */
    token = JWT.create().withHeader(header).withAudience(userVo.getUserAccount()).withIssuedAt(start).withExpiresAt(end)
            .sign(Algorithm.HMAC256(userVo.getUserPassword()));  // Algorithm. 私钥及加密算法
   	 return token;
	}
}

6、编写拦截器

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.hu.libraryManagement.VO.UserVO;
import com.hu.libraryManagement.annotation.PassToken;
import com.hu.libraryManagement.annotation.UserLoginToken;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.annotation.Reference;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.logging.Logger;


// 编写Token拦截器
public class AuthenticationInterceptor implements HandlerInterceptor {

	  Logger logger = 	Logger.getLogger("com.hu.libraryManagement.service.AuthenticationInterceptor");

	@Autowired
    UserService userService;

	@Override
	public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception {
   		 // 从 http 请求头中取出 token
    	String token = httpServletRequest.getHeader("token");
    	// 如果不是映射到方法直接通过
    	if (!(object instanceof HandlerMethod)) {
       		 return true;
   		 }
   		 HandlerMethod handlerMethod = (HandlerMethod) object;
   		 Method method = handlerMethod.getMethod();
    	//检查是否有passtoken注释,有则跳过认证
   		 if (method.isAnnotationPresent(PassToken.class)) {
       		 PassToken passToken = method.getAnnotation(PassToken.class);
      		  if (passToken.required()) {
           		 return true;
       		 }
   		 }
    	//检查有没有需要用户权限的注解
    	if (method.isAnnotationPresent(UserLoginToken.class)) {
        	UserLoginToken userLoginToken = method.getAnnotation(UserLoginToken.class);
       		 if (userLoginToken.required()) {
            	// 执行认证
           		 if (token == null) {
               		 throw new RuntimeException("无token,请重新登录");
           		 }
            	// 获取 token 中的 userAccount
            	String userAccount;
           		 try {
               		 // 获得在Token中的userAccount (TokenService 中withAudience(userVo.getUserAccount()) 添加的userAccount)
                	userAccount = JWT.decode(token).getAudience().get(0);
           		 } catch (JWTDecodeException j) {
                	throw new RuntimeException("401");
            	}
           		 logger.info("userAccount: "+ userAccount );

            	UserVO userVo;
            	userVo = userService.selcet(userAccount);
           		 if (null == userVo) {
               		 throw new RuntimeException("用户不存在,请重新登录");
           		 }
            	// 验证 token
            	JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(userVo.getUserPassword())).build();
           		 try {
               		 jwtVerifier.verify(token);
           		 } catch (JWTVerificationException e) {
               		 throw new RuntimeException("401");
           		 }
           		 return true;
	         }
    	}
    	return true;
	}

	@Override
	public void postHandle(HttpServletRequest httpServletRequest,
                       HttpServletResponse httpServletResponse,
                       Object o, ModelAndView modelAndView) throws Exception {

	}

	@Override
	public void afterCompletion(HttpServletRequest httpServletRequest,
                            HttpServletResponse httpServletResponse,
                            Object o, Exception e) throws Exception {
	}
}

在这里插入图片描述

7、配置拦截器

 @Configuration
public class InterceptorConfig implements WebMvcConfigurer {
  @Override
 public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(authenticationInterceptor())
            .addPathPatterns("/**");
}
@Bean
public AuthenticationInterceptor authenticationInterceptor() {
    return new AuthenticationInterceptor();
}
}

8、在controller中使用

@RestController
@CrossOrigin
@RequestMapping("/user")
@Api(tags = "用户登录")
public class LoginController extends BaseController {
	@Reference
	UserService userService;
	@Reference
	TokenService tokenService;

 //登录
	@PostMapping("/login")
	@ResponseBody
 public Object login(UserVO user) {
    JSONObject jsonObject = new JSONObject();
    UserVO userVo = userService.select(user.getUserAccount());
    if (userVo == null) {
        jsonObject.put("message", "登录失败,用户不存在");
        return jsonObject;
    } else {
        if (!userVo.getUserPasswd().equals(user.getUserPasswd())) {
            jsonObject.put("message", "登录失败,密码错误");
            return jsonObject;
        } else {
            String token = tokenService.getToken(userVo);
            jsonObject.put("token", token);
            jsonObject.put("user", userVo);
            return jsonObject;
        }
    }
}

@UserLoginToken
@GetMapping("/getMessage")
public String getMessage() {
    return "你已通过验证";
}
}

即传入token才可以调用getMessage()方法。

9、测试

接口调试
1、调用getMessage()
使用postmen调用接口:http://localhost:8080/user/getMessage
输出错误信息如下:
在这里插入图片描述
2、调用login()
使用postmen调用接口:http://localhost:8080/user/login
传入id,用户名和密码三个参数,输出结果如下:
在这里插入图片描述
3、再次调用getMessage()
在Headers中加入token参数,输出结果如下:
在这里插入图片描述

————————————————
版权声明:本文为CSDN博主「lamarsan」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_16410733/article/details/96625801

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值