2021-04-01 11:55

SpringBoot集成JWT实现Token验证

王姐姐

JavaEE

(915)

(0)

收藏

1. JWT简介

1.1. 简介

Json web token (JWT,读作 [/dʒɒt/]),是一种基于JSON的,是为了在网络应用环境间传递声明而执行的,定义了一种简洁的,自包含的方法用于通信双方之间以JSON对象的形式安全的传递信息。因为数字签名的存在,这些信息是可信的,JWT可以使用HMAC算法或者是RSA的公私秘钥对进行签名。。

JWT通常由三部分组成: 头信息(header), 消息体(payload)和签名(signature)。JWT一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从服务器获取资源,适用于分布式站点的单点登录(SSO)场景。

JWT官网: https://jwt.io/

JWT(Java版)的github地址:https://github.com/jwtk/jjwt

1.2. JWT优点

因为json的通用性,Token是以JSON加密的形式保存在客户端的,所以JWT是可以跨语言支持的,像JAVA、JavaScript、NodeJS、PHP等很多语言都可以使用。

自包含(Self-contained),有payload部分,所以JWT可以在自身存储一些其他业务逻辑所必要的非敏感信息,负载中包含了所有用户所需要的信息,避免了多次查询数据库。

简洁并便于传输,可以通过URL,POST参数或者在HTTP header发送,jwt的构成非常简单,字节占用很小,传输速度也很快。

不需要在服务端保存会话信息, 所以易于应用的扩展,特别适用于分布式微服务。

2. JWT请求流程

1. 用户使用账号密码发出post登录请求;

2. 服务器验证登录成功则使用私钥并放入一些附带信息创建一个jwt;

3. 服务器通过Cookie返回这个jwt给浏览器;

4. 浏览器将该jwt串在请求头中向服务器发送请求;

5. 服务器验证jwt,从中获取用户信息;

6. 返回响应的资源给浏览器。


 

这个jwt字符串里包含了有用户的相关信息,比如这个用户是谁,id是多少,这个令牌的有效时间是多久等等。下次用户登录的时候,必须把这个令牌也一起带上。

一旦用户完成了登陆,在接下来的每个请求中包含JWT,可以用来验证用户身份以及对路由,服务和资源的访问权限进行验证。由于它的开销非常小,可以轻松的在不同域名的系统中传递,所有目前在单点登录(SSO)中比较广泛的使用了该技术。 信息交换在通信的双方之间使用JWT对数据进行编码是一种非常安全的方式,由于它的信息是经过签名的,可以确保发送者发送的信息是没有经过伪造的。

3. SpringBoot集成JWT实现Token验证

3.1. 添加依赖

    com.auth0groupId>
    java-jwtartifactId>
    3.14.0version>

3.2. 自定义跳过验证注解

根据系统功能,如果需要跳过验证的多,可以定义一个需要验证的注解,如果是需要验证的多,就定义一个跳过验证的注解。验证拦截器本身就可以配置拦截哪些路径并能配置拦截的路径中排除哪些。所以基本上定义一个跳过验证注解就够了。

//免验证注解,比如登录接口方法上可以加该注解
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface IgnoreToken {
    boolean required() default  true;
}

3.3. 实体类

@Entity
public class Userinfo {
    private Integer id;
    private String username;
    private String password;
    private String salt;
    private String name;
    private String pic;
    private Boolean enable;
    private Set
.....
}

我使用的SpringDataJPA操作的数据库。

3.4. JWT工具类

public class JWTUtils {
    /*
     签发对象:用户的id
     签发时间:现在
     有效时间:30分钟
     载荷内容:用户名和姓名,还可以是其它任何信息
     加密密钥:用户的密码
     */
    public static String createToken(Userinfo userinfo) {

        Calendar nowTime = Calendar.getInstance();
        nowTime.add(Calendar.MINUTE,30);
        Date expiresDate = nowTime.getTime();

        return JWT.create().withAudience(userinfo.getId().toString())   //签发对象
                .withIssuedAt(new Date())    //发行时间
                .withExpiresAt(expiresDate)  //有效时间
                .withClaim("userinfoId",userinfo.getId())//载荷
                .withClaim("username", userinfo.getUsername())//载荷
                .withClaim("name", userinfo.getName())//载荷
                .sign(Algorithm.HMAC256(userinfo.getPassword()));   //加密
    }

    //检验合法性,其中secret为用户的密码
    public static void verifyToken(String token, String secret)
    {
        DecodedJWT jwt = null;
        try {
            JWTVerifier verifier = JWT.require(Algorithm.HMAC256(secret)).build();
            jwt = verifier.verify(token);
        } catch (Exception e) {
            //效验失败
            throw new JWTVerificationException("token校验失败,非法认证信息");
        }

    }

   //获取签发对象也就是用户的id
    public static String getAudience(String token) {
        String audience=null;

        try {
            audience = JWT.decode(token).getAudience().get(0);

        } catch (JWTDecodeException j) {
            //这里是token解析失败
            throw new RuntimeException("token解析失败,非法认证信息");
        }
        return audience;
    }


    //通过载荷名字获取载荷的值
    public static Claim getClaimByName(String token, String name){
        return JWT.decode(token).getClaim(name);
    }
}

3.5. 验证token拦截器

@Component
public class JWTAuthcInterceptor implements HandlerInterceptor {
    @Resource
    private UserinfoService userinfoService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("handler..."+handler);
        //如果不是映射到方法,直接验证通过
        if(!(handler instanceof HandlerMethod)){
            return true;
        }

        HandlerMethod handlerMethod = (HandlerMethod)handler;
        Method method = handlerMethod.getMethod();
        //检查是否有IgnoreToken注解 有该注解则跳过验证
        if(method.isAnnotationPresent(IgnoreToken.class)){
            IgnoreToken ignoreToken = method.getAnnotation(IgnoreToken.class);
            if(ignoreToken.required()){
                return true;
            }
        }
        //其他全部检查验证
        System.out.println("JWT拦截验证");
        //从请求头中取出token
        String token = request.getHeader("token");
        if(token==null){
            throw new Exception("没有登录");
        }
        //获取token中的用户id
        Integer id  = Integer.parseInt(JWTUtils.getAudience(token));
        //检查用户是否存在
        Userinfo userinfo = userinfoService.findById(id);
        if(userinfo==null)
        {
            throw new Exception("系统不存在该用户,验证失败");
        }

        //验证token 传递秘钥,不是原始的密码,是数据库中加密后的密码
        JWTUtils.verifyToken(token,userinfo.getPassword());
        //获取载荷内容
        //String username = JWTUtils.getClaimByName(token,"username").asString();
        //....
        //在控制层可以直接使用
        request.setAttribute("userinfo",userinfo);
        return true;
    }
}

3.6. 配置拦截器

@Configuration
public class JWTInterceptorConfig implements WebMvcConfigurer {
    @Resource
    private JWTAuthcInterceptor jwtAuthcInterceptor;

   @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(jwtAuthcInterceptor).addPathPatterns("/**").excludePathPatterns("/web/**");
    }
}

3.7. 登录接口

@RestController
public class CommonController {
    @Resource
    private UserinfoService userinfoService;

    @IgnoreToken
    @PostMapping("/login")
    public Result login(@RequestBody Userinfo userinfo){

        userinfo = userinfoService.login(userinfo);
        if(userinfo!=null){
            String token = JWTUtils.createToken(userinfo);
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("token",token);
            return Result.success(jsonObject);
        }
        else{
            return Result.error("登录验证失败");
        }
    }
}

3.8. 测试接口

使用Talend API Tester测试接口,不登录直接访问/userinfo/update接口:

响应信息:

 

 

测试登录接口/login

 1617249294(1).png

响应信息:

 

把登录时返回的token放在请求头中再次更新:

 

响应信息:

0条评论

点击登录参与评论