用JWT机制实现Token身份验证

用JWT机制实现Token身份验证

1.JWT(JSON WEB TOKEN)

由于HTTP的无状态性,我们无法判断是哪个客户端在请求接口。这里我们把用户看成是客户端,客户端使用用户名还有密码通过了身份验证,不过下回这个客户端再发送请求时候,还得再验证一下。

解决的方法就是,当用户请求登录的时候,如果没有问题,我们在服务端生成一条记录,这个记录里可以说明一下登录的用户是谁,然后把这条记录的 ID 号发送给客户端,客户端收到以后把这个 ID 号存储在 Cookie 里,下次这个用户再向服务端发送请求的时候,可以带着这个 Cookie ,这样服务端会验证一个这个 Cookie 里的信息,看看能不能在服务端这里找到对应的记录,如果可以,说明用户已经通过了身份验证,就把用户请求的数据返回给客户端。

上面说的就是 Session,我们需要在服务端存储为登录的用户生成的 Session ,这些 Session 可能会存储在内存,磁盘,或者数据库里。我们可能需要在服务端定期的去清理过期的 Session 。

但是有些情况下session并不那么好用,一个是session会占用后端内存资源,还有如果用户cookie中的sessionId被窃取,很容易就可以获取用户的私有数据.这时我们可以用token来解决这些问题

使用基于 Token 的身份验证方法,在服务端不需要存储用户的登录记录。大概的流程是这样的:

  • 1.客户端使用用户名跟密码请求登录
  • 2.服务端收到请求,去验证用户名与密码
  • 3.验证成功后,服务端会签发一个 Token,再把这个 Token 发送给客户端
  • 4.客户端收到 Token 以后可以把它存储起来,比如放在 Cookie 里或者 Local Storage 里
  • 5.客户端每次向服务端请求资源的时候需要带着服务端签发的 Token
  • 6.服务端收到请求,然后去验证客户端请求里面带着的 Token,如果验证成功,就向客户端返回请求的数据

实际上,JWT的Token就是 JSON数据转换后的一串字符串 JWT主要包括三个部分:header(头部)payload(数据)signature(签名)

  • header
    header表示头部数据
{
"alg": "HS256",
"typ": "JWT"
}

alg属性表示签名使用的算法,默认为HMAC SHA256(写为HS256);typ属性表示令牌的类型,JWT令牌统一写为JWT

  • payload
    Payload 里面是 Token 的具体内容
{
 "id": 1,
 "username": "wintershii",
 "passwd": "",
 "admin": true
}
  • signature
    JWT 的最后一部分是 Signature ,这部分内容有三个部分,先是用 Base64 编码的 header.payload ,再用加密算法加密一下,加密的时候要放进去一个 Secret (私匙),这个相当于是一个密码,这个密码秘密地存储在服务端。

最终我们将这三部分内容用"."分开,并用Base64编码

2.使用Token进行身份验证

作为一个后端为SSM框架的项目,首先现在pom.xml中引入依赖

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

然后编写Token工具类

public class TokenUtil {

    //token有效时间
    private static final long EXPIRE_TIME = 15 * 60 * 1000;
   
    private static final String TOKEN_SECRET = "thefirsttoken777";//这里填写私匙

    /**
     * 颁发签名
     * @return
     */
    public static String sign(Integer id, String phone) {
        try {
            // 设置过期时间
            Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
            // 私钥和加密算法
            Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
            // 设置头部信息
            Map<String, Object> header = new HashMap<>(2);
            header.put("Type", "Jwt");
            header.put("alg", "HS256");
            // 返回token字符串
            return JWT.create()
                    .withHeader(header)
                    .withClaim("id",id.toString())//在这里可以放自己需要放的数据
                    .withClaim("phone",phone)
                    .withExpiresAt(date)
                    .sign(algorithm);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 检验token是否是正确格式(是否过期,是否可用)
     * @param token
     * @return
     */
    public static boolean verify(String token){
        try {
            Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
            JWTVerifier verifier = JWT.require(algorithm).build();
            DecodedJWT jwt = verifier.verify(token);
            return true;
        } catch (Exception e){
            return false;
        }
    }


    /**
     * 从token中获取相关信息
     * @param token
     * @return
     */
    public static String getInfo(String token ,String type){
        try {
            DecodedJWT jwt = JWT.decode(token);
            if (type.equals("phone")) {
                return jwt.getClaim("phone").asString();
            }
            if (type.equals("id")) {
                return jwt.getClaim("id").asString();
            }

        } catch (JWTDecodeException e){
            e.printStackTrace();
            return null;
        }
        return null;
    }


}

根据上面的TokenUtil,在用户登录后,我们可以签发给该用户Token,并将其添加到响应头(或者在响应体里加也可以),然后下次客户端请求我们的时候就要带着这个Token来请求接口

/**
     * 用户登录
     * @return
     */
    @RequestMapping(value = "/login.do",method = RequestMethod.POST)
    @ResponseBody
    public ServerResponse<User> login(String phone, String password, HttpServletResponse response) {
        ServerResponse<User> serverResponse = userService.login(phone,password);
        if (serverResponse.isSuccess()) {
            Integer id = serverResponse.getData().getId();
            //生成Token
            String token = TokenUtil.sign(id,phone);
            if (token != null) {
            	//添加到响应头中
                response.addHeader("token",token);
                return serverResponse;
            }
        }
        return ServerResponse.createByErrorMessage("登录失败");
    }

那么如何判断请求是否带有Token呢?我们当然可以在每个Controller中都进行判断,但是这也太麻烦了,这时我们想到了过滤器,也就是Spring MVC拦截器.我们把除了注册,登录这些不需要用户登录的接口的其他接口用拦截器拦截,并检测其请求头中是否带有Token信息

/**
 * token拦截器
 */
@Component
public class TokenInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {

        response.setCharacterEncoding("utf-8");
        String token = request.getHeader("token");
        if (token != null){
            boolean result = TokenUtil.verify(token);
            if(result){
                System.out.println("通过拦截器");
                return true;
            }
        }
        System.out.println("认证失败");
        return false;
    }
}<!--配置拦截器-->
    <mvc:interceptors>
    <!--<bean class="com.ma.interceptor.CustomeInterceptor" />-->
    <!--拦截器1-->
    <mvc:interceptor>
        <!--配置拦截器的作用路径-->
        <mvc:mapping path="/**"/>
        <mvc:exclude-mapping path="/user/register.do"/>
        <mvc:exclude-mapping path="/user/login.do"/>
        <!--定义在<mvc:interceptor>下面的表示匹配指定路径的请求才进行拦截-->
        <bean class="com.winter.interceptor.TokenInterceptor"/>
    </mvc:interceptor>
    </mvc:interceptors>

下面是spring mvc配置文件中的拦截器相关注解

<!--配置拦截器-->
    <mvc:interceptors>
    <!--<bean class="com.ma.interceptor.CustomeInterceptor" />-->
    <!--拦截器1-->
    <mvc:interceptor>
        <!--配置拦截器的作用路径-->
        <mvc:mapping path="/**"/>
        <mvc:exclude-mapping path="/user/register.do"/>
        <mvc:exclude-mapping path="/user/login.do"/>
        <!--定义在<mvc:interceptor>下面的表示匹配指定路径的请求才进行拦截-->
        <bean class="com.winter.interceptor.TokenInterceptor"/>
    </mvc:interceptor>
    </mvc:interceptors>

这样我们就可以使用Token进行会话了,但当我们需要进行某些敏感操作(删除,修改信息)时,我们需要在Controller中解析Token中的数据,并将其与要操作的用户数据进行对比,如果不是同一用户的数据,那就拒绝操作,返回失败.

最后,因为JWT如果被窃取,服务器也没有办法去识别,所以为了减少盗用和窃取,JWT不建议使用HTTP协议来传输代码,而是使用加密的HTTPS协议进行传输。

猜你喜欢

转载自blog.csdn.net/wintershii/article/details/88373260