json-web-token(JWT)

一、JWT是什么?

JWT的全称为json web token。不要把它想得多么高深,其实就是一种生成token的方式。一般我们访问一个系统的流程就是:请求登录接口,该接口会返回一个token,请求其他接口都要带上token,token验证通过才能访问成功,而JWT可以理解为就是生成token的一种机制。


欢迎大家关注我的公众号 javawebkf,目前正在慢慢地将简书文章搬到公众号,以后简书和公众号文章将同步更新,且简书上的付费文章在公众号上将免费。


二、JWT怎么用?

1、添加jwt的依赖:

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

2、新建一个TokenService,用来生成token:

@Service
public class TokenService {
    // token过期时间5分钟
    private static final long EXPIRE_TIME = (60 * 1000 * 5);

    public String getToken(User user) {
        return JWT.create()
                // 需要放入token中的信息
                .withAudience(user.getId())
                // 设置token过期时间
                .withExpiresAt(new Date(System.currentTimeMillis() + EXPIRE_TIME))
                // 用户密码当作密钥
                .sign(Algorithm.HMAC256(user.getPassword()));
    }
}

User类就是一个普通的pojo,这里就不把代码贴出来了。这个getToken方法表示将用户的密码作为密钥,把用户的id放进token中,设置token过期时间为5分钟。

3、新建两个注解,一个注解表示需要验证,另一个表示跳过验证:

  • 需要验证:
//可以作用在方法,类和接口上
@Target({ElementType.METHOD, ElementType.TYPE})
//编译器会将SkipToken的信息保存在虚拟机中
@Retention(RetentionPolicy.RUNTIME)
public @interface NeedToken {
    // required 属性默认值为true
    boolean required() default true;
}
  • 跳过验证:
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface SkipToken {
    boolean required() default true;
}

4、新建一个AuthInterceptor类,用于拦截请求:

public class AuthInterceptor implements HandlerInterceptor {
    @Autowired
    private UserService userService;

    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,Object object) {
        // 从 http 请求头中取出 token
        String token = httpServletRequest.getHeader("token");
        // 只拦截方法,不是方法直接返回true
        if (!(object instanceof HandlerMethod)) {
            return true;
        }
        HandlerMethod handlerMethod = (HandlerMethod) object;
        Method method = handlerMethod.getMethod();
        // 有SkipToken注解的直接跳过认证
        if (method.isAnnotationPresent(SkipToken.class)) {
            SkipToken skipToken = method.getAnnotation(SkipToken.class);
            if (skipToken.required()) {
                return true;
            }
        }
        // 有NeedToken注解的就进行认证
        if (method.isAnnotationPresent(NeedToken.class)) {
            NeedToken needToken = method.getAnnotation(NeedToken.class);
            if (needToken.required()) {
                if (token == null) {
                    throw new RuntimeException("无token,请重新登录");
                }
                // 获取 token 中的 userId
                String userId;
                try {
                    userId = JWT.decode(token).getAudience().get(0);
                } catch (JWTDecodeException j) {
                    throw new RuntimeException("401");
                }
                // 根据userId查询数据库
                User user = userService.findUserById(userId);
                if (user == null) {
                    throw new RuntimeException("用户不存在,请重新登录");
                }
                // 用查出来的user的密码去校验token
                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) {
    }

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse,Object o, Exception e) {
    }
}

这个拦截器的处理逻辑就是:

  • 只拦截方法,如果不是方法,就放行;
  • 如果拦截的方法有@SkipToken注解,放行;
  • 如果拦截的方法有@NeedToken注解,则需要验证token;
  • 取出请求头中的token,拿出token中的userId,根据此userId去数据库查询对应的记录;
  • 再将查出来的user的password去验证token,验证成功则放行;
  • 如果没有@NeedToken也没有@SkipToken注解的,也放行。

5、新建一个InterceptorConfig类,将我们上面写的拦截器配置到spring容器中去:

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authInterceptor())
                .addPathPatterns("/**");   
    }
    @Bean
    public AuthInterceptor authInterceptor() {
        return new AuthInterceptor();
    }
}

6、在controller中的用法:

@RestController
@RequestMapping("api")
public class UserController {
    @Autowired
    private UserService userService;
    @Autowired
    private TokenService tokenService;
   
    @SkipToken
    @PostMapping("/user")
    public JsonResult login(User user){
        User dbUser = userService.findForLogin(user);
        if (dbUser == null){
            return new JsonResult(200, "用户名或密码错误", null);
        } else {
            String token = tokenService.getToken(dbUser);
            Map<String, Object> resultMap = new HashMap<>();
            resultMap.put("token", token);
            resultMap.put("user", dbUser);
            return new JsonResult(200, "登录成功", resultMap);
        }
    }
    
    @NeedToken
    @GetMapping("/getMessage")
    public String getMessage(){
        return "你已通过验证";
    }
    
    @SkipToken
    @GetMapping("/noToken")
    public String noToken() {
        return "无需token访问";
    }
}

以上就是JWT的用法,其实在实际生产中一般会配合shiro使用。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,084评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,623评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,450评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,322评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,370评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,274评论 1 300
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,126评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,980评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,414评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,599评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,773评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,470评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,080评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,713评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,852评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,865评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,689评论 2 354

推荐阅读更多精彩内容