基于JWT的用户登录认证的原理与实现

JWT认证原理

JWT简介: JWT(JSON Web Token)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于JSON对象在各方之间安全的传输信息。该信息可以被验证和信任,因为它是数字签名的。

JWT的结构: JWT由三个部分组成,各部分之间用小数点连接。这三部分分别是Header(请求头)、Payload(有效载荷)、Signature(签名),一个典型的JWT看起来是这个样子的:xxx.yyy.zzz
1. header请求头: 由Token的类型和算法名称组成,如下所示。然后用Base64对这个JSON编码就得到JWT的第一部分。

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

2. Payload载荷: 载荷就是存放有效信息的地方。如:用户注册信息中可公开的数据,如下所示。对Payload进行Base64编码就得到JWT的第二部分。
注意: 不要在JWT的payload或header中放置敏感信息,除非它们是加密的。

{
	"username": "BigHu",
	"age": "25"
}

3. Signature签名: 为了得到签名部分,必须有编码过的header、编码过的payload、一个密钥,签名算法是header中指定的那个,然后对它们签名即可。签名是用于验证消息在传递过程中是否被更改。

生成的Token:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.1eyJleHAiOjE2MDUwMTY2MzcsInVzZXJJZCI6MTIzLCJ1c2VybmFtZSI6ImJpZy5odSJ9.QuCF1F2_J-oX4UMKzgehRZxRgimPIXktTMfVgO6MO4Y

JWT与OAuth的区别?

  1. OAuth2是一种授权框架,JWT是一种认证协议。
  2. 无论使用哪种方式切记用https来保证数据的安全性。
  3. OAuth2用在使用第三方账号登录的情况下(如QQ、微信…),而JWT是用在前后端分离,需要见到那的对后台API进行保护使用。

JWT认证流程

  1. 前端通过Web表单将自己的用户名和密码发送到后端接口。
  2. 后端核对用户名和密码成功后,将用户的id等其他信息作为JWT Payload(负载),将其与头部分别进行Base64编码拼接后签名,形成一个JWT(Token)。形成的JWT就是一共形同xxx.yyy.zzz的字符串。
  3. 后端将JWT字符串作为登录成功的返回结果返回给前端。前端可以将返回结果保存在localStoreage或sessionStoreage上,退出登陆时前端删除保存的JWT即可。
  4. 前端每次请求时将JWT放入HTTP Header的Authorization位,(解决XSS和XSRF问题)HEADER
  5. 后端检查是否存在,如存在验证JWT的有效性。例如检查签名是否正确;检查Token是否过期;检查Token接收方是否是自己(可选)。
  6. 验证通过后后端使用JWT中包含的用户信息进行其他逻辑操作,返回相应结果。

JWT实现

引入jar包

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

用户实体类

@Data
@Accessors(chain = true)
public class User {

    private String id;
    private String username;
    private String password;
}

JWT工具类

package com.jiezai.jwt.utils;

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

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

/**
 * JWT工具类
 *
 * @author Big.Hu
 * @date 2020/11/11 14:30
 */
public class JWTUtils {

    private static final String SIGN = "!@#$%--Big.Hu--456789";

    /**
     * 生成token:header.payload.sign
     *
     * @param map 登陆信息,如:{"username":"张三"}
     * @return token
     */
    public static String getToken(Map<String, String> map) {
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.DATE, 7); // 过期时间:7天
        JWTCreator.Builder builder = JWT.create();
        map.forEach((k, v) -> {
            builder.withClaim(k, v);
        });
        Map<String, Object> header = new HashMap<>();
        header.put("typ", "JWT");
        header.put("alg", "HMAC256");
        String token = builder.withHeader(header)
                .withExpiresAt(calendar.getTime())
                .sign(Algorithm.HMAC256(SIGN));
        return token;
    }

    /**
     * 验证token的合法性并返回用户名
     *
     * @param token
     * @return 用户名
     */
    public static String verify(String token) {
        JWTVerifier build = JWT.require(Algorithm.HMAC256(SIGN)).build();
        DecodedJWT decodedJWT = build.verify(token);
        return decodedJWT.getClaim("username").asString();
    }
}

自定义Token拦截器

package com.jiezai.jwt.interceptors;

import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jiezai.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.util.HashMap;

/**
 * 登陆拦截器
 * @author Big.Hu
 * @date 2020/11/11 13:32
 */
@Component
public class LoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 从请求头中获取Token
        String token = request.getHeader("token");
        HashMap<String, Object> map = new HashMap<>();
        try {
            // 认证token,如果没报错证明token有效,直接放行
            String username = JWTUtils.verify(token);
            System.out.println("用户 " + username + " 认证成功!");
            return true;
        } catch (AlgorithmMismatchException e) {
            map.put("msg", "签名算法不对");
        } catch (SignatureVerificationException e) {
            map.put("msg", "签名不对");
        } catch (TokenExpiredException e) {
            map.put("msg", "令牌过期");
        } catch (Exception e) {
            map.put("msg", "token无效");
            e.printStackTrace();
        }
        map.put("status", false);
        // 转换成json格式并响应
        String json = new ObjectMapper().writeValueAsString(map);
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().println(json);
        return false;
    }
}

配置拦截器

package com.jiezai.jwt.config;

import com.jiezai.jwt.interceptors.LoginInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * 拦截器配置类
 * @author Big.Hu
 * @date 2020/11/11 15:18
 */
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {

    @Autowired
    private LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/**") // 拦截所有请求
                .excludePathPatterns("/user/login"); // 排除登陆请求
    }
}

controller类

package com.jiezai.jwt.controller;

import com.jiezai.jwt.entity.User;
import com.jiezai.jwt.service.UserService;
import com.jiezai.jwt.utils.JWTUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

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

/**
 * @author Big.Hu
 * @date 2020/11/11 15:30
 */
@RestController
@RequestMapping("user")
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("login")
    public Map<String, Object> login(User user) {
        HashMap<String, Object> map = new HashMap<>();
        try {
        	// 去数据库验证账号密码,UserService这部分代码省略
            User userDB = userService.login(user);
            HashMap<String, String> userMap = new HashMap<>();
            userMap.put("userId", userDB.getId());
            userMap.put("username", userDB.getUsername());
            // 生成Token并返回
            String token = JWTUtils.getToken(userMap);
            map.put("status", true);
            map.put("token", token);
            map.put("msg", "认证成功");
        } catch (Exception e) {
            map.put("status", false);
            map.put("msg", "认证失败");
            e.printStackTrace();
        }
        return map;
    }

    @PostMapping("test")
    public Map<String, Object> test() {
        HashMap<String, Object> map = new HashMap<>();
        map.put("status", true);
        map.put("msg", "认证成功");
        return map;
    }
}

以上文章有什么不对的地方,望大佬们不吝赐教!

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值