JWT(java web token)
JWT包含三部分: Header 头部 Payload 负载 Signature 签名
其结构看起来是这样的 Header.Payload.Signature。

#JWT的组成
##Header
在header中通常包含了两部分:token类型和采用的加密算法。{ “alg”: “HS256”, “typ”: “JWT”}
接下来对这部分内容使用 Base64Url 编码组成了JWT结构的第一部分。

##Payload
它包含了claim, Claim是一些实体(通常指的用户)的状态和额外的元数据,有三种类型的claim:reserved, public 和 private.Reserved claims: 这些claim是JWT预先定义的,在JWT中并不会强制使用它们,而是推荐使用,常用的有 iss(签发者),exp(过期时间戳), sub(面向的用户), aud(接收方), iat(签发时间)。 Public claims:根据需要定义自己的字段,注意应该避免冲突 Private claims:这些是自定义的字段,可以用来在双方之间交换信息 负载使用的例子:{ “sub”: “1234567890”, “name”: “John Doe”, “admin”: true}

负载需要经过Base64Url编码后作为JWT结构的第二部分

##Signature
建签名需要使用编码后的header和payload以及一个秘钥,使用header中指定签名算法进行签名。例如如果希望使用HMAC SHA256算法,那么签名应该使用下列方式创建: HMACSHA256( base64UrlEncode(header) + “.” + base64UrlEncode(payload), secret) 签名用于验证消息的发送者以及消息是没有经过篡改的。

#JWT的使用
客户端收到服务器返回的 JWT,可以储存在 Cookie 里面,也可以储存在 localStorage。

此后,客户端每次与服务器通信,都要带上这个 JWT。你可以把它放在 Cookie 里面自动发送,但是这样不能跨域,所以更好的做法是放在 HTTP 请求的头信息里面。另一种做法是,跨域的时候,JWT 就放在 POST 请求的数据体里面。

#JWT的使用场景

Authorization (授权) :

这是使用JWT的最常见场景。一旦用户登录,后续每个请求都将包含JWT,允许用户访问该令牌允许的路由、服务和资源。单点登录是现在广泛使用的JWT的一个特性,因为它的开销很小,并且可以轻松地跨域使用。

Information Exchange (信息交换) :

对于安全的在各方之间传输信息而言,JSON Web Tokens无疑是一种很好的方式。因为JWTs可以被签名,例如,用公钥/私钥对,你可以确定发送人就是它们所说的那个人。另外,由于签名是使用头和有效负载计算的,您还可以验证内容没有被篡改。

#JWT和Session的区别
相同点是,它们都是存储用户信息;然而,Session是在服务器端的,而JWT是在客户端的。

Session方式存储用户信息的最大问题在于要占用大量服务器内存,增加服务器的开销。

而JWT方式将用户状态分散到了客户端中,可以明显减轻服务端的内存压力。

Session的状态是存储在服务器端,客户端只有session id;而Token的状态是存储在客户端。

JWT的客户端和服务端的原理

从下图可以看到:

  • JWT是由服务端产生的,返回给客户端
  • 客户端每次请求服务端的时候都要带上JWT
  • 服务端去通过过滤器拦截请求,验证JWT

基于Token的身份认证是无状态的,服务器或者Session中不会存储任何用户信息。

没有会话信息意味着应用程序可以根据需要扩展和添加更多的机器,而不必担心用户登录的位置。

虽然这一实现可能会有所不同,但其主要流程如下:

1: 用户携带用户名和密码请求访问。

2: 服务器校验用户凭据。

3: 应用提供一个token给客户端。

4: 客户端存储token,并且在随后的每一次请求中都带着它。

5: 服务器校验token并返回数据。
注意:

1: 每一次请求都需要token。

2: Token应该放在请求header中。

3:我们还需要将服务器设置为接受来自所有域的请求,用Access-Control-Allow-Origin: *。

4.为了减少盗用,JWT 不应该使用 HTTP 协议明码传输,要使用 HTTPS 协议传输。

#JWT的使用
第一步:添加依赖

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

第二步:创建工具类



import com.auth0.jwt.JWTSigner;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.internal.com.fasterxml.jackson.databind.ObjectMapper;
 
import java.util.HashMap;
import java.util.Map;
 
/**
 * JWT工具类
 */
public class JWTUtils {
 
    private static final String SECRET = "XX#$%()(#*!()!KL<><MQLMNQNQJQK sdfkjsdrow32234545fdf>?N<:{LWPW";
    private static final String EXP = "exp";
    private static final String PAYLOAD = "payload";
    //加密,传入一个对象和有效期
    public static <T> String sign(T object, long maxAge) {
        try {
            final JWTSigner signer = new JWTSigner(SECRET);
            final Map<String, Object> claims = new HashMap<String, Object>();
            ObjectMapper mapper = new ObjectMapper();
            String jsonString = mapper.writeValueAsString(object);
            claims.put(PAYLOAD, jsonString);//传输相关信息
            claims.put(EXP, System.currentTimeMillis() + maxAge);//过期时长
            return signer.sign(claims);
        } catch (Exception e) {
            return null;
        }
    }
 
    //解密,传入一个加密后的token字符串和解密后的类型
    public static <T> T unsign(String jwt, Class<T> classT) {
        final JWTVerifier verifier = new JWTVerifier(SECRET);
        try {
            final Map<String, Object> claims = verifier.verify(jwt);
            if (claims.containsKey(EXP) && claims.containsKey(PAYLOAD)) {
                long exp = (Long) claims.get(EXP);
                long currentTimeMillis = System.currentTimeMillis();
                if (exp > currentTimeMillis) {
                    String json = (String) claims.get(PAYLOAD);
                    ObjectMapper objectMapper = new ObjectMapper();
                    return objectMapper.readValue(json, classT);
                }
            }
            return null;
        } catch (Exception e) {
            return null;
        }
    }
 
}

第三步:把token放进header中

第四步:每次请求都验证token(通过aop实现拦截)
前端需要把请求头中放入token参数,里面放入最开始后端返回的token



import com.rongji.common.annotation.Log;
import com.rongji.common.config.Constant;
import com.rongji.common.entity.LogEntity;
import com.rongji.common.service.LogService;
import com.rongji.common.utils.*;
import com.rongji.system.entity.JWTentity;
import com.rongji.system.entity.SysUserTokenEntity;
import com.rongji.system.service.SysUserService;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
 
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.*;
 
 
@Aspect
@Component
public class LogAspect {
    private static final Logger logger = LoggerFactory.getLogger(LogAspect.class);
 
    @Autowired
    LogService logService;
 
    @Autowired
    SysUserService userService;
 
    @Pointcut("execution(* com.*.controller..*(..)))")
    public void logPointCut() {
    }
 
    @Around("logPointCut()")
    public Object around(ProceedingJoinPoint point) {
        //判断token信息
        HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
        String token = request.getHeader("token");
        //如果传的值不存在:1:登录 2:过期
        String uri = request.getRequestURI();
        String method = request.getMethod();
        boolean isLogin = StringUtils.equals("/login", uri) && StringUtils.equals("POST", method);
        JWTentity user = null;
        R r =R.error(Constant.jwtErrorCode, "session失效,请重新登录");
        if (StringUtils.isEmpty(token)) {
            //进入的是登录界面
            if (!isLogin) {
                //其他接口:session过期
                return r;
            }
        } else if (!isLogin) {
            //校验
            user = JWTUtils.unsign(token, JWTentity.class);
            if (user == null) {
                //返回错误信息
                return r;
            } else {
                Long userId = user.getUserId();
                //判断是否在黑名单里面
                List tokens = userService.getUserToken(userId);
                if (tokens.contains(token)) {
                    return r;
                }
                // 执行时长(毫秒)
                long time = Constant.jwtMaxAge - user.getLoginDate().getSeconds();
                if (0 < time && time <= Constant.jwtDelayMinAge) {
                    return r;
                }
                //延时
                delayTime(token, user, userId, time);
            }
        }
        long beginTime = System.currentTimeMillis();
        // 执行方法
        Object result = null;
        try {
            result = point.proceed();
        } catch (Throwable throwable) {
           logger.error(throwable.toString());
        }
        // 执行时长(毫秒)
        long time = System.currentTimeMillis() - beginTime;
        //异步保存日志
        saveLog(point, time, user);
        return result;
    }
    private void delayTime(String token, JWTentity user, Long userId, long time) {
        if (time > 0 && time < Constant.jwtDelayMaxAge) { //延时
            HttpServletResponse response = HttpContextUtils.getHttpServletResponse();
            user.setLoginDate(new Date());
            String jwtToken = JWTUtils.sign(user, Constant.jwtMaxAge);
            response.setHeader("token", jwtToken);
            //保存黑名单
            SysUserTokenEntity userToken = new SysUserTokenEntity();
            userToken.setUserId(userId);
            userToken.setTokenId(token);
            userToken.setOperDate(new Date());
            userService.saveUserTokenEntity(userToken);
        }
    }
    void saveLog(ProceedingJoinPoint joinPoint, long time, JWTentity user) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        LogEntity sysLog = new LogEntity();
        //Log syslog = method.getAnnotation(Log.class);
        //if (syslog != null) {
            // 注解上的描述
           // sysLog.setOperation(className + "." + methodName + "()");
       // }
        // 请求的方法名
        String className = joinPoint.getTarget().getClass().getName();
        String methodName = signature.getName();
        sysLog.setMethod(className + "." + methodName + "()");
        // 请求的参数
        HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
        Object[] args = joinPoint.getArgs();
//        String params1 = JSONUtils.beanToJson(args[0]);
  //      sysLog.setParams(params1);
        // 获取request
        // HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
        StringBuffer url = request.getRequestURL();
        String uri = request.getRequestURI();
        // 设置IP地址
        String ip = IPUtils.getIpAddr(request);
        sysLog.setIp(ip);
        if (user != null) {
            Long userId = user.getUserId();
            if (userId != null) {
                sysLog.setUserId(userId);
            }
            String userName = user.getUserName();
            if (StringUtils.isNotEmpty(userName)) {
                sysLog.setUsername(userName);
            }
        }
        sysLog.setTime((int) time);
        // 系统当前时间
        Date date = new Date();
        sysLog.setGmtCreate(date);
        // 保存系统日志
        logService.save(sysLog);
    }
}

第五步:由于前后端分离问题,需要在response中设置参数:



import org.apache.commons.lang.StringUtils;
import org.apache.http.HttpStatus;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.stereotype.Component;
 
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
 
@Component
@ServletComponentScan
@WebFilter(urlPatterns = "/*",filterName = "CORSFilter")
public class CORSFilter implements Filter {
 
    private static final long serialVersionUID = 1L;
 
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
 
    }
 
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        HttpServletRequest request=(HttpServletRequest)servletRequest;
        response.setContentType("text/plain;charset=UTF-8");
        response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, DELETE, PUT, OPTIONS");
        response.setHeader("Access-Control-Max-Age", "1728000");
        response.setHeader("Access-Control-Allow-Headers", "Origin, No-Cache, X-Requested-With, If-Modified-Since, " +
                "Pragma, Last-Modified, Cache-Control, Expires, Content-Type, X-E4M-With,userId,token");
        response.setHeader("Access-Control-Allow-Credentials", "true");
        response.setHeader("XDomainRequestAllowed","1");
        //添加可以返回自定义header信息
        response.setHeader("Access-Control-Expose-Headers","token");
        if ("OPTIONS".equals(request.getMethod())){//这里通过判断请求的方法,判断此次是否是预检请求,如果是,立即返回一个204状态吗,标示,允许跨域;预检后,正式请求
            response.setStatus(HttpStatus.SC_NO_CONTENT); //HttpStatus.SC_NO_CONTENT = 204
            return;
        }
     
        filterChain.doFilter(servletRequest,servletResponse);
    }
 
    @Override
    public void destroy() {
 
    }
 
    /**
     * 重新封装request包装类
     */
    class MyHttpServletRequestWrapper extends HttpServletRequestWrapper {
        private String url;
 
        public MyHttpServletRequestWrapper(HttpServletRequest request,String url) {
            super(request);
            this.url=url;
        }
        @Override
        public String getServletPath() {
            if(super.getDispatcherType().name().equals("REQUEST")) {
                return url;
            } else {
                return super.getServletPath();
            }
        }
    }
 
}

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐