token的问题和原理解析

1.token的原理和作用

​ token是用于验证身份的,在web系统中验证前端是否能够访问后端系统

​ token在服务端产生,如果前端使用用户名/密码向服务端请求认证,服务端认证成功,那么在服务端会返回 Token 给前端。前端可以在每次请求的时候带上 token 证明自己的合法地位。token是服务端生成的一串字符串,以作前端进行请求的一个令牌,当第一次登录后,服务器生成一个token并返回给前端,之后前端只需带上这个token前来请求数据即可,无需再次带上用户名和密码。

​ token相比其他验证方式,主要是无需每次都去验证用户名密码,同时由于token保存在前端,服务端无需保存每个用户的登录状态,只需要验证访问时带着的token是不是自己签发的,可以减轻服务器的压力,减少频繁的查询数据库,使服务器更加健壮。

2.token可以解决的问题

​ token的作用是验证身份,那token可以解决那些问题呢?

1)避开同源策略

​ web页面中,不同源的客户端脚本在没有明确授权的情况下,不能读写对方资源,这样也给不同系统的交互验证带来了较大的问题,但是token完全由应用管理,不涉及服务端,只要使用同样的密钥与算法,token就是有效的。

2)避免CSRF攻击

​ 使用cookie保存用户信息,可能会造成黑客恶意伪造用户的请求,该请求中所有的用户验证信息都是存在于 cookie 中,因此黑客可以在不知道这些验证信息的情况下直接利用用户自己的 cookie 来通过安全验证。但是token放入header之后,是无法被直接伪造的。

3)token可以在多个应用直接共享

​ 在多个系统协同工作的时候,token可以被共享到多个服务中,避免用户多次登录

3.token的应用

​ 我们应用token在我们的系统中,一般会使用jwt来进行

​ jwt(Json Web Token)是实现token技术的一种解决方案,可以使用各种语言实现,java自然也在其中。

​ jwt的字符串有三部分组成,头部(header),载荷(playload),签证(signature)。头部用于声明类型和加密算法,载荷用于保存各种信息,token的有效信息就保存在此,但是因为base64是对称加密的,故载荷不能保存机密信息。签证部分是非常重要的,因为其中需要使用前两部分base64之后的字符串,进行加盐(secret)后再加密,生成一个字符串。这三部分字符串连接起来,就是完整的jwt字符串了。需要注意的是,载荷的信息越多,jwt字符串就会越长。

​ jwt应用中需要注意三点,载荷部分存放的信息是客户端可解密的部分,所以不能存放敏感信息,第二就是由于使用了对称算法,双方之间仅共享一个,密钥签证中的盐(secret)是属于私钥的,一旦泄露,非常麻烦,第三尽量使用https。

​ 当然,jwt不止可以使用对称算法,也可以使用不对称算法,但是这样会造成不同系统之间配合更为复杂。

1)jwt的具体实现

​ 我们一般在springboot中使用jwt,故先导入maven的依赖

		<dependency>
		    <groupId>com.auth0groupId>
		    <artifactId>java-jwtartifactId>
		    <version>3.4.1version>
		dependency>
		<dependency>
		    <groupId>io.jsonwebtokengroupId>
		    <artifactId>jjwtartifactId>
		    <version>0.9.1version>
		dependency>

​ 然后编写jwt的工具类

public class JwtUtils {
    /**
     * 签发JWT
     */
    public static String generateToken(String id, String subject, long ttlMillis) {
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);
        SecretKey TokenEncryptKey = encrypt();
        JwtBuilder builder = Jwts.builder()
                .setId(id)
                .setSubject(subject)   // 主题
                .setIssuer("user")     // 签发者
                .setIssuedAt(now)      // 签发时间
                .signWith(signatureAlgorithm, TokenEncryptKey); // 签名算法以及密匙
        if (ttlMillis >= 0) {
            long expMillis = nowMillis + ttlMillis;
            Date expDate = new Date(expMillis);
            builder.setExpiration(expDate); // 过期时间
        }
        return builder.compact();
    }
    /**
     * 验证JWT
     * @param jwtStr
     * @return
     */
    public static CheckResult validateToken(String jwtStr) {
        CheckResult checkResult = new CheckResult();
        Claims claims = null;
        try {
            claims = decrypt(jwtStr);
            checkResult.setSuccess(true);
            checkResult.setClaims(claims);
        } catch (ExpiredJwtException e) {
            checkResult.setErrCode(SystemConstant.JWT_ERRCODE_EXPIRE);
            checkResult.setSuccess(false);
        } catch (SignatureException e) {
            checkResult.setErrCode(SystemConstant.JWT_ERRCODE_FAIL);
            checkResult.setSuccess(false);
        } catch (Exception e) {
            checkResult.setErrCode(SystemConstant.JWT_ERRCODE_FAIL);
            checkResult.setSuccess(false);
        }
        return checkResult;
    }
    public static SecretKey encrypt() {
        byte[] encodedKey = Base64.decode(SystemConstant.JWT_SECERT);
        SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
        return key;
    }
    
    /**
     * 解析JWT字符串
     */
    public static Claims decrypt(String jwt) throws Exception {
        SecretKey secretKey = generalKey();
        return Jwts.parser()
            .setSigningKey(secretKey)
            .parseClaimsJws(jwt)
            .getBody();
    }
}

工具类完成后就可以使用了,但是要注意,由于jwt应用与web页面中,可能经常会被URL编码解码,因为其中含有特殊字符,要依据流程进行url编解码,以防止错误的jwt进入工具类,造成异常。

2)工具类的应用实例

​ 工具类编写好之后,要考虑如何在系统中使用。

​ 一般来说,用户登录。输入用户名和密码后,验证通过了,就可以token发送到前端了,但是要考虑前端应该如何使用这个token,一般来说,有两种,一种是在http请求参数中放token,并在服务器端建立一个拦截器来验证这个 token,但这种请求仍然有可能被CSRF攻击,而且如果系统已经完成了大量的功能,还需要去修改请求方法以及后台接口。另一种就是给页面各个请求都统一配置header,在http请求头中放置token,这样对于请求本身没有影响,后台也只需要拦截器来验证这个 token就行了。

3)不同系统之间的配合

​ 使用不同系统的配合时,考虑到用户统一登录,可以考虑如下方案

​ 设置一个主要的登录系统,用户登录之后,通过该系统,把token分享给各个应用,分享的方法可以有很多种,比如通过请求参数直接发送给另一个应用。考虑到安全性问题,该token需要设置较短的失效时间,防止滥用。然后各个系统因为都具有登录系统一样的密钥,就可以进行验证,这个通过参数传来的token是不是可以使用,如果可以,就通过。由于登录系统的token设置的过期时间很短,各个应用可以自行设置token的策略,只是把登录系统的token作为第一次的登录token。

​ 这个方法仅限于互相信任的系统,如果系统互相不信任,最好不要这样使用。

4.token的安全问题

1)过期时间问题

​ jwt在载荷中,提供了一些方法(就是标准中注册的声明),其中就有过期时间的设置。考虑安全性,token肯定不能一直有效,设置过期时间就成了必然。但是token有了过期时间,如果比较长,用户可以使用比较长的时间而不会掉出有效期重新登录,但是这样较长的时间,安全性又会打折扣,毕竟在token过期之前,服务都是可以一直访问的。但是设置过短的话,安全性得到了保证,用户却会在使用中频繁掉线。

​ 为了解决这个问题,保存token状态到服务器肯定是不行的,比较token的初衷就是为了减轻服务器压力。使用就需要使用Refresh Token,就是服务端不需要刷新 Token 的过期时间,一旦 Token 过期,就反馈给前端,前端使用 Refresh Token 申请一个全新 Token 继续使用。Refresh Token就相当于一个长效的token,用于请求真正的访问token。Refresh Token失效的时候,就可以让用户重新登录了。

​ 但是Refresh Token这种方法并非完美,比较用户还是有了一个长效的token的,所以使用这种方法要注意,Refresh Token需要在服务端监控状态,用户手动登出的时候,需要注销掉用户使用的Refresh Token。同时,在进行敏感操作时,注意进行二次验证或者其他方式确保操作安全。

2)信任和不信任

​ 我们前面说过,使用同一套密钥和算法搭建多系统协同时,需要互相信任的系统,就是比如说,同一家公司为某一个项目开发的多个系统,可以使用同一套密钥和算法。但是如果无法绝对信任的系统之间相互配合,就不能使用了,这时可能就要涉及多种业务直接的互相信任,授信,还可能需要用户主动授信,还有需要非对称算法,保证安全。这时可能简单使用token不是很合适,最好使用类似于OAuth认证的开放式 API 认证标准。

3)token本身被盗用

​ token本身并非不会被获取或滥用,所以,如果风险比较高,,还需要再token中加入一些不易伪造的信息,比如,ip,时间戳,客户端信息等。

5.总结

​ token本身来说是比较容易理解和使用的,实现方式简单,操作方便,能够快速实现。由于服务端不存储用户状态信息,因此大用户量,对后台服务也不会造成压力。但是并非所有系统都适合使用,其本身也存在一些漏洞和问题,还有其本身无状态的特征,也可能导致被恶意使用。

参考链接:

https://www.jianshu.com/p/fe67b4bb6f2c

https://www.jianshu.com/p/cba0dfe4ad4a

https://www.jianshu.com/p/576dbf44b2ae

https://www.jianshu.com/p/24825a2683e6

https://www.cnblogs.com/xuxinstyle/p/9675541.html

https://blog.csdn.net/weixin_44172434/article/details/99594870

https://www.cnblogs.com/lingyejun/p/9282169.html

https://blog.csdn.net/downloads_zip/article/details/79171368

https://www.cnblogs.com/hyddd/archive/2009/04/09/1432744.html

https://blog.csdn.net/xunfeng13/article/details/52371562

https://www.cnblogs.com/shanyou/p/5038794.html

你可能感兴趣的:(H2,java,jwt,spring,csrf)