java多线程    Java入门    vsftp    ftp    linux配置    centos    FRP教程    HBase    Html5缓存    webp    zabbix    分布式    neo4j图数据库    

JWT跨域认证解决方案

总结
1.传统的session在分布式的情况下不太方便进行身份验证,有一个方案是跨服务器设置redis集群,将session写入redis,但是会影响集群性能。数据量越大,越影响

2.JWT的算法是生成了JSON串靠json串来进行验证用户身份

3.JWT的结果中包含了需要附带的信息,比如用户账号,昵称,客户等级

4.JWT多台机器验证靠底层的密钥来判定,所以能解决跨域问题

5.JWT存储在客户端,不给服务器端增加压力

1.JWT的缘由

JWT,是目前最流行的一个跨域认证解决方案:客户端发起用户登录请求,服务器端接收并认证成功后,生成一个 JSON 对象,然后将其返回给客户端。

2.JWT身份认证方面能做什么

常规的cookie和session在单机版本没有问题,现在假设,后端部署了2台机器AB,如何同步用户登录用户登录的时候访问了A,留下来session ,结果刷新list页面,去请求了B,那么B服务器没有A的信息,就乱套了。(其实有个办法session同步到redis)

另外如果前端页面ajax请求后台页面,如何判断这次请求是否合法

JWT只需要将数据记录在客户端,然后发给服务器,服务器来验证这个串就好了

3.JWT 原理

假设用户名abc,密码123456,用户登录时候进行密码验证,用abc 和我的私钥比如mysecert来进行加密得到一串token格式如下

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwYXNzd29yZCI6IjEyMzQiLCJleHAiOjE2NjE3Njc0OTksInVzZXJuYW1lIjoiZ2hqIn0.AuICqTRRoa2OCrhwE34yxmSRRzIwsGPqHuGQr27raiM

这里可以反读出来我的账户为abc

假设A服务器生成了这个串串,B服务器算法一样,就能验证这个token。

这一串加密串,中间用「.」分割成了三部分:

Header(头部),描述 JWT 的元数据,其中 alg 属性表示签名的算法(当前为 HS256);

Payload(负载),用来存放实际需要传递的数据,其中 name 属性表示用户名,password 属性表示密码(实际生产中不能带密码这个字段,因为会泄露),exp 属性表示过期时间

Signature(签名),对前两部分的签名,防止数据篡改;这里需要服务器端指定一个密钥(只有服务器端才知道),不能泄露给客户端,然后使用 Header 中指定的签名算法,按照下面的公式产生签名:

HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
your-256-bit-secret
)

4.JWT实现算法

4.1 pom.xml引入jwt

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

4.2 URL访问文件 / /login

@RestController
public class BookController {
    @GetMapping("/")
    public String index(){
        return "i am real index page";
    }

    @GetMapping("/index")
    public String index2(){
        return "i am index page";
    }

    @GetMapping("/login")
    public String user(){
        return "<form method=post action='/login'><input type='text' name='name'><input type='text' name='pwd'><input type='submit' value=submit></form>";
    }

    @PostMapping("/login")
    public String userLoginIn(@RequestParam(name = "name") String name,@RequestParam(name = "pwd") String pwd)
    {
        String token = Token.token(name,pwd);
        return "post me:" + name + " | "+  pwd + " | token is " + token;
    }
}

这个文件写了个普通的访问页面/ 登录/login 如果get那么展示登录框,如果为post执行登录算法生成token

4.3 放上jwt, token核心算法

public class Token {
    //设置过期时间
    private static final long EXPIRE_DATE=30*1000;//30秒
    //token秘钥
    private static final String TOKEN_SECRET = "ZCEQIUBFKSJBFJH2020BQWE";
    public static String token (String username,String password){

        String token = "";
        try {
            //过期时间
            Date date = new Date(System.currentTimeMillis() + EXPIRE_DATE);
            //秘钥及加密算法
            Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);

            //设置头部信息
            Map<String,Object> header = new HashMap<>();
            header.put("typ","JWT");
            header.put("alg","HS256");
            //携带username,password信息,生成签名
            token = JWT.create()
                    .withHeader(header)
                    .withClaim("username",username)
                    .withClaim("password",password).withExpiresAt(date)
                    .sign(algorithm);
        }catch (Exception e){
            e.printStackTrace();
            return  null;
        }
        return token;
    }

    /**
     * 获取token中的参数值
     * @param token
     * @return
     */
    public static String getToken(String token,String field){
        try{
            DecodedJWT jwt = JWT.decode(token);
            return jwt.getClaim(field).asString();
        }catch (Exception e){
            return null;
        }

    }

    public static boolean verify(String token){
        /**
         * @desc   验证token,通过返回true
         * @create 2019/1/18/018 9:39
         * @params [token]需要校验的串
         **/
        try {
            Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
            JWTVerifier verifier = JWT.require(algorithm).build();
            DecodedJWT jwt = verifier.verify(token);
//            System.out.println("------------");
//            System.out.println(jwt.getHeader());
//            System.out.println(jwt.getPayload());
//            System.out.println(jwt.getSignature());
//            System.out.println(jwt.getToken());
//            System.out.println("------------");
            return true;
        }catch (Exception e){
            e.printStackTrace();
            return  false;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        String username ="zhangsan";
        String password = "123";
        String token = token(username,password);

       // Thread.sleep(1000);
        System.out.println(token);
        boolean b = verify(token);
        System.out.println(b);
        String userName = getToken(token,"username");
        String pwd = getToken(token,"password");
        System.out.println(userName);
        System.out.println(pwd);

    }
}

4.4 网络拦截器

@Configuration
public class SpringMvcConfig implements WebMvcConfigurer {
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(ipIntercepter()).addPathPatterns("/**")
                .excludePathPatterns("/login");

    }

    @Bean
    public IpIntercepter ipIntercepter(){
        return new IpIntercepter();
    }
}
public class IpIntercepter implements HandlerInterceptor {

    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("--------------");
        System.out.println("我拦截了url:" + request.getRequestURI());
        String authorization = request.getHeader("authorization");
        String token = authorization.replace("Bearer ","");

        if (null == authorization|| "".equals(authorization) ){
            System.out.println("authorization为空");
            return false;
        }else{
            System.out.println("放行" + token); //Bearer 123456

            boolean b = Token.verify(token);
            System.out.println(b);
            if(!b){
                System.out.println("登录失败");
                return false;
            }
            String userName1 = Token.getToken(token,"username");
            System.out.println(userName1);
        }
        System.out.println("--------------");
        return true;
    }
}

4.5 访问一下

image-20220829183916258

附录:

整体文件结构

image-20220829183803774

5 总结

JWT 有个问题,导致很多开发团队放弃使用它,那就是一旦颁发一个 JWT 令牌,服务端就没办法废弃掉它,除非等到它自身过期。

方案:将JWT每次登录生成的串存入数据库redis,然后每次和redis去验证这个JWT是否有效,这样可以强制将一个JWT串失效。因为每次认证以后要去redis数据比对下是否还有效。就能解决JWT不过期问题。

本文使用的源码地址
https://github.com/yuexiaosheng/javaer/tree/main/jwtdemo

Session,Cookie, JWT的优缺点

存储位置

三者都是应用在web中对http无状态协议的补充,达到状态保持的目的

cookie:cookie中的信息是以键值对的形式储存在浏览器中,而且在浏览器中可以直接看到数据。

session:session存储在服务器中,然后发送一个cookie存储在浏览器中,cookie中存储的是session_id,之后每次请求服务器通过session_id可以获取对应的session信息

JWT:JWT存储在浏览器的storage或者cookie中。由服务器产生加密的json数据包括:header,payload和signature三部分组成。header中通常来说由token的生成算法和类型组成;payload中则用来保存相关的状态信息;signature部分由header,payload,secret_key三部分加密生成。 注意,不要在JWT的payload或header中放置敏感信息,除非它们是加密的。

优缺点

1.cookie:

优点:
结构简单。cookie是一种基于文本的轻量结构,包含简单的键值对。
数据持久。虽然客户端计算机上cookie的持续时间取决于客户端上的cookie过期处理和用户干预,cookie通常是客户端上持续时间最长的数据保留形式。
缺点:
大小受到限制。大多数浏览器对 cookie 的大小有 4096 字节的限制,尽管在当今新的浏览器和客户端设备版本中,支持 8192 字节的 cookie 大小已愈发常见。
非常不安全。cookie将数据裸露在浏览器中,这样大大增大了数据被盗取的风险,所有我们不应该将中要的数据放在cookie中,或者将数据加密处理。
容易被csrf攻击。可以设置csrf_token来避免攻击。

2.session:

优点:
session的信息存储在服务端,相比于cookie就在一定程度上加大了数据的安全性;相比于jwt方便进行管理,也就是说当用户登录和主动注销,只需要添加删除对应的session就可以,这样管理起来很方便。
缺点:
session存储在服务端,这就增大了服务器的开销,当用户多的情况下,服务器性能会大大降低。
因为是基于cookie来进行用户识别的, cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。
用户认证之后,服务端做认证记录,如果认证的记录被保存在内存中的话,这意味着用户下次请求还必须要请求在这台服务器上,这样才能拿到授权的资源,这样在分布式的应用上,会限制负载均衡和集群水平拓展的能力。

3.JWT:

优点:
因为json的通用性,jwt可以支持跨语言请求,像JAVA,JavaScript,PHP等很多语言都可以使用。
因为有了payload部分,所以JWT可以在自身存储一些其他业务逻辑所必要的非敏感信息。
便于传输,JWT的构成非常简单,字节占用很小,所以它是非常便于传输的。
不需要在服务端保存会话信息, 利于服务器横向拓展。

缺点:
登录状态信息续签问题。比如设置token的有效期为一个小时,那么一个小时后,如果用户仍然在这个web应用上,这个时候当然不能指望用户再登录一次。目前可用的解决办法是在每次用户发出请求都返回一个新的token,前端再用这个新的token来替代旧的,这样每一次请求都会刷新token的有效期。但是这样,需要频繁的生成token。另外一种方案是判断还有多久这个token会过期,在token快要过期时,返回一个新的token。

用户主动注销。JWT并不支持用户主动退出登录,客户端在别处使用token仍然可以正常访问。为了支持注销,我的解决方案是在注销时将该token加入到服务器的redis黑名单中。

讲的比较透的JWT登录,顺道redis的模式也讲解了

https://www.cnblogs.com/ygyy/p/14601049.html
https://zhuanlan.zhihu.com/p/473949200
https://blog.csdn.net/weixin_49031153/article/details/121409269

提出了一种解决方案,将JWT存入数据库redis,然后每次和redis去验证这个JWT是否有效,这样可以强制将一个JWT串失效
https://github.com/dwyl/learn-json-web-tokens/blob/master/README-zh_CN.md

https://blog.csdn.net/ssjdoudou/article/details/107683825

有个案例
https://zhuanlan.zhihu.com/p/473949200

新话题 redis读写分离

https://blog.csdn.net/qq_42773863/article/details/107769211

阿里云大量redis实践

https://help.aliyun.com/document_detail/63920.html

redis的几种集群模式

https://blog.csdn.net/l688899886/article/details/126275172


This entry was posted in JAVA and tagged , , . Bookmark the permalink.
月小升QQ 2651044202, 技术交流QQ群 178491360
首发地址:月小升博客https://java-er.com/blog/jwt-auth-json/
无特殊说明,文章均为月小升原创,欢迎转载,转载请注明本文地址,谢谢
您的评论是我写作的动力.

Leave a Reply