使用JWT Token进行RestFul API权限验证

问题

后端提供的RestFul API一般都需要进行权限验证,而Web框架一般采用Cookies+Session来进行认证。但RestFul API属于无状态协议,而在后台使用Session的话,由于Session本身需要服务端进行维持,这样就破坏了Rest的无状态性。

解决方案

抛弃Cookie+Session的认证架构,选用Token作为认证手段。在登录验证时,后台收集账号信息、IP、访问时间等信息,生成对应Token,通过cookies传回客户端。之后的每次请求API,都需要带上Token;后台对Token进行解码与鉴权操作。这样就可以在服务端本身不维护登录状态的情况下,进行权限认证,不会破坏RestFul的特性。

Java Web 示例

由于Web后台框架是SpringMVC,这里选择使用JWT作为Token生成工具。

编写一个Token生成工具类,完成两个方法:Token生成以及Token验证。

package com.example.utils;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;

import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * 用于JWT Token的生成与解码验证
 */
public class JwtTokenUtils {

    // 加密用的私钥
    private static String TOKEN_KEY = "keys";

    /**
     * 生成返回给客户端的token
     * @param username 登录用户名
     * @return token
     * @throws Exception
     */
    public static String createToken(String username) throws Exception {
        // Token产生时间点
        Date startDate = new Date();

        // 设置过期时间
        Calendar now = Calendar.getInstance();
        now.add(Calendar.HOUR, 24);
        Date expireDate = now.getTime();

        Map map = new HashMap();
        map.put("alg", "HS256");
        map.put("typ", "JWT");
        String token = JWT.create()
                .withHeader(map)
                .withClaim("username", username)
                .withExpiresAt(expireDate)      // 过期时间
                .withIssuedAt(startDate)        // 签发时间
                .sign(Algorithm.HMAC256(TOKEN_KEY));        // 加密签名算法
        return token;
    }

    /**
     * 验证客户端token合法性
     * @param token
     * @return 解码token,获得的claim对
     * @throws Exception
     */
    public static Map vertifyToken(String token) throws Exception {
        JWTVerifier verifier = JWT.require(Algorithm.HMAC256(TOKEN_KEY)).build();
        DecodedJWT jwt = null;
        try {
            jwt = verifier.verify(token);
        } catch (Exception e) {
            throw new RuntimeException("token无效");
        }
        return jwt.getClaims();
    }
}

这里采用的是自定义私钥以及用户名进行混合加密,有需要可以添加IP等其他信息。

登录验证后,生成Token,通过Cookies返回给客户端:

try {
    String token = JwtTokenUtils.createToken(username);
    Cookie cookie = new Cookie("token", token);
    response.addCookie(cookie);
} catch (Exception e) {
    e.printStackTrace();
    logger.error("create token error : " + username);
}

注意,用户注销后,需要使Token失效。

自定义拦截器,对需要权限控制的API进行拦截,验证Token:

package com.example.interceptor;


import com.auth0.jwt.interfaces.Claim;
import com.example.service.IManagerService;
import com.example.utils.JwtTokenUtils;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;

/**
 * Access Token 拦截器
 * 验证Api权限
 */
@Component
public class VerifyInterceptor extends HandlerInterceptorAdapter {

    private static final Logger logger = Logger.getLogger(VerifyInterceptor.class);

    @Autowired
    private IManagerService managerService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        logger.debug("Access token executing...");

        // 标记,用于最后返回值
        boolean flag = false;

        // 从请求报文cookies中获取token
        String token = null;
        Cookie[] cookies = request.getCookies();
        // 结果非空时,从数组中查找名为token的cookie
        if (null != cookies) {
            for (Cookie cookie : cookies) {
                if ("token".equals(cookie.getName())) {
                    token = cookie.getValue();
                    break;
                }
            }
        }

        // 开发时无需token,测试or部署时需要改成false
        if (null == token || "".equals(token)) {
            System.out.println("empty token");
            flag = true;
        } else {
            try {
                Map map = JwtTokenUtils.vertifyToken(token);
                String name = map.get("username").asString();
                flag = managerService.isUsernameExist(name);
            } catch (RuntimeException e) {
                e.printStackTrace();
                System.out.println("权限认证失败");
                logger.debug("权限认证失败");
            }
        }
        if (!flag) {    // 验证失败时设置response错误码
            response.setStatus(401);
        }
        return flag;
    }
}

这样我们的RestFul API简易权限验证模块就基本完成了。

你可能感兴趣的:(SpringMVC)