【SpringBoot】DEMO:集成JWT实现token验证

之前使用Bootstrap和springBoot开发的时候,都是用cookie和session进行登录验证,在接触分布式之后,cookie和session明显没有token方便,在这里记录一下开发过程

一、了解一下 Token 身份验证

在这里插入图片描述

二、项目实现

  • 使用 IDEA 和 mysql5.7 进行开发
1. 目录结构介绍
  • Annotation:自定义注解
  • Configuration:配置类
  • Controller:控制层
  • Interceptor:拦截器
  • Mapping:数据库语句映射
  • Service:服务层
    在这里插入图片描述
2. 关键maven依赖
		<!-- JWT token验证 -->
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.4.0</version>
        </dependency>
        <!--自定义注解-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
  • 使用自定义注解,可以很方便的对 Controller 进行监控:是否需要 token 才可以发起请求
3. 配置数据库、Model:模型层
public class User {
    String Id;
    String username;
    String password;
	——————  省略 get 和 set 方法	——————
}
  • 数据库中 id 是 int 型,但是为了使用 jwt 生成 token,在 Model层 中使用 String 型 ,不影响后续操作
4. 创建两个自定义注解
  • 跳过拦截器校验:PassToken
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PassToken {
    boolean required() default true;
}
  • 需要有 token 才可以进行操作: UserLoginToken
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface UserLoginToken {
    boolean required() default true;
}

@Target:注解的作用目标
@Target(ElementType.TYPE)——接口、类、枚举、注解
@Target(ElementType.FIELD)——字段、枚举的常量
@Target(ElementType.METHOD)——方法
@Target(ElementType.PARAMETER)——方法参数
@Target(ElementType.CONSTRUCTOR) ——构造函数
@Target(ElementType.LOCAL_VARIABLE)——局部变量
@Target(ElementType.ANNOTATION_TYPE)——注解
@Target(ElementType.PACKAGE)——包
@Retention:注解的保留位置
RetentionPolicy.SOURCE:这种类型的Annotations只在源代码级别保留,编译时就会被忽略,在class字节码文件中不包含。
RetentionPolicy.CLASS:这种类型的Annotations编译时被保留,默认的保留策略,在class文件中存在,但JVM将会忽略,运行时无法获得。
RetentionPolicy.RUNTIME:这种类型的Annotations将被JVM保留,所以他们能在运行时被JVM或其他使用反射机制的代码所读取和使用。
@Document:说明该注解将被包含在javadoc中
@Inherited:说明子类可以继承父类中的该注解

5. 使用 JWT 生成 token
@Service("TokenService")
public class TokenService {
    public String getToken(User user) {
        String token="";
        token= JWT.create().withAudience(user.getId())
                .sign(Algorithm.HMAC256(user.getPassword()));
        return token;
    }
}
  • 将 id 保存到 token 里面,作标识
  • 以 password 作为 token 的签名
6. 编写配置类
  • 通过编写配置类,启动拦截器
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authenticationInterceptor())
                .addPathPatterns("/**");    // 拦截所有请求,通过判断是否有 @LoginRequired 注解 决定是否需要登录
    }
    @Bean
    public AuthenticationInterceptor authenticationInterceptor() {
        return new AuthenticationInterceptor();
    }
}
  • addPathPatterns 方法用于设置拦截器的过滤路径规则
  • /** 拦截所有请求,通过判断是否有 @LoginRequired注解 决定是否需要登录
7. 编写拦截器
public class AuthenticationInterceptor implements HandlerInterceptor {
    @Autowired
    private UserMapping userMapping;

    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception {
        // 从 http 请求头中取出 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;
            }
        }

        // 检查是否有 UserLoginToken 注解,如果有,进行拦截
        if (method.isAnnotationPresent(UserLoginToken.class)) {
            /* 获取 user 的 token */
            UserLoginToken userLoginToken = method.getAnnotation(UserLoginToken.class);
            if (userLoginToken.required()) {
                // 没有 token
                if (token == null) {
                    throw new RuntimeException("无token,请重新登录");
                }
                // 获取 token 中的 user id ,查询数据库是否存在 user
                String userId;
                try {
                    userId = JWT.decode(token).getAudience().get(0);
                } catch (JWTDecodeException j) {
                    throw new RuntimeException("401");
                }
                User user = userMapping.findUserById(userId);
                if (user == null) {
                    throw new RuntimeException("用户不存在,请重新登录");
                }
                // 验证 token ,返回 true
                JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getPassword())).build();
                try {
                    jwtVerifier.verify(token);
                } catch (JWTVerificationException e) {
                    throw new RuntimeException("401");
                }
                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 {
    }
}
  • 实现一个拦截器就需要实现 HandlerInterceptor 接口
    • boolean preHandle ()
      预处理回调方法,实现处理器的预处理
      第三个参数为响应的处理器,自定义Controller,返回值为true表示继续流程(如调用下一个拦截器或处理器)或者接着执行postHandle()和afterCompletion();false表示流程中断,不会继续调用其他的拦截器或处理器,中断执行
    • void postHandle():
      后处理回调方法,实现处理器的后处理(DispatcherServlet进行视图返回渲染之前进行调用)
      通过modelAndView(模型和视图对象)对模型数据进行处理或对视图进行处理,modelAndView也可能为null
    • void afterCompletion():
      整个请求处理完毕回调方法,该方法也是需要当前对应的Interceptor的preHandle()的返回值为true时才会执行在DispatcherServlet渲染了对应的视图之后执行
      用于进行资源清理。整个请求处理完毕回调方法。如性能监控中我们可以在此记录结束时间并输出消耗时间,还可以进行一些资源清理,类似于try-catch-finally中的finally,但仅调用处理器执行链中
8. 编写控制器
@RestController
@RequestMapping("api")
public class ApiController {

    @Autowired
    private UserMapping userMapping;

    @Autowired
    private TokenService tokenService;

    @PostMapping("/login")
    @ResponseBody
    public Object apiLogin(@RequestParam(value = "username") String username,
                           @RequestParam(value = "password") String password){
        User user = new User();
        user.setUsername(username);
        user.setPassword(password);
        JSONObject jsonObject=new JSONObject();
        User userForBase = userMapping.findUserUsername(username);
        if (userForBase==null){
            jsonObject.put("message","登录失败,用户不存在");
            return jsonObject;
        }else{
            if (!userForBase.getPassword().equals(user.getPassword())){
                jsonObject.put("message","登录失败,密码错误");
            }else{
                String token = tokenService.getToken(userForBase);
                jsonObject.put("token", token);
                jsonObject.put("user", userForBase);
            }
            return jsonObject;
        }
    }

    @UserLoginToken
    @GetMapping("/getMessage")
    public String getMessage(){
        return "你已通过验证";
    }
}
  • Get方法:getMessage 加入了 @UserLoginToken 注解,需要拿到 token 才可以访问
9. Mapper映射层
@Mapper
@Repository
public interface UserMapping {

    @Select("select * from user where id = #{userId}")
    User findUserById(@Param(value = "userId") String userId);

    @Select("select * from user where username = #{username}")
    User findUserUsername(@Param(value = "username") String username);

}

三、接口测试

  • 我们使用 postman 进行接口测试
1. 当 user 没有 token 的时候,无法对 getMessage 进行访问

在这里插入图片描述

2. 用户登录,获取 token

在这里插入图片描述

3. 把 token 写入 getMessage请求中

在这里插入图片描述

  • 大功告成!
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值