【应用】SpringBoot -- JWT 实现 token 验证

JWT 基本介绍

JWT 全称 Json Web token,它将用户信息加密到 token 当中,服务端不保存任何用户信息。当服务端发送请求时,服务器通过保存的密钥验证 token 的正确性,正确则放行。

JWT 的优缺点

  • 优点

    • 使用简洁,数据量小,传输速度快,可以直接在 HTTP header 中发送

    • token 中包含了用户所需要的信息,避免了多次查询数据库

    • token 以 JSON 加密的形式在客户端存储,不需要在服务端保持会话

  • 缺点

    • 已经颁布的令牌无法作废

    • 不易处理数据过期的问题

JWT 消息构成

token 主要由 3 部分构成,分别为 header、payload 以及 signature,三部分之间以`.``分割,例如:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
.eyJhdWQiOiJhZG1pbiIsImV4cCI6MTY2NDQxMTI4Mn0
.cNvdGNNsNl8UJX_PIz40h9iJtDWexoymnC3h_b42iMA
  • header

    • 声明类型,标识这是一条 JWT 信息

    • 声明 JWT 加密的算法

  • payload

    • 存放有效信息,一般存放我们自定义的 key-value 键值对
  • signature

    • 存放签证信息

SpringBoot 集成 JWT 的简单使用

引入 JWT 依赖

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

配置自定义注解进行访问控制

配置@LoginToken注解,标识需要进行 JWT 验证才能访问的方法

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginToken {

    boolean required() default true;

}

配置 JWT 拦截器

通过拦截器对请求进行拦截,判断请求访问的方法和注解,根据条件对 token 进行验证

tips:ApplicationContextUtil 是上下文工具类,可自行搜索…

@Slf4j
public class JwtInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 获取业务类
        UserService userService = ApplicationContextUtil.getBean(UserService.class);

        // 获取请求头中的 token
        String token = request.getHeader("token");
        // 如果不是映射到方法的,直接通过
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }
        // 获取请求的方法
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method = handlerMethod.getMethod();
        // 验证是否存在需要验证用户权限的注释
        if (method.isAnnotationPresent(LoginToken.class)) {
            if (method.getAnnotation(LoginToken.class).required()) {
                // 执行认证
                if (token == null) {
                    log.error("token 不存在,请重新登录");
                    throw new RuntimeException("token 不存在,请重新登录");
                }
                // 获取 userId
                String userId;
                try {
                    userId = JWT.decode(token).getAudience().get(0);
                } catch (JWTDecodeException e) {
                    log.error("token 解码失败");
                    throw new RuntimeException("token 解码失败");
                }
                // 根据 userId 查询 User
                User user = userService.getUser(userId);
                if (user == null) {
                    log.error("用户不存在,请重新登录");
                    throw new RuntimeException("用户不存在,请重新登录");
                }
                // 验证 token
                JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getPassword())).build();
                try {
                    jwtVerifier.verify(token);
                } catch (JWTVerificationException e) {
                    log.error("token 校验失败");
                    throw new RuntimeException("token 校验失败,请重新登录");
                }
                return true;
            }
        }
        return true;
    }
}

注册连接器并配置全局异常处理器

拦截器配置类

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {

    /**
     * 注册 JWT 拦截器
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new JwtInterceptor()).addPathPatterns("/**");
    }
}

全局异常处理器

@RestControllerAdvice
public class GlobalExceptionHandler {
    @ResponseBody
    @ExceptionHandler(Exception.class)
    public Object handleException(Exception e) {
        String msg = e.getMessage();
        if (msg == null || "".equals(msg)) {
            msg = "服务器出错";
        }
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("code", 50000);
        jsonObject.put("message", msg);
        return jsonObject;
    }
}

配置 token 注册业务类

构造tokenService,对用户的信息执行注册,返回对应用户的 token

@Service
public class TokenService {

    /**
     * 设置过期时间
     */
    private static final long EXPIRE_TIME = 5 * 60 * 1000;

    /**
     * 获取 token
     */
    public String getToken(User user) {
        Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
        return JWT.create()
                // 将 userId 保存在 token 中
                .withAudience(user.getUserId())
                // 设置过期时间
                .withExpiresAt(date)
                // 将 password 设置为 token 的密钥
                .sign(Algorithm.HMAC256(user.getPassword()));
    }

}

构造userService,模拟从数据库中查询对应用户信息的过程

@Service
public class UserService {

    /**
     * 模拟查询参数
     */
    private final static String PARAM = "admin";

    /**
     * 根据 id 查询 User
     */
    public User getUser(String userId) {
        if (PARAM.equals(userId)) {
            return User.builder()
                    .userId(userId)
                    .username("admin")
                    .password("admin")
                    .build();
        }
        return null;
    }

    /**
     * 根据用户名查询 User
     */
    public User getUser(String username, String password) {
        if (PARAM.equals(username)) {
            return User.builder()
                    .userId("admin")
                    .username(username)
                    .password(password)
                    .build();
        }
        return null;
    }

}

编写控制层代码

@RestController
@RequestMapping("/JWT")
public class LoginController {

    @Resource
    private UserService userService;
    @Resource
    private TokenService tokenService;

    /**
     * 登录
     */
    @PostMapping("/sign")
    public Object sign(String username, String password){
        JSONObject jsonObject = new JSONObject();
        User user = userService.getUser(username, password);
        if (user == null) {
            jsonObject.put("message", "登录失败!");
        } else {
            String token = tokenService.getToken(user);
            jsonObject.put("token", token);
            jsonObject.put("user", user);
        }
        return jsonObject;
    }

    /**
     * 验证登录
     */
    @LoginToken
    @PostMapping("/getMessageWithToken")
    public String getMessageWithToken(){
        return "已通过验证,允许获取信息~";
    }

    /**
     * 验证登录
     */
    @LoginToken(required = false)
    @PostMapping("/getMessageWithoutToken")
    public String getMessageWithoutToken(){
        return "无需验证即可获取对应信息~";
    }

}

访问接口执行测试

首先测试不携带 token 访问getMessageWithTokengetMessageWithoutToken接口

getMessageWithToken未携带 token,禁止访问

在这里插入图片描述

getMessageWithoutToken不需要 token 认证即可访问

在这里插入图片描述

访问sign接口执行注册,获得返回的 token

在这里插入图片描述

携带 token 重新访问getMessageWithToken,可正确执行访问

在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值