常用工具类之jwt的学习使用

什么是jwt

首先jwt其实是三个英语单词JSON Web Token的缩写。通过全名你可能就有一个基本的认知了。token一般都是用来认证的,比如我们系统中常用的用户登录token可以用来认证该用户是否登录。jwt也是经常作为一种安全的token使用。

JWT的定义:
JWT是一种用于双方之间传递安全信息的简洁的、URL安全的表述性声明规范。JWT作为一个开放的标准(RFC 7519),定义了一种简洁的,自包含的方法用于通信双方之间以Json对象的形式安全的传递信息。因为数字签名的存在,这些信息是可信的,JWT可以使用HMAC算法或者是RSA的公私秘钥对进行签名。
JWT特点:
简洁(Compact): 可以通过URL,POST参数或者在HTTP header发送,因为数据量小,传输速度也很快
自包含(Self-contained):负载中包含了所有用户所需要的信息,避免了多次查询数据库

简单来说:

  • JWT 全称JSON Web Token ,简单的说就是用户登录成功之后,将用户的信息进行加密,然后生成一个token 返回给客户端

JWT可以做什么

授权:这是使用 JWT 最常见的场景。用户登录后,每个后续请求都将包含 JWT,从而允许用户访问该令牌允许的路由、服务和资源。单点登录是当今广泛使用 JWT 的一项功能,因为它的开销很小并且能够在不同的域中轻松使用。
信息交换:JSON Web 令牌是在各方之间安全传输信息的好方法。因为可以对 JWT 进行签名(例如,使用公钥/私钥对),所以可以确定发件人就是他们所说的那个人。此外,由于使用标头和有效负载计算签名,还可以验证内容没有被篡改。

为什么是jwt

基于传统的session

认证方式

我们知道,http协议本身是一种无状态的协议,而这就意味着如果用户向我们的应用提供了
用户名和密码来进行用户认证,那么下一次请求时,用户还要进行一次用户认证才行,因为
根据http协议,我们并不知道是那个用户发出的请求。所以为了让我们的应用能识别是那个
用户发出的请求,我们只能在服务器存储一份用户的登陆信息,这份登陆信息会在响应时传
递给浏览器,告诉其保存为cookie,以便下次请求发送给我们的应用,这样我们的应用就能
识别请求来自那个用户了,这就是传统的基于session认证

认证流程

交互流程

暴露问题

1.每个用户经过我们的应用认证之后,我们的应用都是要在服务器端做一次记录,以方便用户下次请求的鉴别,通常而言session都是保存在内存中的,而随着认证用户的增多,服务器的压力开销就会明显增大

2,用户认证之后,服务端做认证记录,如果认证的记录被保存在内存中的话,这意味着用户下次请求还必须要请求在这台服务器上,这样才能拿到授权的资源,这样在分布式的应用上,相应限制了负载均衡的能力,遮这也意味着限制了应用的扩展。

3,因为是基于cookie来进行用户识别,cokkie如果被拦截,用就很容易受到跨站请求伪造的攻击

4,前后盾分离系统中更痛苦,
- 前后端分离在应用解耦后增加了部署的复杂性。通常用户一次请求就要转发多次,如果用session每次携带sessionid 到服务器,服务器还要查询用户信息。
- 同时如果用户很多,这些信息存储在服务器内存中,给服务器增加负担。
- 容易受到CSRF(跨站伪造请求攻击)攻击,session是基于cookie进行用户识别的,cookie如果被拦截,用户就会很容易受到跨站请求伪造的攻击。
- sessionid就是一个特征值,表达的信息不够准确,不容易扩展。而且如果你后端应用是多节点部署。那么就需要实现session共享机制,不方便集群

认证:

用户认证是判断一个用户的身份是否合法的过程,用户去访问系统资源时系统要求验证用户的身份信 息,身份合法方可继续访问,不合法则拒绝访问。常见的用户身份认证方式有:用户名密码登录,二维码登录,手 机短信登录,指纹认证等方式。

会话:

用户认证通过后,为了避免用户的每次操作都进行认证可将用户的信息保证在会话中。会话就是系统为了保持当前 用户的登录状态所提供的机制,常见的有基于session方式、基于token方式等。

session认证方式‘

它的交互流程是,用户认证成功后,在服务端生成用户相关的数据保存在session(当前会话)中,发给客户端的
sesssion_id 存放到 cookie 中,这样用户客户端请求时带上 session_id 就可以验证服务器端是否存在 session 数
据,以此完成用户的合法校验,当用户退出系统或session过期销毁时,客户端的session_id也就无效了。

授权:

授权是用户认证通过根据用户的权限来控制用户访问资源的过程,拥有资源的访问权限则正常访问,没有 权限则拒绝访问。

JWT的结构是什么

令牌的组成

	1. 标头(Header)
	2,有效荷载(Payload)
	3,签名(Stringture)
	因此,JWT通常为 Header.Payload,Signature

Header

- 标头通常由两部分组成:令牌的类型(即JWT)和所使用的签名算法,例如HMAC SHA256 或RSA 。他会使用Base64 编码组成JWT结构的第一部分

- 注意:Base64 是一种编码,也就是说,它是可以被翻译会原来的样子。并不是一种加密过程
{
	"alg":"HS256",
	"typ":"JWT"
}

Payload(不要把敏感信息放在这里,会被解密)

- 令牌的第二部分是有效负载,其中包含声明。声明是有关实体(通常是用户)和其他数据的
声明。同样的,他会使用Base64编码组成JWT结构的第二部分
{
	"sub":"12345",
	"name":"John Doe",
	"admin":true
}

Signature

- 前两部分都是使用Base64进行编码的,即前端可以解开知道里面的信息。Signature需要使
用编码后的Header和payload以及我们提供的一个秘钥,然后使用haeder中指定的算法(HS256)
进行签名,签名的作用是保证JWT没有被篡改过

签名的目的

-保证内容没有被篡改过
最后一步签名的过程实际上对头部以及负载内容进行签名,防止内容被更改。第三部分的内容是
第一部分加第二部分和加密盐生成的,如果更改了前面的内容生成的签名就会发生改变。

信息安全

- 在这里我们一定会问一个问题:Base64是一种编码,是可逆的,那我们的数据不就被暴露了吗

- 是的,因为我们一般不会将敏感信息放在负载里面。因此JWT适用于向web应用传递非敏感信息。
JWT还用于设计用户的认证和授权系统,甚至实现web应用单点登陆

使用jwt

1.引入依赖

<!-- https://mvnrepository.com/artifact/com.auth0/java-jwt -->
<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.19.1</version>
</dependency>

2.生成token

     public static void main(String[] args) {
       HashMap<String, Object> map = new HashMap<>();
        Calendar instance = Calendar.getInstance();
        instance.add(Calendar.SECOND,98);
        //生成令牌
        String token=JWT.create()
                .withHeader(map)//header
                .withClaim("userId",21)//设置自定义用户id
                .withClaim("username","张三")//设置自定义用户名
                .withExpiresAt(instance.getTime())//设置过期时间
                .sign(Algorithm.HMAC256("token!Q2M#E$RW"));//设置签名 保留 复杂
        //输出打印
        System.out.println(token);
    }

根据令牌和签名解析数据

 //验证对象
    public static void main(String[] args) {
        //创建验证对象
        JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("token!Q2M#E$RW")).build();

        DecodedJWT verify = jwtVerifier.verify("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2NjY1MDY2NDgsInVzZXJJZCI6MjEsInVzZXJuYW1lIjoi5byg5LiJIn0.erWPVY6un_1a9-l1IYEibQW_XCYYmTIjQE4Iqkw9Iz0");

        System.out.println(verify.getClaim("userid").asInt());
        System.out.println(verify.getClaim("username").asString());
        System.out.println("过期时间"+verify.getExpiresAt());
//        System.out.println(verify.getClaim("username").asString());
//        System.out.println(verify.getClaims().get("userid").asString());
//        System.out.println(verify.getClaims().get("username").asString());

    }

生成令牌/解析令牌(签名算法和密钥要相同,否则会抛异常)案例

public static void main(String[] args) {
      // 生成令牌
      Map<String, Object> map = new HashMap<>();
      map.put("id", 100);
      map.put("name", "李四");
      String token = JWT.create()
              .withClaim("userId", 1) // 设置payload
              .withClaim("userName", "张三")
              .withClaim("user", map) // 自定义Map
              .withIssuedAt(new Date()) // 令牌生成时间
              .withExpiresAt(new Date(System.currentTimeMillis() + 8 * 60 * 60 * 1000)) // 令牌过期时间(8小时有效)
              .sign(Algorithm.HMAC256("Test666"));// 设置签名算法和密钥
      System.out.println("token = " + token);

      // 解析令牌
      JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("Test666")).build();
      DecodedJWT verify = jwtVerifier.verify(token);
      System.out.println("解析结果:" + verify.getClaim("userId").asInt());
      System.out.println("解析结果:" + verify.getClaim("userName").asString());
      System.out.println("解析结果:" + verify.getClaim("user").asMap());
      System.out.println("解析结果:" + verify.getClaim("user").asMap().get("id"));
      System.out.println("解析结果:" + verify.getClaim("user").asMap().get("name"));
  }

JWT工具类封装



import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;

import java.util.Calendar;
import java.util.Map;

public class JWTUtils {
private static String TOKEN="token!QM3#e4r";
/*
* 生成token
* */
public static String getToken(Map<String,String>map){
    JWTCreator.Builder builder = JWT.create();
    map.forEach((k,v)->{
        builder.withClaim(k,v);
    });
    Calendar instance = Calendar.getInstance();
    instance.add(Calendar.DATE,7);//默认7天过期
    instance.add(Calendar.MINUTE,30);// 有效时间:30分钟
    builder.withExpiresAt(instance.getTime());
    return builder.sign(Algorithm.HMAC256(TOKEN)).toString();
}

    /*
     * 生成token写法2
     * */
    public static String getToken1(Map<String,String>map){
        Calendar instance = Calendar.getInstance();
        instance.add(Calendar.DATE,7);//默认7天过期
        //创建jwt builder
        JWTCreator.Builder builder = JWT.create();
        //payload
        map.forEach((k,v)->{
            builder.withClaim(k,v);
        });
       String token= builder.withExpiresAt(instance.getTime())
                .sign(Algorithm.HMAC256(TOKEN));
       return token;
    }

/*
* 验证token
* 原本写法
*  public static void verity(String token){
     JWT.require(Algorithm.HMAC256(TOKEN)).build().verify(token);
 }
* */
//优化
    public static DecodedJWT verity(String token){
        return JWT.require(Algorithm.HMAC256(TOKEN)).build().verify(token);
    }

 /*
 *获取token中payload
 * */
/* public static DecodedJWT getToken(String token){
     return JWT.require(Algorithm.HMAC256(TOKEN)).build().verify(token);
 }*/

}

Springboot整合jwt

 <!--引入jwt-->
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.19.2</version>
        </dependency>
        <!--使用mybatis-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.2</version>
        </dependency>
        <!--使用lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!--引入druid-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.9</version>
        </dependency>
        <!--使用sql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

配置文件

server.port=9000

spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/cs?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true
spring.datasource.username=root
spring.datasource.password=root

mybatis.type-aliases-package=com.xcuin.jwt
mybatis.mapper-locations=classpath:com/xcuin/jwt/mapper/*.xml

logging.level.com.xcuin.jwt.dao=debug







实体类

import lombok.Data;
import lombok.experimental.Accessors;

@Data
@Accessors(chain = true)
public class User {
    private String id;
    private String name;
    private String password;
}

Dao+Dao.xml层

UserDao

import com.xcuin.jwt.entity.User;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserDao {
    //直接根据用户名和密码登录
    User login(User user);

}

UserDao.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.xcuin.jwt.Dao.UserDao">
    <select id="login" parameterType="User" resultType="User">
        select * from user where name=#{name} and
                                 password =#{password}
    </select>
</mapper>

UserService+UserServiceImpl层

userService

public interface UserService {
    User login(User user);//登录接口
}

userServiceImpl


import com.xcuin.jwt.Dao.UserDao;
import com.xcuin.jwt.entity.User;
import com.xcuin.jwt.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional
public class UserServiceImpl implements UserService {
    @Autowired
    private UserDao userDao;
    @Override
    @Transactional(propagation = Propagation.SUPPORTS)
    public User login(User user){
        //根据接收用户名密码查询数据库
        User login = userDao.login(user);
        if (login!=null){
            return login;
        }
        throw new RuntimeException("登陆失败");

    }

}

Controller层


import com.xcuin.jwt.Dao.UserDao;
import com.xcuin.jwt.entity.User;
import com.xcuin.jwt.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional
public class UserServiceImpl implements UserService {
    @Autowired
    private UserDao userDao;
    @Override
    @Transactional(propagation = Propagation.SUPPORTS)
    public User login(User user){
        //根据接收用户名密码查询数据库
        User login = userDao.login(user);
        if (login!=null){
            return login;
        }
        throw new RuntimeException("登陆失败");

    }

}

工具类


import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;

import java.util.Calendar;
import java.util.Map;

public class JWTUtils {
private static String TOKEN="token!QM3#e4r";
/*
* 生成token
* */
public static String getToken(Map<String,String>map){
    JWTCreator.Builder builder = JWT.create();
    map.forEach((k,v)->{
        builder.withClaim(k,v);
    });
    Calendar instance = Calendar.getInstance();
    instance.add(Calendar.DATE,7);//默认7天过期
    builder.withExpiresAt(instance.getTime());
    return builder.sign(Algorithm.HMAC256(TOKEN)).toString();
}

    /*
     * 生成token写法2
     * */
  /*  public static String getToken1(Map<String,String>map){
        Calendar instance = Calendar.getInstance();
        instance.add(Calendar.DATE,7);//默认7天过期
        //创建jwt builder
        JWTCreator.Builder builder = JWT.create();
        //payload
        map.forEach((k,v)->{
            builder.withClaim(k,v);
        });
       String token= builder.withExpiresAt(instance.getTime())
                .sign(Algorithm.HMAC256(TOKEN));
       return token;
    }*/

/*
* 验证token
* 原本写法
*  public static void verity(String token){
     JWT.require(Algorithm.HMAC256(TOKEN)).build().verify(token);
 }
* */
//优化验证token+获取token中payload
    public static DecodedJWT verity(String token){
        return JWT.require(Algorithm.HMAC256(TOKEN)).build().verify(token);
    }

 /*
 *获取token中payload
 * */
/* public static DecodedJWT getToken(String token){
     return JWT.require(Algorithm.HMAC256(TOKEN)).build().verify(token);
 }*/

}

 增加回响token


import com.xcuin.jwt.entity.User;
import com.xcuin.jwt.service.UserService;
import com.xcuin.jwt.utils.JWTUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

@RestController
@Slf4j
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/user/login")
    public Map<String,Object>login(User user){
        log.info("用户名:{{}}",user.getName());
        log.info("用户名:{{}}",user.getPassword());
        HashMap<String, Object> map = new HashMap<>();
        try {
            User login = userService.login(user);
            HashMap<String, String> payload = new HashMap<>();
            payload.put("id",login.getId());
            payload.put("name",login.getName());
            //生成jwt的令牌
            String token = JWTUtils.getToken(payload);
            map.put("state",true);
            map.put("msg","认证成功");
            map.put("token",token);
        } catch (Exception e) {
            map.put("state",false);
            map.put("msg",e.getMessage());
        }
        return map;
    }
}

新增加拦截器


import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.xcuin.jwt.utils.JWTUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;

/**
 * 定义拦截器
 * @Date: 2022/04/14 12:26
 * @Version 1.0
 **/
@Component
public class JWTInterceptor implements HandlerInterceptor {

 @Override
 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {

     HashMap<String, Object> map = new HashMap<>();

     //获取请求头中的令牌
     String token = request.getHeader("token");

   try {
    JWTUtils.verity(token);
   return  true;//放行请求
    /*
   常见异常
   SignatureVerificationException: 签名不一致异常
   TokenExpiredException: 令牌过期异常
   AlgorithmMismatchException: 算法不匹配异常
   */
   } catch (SignatureVerificationException e) {
   e.printStackTrace();
    map.put("msg","无效签名");
   } catch (TokenExpiredException e) {
       e.printStackTrace();
       map.put("msg","token过期");
   } catch (AlgorithmMismatchException e){
       e.printStackTrace();
       map.put("msg","token算法不一致");
   }catch (Exception e){
       e.printStackTrace();
       map.put("msg","token无效");
   }
   map.put("state",false);//设置状态
    //将map转为json jackjon
     String json = new ObjectMapper().writeValueAsString(map);
     response.setContentType("application/json;charset=UTF-8");
     response.getWriter().println(json);
     return false;
  }



}

配置拦截器:新建InterceptorConfig类


import com.xcuin.jwt.interceptor.JWTInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {

     @Override
    public void addInterceptors(InterceptorRegistry registry) {
      registry.addInterceptor(new JWTInterceptor())  // 注册拦截器
      .addPathPatterns("/**")   // 设置需要拦截的请求(也可设置成list集合)
      .excludePathPatterns("/**");  // 设置不需要拦截的请求(也可设置成list集合)

       }
    }

优化原先的接口


import com.auth0.jwt.interfaces.DecodedJWT;
import com.xcuin.jwt.entity.User;
import com.xcuin.jwt.service.UserService;
import com.xcuin.jwt.utils.JWTUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

@RestController
@Slf4j
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/user/login")
    public Map<String,Object>login(User user){
        log.info("用户名:{{}}",user.getName());
        log.info("用户名:{{}}",user.getPassword());
        HashMap<String, Object> map = new HashMap<>();
        try {
            User login = userService.login(user);
            HashMap<String, String> payload = new HashMap<>();
            payload.put("id",login.getId());
            payload.put("name",login.getName());
            //生成jwt的令牌
            String token = JWTUtils.getToken(payload);
            map.put("state",true);
            map.put("msg","认证成功");
            map.put("token",token);
        } catch (Exception e) {
            map.put("state",false);
            map.put("msg",e.getMessage());
        }
        return map;
    }


    @PostMapping("/user/test")
    Map<String,Object>test(HttpServletRequest request){
        HashMap<String, Object> map = new HashMap<>();
            //除理自己的业务
        String token = request.getHeader("token");
        DecodedJWT verity = JWTUtils.verity(token);
        log.info("用户id:{{}}",verity.getClaim("id").asString());
        log.info("用户name:{{}}",verity.getClaim("name").asString());
        map.put("state",true);
            map.put("msg","请求成功");
            return map;
    }
}

扩展:

扩展1:

工具类的编写

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.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
 
import java.io.Serializable;
import java.util.Calendar;
import java.util.Date;
 
/**
 * @author Lehr
 * @create: 2020-02-04
 */
public class JwtUtils {
 
    /**
     签发对象:这个用户的id
     签发时间:现在
     有效时间:30分钟
     载荷内容:暂时设计为:这个人的名字,这个人的昵称
     加密密钥:这个人的id加上一串字符串
     */
    public static String createToken(String userId,String realName, String userName) {
 
        Calendar nowTime = Calendar.getInstance();
        nowTime.add(Calendar.MINUTE,30);
        Date expiresDate = nowTime.getTime();
 
        return JWT.create().withAudience(userId)   //签发对象
                .withIssuedAt(new Date())    //发行时间
                .withExpiresAt(expiresDate)  //有效时间
                .withClaim("userName", userName)    //载荷,随便写几个都可以
                .withClaim("realName", realName)
                .sign(Algorithm.HMAC256(userId+"HelloLehr"));   //加密
    }
 
    /**
     * 检验合法性,其中secret参数就应该传入的是用户的id
     * @param token
     * @throws TokenUnavailable
     */
    public static void verifyToken(String token, String secret) throws TokenUnavailable {
        DecodedJWT jwt = null;
        try {
            JWTVerifier verifier = JWT.require(Algorithm.HMAC256(secret+"HelloLehr")).build();
            jwt = verifier.verify(token);
        } catch (Exception e) {
            //效验失败
            //这里抛出的异常是我自定义的一个异常,你也可以写成别的
            throw new TokenUnavailable();
        }
    }
 
    /**
    * 获取签发对象
    */
    public static String getAudience(String token) throws TokenUnavailable {
        String audience = null;
        try {
            audience = JWT.decode(token).getAudience().get(0);
        } catch (JWTDecodeException j) {
            //这里是token解析失败
            throw new TokenUnavailable();
        }
        return audience;
    }
 
 
    /**
    * 通过载荷名字获取载荷的值
    */
    public static Claim getClaimByName(String token, String name){
        return JWT.decode(token).getClaim(name);
    }
}

注解类的编写

在controller层上的每个方法上,可以使用这些注解,来决定访问这个方法是否需要携带token,由于默认是全部检查,所以对于某些特殊接口需要有免验证注解

免验证注解

@PassToken:跳过验证,通常是入口方法上用这个,比如登录接口

import java.lang.annotation.ElementType;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;



/**

 * @author Lehr

 * @create: 2020-02-03

 */

@Target({ElementType.METHOD, ElementType.TYPE})

@Retention(RetentionPolicy.RUNTIME)

public @interface PassToken {

    boolean required() default true;

}

拦截器的编写

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.web.servlet.config.annotation.InterceptorRegistry;

import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;



/**

* @author lehr

*/

@Configuration

public class JwtInterceptorConfig implements WebMvcConfigurer {

   @Override

   public void addInterceptors(InterceptorRegistry registry) {



       //默认拦截所有路径

       registry.addInterceptor(authenticationInterceptor())

               .addPathPatterns("/**");

   }

   @Bean

   public JwtAuthenticationInterceptor authenticationInterceptor() {

       return new JwtAuthenticationInterceptor();

   }

} 

配置拦截器:新建InterceptorConfig类

import com.auth0.jwt.interfaces.Claim;

import com.imlehr.internship.annotation.PassToken;

import com.imlehr.internship.dto.AccountDTO;

import com.imlehr.internship.exception.NeedToLogin;

import com.imlehr.internship.exception.UserNotExist;

import com.imlehr.internship.service.AccountService;

import org.springframework.beans.factory.annotation.Autowired;

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.Map;



/**

 * @author Lehr

 * @create: 2020-02-03

 */

public class JwtAuthenticationInterceptor implements HandlerInterceptor {

    @Autowired

    AccountService accountService;



    @Override

    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception {

        // 从请求头中取出 token  这里需要和前端约定好把jwt放到请求头一个叫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;

            }

        }

        //默认全部检查

        else {

            System.out.println("被jwt拦截需要验证");

            // 执行认证

            if (token == null) {

                //这里其实是登录失效,没token了   这个错误也是我自定义的,读者需要自己修改

                throw new NeedToLogin();

            }

             

            // 获取 token 中的 user Name

            String userId = JwtUtils.getAudience(token);



            //找找看是否有这个user   因为我们需要检查用户是否存在,读者可以自行修改逻辑

            AccountDTO user = accountService.getByUserName(userId);



            if (user == null) {

                //这个错误也是我自定义的

                throw new UserNotExist();

            }



            // 验证 token

            JwtUtils.verifyToken(token, userId)

                 

            //获取载荷内容

            String userName = JwtUtils.getClaimByName(token, "userName").asString();

            String realName = JwtUtils.getClaimByName(token, "realName").asString();

             

            //放入attribute以便后面调用

            request.setAttribute("userName", userName);

            request.setAttribute("realName", realName);

             



            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 {

    }

}

这段代码的执行逻辑大概是这样的:

目标方法是否有注解?如果有PassToken的话就不用执行后面的验证直接放行,不然全部需要验证
开始验证:有没有token?没有?那么返回错误
从token的audience中获取签发对象,查看是否有这个用户(有可能客户端造假,有可能这个用户的账户被冻结了),查看用户的逻辑就是调用Service方法直接比对即可
检验Jwt的有效性,如果无效或者过期了就返回错误
Jwt有效性检验成功:把Jwt的载荷内容获取到,可以在接下来的controller层中直接使用了(具体使用方法看后面的代码)
接口的编写
这里设计了两个接口:登录和查询名字,来模拟一个迷你业务,其中后者需要登录之后才能使用,大致流程如下:

登录代码

/**

     * 用户登录:获取账号密码并登录,如果不对就报错,对了就返回用户的登录信息

     * 同时生成jwt返回给用户

     *

     * @return

     * @throws LoginFailed  这个LoginFailed也是我自定义的

     */

    @PassToken

    @GetMapping(value = "/login")

    public AccountVO login(String userName, String password) throws LoginFailed{

         

        try{

            service.login(userName,password);

        }

        catch (AuthenticationException e)

        {

            throw new LoginFailed();

        }



        //如果成功了,聚合需要返回的信息

        AccountVO account = accountService.getAccountByUserName(userName);

         

        //给分配一个token 然后返回

        String jwtToken = JwtUtils.createToken(account);



        //我的处理方式是把token放到accountVO里去了

        account.setToken(jwtToken);



        return account;



    }

业务代码

这里列举一个需要登录,用来测试用户名字的接口(其中用户的名字来源于jwt的载荷部分)

@GetMapping(value = "/username")

    public String checkName(HttpServletRequest req) {

        //之前在拦截器里设置好的名字现在可以取出来直接用了

        String name = (String) req.getAttribute("userName");

        return name;

    }

扩展2:

引入依赖jar:

<!-- JWT依赖 -->
<dependency>
        <groupId>com.auth0</groupId>
        <artifactId>java-jwt</artifactId>
        <version>3.4.1</version>
    </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
        <version>0.9.1</version>
    </dependency>

添加请求拦截器WebConfig(实现WebMvcConfigurer重写addInterceptors),对请求url进行token认证

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Resource
    private JwtFilter jwtFilter ;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(jwtFilter).addPathPatterns("/**");
    }
}

新增拦截器JwtFilter

@Component
public class JwtFilter extends HandlerInterceptorAdapter {
 
    public static final String LOGIN_URL = "/login";
 
    @Resource
    private JwtTokenUtil jwtTokenUtil;
 
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws SignatureException {
        String uri = request.getRequestURI();
        if(uri.contains(LOGIN_URL) || uri.contains("/doc.html") || uri.contains("/swagger-resources") ){
            return true;
        }
        //获取token
        String token = request.getHeader(jwtTokenUtil.header);
        if(StringUtils.isEmpty(token)){
            token = request.getParameter(jwtTokenUtil.header);
        }
        if(StringUtils.isEmpty(token)){
            throw new SignatureException(jwtTokenUtil.header+"不能为空");
        }
 
        //判断token是否超时
        Claims claims = jwtTokenUtil.getTokenClaim(token);
        if(null == claims || jwtTokenUtil.isTokenExpired(claims.getExpiration())){
            throw new SignatureException(jwtTokenUtil.header+"失效,请重新登录");
        }
        return true;
    }
}

异常处理类,springAop实现(异常通知)

@RestControllerAdvice
public class SecurityExceptionHandler {
    @ExceptionHandler(value = {SignatureException.class})
    public Result authorizationException(SignatureException e){
        return Result.failedWith(null,CodeEnum.FORBBIDEN.getCode(),"权限不足");
    }
}

tokenUtil工具类

@Component
public class JwtTokenUtil {
    @Value("${jwt.secret}")
    public String secret;
    @Value("${jwt.expire}")
    public int expire;
    @Value("${jwt.header}")
    public 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){
            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();
    }
}

类中使用配置添加,application.yml中增加token的有效时间等

jwt:
  # 加密密钥
  secret: abcdefg1234567
  # token有效时长
  expire: 3600
  # header 名称
  header: token

jwt生成token使用

@Resource
JwtTokenUtil jwtTokenUtil;
 
@Override
public String login(String userName, String password) {
    //验证用户名密码
    ......
    //生成token
    return jwtTokenUtil.createToken("admin");
}

扩展3:

 引入依赖jar:

<!-- JWT依赖 -->
<dependency>
        <groupId>com.auth0</groupId>
        <artifactId>java-jwt</artifactId>
        <version>3.4.1</version>
    </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
        <version>0.9.1</version>
    </dependency>

 类中使用配置添加,application.yml中增加token的有效时间等

jwt:
  # 加密密钥
  secret: abcdefg1234567
  # token有效时长
  expire: 3600
  # header 名称
  header: token

启动类

这里选择使用@ServletComponentScan,是因为在Filter类用@component和@configuration会导致

@WebFilter(urlPatterns = “/testToken”, filterName = “jwtFilter”) url失效变成拦截所有

package com.example.bootjwt;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;

@SpringBootApplication
@ServletComponentScan //这里是将filter扫描加载进spring容器
public class BootJwtApplication {

    public static void main(String[] args) {
        SpringApplication.run(BootJwtApplication.class, args);
    }

}

实现代码

这里主要是做一个简单的demo验证,有三个类JwtController、CreatToken、JwtFilter。

JwtController:用来接收rest请求。

JwtUtil:用来生成token,解密token,验证token

JwtFilter:用来拦截请求对http请求中携带的token进行验证

JwtController

package com.example.bootjwt.controller;

import com.example.bootjwt.Util.JwtUtil;
import com.example.bootjwt.domain.User;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@RestController
public class JwtController {

    @PostMapping("/get")
    public String creatToken2(){
        User user = new User();
        user.setId("1");
        user.setUsername("hand2020");
        user.setPassword("123456");
        return JwtUtil.createJWT(40000,user);
    }

    @PostMapping("/test")
    public String testToken2(HttpServletRequest request, HttpServletResponse response){
        String token= request.getHeader("Authorization");
        User user = new User();
        user.setId("1");
        user.setUsername("hand2020");
        user.setPassword("123456");
        if (JwtUtil.isVerify(token,user)){
            return "success";
        }
        return "fail";
    }
}

JwtUtil

这里我是在配置文件中读需要加密的明文,和过期时间。也可以在controller里处理参数设置。

package com.example.bootjwt.Util;

import com.example.bootjwt.domain.User;
import io.jsonwebtoken.*;
import org.springframework.beans.factory.annotation.Value;


import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

public class JwtUtil {



//    @Value("${jwt.secret}")
//    private static String key;
    /**
     * 用户登录成功后生成Jwt
     * 使用Hs256算法  私匙使用用户密码
     *
     * @param ttlMillis jwt过期时间
     * @param user      登录成功的user对象
     * @return
     */
    public static String createJWT(long ttlMillis, User user) {
        //指定签名的时候使用的签名算法,也就是header那部分,jjwt已经将这部分内容封装好了。
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;

        //生成JWT的时间
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);

        //创建payload的私有声明(根据特定的业务需要添加,如果要拿这个做验证,一般是需要和jwt的接收方提前沟通好验证方式的)
        Map<String, Object> claims = new HashMap<String, Object>();
        claims.put("id", user.getId());
        claims.put("username", user.getUsername());
        claims.put("password", user.getPassword());

        //生成签名的时候使用的秘钥secret,这个方法本地封装了的,一般可以从本地配置文件中读取,切记这个秘钥不能外露哦。它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。
        String key = user.getPassword();

        //生成签发人
        String subject = user.getUsername();



        //下面就是在为payload添加各种标准声明和私有声明了
        //这里其实就是new一个JwtBuilder,设置jwt的body
        JwtBuilder builder = Jwts.builder()
                //如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
                .setClaims(claims)
                //设置jti(JWT ID):是JWT的唯一标识,根据业务需要,这个可以设置为一个不重复的值,主要用来作为一次性token,从而回避重放攻击。
                .setId(UUID.randomUUID().toString())
                //iat: jwt的签发时间
                .setIssuedAt(now)
                //代表这个JWT的主体,即它的所有人,这个是一个json格式的字符串,可以存放什么userid,roldid之类的,作为什么用户的唯一标志。
                .setSubject(subject)
                //设置签名使用的签名算法和签名使用的秘钥
                .signWith(signatureAlgorithm, key);
        if (ttlMillis >= 0) {
            long expMillis = nowMillis + ttlMillis;
            Date exp = new Date(expMillis);
            //设置过期时间
            builder.setExpiration(exp);
        }
        return builder.compact();
    }


    /**
     * Token的解密
     * @param token 加密后的token
     * @param user  用户的对象
     * @return
     */
    public static Claims parseJWT(String token, User user) {
        //签名秘钥,和生成的签名的秘钥一模一样
        String key = user.getPassword();

        //得到DefaultJwtParser
        Claims claims = Jwts.parser()
                //设置签名的秘钥
                .setSigningKey(key)
                //设置需要解析的jwt
                .parseClaimsJws(token).getBody();
        return claims;
    }


    /**
     * 校验token
     * 在这里可以使用官方的校验,我这里校验的是token中携带的密码于数据库一致的话就校验通过
     * @param token
     * @param user
     * @return
     */
    public static Boolean isVerify(String token, User user) {
        //签名秘钥,和生成的签名的秘钥一模一样
        String key = user.getPassword();
		//Jwts.parser在执行parseClaimsJws(token)时如果token时间过期会抛出ExpiredJwtException异常
        try {
            //得到DefaultJwtParser
            Claims claims = Jwts.parser()
                    //设置签名的秘钥
                    .setSigningKey(key)
                    //设置需要解析的jwt
                    .parseClaimsJws(token).getBody();
            if (claims.get("password").equals(user.getPassword())) {
                return true;
            }

        }catch (ExpiredJwtException e){
            e.printStackTrace();
        }
        return false;
    }

}


JwtFilter

过滤器是通过实现Filter接口,注意@WebFilter相当于xml配置,但是需要在启动类上注解

@ServletComponentScan,将JwtFilter加入到spring容器中。

在JwtFilter类上注解@component或@configuration会导致@WebFilter失效从而拦截所有请求

目前这个没用到,直接在controller里做了判断,这个是后续业务需求的demo

package com.example.bootjwt;

import com.example.bootjwt.Util.JwtUtil;
import com.example.bootjwt.domain.User;
import org.springframework.beans.factory.annotation.Autowired;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebFilter(urlPatterns = "/testToken", filterName = "jwtFilter")
public class JwtFilter implements Filter {

    @Autowired
    private CreatToken creatToken;
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest)servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        response.setHeader("Access-Control-Allow-Origin", "*");
        String token= request.getHeader("Authorization");
        User user = new User();
        user.setId("1");
        user.setUsername("hand2020");
        user.setPassword("123456");
        boolean flag = JwtUtil.isVerify(token,user);
        if (flag){
            filterChain.doFilter(servletRequest,servletResponse);
        }else {
            System.out.println("失败。。。。。。。。");
            response.getWriter().write("失败。。。。。。。。");
        }
    }

    @Override
    public void destroy() {

    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值