无状态session 解决方案 JWT

问题描述:

        现在分布式微服务的逐渐使用广泛,前后端分离已经成为互联网项目开发标准,它会为以后的大型分布式架构打下基础。为以后服务的横向扩展提供了方便

 JSON Web Tokens(JWT)能提供基于JSON格式的安全认证。JWT可以跨不同语言,自带身份信息,并且非常容易传递。

  JWT即JSON WEB TOKEN的缩写,轻量级的令牌认证(相比于oauth),可用于数据交换间的安全传输,同时可使用公钥/私钥的非对称算法对信息进行数字签名,如RS256算法。

  它由3部分组成:

  • Header  头信息
  • Payload  荷载信息,实际数据;一个token的第二个部分是荷载信息,它包含一些声明Claim(实体的描述,通常是一个User信息,还包括一些其他的元数据)
  • Signature  由头信息+荷载信息+密钥 组合之后进行加密得到,使用header中指定的算法将编码后的header、编码后的payload、一个secret进行加密

使用场景:

       1、单点登录,此场景也是使用最广的一种,流程原理很简单:用户登录成功-》生成token返回前端并保存(cookie或localstorage)-》之后每次请求在header或body中带此token-》服务器验证此token以保证数据合法性

            官方给的单点登录流程图

无状态session 解决方案 JWT_第1张图片

        2、与第三方接口的数据传输,在处理对外接口时(特别是公司之外业务)可很方便的作为一个约定,可很好的减少由于数据安全性问题所要做的沟通工作。


优点:

    JWT是基于JSON格式的数据安全认证,可以跨平台、跨语言、自带身份信息、非常容易传递

缺点:

    jwt涉及base64及加密处理,所以会使传输的数据会比裸传数据大很多,这方面大家可以根据实际情况做平衡取舍。对于公司内部的项目及对传输数据量有极高要求的更要慎重考虑用jwt方式;


和Session方式存储id的差异

Session方式存储用户id的最大弊病在于Session是存储在服务器端的,所以需要占用大量服务器内存,对于较大型应用而言可能还要保存许多的状态。一般而言,大型应用还需要借助一些KV数据库和一系列缓存机制来实现Session的存储。

而JWT方式将用户状态分散到了客户端中,可以明显减轻服务端的内存压力。除了用户id之外,还可以存储其他的和用户相关的信息,例如该用户是否是管理员、用户所在的分组等。虽说JWT方式让服务器有一些计算压力(例如加密、编码和解码),但是这些压力相比磁盘存储而言可能就不算什么了。具体是否采用,需要在不同场景下用数据说话。


实战部分

    创建实体类    

@Component
public class JWT {

    private Logger logger = LoggerFactory.getLogger(getClass());

    /**
     * 加密秘钥
     */
    @Value( "${secret}" )
    private String secret;
    /**
     * 有效时间
     */
    @Value( "${expire}" )
    private long expire;
    /**
     * 用户凭证
     */
    @Value( "${header}" )
    private String header;

    /**
     * 获取:加密秘钥
     */
    public String getSecret() {
        return secret;
    }

    /**
     * 设置:加密秘钥
     */
    public void setSecret(String secret) {
        this.secret = secret;
    }

    /**
     * 获取:有效期(s)
     * */
    public long getExpire() {
        return expire;
    }
    /**
     * 设置:有效期(s)
     * */
    public void setExpire(long expire) {
        this.expire = expire;
    }

    /**
     * 获取:凭证
     * */
    public String getHeader() {
        return header;
    }
    /**
     * 设置:凭证
     * */
    public void setHeader(String header) {
        this.header = header;
    }

    /**
     * 生成Token签名
     * @param userId 用户ID
     * @return 签名字符串
     */
    public String generateToken(long userId) {

        logger.info("header= " + getHeader() + ", expire=" + getExpire() + ", secret=" + getSecret());
        Date nowDate = new Date();
        // 过期时间
        Date expireDate = new Date(nowDate.getTime() + expire * 1000);
        return Jwts.builder()
                .setHeaderParam("typ", "JWT")
                .setSubject(String.valueOf(userId))
                .setIssuedAt(nowDate)
                .setExpiration(expireDate)
                .signWith(SignatureAlgorithm.HS512, getSecret()).compact();
        // 注意: JDK版本高于1.8, 缺少 javax.xml.bind.DatatypeConverter jar包,编译出错
    }

    /**
     * 获取签名信息
     * @param token
     */
    public Claims getClaimByToken(String token) {
        try {
            return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
        } catch (Exception e) {
            logger.debug("validate is token error ", e);
            return null;
        }
    }

    /**
     * 判断Token是否过期
     * @param expiration
     * @return true 过期
     */
    public boolean isTokenExpired(Date expiration) {
        return expiration.before(new Date());
    }
}
@Component
public class JwtInterceptor extends HandlerInterceptorAdapter {

    @Autowired
    private JWT jwt;

    public static final String USER_KEY = "userId";

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        String servletPath = request.getServletPath();
        System.out.println("ServletPath: " + servletPath);
        boolean isCheck = isCheck(servletPath);
        // 不需要验证,直接放行
        if (!isCheck) {
            return true;
        }
        // 需要验证
        String token = getToken(request);

        if (StringUtils.isBlank(token)) {
            throw new RuntimeException(jwt.getHeader() + "失效,请重新登录");
        }
        // 获取签名信息
        Claims claims = jwt.getClaimByToken(token);
        System.out.println("TOKEN: " + claims);
        // 判断签名是否存在或过期
        boolean b = claims==null || claims.isEmpty() || jwt.isTokenExpired(claims.getExpiration());
        if (b) {
            throw new RuntimeException(jwt.getHeader() + "失效,请重新登录");
        }
        // 将签名中获取的用户信息放入request中;
        request.setAttribute(USER_KEY, claims.getSubject());
        return true;
    }
    /**
     * 根据URL判断当前请求是否需要校验, true:需要校验
     */
    private boolean isCheck(String servletPath) {
//        for (String path : NOT_CHECK_URL) {
//            if (servletPath.startsWith(path)) {
//                return false;
//            }
//        }
        return false;
    }

    /**
     * 获取请求Token
     */
    private String getToken(HttpServletRequest request) {
        String token = request.getHeader(jwt.getHeader());
        if (StringUtils.isBlank(token)) {
            token = request.getParameter(jwt.getHeader());
        }
        return token;
    }

    /**
     * 不用拦截的页面路径(也可存入数据库中)
     */
    private static final String[] NOT_CHECK_URL = {};
}
讲拦截器注册到spring中
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {

    @Autowired
    private JwtInterceptor jwtInterceptor;

    /**
     * 设置资源文件路径
     * @param registry
     */
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/statics/**").addResourceLocations("classpath:/statics/");
    }

    /**
     *
     * APP接口拦截器
     * */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(jwtInterceptor).addPathPatterns("/*");
    }

}





你可能感兴趣的:(java设计模式,spring)