首先认识一下什么是jwt?
首先jwt其实是三个英语单词JSON Web Token的缩写。通过全名你可能就有一个基本的认知了。token一般都是用来认证的,比如我们系统中常用的用户登录token可以用来认证该用户是否登录。jwt也是经常作为一种安全的token使用。
与传统的单点登录相比?
1、在传统的用户登录认证中,因为http是无状态的,所以都是采用session方式。用户登录成功,服务端会保证一个session,当然会给客户端一个sessionId,客户端会把sessionId保存在cookie中,每次请求都会携带这个sessionId。
cookie+session这种模式通常是保存在内存中,而且服务从单服务到多服务会面临的session共享问题,随着用户量的增多,开销就会越大。而JWT不是这样的,只需要服务端生成token,客户端保存这个token,每次请求携带这个token,服务端认证解析就可。
2、 JWT方式校验方式更加简单便捷化,无需通过redis缓存,而是直接根据token取出保存的用户信息,负载中包含了所有用户所需要的信息,避免了多次查询数据库,以及对token可用性校验,单点登录,验证token更为简单。
jwt的组成
JWT主要包含三个部分之间用英语句号'.'隔开
- Header 头部
- Payload 负载
- Signature 签名
注意,顺序是 header.payload.signature
一个完整的token是:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJzdWIiOiI1ZDFiZmM1MmFiNWU0MzU2OGJjNmExYWRlOGYxNjYzYiIsImlhdCI6MTU5NjE3Nzc0NCwiZXhwIjoxNTk2MTgxMzQ0fQ.KC-8HACY9QAGygdClX12W_alg3jbKcTZPCS2-He51bYaMu1lzeQX6IP_GdV2nuEZI5bH8-EYTM1hsu22czEY8g
jwt的验证流程:
1、在头部信息中声明加密算法和常量, 然后把header使用json转化为字符串
2、 在载荷中声明用户信息,同时还有一些其他的内容;再次使用json 把载荷部分进行转化,转化为字符串
3、 使用在header中声明的加密算法和每个项目随机生成的secret来进行加密, 把第一步分字符串和第二部分的字符串进行加密, 生成新的字符串。词字符串是独一无二的。
4、 解密的时候,只要客户端带着JWT来发起请求,服务端就直接使用secret进行解密。
下边跟着我可以一步一步部署,可以直接粘贴进行运行部署
一、加入maven依赖,和application的配置文件
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.7.0</version>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
配置文件:
#springboot整合jwt #加密秘钥,可以随意定义 config.jwt.secret=abcdefg1234567 #超时时间,token有效时长,单位是秒 config.jwt.expire=3600 #header名称 config.jwt.header=token
二、具体代码
目录结构如下 以下代码可以直接复制就可以。
JwtConfig 类
package com.honghe.cutpicture.common.config;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
* JWT的token,区分大小写
*
* @Auther: 赵利伟
* @Date: 2020/7/31 0031 10:06
* @Description: jwt的配置
*/
@ConfigurationProperties(prefix = "config.jwt")
@Component
public class JwtConfig {
// 秘钥
private String secret;
// 超时时间
private long expire;
//头信息
private String header;
/**
* 生成token
*
* @param subject
* @return
*/
public String createToken(String subject) {
Date nowDate = new Date();
//过期时间
Date expireDate = new Date(nowDate.getTime() + expire * 1000);
return Jwts.builder()
.setHeaderParam("typ", "JWT")
.setSubject(subject)
.setIssuedAt(nowDate)
.setExpiration(expireDate)
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
/**
* 获取token中注册信息
*
* @param token
* @return
*/
public Claims getTokenClaim(String token) {
try {
return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 验证token是否过期失效
*
* @param expirationTime
* @return
*/
public boolean isTokenExpired(Date expirationTime) {
return expirationTime.before(new Date());
}
/**
* 获取token失效时间
*
* @param token
* @return
*/
public Date getExpirationDateFromToken(String token) {
return getTokenClaim(token).getExpiration();
}
/**
* 获取用户名从token中
*/
public String getUsernameFromToken(String token) {
return getTokenClaim(token).getSubject();
}
/**
* 获取jwt发布时间
*/
public Date getIssuedAtDateFromToken(String token) {
return getTokenClaim(token).getIssuedAt();
}
// --------------------- getter & setter ---------------------
public String getSecret() {
return secret;
}
public void setSecret(String secret) {
this.secret = secret;
}
public long getExpire() {
return expire;
}
public void setExpire(long expire) {
this.expire = expire;
}
public String getHeader() {
return header;
}
public void setHeader(String header) {
this.header = header;
}
}
WebConfig 类
package com.honghe.cutpicture.common.config;
import com.honghe.cutpicture.common.interceptor.TokenInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.validation.MessageCodesResolver;
import org.springframework.validation.Validator;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.config.annotation.*;
import javax.annotation.Resource;
import java.util.List;
/**
* @Auther: 注册拦截到MVC
* @Date: 2020/7/31 0031 10:50
* @Description: 赵利伟
*/
@Configuration
public class WebConfig implements WebMvcConfigurer{
@Resource
private TokenInterceptor tokenInterceptor ;
@Override
public void configurePathMatch(PathMatchConfigurer pathMatchConfigurer) {
}
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer contentNegotiationConfigurer) {
}
@Override
public void configureAsyncSupport(AsyncSupportConfigurer asyncSupportConfigurer) {
}
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer defaultServletHandlerConfigurer) {
}
@Override
public void addFormatters(FormatterRegistry formatterRegistry) {
}
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(tokenInterceptor).addPathPatterns("/**");
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry resourceHandlerRegistry) {
}
@Override
public void addCorsMappings(CorsRegistry corsRegistry) {
}
@Override
public void addViewControllers(ViewControllerRegistry viewControllerRegistry) {
}
@Override
public void configureViewResolvers(ViewResolverRegistry viewResolverRegistry) {
}
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> list) {
}
@Override
public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> list) {
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> list) {
}
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> list) {
}
@Override
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> list) {
}
@Override
public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> list) {
}
@Override
public Validator getValidator() {
return null;
}
@Override
public MessageCodesResolver getMessageCodesResolver() {
return null;
}
}
TokenInterceptor拦截器,用户登录返回token信息,登录的时候不需要token验证,拦截器里边会直接过滤掉。以后所有的前端发过来的接口都会带着token验证
package com.honghe.cutpicture.common.interceptor;
import com.honghe.cutpicture.common.Result;
import com.honghe.cutpicture.common.config.JwtConfig;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.SignatureException;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @Auther: 赵利伟
* @Date: 2020/7/31 0031 10:43
* @Description: 拦截器
*/
@Component
public class TokenInterceptor extends HandlerInterceptorAdapter{
@Resource
private JwtConfig jwtConfig ;
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws SignatureException {
/** 地址过滤 */
String uri = request.getRequestURI() ;
if (uri.contains("/login")){
return true ;
}
/** Token 验证 */
String token = request.getHeader(jwtConfig.getHeader());
if(StringUtils.isEmpty(token)){
token = request.getParameter(jwtConfig.getHeader());
}
if(StringUtils.isEmpty(token)){
throw new SignatureException(jwtConfig.getHeader()+ "不能为空");
}
Claims claims = null;
try{
claims = jwtConfig.getTokenClaim(token);
if(claims == null || jwtConfig.isTokenExpired(claims.getExpiration())){
// throw new Exception(jwtConfig.getHeader() + "失效,请重新登录。");
throw new SignatureException(jwtConfig.getHeader() + "失效,请重新登录。");
}
}catch (Exception e){
throw new SignatureException(jwtConfig.getHeader() + "失效,请重新登录。");
}
/** 设置 identityId 用户身份ID */
if (claims!=null){
request.setAttribute("identityId", claims.getSubject());
return true;
}else {
return false;
}
}
}
三、自己封装的result结果类和测试的controller
Result.java
package com.honghe.cutpicture.common;
import com.alibaba.fastjson.JSONObject;
/**
* @Description 返回结果实体对象
* @Author zhaoliwei
* @Date: 2017-11-29 14:43
*/
//@JsonInclude(NON_NULL)
public final class Result {
private String type;
private int code = 0;
private String msg = "";
private Object result;
JSONObject other = new JSONObject();
public Result(){}
/**
* 构造方法
* @param type 请求
* @param statusCode 错误码
* @param msgInfo 提示信息
*/
public Result(String type, int statusCode, String msgInfo) {
other.put("msg",msgInfo);
this.type = type;
this.code = statusCode;
}
public Result(String type, int statusCode, Object result) {
this(type, statusCode, result, "");
}
public Result(String type, int statusCode) {
this(type, statusCode, null, "");
}
public Result(String type, int statusCode, Object result, String msgInfo) {
other.put("msg",msgInfo);
this.type = type;
this.code = statusCode;
this.result = result;
}
public Result(int statusCode, String msgInfo) {
other.put("msg",msgInfo);
this.code = statusCode;
}
public Result(int statusCode, Object result) {
this(statusCode, result, "");
}
public Result(int statusCode) {
this(statusCode, null, "");
}
public Result(int statusCode, Object result, String msgInfo) {
other.put("msg",msgInfo);
this.code = statusCode;
this.result = result;
}
public String getMsg() {
return msg;
}
public JSONObject getOther() {
return other;
}
public void setOther(JSONObject other) {
this.other = other;
}
public void setMsg(String other) {
this.other.put("msg",other);
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public int getCode() {
return code;
}
public void setCode(int statusCode) {
this.code = statusCode;
}
public Object getResult() {
return result;
}
public void setResult(Object result) {
this.result = result;
}
public enum Code{
NoSuchMethod(-2),
Success(0),
ParamError(-1),
UnKnowError(-3);
private Code(int value){
this.value = value;
}
private int value = 0;
public int value(){
return value;
}
}
public enum Msg{
NoSuchMethod("没有此方法"),
Success("执行成功"),
ParamError("参数错误"),
UnKnowError("内部错误");
private Msg(String msg){
this.msg = msg;
}
private String msg = "";
public String value(){
return msg;
}
}
}
TokenController 我自己封装的测试类,大家可以按自己的想法去测试 也可以直接复制我的,来搭建
package com.honghe.cutpicture.controller;
import com.alibaba.fastjson.JSONObject;
import com.honghe.cutpicture.common.Result;
import com.honghe.cutpicture.common.config.JwtConfig;
import com.honghe.cutpicture.service.UserService;
import org.springframework.util.StringUtils;
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.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
/**
* @Auther: 赵利伟
* @Date: 2020/7/31 0031 13:27
* @Description:
*/
@RestController
public class TokenController {
@Resource
private JwtConfig jwtConfig ;
@Resource
private UserService userService;
/**
* 用户登录接口
* @param userName
* @param passWord
* @return
*/
@PostMapping("/login")
public Result login (@RequestParam("userName") String userName,
@RequestParam("passWord") String passWord){
JSONObject json = new JSONObject();
Result result = new Result();
String userId = userService.login(userName,passWord);
// 这里模拟通过用户名和密码,从数据库查询userId
String token = jwtConfig.createToken(userId) ;
if (!StringUtils.isEmpty(token)) {
json.put("token",token) ;
result.setCode(0);
result.setResult(json);
result.setMsg("登录成功");
}else {
result.setCode(-1);
result.setResult(json);
result.setMsg("登录失败");
}
return result ;
}
/**
* 需要 Token 验证的接口
*/
@PostMapping("/info")
public Result info (){
return new Result("info_ACK",0,"接口调用成功") ;
}
/**
* 根据请求头的token获取userId
* @param request
* @return
*/
@GetMapping("/getUserInfo")
public Result getUserInfo(HttpServletRequest request){
String usernameFromToken = jwtConfig.getUsernameFromToken(request.getHeader("token"));
return new Result(0,usernameFromToken);
}
/*
为什么项目重启后,带着之前的token还可以访问到需要info等需要token验证的接口?
答案:只要不过期,会一直存在,类似于redis
*/
}