JWT实战教程

JWT实战教程

在这里插入图片描述

接下来我将实现一个员工登录接口并通过JWT令牌校验登录的合法性

实战框架模版

第一步

  • pom文件引入JWT依赖
<!--Java8及以下引入jjwt依赖即可-->
<!--JWT令牌-->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>
<!--如果是Java8以上版本还需要添加以下依赖:
因为 javax.xml.bind.DatatypeConverter 类已被弃用并从 Java SE 9 中移除。-->
<dependency>
    <groupId>javax.xml.bind</groupId>
    <artifactId>jaxb-api</artifactId>
    <version>2.3.1</version>
</dependency>
<!--如果您使用的是 Java 11 或更高版本,则还需要添加 JAXB Runtime 依赖项-->
<dependency>
    <groupId>javax.xml.bind</groupId>
    <artifactId>jaxb-runtime</artifactId>
    <version>2.3.3</version>
</dependency>

第二步

  • yaml文件添加配置
sky:
  jwt:
    # 设置jwt签名加密时使用的秘钥
    admin-secret-key: xiaoChenCoding
    # 设置jwt过期时间(毫秒) time to line
    admin-ttl: 7200000
    # 设置前端传递过来的令牌名称
    admin-token-name: token

第三步

@Component
@ConfigurationProperties(prefix = "sky.jwt")
@Data
//JWT配置类
public class JwtProperties {

    /**
     * 管理端员工生成jwt令牌相关配置
     */
    private String adminSecretKey;//和yml文件不相同,需要用驼峰命名法
    private long adminTtl;
    private String adminTokenName;

}

第四步

  • 定义一个JWT封装类,用于创建和解析JWT令牌(不定义也可以,但不建议在Controller类里编写业务代码)
public class JwtUtil {
    /**
     * 生成jwt
     * 使用Hs256算法, 私匙使用固定秘钥
     *
     * @param secretKey jwt秘钥
     * @param ttlMillis jwt过期时间(毫秒)
     * @param claims    设置的信息
     * @return
     */
    public static String createJWT(String secretKey, long ttlMillis, Map<String, Object> claims) {
        // 指定签名的时候使用的签名算法,也就是header那部分
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;

        // JWT的到期时间
        long expMillis = System.currentTimeMillis() + ttlMillis;
        Date exp = new Date(expMillis);

        // 设置jwt的body
        JwtBuilder builder = Jwts.builder()
                // 如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
                .setClaims(claims)
                // 设置签名使用的签名算法和签名使用的秘钥
                .signWith(signatureAlgorithm, secretKey.getBytes(StandardCharsets.UTF_8))
                // 设置过期时间
                .setExpiration(exp);

        return builder.compact();
    }

    /**
     * Token解密
     *
     * @param secretKey jwt秘钥 此秘钥一定要保留好在服务端, 不能暴露出去, 否则sign就可以被伪造, 如果对接多个客户端建议改造成多个
     * @param token     加密后的token
     * @return
     */
    public static Claims parseJWT(String secretKey, String token) {
        // 得到DefaultJwtParser
        return Jwts.parser()
                // 设置签名的秘钥
                .setSigningKey(secretKey.getBytes(StandardCharsets.UTF_8))
                // 设置需要解析的jwt
                .parseClaimsJws(token)
                .getBody();
    }

}

第五步

  • 编写Controller类来生成JWT令牌
@RestController
@RequestMapping("/admin/employee")
@Slf4j
public class EmployeeController {

    /**
     * DTO:Data Transfer Object ,数据传输对象(用于在各个层之间传输数据)
     * <p>
     * VO:View Object ,返回给前端的数据
     * <p>
     * Entity:与数据库表对应的实体类
     */
     
    @Autowired
    private EmployeeService employeeService;
    @Autowired
    private JwtProperties jwtProperties;

    /**
     * 登录
     *
     * @param employeeLoginDTO
     * @return
     */
    @PostMapping("/login")
    public Result<EmployeeLoginVO> login(@RequestBody EmployeeLoginDTO employeeLoginDTO) {
        log.info("员工登录:{}", employeeLoginDTO);
		
		// 自定义的查询,具体实现看业务需求
        Employee employee = employeeService.login(employeeLoginDTO);
		// 假设员工存在并查询成功,返回的employee != null
		
        //登录成功后,生成jwt令牌
        Map<String, Object> claims = new HashMap<>();
        claims.put(JwtClaimsConstant.EMP_ID, employee.getId());
        //可放置多个
        /*claims.put(JwtClaimsConstant.USERNAME,employee.getUsername());
        claims.put(JwtClaimsConstant.NAME,employee.getName());*/
        String token = JwtUtil.createJWT(
                jwtProperties.getAdminSecretKey(),
                jwtProperties.getAdminTtl(),
                claims
                );

        EmployeeLoginVO employeeLoginVO = EmployeeLoginVO.builder()
                .id(employee.getId())
                .userName(employee.getUsername())
                .name(employee.getName())
                .token(token)
                .build();

        return Result.success(employeeLoginVO);
    }
}

第六步

  • 编写拦截器来校验jwt令牌的合法性以验证登录
/**
 * jwt令牌校验的拦截器
 */
@Component
@Slf4j
public class JwtTokenAdminInterceptor implements HandlerInterceptor {//定义拦截器必须实现HandlerInterceptor接口

    @Autowired
    private JwtProperties jwtProperties;

    /**
     * 校验jwt
     *
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    //方法三个变量分别为:请求对象,响应对象 映射方法对象
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info("拦截到了请求:{}",request.getRequestURL().toString());

        //判断当前拦截到的是Controller的方法还是其他资源
        //instanceof作用是:测试左边的对象是否是右边类或者该类的子类创建的实例对象,是,则返回true,否则返回false。

        //TODO springMVC解决拦截器跨域问题
        //解决跨域问题
        //1.如果请求不是动态的,handler对象不是HandlerMethod的实例(静态页面).放行
        //2.如果请求是跨域请求(请求方法是:OPTIONS),handler对象不是HandlerMethod当发生跨域时,
        //系统会先发送一条预请求,请求路径与真实请求一样,但是请求方式为OPTIONS它不属于handler实例,
        //如果我们不写下面这个判断,它会继续后面的操作,但是由于预请求没有携带参数,
        //在后面的判断中就会被直接拦截下来,这样导致这个请求失败
        if (!(handler instanceof HandlerMethod)) {
            //当前拦截到的不是动态方法,直接放行
            return true;
        }
        //这样就不会将预请求拦截下来,可以解决拦截器跨域问题

        // TODO 指定登录页面不拦截
        //1、从请求头中获取令牌
        String token = request.getHeader(jwtProperties.getAdminTokenName());

        //2、校验令牌
        try {
            log.info("jwt校验:{}", token);
            Claims claims = JwtUtil.parseJWT(jwtProperties.getAdminSecretKey(), token);
            Long empId = Long.valueOf(claims.get(JwtClaimsConstant.EMP_ID).toString());
            log.info("当前员工id:{}", empId);
            //把员工ID存入线程变量
            BaseContext.setCurrentId(empId);
            //3、通过,放行
            log.info("令牌校验通过");
            return true;
        } catch (Exception ex) {
            //4、不通过,响应401状态码
            log.info("校验令牌不通过");
            response.setStatus(401);
            return false;
        }
    }

    /**
     * 响应结束之后执行
     * @param request
     * @param response
     * @param handler
     * @param ex
     * @throws Exception
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        //移除当前线程数据
        BaseContext.removeCurrentId();
    }
}

第七步

  • 定义全局拦截器并注册自定义拦截器
/**
 * 配置类,注册web层相关组件
 */
@Configuration
@Slf4j
public class WebMvcConfiguration extends WebMvcConfigurationSupport {

    // TODO 配置类继承问题
    /*WebMvcConfigurerAdapter已被弃用
    不推荐通过继承WebMvcConfigurationSupport的方式来配置SpringMVC,
        因为会导致SpringMVC中的默认配置失效,
        同时SpringBoot中SpringMVC的自动配置失效。
    推荐使用实现WebMvcConfigurer接口的方式来配置SpringMvc
        非SpringBoot的环境下,需要配合@EnableWebMvc注解使用
        SpringBoot中不要使用@EnableWebMvc注解*/

    @Autowired
    private JwtTokenAdminInterceptor jwtTokenAdminInterceptor;
    /**
     * 注册自定义拦截器
     *
     */
    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        log.info("开始注册管理端拦截器...");
        registry.addInterceptor(jwtTokenAdminInterceptor)
                .addPathPatterns("/admin/**") // 拦截的路径,需根据真实项目结构修改
                .excludePathPatterns("/admin/employee/login"); // 排除的拦截路径,比如:登录接口、注册接口等
    }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值