Java实现Token登录验证(基于JWT的token认证实现)

Posted 普通网友

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java实现Token登录验证(基于JWT的token认证实现)相关的知识,希望对你有一定的参考价值。

文章目录


一、JWT是什么?

在介绍JWT之前,我们先来回顾一下利用token进行用户身份验证的流程:

1、客户端使用用户名和密码请求登录

2、服务端收到请求,验证用户名和密码

3、验证成功后,服务端会签发一个token,再把这个token返回给客户端

4、客户端收到token后可以把它存储起来,比如放到cookie中

5、客户端每次向服务端请求资源时需要携带服务端签发的token,可以在cookie或者header中携带

6、服务端收到请求,然后去验证客户端请求里面带着的token,如果验证成功,就向客户端返回请求数据

这种基于token的认证方式相比传统的session认证方式更节约服务器资源,并且对移动端和分布式更加友好。其优点如下

支持跨域访问:cookie是无法跨域的,而token由于没有用到cookie(前提是将token放到请求头中),所以跨域后不会存在信息丢失问题
无状态:token机制在服务端不需要存储session信息,因为token自身包含了所有登录用户的信息,所以可以减轻服务端压力
更适用CDN:可以通过内容分发网络请求服务端的所有资料
更适用于移动端:当客户端是非浏览器平台时,cookie是不被支持的,此时采用token认证方式会简单很多
无需考虑CSRF:由于不再依赖cookie,所以采用token认证方式不会发生CSRF,所以也就无需考虑CSRF的防御

而JWT就是上述流程当中token的一种具体实现方式,其全称是JSON Web Token,官网地址:https://jwt.io/

通俗地说,JWT的本质就是一个字符串,它是将用户信息保存到一个Json字符串中,然后进行编码后得到一个JWT token,并且这个JWT token带有签名信息,接收后可以校验是否被篡改,所以可以用于在各方之间安全地将信息作为Json对象传输。JWT的认证流程如下:

1、首先,前端通过Web表单将自己的用户名和密码发送到后端的接口,这个过程一般是一个POST请求。建议的方式是通过SSL加密的传输(HTTPS),从而避免敏感信息被嗅探

2、后端核对用户名和密码成功后,将包含用户信息的数据作为JWT的Payload,将其与JWT Header分别进行Base64编码拼接后签名,形成一个JWT Token,形成的JWT Token就是一个如同lll.zzz.xxx的字符串
3、后端将JWT Token字符串作为登录成功的结果返回给前端。前端可以将返回的结果保存在浏览器中,退出登录时删除保存的JWT Token即可

4、前端在每次请求时将JWT Token放入HTTP请求头中的Authorization属性中(解决XSS和XSRF问题)

5、后端检查前端传过来的JWT Token,验证其有效性,比如检查签名是否正确、是否过期、token的接收方是否是自己等等

6、验证通过后,后端解析出JWT Token中包含的用户信息,进行其他逻辑操作(一般是根据用户信息得到权限等),返回结果

最后:说白了,JWT:JSON Web Token,其实token就是一段字符串,由三部分组成:Header,Payload,Signature

二、使用步骤

1.项目结构

2.相关依赖

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.22</version>
        </dependency>

        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.7.21</version>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.3.2</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.79</version>
        </dependency>

        <dependency>
            <groupId>commons-beanutils</groupId>
            <artifactId>commons-beanutils</artifactId>
            <version>1.9.4</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.22</version>
        </dependency>

        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.11.0</version>
        </dependency>

        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>4.1.2</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.1</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <version>2.3.8.RELEASE</version>
        </dependency>

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

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>
    </dependencies>

3.数据库

这里进行测试,所以用户类只有用户名密码,自行创建

4.相关代码

1、annotation包
PassToken:

package com.geesun.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author :Mr.ZJW
 * @date :Created 2022/2/28 10:26
 * @description:用来跳过验证的 PassToken
 */
@Target(ElementType.METHOD, ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface PassToken 
    boolean required() default true;

UserLoginToken:

package com.geesun.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author :Mr.ZJW
 * @date :Created 2022/2/28 10:26
 * @description:用于登录后才能操作的token
 */
/*RetentionPolicy.RUNTIME:这种类型的Annotations将被JVM保留,
所以他们能在运行时被JVM或其他使用反射机制的代码所读取和使用。*/
@Target(ElementType.METHOD, ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface UserLoginToken 
    boolean required() default true;

2、common包
CodeMsg:

package com.geesun.common;

/**
 * @author :Mr.ZJW
 * @date :Created 2022/2/28 10:26
 * @description:返回提示
 */
public class CodeMsg 
    private int retCode;
    private String message;
    // 按照模块定义CodeMsg
    // 通用异常
    public static CodeMsg SUCCESS = new CodeMsg(0,"success");
    public static CodeMsg SERVER_EXCEPTION = new CodeMsg(500100,"服务端异常");
    public static CodeMsg PARAMETER_ISNULL = new CodeMsg(500101,"输入参数为空");
    // 业务异常
    public static CodeMsg USER_NOT_EXSIST = new CodeMsg(500102,"用户不存在");
    public static CodeMsg ONLINE_USER_OVER = new CodeMsg(500103,"在线用户数超出允许登录的最大用户限制。");
    public static CodeMsg SESSION_NOT_EXSIST =  new CodeMsg(500104,"不存在离线session数据");
    public static CodeMsg NOT_FIND_DATA = new CodeMsg(500105,"查找不到对应数据");
    public static CodeMsg USER_OR_PASS_ERROR = new CodeMsg(500102,"账号或者密码错误,请重试!");


    private CodeMsg(int retCode, String message) 
        this.retCode = retCode;
        this.message = message;
    

    public int getRetCode() 
        return retCode;
    
    public String getMessage() 
        return message;
    
    public void setMessage(String message) 
        this.message = message;
    

Result:

package com.geesun.common;

/**
 * @author :Mr.ZJW
 * @date :Created 2022/2/28 10:26
 * @description:返回统一结果集
 */
public class Result<T> 

    private String message;
    private int retCode;
    private T data;

    private Result(T data) 
        this.retCode = 200;
        this.message = "成功";
        this.data = data;
    

    private Result(CodeMsg cm) 
        if(cm == null)
            return;
        
        this.retCode = cm.getRetCode();
        this.message = cm.getMessage();
    

    /**
     * 成功时候的调用
     * @return
     */
    public static <T> Result<T> success(T data)
        return new Result<T>(data);
    

    /**
     * 成功,不需要传入参数
     * @return
     */
    @SuppressWarnings("unchecked")
    public static <T> Result<T> success()
        return (Result<T>) success("");
    
    /**
     * 失败时候的调用
     * @return
     */
    public static <T> Result<T> error(CodeMsg cm)
        return new Result<T>(cm);
    
    /**
     * 失败时候的调用,扩展消息参数
     * @param cm
     * @param msg
     * @return
     */
    public static <T> Result<T> error(CodeMsg cm,String msg)
        cm.setMessage(cm.getMessage()+"--"+msg);
        return new Result<T>(cm);
    
    public T getData() 
        return data;
    
    public String getMessage() 
        return message;
    
    public int getRetCode() 
        return retCode;
    

3、config包
InterceptorConfig:

package com.geesun.config;

import com.geesun.Interceptor.AuthenticationInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.validation.MessageCodesResolver;
import org.springframework.validation.Validator;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.config.annotation.*;
import java.util.List;

/**
 * @author :Mr.ZJW
 * @date :Created 2022/2/28 10:25
 * @description:新建Token拦截器
 */
@Configuration
public class InterceptorConfig implements WebMvcConfigurer 
    @Override
    public void addInterceptors(InterceptorRegistry registry) 
        registry.addInterceptor(authenticationInterceptor())
                .addPathPatterns("/**");    // 拦截所有请求,通过判断是否有 @LoginRequired 注解 决定是否需要登录
    
    @Bean
    public AuthenticationInterceptor authenticationInterceptor() 
        return new AuthenticationInterceptor();
    
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> arg0) 
        // TODO Auto-generated method stub

    
    @Override
    public void addCorsMappings(CorsRegistry arg0) 
        // TODO Auto-generated method stub

    
    @Override
    public void addFormatters(FormatterRegistry arg0) 
        // TODO Auto-generated method stub

    
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry arg0) 
        // TODO Auto-generated method stub

    
    @Override
    public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> arg0) 
        // TODO Auto-generated method stub

    
    @Override
    public void addViewControllers(ViewControllerRegistry arg0) 
        // TODO Auto-generated method stub

    
    @Override
    public void configureAsyncSupport(AsyncSupportConfigurer arg0) 
        // TODO Auto-generated method stub

    
    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer arg0) 
        // TODO Auto-generated method stub

    
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer arg0) 
        // TODO Auto-generated method stub

    
    @Override
    public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> arg0) 
        // TODO Auto-generated method stub

    
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> arg0) 
        // TODO Auto-generated method stub

    
    @Override
    public void configurePathMatch(PathMatchConfigurer arg0) 
        // TODO Auto-generated method stub

    
    @Override
    public void configureViewResolvers(ViewResolverRegistry arg0) 
        // TODO Auto-generated method stub

    
    @Override
    public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> arg0) 
        // TODO Auto-generated method stub

    
    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> arg0) 
        // TODO Auto-generated method stub

    
    @Override
    public MessageCodesResolver getMessageCodesResolver() 
        // TODO Auto-generated method stub
        return null;
    
    @Override
    public Validator getValidator() 
        // TODO Auto-generated method stub
        return null;
    


4、Interceptor包
AuthenticationInterceptor:

package com.geesun.Interceptor;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.geesun.annotation.PassToken;
import com.geesun.annotation.UserLoginToken;
import com.geesun.pojo.User;
import com.geesun.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;

/**
 * @author :Mr.ZJW
 * @date :Created 2022/2/28 10:24
 * @description:拦截器
 */
public class AuthenticationInterceptor implements HandlerInterceptor 

    @Autowired
    UserService userService;

    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception 
        String token = httpServletRequest.getHeader("token");// 从 http 请求头中取出 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;
            
        
        //检查有没有需要用户权限的注解
        if (method.isAnnotationPresent(UserLoginToken.class)) 
            UserLoginToken userLoginToken = method.getAnnotation(UserLoginToken.class);
            if (userLoginToken.required()) 
                // 执行认证
                if (token == null) 
                    throw new RuntimeException("无token,请重新登录");
                
                // 获取 token 中的 user id
                String userId;
                try 
                    userId = JWT.decode(token).getAudience().get(0);
                 catch (JWTDecodeException j) 
                    throw new RuntimeException("401");
                
                User user = userService.findUserById(userId);
                if (user == null) 
                    throw new RuntimeException("用户不存在,请重新登录");
                
                // 验证 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) throws Exception 

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

    

5、utils包
TokenUtil:

package com.geesun.utils;

import com.auth0.jwt.JWT;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;

/**
 * @author :Mr.ZJW
 * @date :Created 2022/2/28 10:24
 * @description:
 */
public class TokenUtil 

    public static String getTokenUserId() 
        String token = getRequest().getHeader("token");// 从 http 请求头中取出 token
        String userId = JWT.decode(token).getAudience().get(0);
        return userId;
    

    /**
     * 获取request
     * @return
     */
    public static HttpServletRequest getRequest() 
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder
                .getRequestAttributes();
        return requestAttributes == null ? null : requestAttributes.getRequest();
    


6、pojo包
User:

package com.geesun.pojo;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName(value = "`user`")
public class User implements Serializable 
    @TableId(value = "id", type = IdType.NONE)
    private String id;

    @TableField(value = "username")
    private String username;

    @TableField(value = "password")
    private String password;

    private static final long serialVersionUID = 1L;

7、controller包
UserController:

package com.geesun.controller;

import cn.hutool.json.JSONObject;
import com.geesun.annotation.UserLoginToken;
import com.geesun.common.CodeMsg;
import com.geesun.common.Result;
import com.geesun.pojo.User;
import com.geesun.service.UserService;
import com.geesun.service.impl.TokenService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;

/**
 * @author :Mr.ZJW
 * @date :Created 2022/2/26 10:47
 * @description:
 */
@RestController
@RequestMapping("/user")
public class UserController 

    @Autowired
    private UserService userService;

    @Autowired
    private TokenService tokenService;

    /**
     * 查询用户信息
     * @return
     */
    @UserLoginToken
    @GetMapping("/list")
    public Result<Object> list()
        return Result.success(userService.list());
    


    /**
     * 登录验证
     * @param user
     * @param response
     * @return
     */
    @RequestMapping(value = "/login" ,method = RequestMethod.GET)
    public Result<Object> login(User user, HttpServletResponse response) 
        JSONObject jsonObject = new JSONObject();
        //获取用户账号密码
        User userForBase = new User();
        userForBase.setId(userService.findByUsername(user).getId());
        userForBase.setUsername(userService.findByUsername(user).getUsername());
        userForBase.setPassword(userService.findByUsername(user).getPassword());
        //判断账号或密码是否正确
        if (!userForBase.getPassword().equals(user.getPassword())) 
            return Result.error(CodeMsg.USER_OR_PASS_ERROR);
         else 
            String token = tokenService.getToken(userForBase);
            jsonObject.put("token", token);
            Cookie cookie = new Cookie("token", token);
            cookie.setPath("/");
            response.addCookie(cookie);
            return Result.success(jsonObject);
        
    
    

8、service包
UserService接口:

package com.geesun.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.geesun.pojo.User;

public interface UserService extends IService<User> 


    int deleteByIds(Long[] ids);

    int addUser(User user);

    User findByUsername(User user);

    User findUserById(String userId);

UserServiceImpl实现类:

package com.geesun.service.impl;

import cn.hutool.core.util.ArrayUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.geesun.mapper.UserMapper;
import com.geesun.pojo.User;
import com.geesun.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Arrays;

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService 

    @Autowired
    private UserMapper userMapper;
    
    /**
     * 判断用户名
     * @param user
     * @return
     */
    public User findByUsername(User user)
        return userMapper.selectOne(new LambdaQueryWrapper<User>().eq(User::getUsername,user.getUsername()));
    

    public User findUserById(String userId) 
        return userMapper.selectById(userId);
    


TokenService:

package com.geesun.service.impl;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.geesun.pojo.User;
import org.springframework.stereotype.Service;
import java.util.Date;

/**
 * @author :Mr.ZJW
 * @date :Created 2022/2/28 10:20
 * @description:
 */
@Service
public class TokenService 

    public String getToken(User user) 
        Date start = new Date();
        long currentTime = System.currentTimeMillis() + 60* 60 * 1000;//一小时有效时间
        Date end = new Date(currentTime);
        String token = "";

        token = JWT.create().withAudience(user.getId()).withIssuedAt(start).withExpiresAt(end)
                .sign(Algorithm.HMAC256(user.getPassword()));
        return token;
    

9、mapper包

package com.geesun.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.geesun.pojo.User;

public interface UserMapper extends BaseMapper<User> 


三、测试结果

1、登录验证

2、查询用户信息
这个方法加上了@UserLoginToken,所以要token才能查询


3、不加上Token进行测试就会出错提示

出错提示:

NodeJS(Express框架)实现 Token 验证免密登录

看文章之前,强烈建议先把项目拉取下来!案例来自小弟的开源项目「项目Github」

文章内容只是个人学习的一些总结经验,不具有权威性,这是 Node 服务端的实现,后面会写前端的实现

什么是 Token 验证

常见的 Token 验证方式种:

  • OAuth2,例如:微信授权登录 (貌似也属于 Token 验证)
  • 基于 JWT 的 Token 验证,KiteBlog 里面使用的就是这种。

推荐阅读:

JWT 超详细分析

说一说几种常用的登录认证方式,你用的哪种

什么是 JWT (JSON WEB TOKEN) ?

推荐阅读:

JSON Web Token 入门教程

JSON Web Token - 在Web应用间安全地传递信息

NodeJS (Express) 中实现 Token 的生成和验证

安装 jsonwebtoken 和 express-jwt

首先我们先安装 jsonwebtokenexpress-jwt 这两个中间件

jsonwebtoken: 用于生成 Token 。它也有解析 Token 的功能

express-jwt: 用于解析 Token(比 jsonwebtoken 解决方便) , 它把解析之后的数据,存放到 requset.user

# 安装 jsonwebtoken
npm i jsonwebtoken

# 安装 express-jwt
npm i express-jwt

复制代码

生成 token

如果你看了上面 JWT 介绍的文章,就知道 JWT 是由三部分组成的,分别是载荷(Payload)头部(Header)签名(Signature)

jsonwebtoken 给我们提供了sign(payload, secretOrPrivateKey, [options, callback])方法。sign 方法对应的其实就是 JWT 签名(Signature)的动作

payload:荷载 ,参数类型:对象 secretOrPrivateKey:自定义的密钥,密钥属于敏感信息。参数类型:字符串 options:可以配置 header 、荷载、指定算法类型。参数类型:对象 callback:回调

眼尖的朋友应该发现,payloadoptions 两个参数都可以配置荷,下面有例子。根据自己的习惯选择就好

Payload 部分 JWT 规定了7个官方字段,这些字段都是可选字段。可直接以对象的形式传给 payload 参数。

iss (issuer):签发人
exp (expiration time):过期时间
sub (subject):主题
aud (audience):受众
nbf (Not Before):生效时间
iat (Issued At):签发时间
jti (JWT ID):编号
复制代码

options 中也可以接受以上七个字段,不过字段名称有所区别。

iss ---- issuer
exp ---- expiresIn
sub ---- subject
aud ---- audience
nbf ---- notBefore
iat ---- noTimestamp
jti ---- jwtid
复制代码

除此之后 options 提供了algorithmheader,分别对应使用的加密算法和 JWT 的 Header 部分,其实 algorithm 应该也是属于 Header 部分的。

说了这么多,其实我们一般常用的只有 exp(expiresIn)algorithm 这两个字段,

例子一:

token 的有效时间是配置在 option

//先引入 jsonwebtoken
var jsonWebToken = require(\'jsonwebtoken\');

//密钥,当然实际的项目中密钥应该变态一些
const SECRET_KEY = \'kite1874\'

const token = jsonWebToken.sign({
    // Payload 部分,官方提供七个字段这边省略,可以携带一些可以识别用户的信息。例如 userId。
    // 千万不要是用敏感信息,例如密码,Payload 是可以解析出来的。
    userId:user.userId
    role:user.role
},SECRET_KEY,{
    expiresIn:"24h", //token有效期
    // expiresIn: 60 * 60 * 24 * 7,  两种写法
    // algorithm:"HS256"  默认使用 "HS256" 算法
})

console.log(token)
复制代码

例子二:

我们也可以在 payload 里配置有效时间

const token = jsonWebToken.sign({
    //exp 的值是一个时间戳,这里表示 1h 后 token 失效
    exp:Math.floor(Date.now() / 1000) + (60 * 60)
    userId:user.userId
    role:user.role
},SECRET_KEY)
复制代码

jsonwebtoken 除了生成 token 外,还提供了解析验证 token 的方法,jwt.verify(token, secretOrPublicKey, [options, callback])

这里就不演示了, 感兴趣的朋友可以参考文档:「JsonWebToken」

验证 token

express-jwt 是针对 express框架开发的 JWT Token 验证中间件。我先来简单说以下它的用法。

主要有两种方式,一种是哪些请求需要验证就往哪里加验证;另外一种是先给全部请求加上验证,再给不需要验证的请求配置白名单

方式一:

var express = require(\'express\');
var jwt = require(\'express-jwt\');
var app = express();

//SECRET_KEY 要与生成 Token 时保持一致 
const SECRET_KEY = \'kite1874\' 

// secret 为密钥,algorithms 为算法。需要注意的是,如果你生成 Token 的时候没有手动设置 algorithm 
// 默认是使用 HS256 来加密的。「express-jwt 6.0」版本需要加 algorithms: [\'HS256\'] , 说起来都是泪!
app.get("/test", 
jwt({ secret: SECRET_KEY, algorithms: [\'HS256\']}),
function(req,res){
    //do something...
})
复制代码

看完上面的例子,很显然不符合我们的逾期,一个正常的项目有个几十个 api 是分分钟的事。我们不可能一个个给他加上检验

方式二:

//注册中间件,相当于配置一个全局 token 验证,unless 就是上面说的白名单
//把不需要 token 验证的请求填进 path 里即可, 支持数组、字符串、正则

app.use(jwt({ secret: SECRET_KEY, algorithms: [\'HS256\']})
.unless({path: [\'/auth/adminLogin\',/^\\/public\\/.*/]}));
// /auth/adminLogin api 和 public 下的文件都不需要 token 验证
复制代码

这种方式是不是方便很多,而且更美观,维护起来也更方便

Token 解析出来的用户信息,默认存放在 req.user, 可以直接 req.user.userId来使用生成 Token 时填进去的用户id

你也通过 requestPropertyresultProperty 来设置用户信息存放的对象。

这里就不展开,详细文档参考:express-jwt

验证错误处理

可以使用 app.use() 来注册处理验证不通过的情况

app.use(function (err, req, res, next) {
  if (err.name === \'UnauthorizedError\') {
    res.status(401).send("干嘛呢?你想硬闯?!")
  }
})
复制代码

注意(个人理解,不具有权威性)

到这里 Token 的生成、验证、检验不通过错误处理就完成了。 Token 生成一般是在登录之后生成,并返回给前端,前端拿到 Token ,并在每次请求 api 的时候携带上 Token , Token 就相当于这个用户的身份,不要轻易泄露。

Token一旦签发,不能主动让它失效,只能等待它有效期过才能失效。也就是说就算你修改了密码,之前的 Token 也还是有效的。你可以修改后端生成 Token 时使用的密钥,不让之前的 Token 检验通过,但是这就表示之前所有生成 Token 都失效了,做不到针对某个用户进行注销。这显然也不合适的。 所以用户修改密码时,前端一般都要清除之前保存的 Token,再重获取新的 Token

有朋友应该会想到在后端把 Token 储存起来,每一个用户对应一个 token。修改账号时,再生成一个新的 Token 覆盖之前的 Token,但这就违背了使用 Token 的目的,Token 的使用很大程度就为了减少服务器的压力。把尽可能多的信息存储在客户端而不是服务端。

使用 Token 可以防御 CSRF 攻击,之前写过一篇关于网络安全的文章,感兴趣的朋友可以看一下「XSS 攻击、CSRF 攻击、SQL 注入、流量劫持(DNS 劫持、HTTP 劫持)—— 浏览器安全」

以上是关于Java实现Token登录验证(基于JWT的token认证实现)的主要内容,如果未能解决你的问题,请参考以下文章

掌握基于 JWT 实现的 Token 身份认证

基于Token登录验证与统一拦截(一个JWT的Demo)

NodeJS(Express框架)实现 Token 验证免密登录

java小技能:JWT(json web token)认证实现

基于gin框架和jwt-go中间件实现小程序用户登陆和token验证

SpringBoot集成JWT实现Token登录验证