zoukankan      html  css  js  c++  java
  • 初识单点登录及JWT实现

    单点登录

    多系统,单一位置登录,实现多系统同时登录的一种技术

    (三方登录:某系统使用其他系统的用户,实现本系统登录的方式。如微信登录、支付宝登录)

    单点登录一般是用于互相授信的系统,实现单一位置登录,全系统有效

    一、Session跨域

      所谓 Session 跨域就是摒弃了系统提供的 Session ,而使用自定义的类似 Session 的机制来保存客户端数据的一种解决方案。

      如:通过设置 cookie 的 domain 来实现 cookie 的跨域传递。在 cookie 中传递一个自定义的 session_id。这个 session_id 是客户端的唯一标记,将这个标记作为key,将客户需要保存的数据作为value,在服务端进行保存(数据库保存或nosql保存)。这种机制就是 Session 的跨域解决。

      什么为跨域:客户端请求的时候,请求的服务器,不是同一个IP、端口、域名、主机名的时候,都称为跨域。

      什么是域:在应用模型中,一个完整的、有独立访问路径的功能集合成为一个域。

           如:百度称为一个应用或系统,其下有若干个域,如搜索引擎(www.baidu.com),百度贴吧(tie.baidu.com),百度知道(zhidao.baidu.com)等。

           有时也称为多级域名。域的划分:以IP、端口、域名、主机名为标准,实现划分。

    二、Spring Session 共享

      spring-session 技术是 spring 提供的用于处理集群会话共享的解决方案。spring-session技术是将用户 session 数据保存到第三方容器中,如数据库。

      Spring-session 技术是解决同域名下的多服务器集群 session 共享问题的,不能解决跨域 Session 共享问题

    三、Nginx Session 共享

      nginx中的 ip_hash 技术能够将某个 ip 的请求定向到同一台后端,这样一来这个ip下的某个客户端和某个后端就能建立起稳固的session,ip_hash是在upstream配置中定义的

    四、Token身份认证

      使用基于 Token 的身份验证方法,在服务端不需要存储用户的登录记录,大概流程如下:

      1)客户端使用用户名、密码请求登录

      2)服务端收到请求、去验证用户名与密码

      3)验证成功后,服务端会签发y一个 token ,再把这个 token 发送给客户端

      4)客户端收到 token 以后可以把它存储起来,比如放在 cookie 里或者 Local Storage里

      5)客户端每次向服务器请求资源的时候需要带着服务器签发的 token

      6)服务端收到请求,然后去验证客户端请求里面带着的 token,如果验证成功,就向客户端返回请求的数据

      使用token的优势:

      无状态、可扩展:

        在客户端存储的 token 是无状态的,并且能够被扩展,基于这种无状态和不存储session信息,负载均衡器能够将用户信息从一个服务传到其他服务器上。

      安全性:

        请求中发送token而不再发送cookie能够防止CSRF(跨域请求伪造)。即使在客户端使用cookie存储token。cookie也仅仅是一个存储机制而不是用于认证。

      不将信息存储在session中,让我们少了对session的操作。

    五、JSON Web Token(JWT)机制

      JWT是一种紧凑且自包含的,用于在多方传递 json 对象的技术。传递的数据可以使用数字签名增加其安全性。可以使用HMAC加密算法或RSA公钥/私钥加密方式。

      紧凑:数据小,可以通过URL、POST参数,请求头发送,且数据小代表传输速度快。

      自包含:使用 payload 数据块j记录用户必要且不隐私的数据,可以有效的减少数据库访问次数,提高代码性能

      JWT一般用于处理用户身份校验或数据信息交换

      JWT的数据结构

        JWT的数据结构:A.B.C  以.(点)来划分

        A-header  头信息

        B-payload  (有效荷载?)

        C-Signature  签名

      header:

      数据结构:{"alg":"加密算法名称","typ":"JWT"}

      alg可以有 HMAC 或 SHA256 或 RSA 等

      payload:主要分为三部分:已注册信息、公开数据、私有数据

      singature:

        签名信息,这是一个由开发者提供的信息。是服务器验证的传递的数据是否有效安全的标准。  

      执行流程

      

     简单实现

    1)造数据 JWTUsers模拟数据库用户名密码

    package cn.zytao.taosir;
    
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * 用于模拟用户数据的,开发中应访问数据库验证用户
     * @author TAOSIR
     *
     */
    public class JWTUsers {
        
        private static final Map<String,String> USERS =new HashMap<>(11);
        
        static {
            for(int i=0;i<10;i++) {
                USERS.put("admin"+i, "pwd"+i);
            }
        }
        
        //验证是否可以登录
        public static boolean isLogin(String username,String pwd) {
            if(null == username || username.trim().length()==0)
                return false;
            String obj=USERS.get(username);
            if(null ==obj||!obj.equals(pwd))
                return false;
            return true;
        }
    }

    2)JWTSubject

    package cn.zytao.taosir;
    /**
     * 作为Subject数据使用,也就是payload中保存的public claims
     * 其中不应包含任何敏感数据
     * 开发中建议使用实体类型,或BO,DTO数据对象
     * @author TAOSIR
     *
     */
    public class JWTSubject {
        
        private String username;
        
        public JWTSubject() {
            super();
        }
        
        public JWTSubject(String username) {
            super();
            this.username = username;
        }
        
        public String getUsername() {
            return username;
        }
        
        public void setUsername(String username) {
            this.username=username;
        }
    }

    3)JWT结果对象

    package cn.zytao.taosir;
    /**
     * 作为Subject数据使用,也就是payload中保存的public claims
     * 其中不应包含任何敏感数据
     * 开发中建议使用实体类型,或BO,DTO数据对象
     * @author TAOSIR
     *
     */
    public class JWTSubject {
        
        private String username;
        
        public JWTSubject() {
            super();
        }
        
        public JWTSubject(String username) {
            super();
            this.username = username;
        }
        
        public String getUsername() {
            return username;
        }
        
        public void setUsername(String username) {
            this.username=username;
        }
    }

    4)响应对象

    package cn.zytao.taosir;
    
    public class JWTResponseData {
        
        private Integer code;//返回码
        
        private Object data;//业务数据
        
        private String msg;//返回描述
        
        private String token;//身份标识
    
        public Integer getCode() {
            return code;
        }
    
        public void setCode(Integer code) {
            this.code = code;
        }
    
        public Object getData() {
            return data;
        }
    
        public void setData(Object data) {
            this.data = data;
        }
    
        public String getMsg() {
            return msg;
        }
    
        public void setMsg(String msg) {
            this.msg = msg;
        }
    
        public String getToken() {
            return token;
        }
    
        public void setToken(String token) {
            this.token = token;
        }
    }

    5)JWT控制类

    package cn.zytao.taosir;
    /**
     * JWT工具类
     * @author TAOSIR
     *
     */
    
    import java.util.Date;
    
    import javax.crypto.SecretKey;
    import javax.crypto.spec.SecretKeySpec;
    
    import com.fasterxml.jackson.databind.ObjectMapper;
    
    import io.jsonwebtoken.Claims;
    import io.jsonwebtoken.ExpiredJwtException;
    import io.jsonwebtoken.JwtBuilder;
    import io.jsonwebtoken.Jwts;
    import io.jsonwebtoken.SignatureAlgorithm;
    import io.jsonwebtoken.SignatureException;
    
    public class JWTUtils {
        
        private static final String JWT_SECERT = "test_jwt_secert";//服务器的key,密钥
        private static final ObjectMapper MAPPER = new ObjectMapper();//用户java对象和json字符串转换
        public static final int JWT_ERRCODE_EXPIRE = 1005;//Token过期
        public static final int JWT_ERRCODE_FAIL = 1006;//验证不通过
        
        public static SecretKey generalKey() {
            try {
                byte[] encodedKey=JWT_SECERT.getBytes("UTF-8");
                SecretKey key=new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
                return key;
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
        
        /**
         * 签发JWT,即创建token的方法
         * @param id    jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击
         * @param iss    jwt签发者
         * @param subject    jwt所面向的用户,payload中记录的public,claims,当前环境中就是用户的登录名
         * @param ttlMills    有效期,单位毫秒
         * @return
         */
        public static String createJWT(String id,String iss,String subject,long ttlMillis) {
            //加密算法
            SignatureAlgorithm signatureAlgorithm=SignatureAlgorithm.HS256;
            long nowMillis = System.currentTimeMillis();
            Date now=new Date(nowMillis);
            SecretKey secretKey=generalKey();
            //创建JWT的构造器用于生成token
            JwtBuilder builder=Jwts.builder()
                    .setId(id)
                    .setIssuer(iss)
                    .setSubject(subject)
                    .setIssuedAt(now)
                    .signWith(signatureAlgorithm, secretKey);
            if(ttlMillis >= 0) {
                long expMillis =nowMillis+ttlMillis;
                Date exDate = new Date(expMillis);
                builder.setExpiration(exDate);
            }
            return builder.compact();
        }
        
        /**
         * 验证JWT
         * @param jwtStr
         * @return
         */
        public static JWTResult validateJWT(String jwtStr) {
            JWTResult checkResult=new JWTResult();
            Claims claims=null;
            try {
                claims=parseJWT(jwtStr);
                checkResult.setSuccess(true);
                checkResult.setClaims(claims);
            } catch (ExpiredJwtException e) {
                checkResult.setSuccess(false);
                checkResult.setErrCode(JWT_ERRCODE_EXPIRE);
            } catch (SignatureException e) {
                checkResult.setSuccess(true);
                checkResult.setErrCode(JWT_ERRCODE_FAIL);
            }
            return checkResult;
        }
        
        /**
         * 解析JWT字符串
         * @param jwt    就是token
         * @return
         */
        public static Claims parseJWT(String jwt) {
            SecretKey secretKey=generalKey();
            //getBody获取值就是token中记录的payload数据,就是其中保存的claims
            return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwt).getBody();
        }
        
        /**
         * 生成subject信息
         * @param subObj
         * @return
         */
        public static String generalSubject(Object subObj) {
            try {
                return MAPPER.writeValueAsString(subObj);
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
    }

    6)写个简单的controller实践

    package cn.zytao.taosir.controller;
    
    import java.util.UUID;
    
    import javax.servlet.http.HttpServletRequest;
    
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import cn.zytao.taosir.JWTResponseData;
    import cn.zytao.taosir.JWTResult;
    import cn.zytao.taosir.JWTSubject;
    import cn.zytao.taosir.JWTUsers;
    import cn.zytao.taosir.JWTUtils;
    
    @RestController
    public class JWTController {
    
        @RequestMapping("testAll")
        public Object testAll(HttpServletRequest request) {
            String token=request.getHeader("Authorization");
            JWTResult result=JWTUtils.validateJWT(token);
            
            JWTResponseData responseData=new JWTResponseData();
            
            if(result.isSuccess()) {
                responseData.setCode(200);
                responseData.setData(result.getClaims().getSubject());
                String newToken=JWTUtils.createJWT(result.getClaims().getId(), result.getClaims().getIssuer(), result.getClaims().getSubject(), 1*60*1000);
                responseData.setToken(newToken);
                return responseData;
            }else {
                responseData.setCode(500);
                responseData.setMsg("用户未登录");
                return responseData;
            }
        }
        
        @RequestMapping("login")
        public Object login(String username,String password) {
            JWTResponseData responseData=null;
            //认证用户信息
            if(JWTUsers.isLogin(username, password)) {
                JWTSubject subject=new JWTSubject(username);
                String jwtToken = JWTUtils.createJWT(UUID.randomUUID().toString(),"sxt-test-jwt", JWTUtils.generalSubject(subject), 1*60*1000);
                responseData=new JWTResponseData();
                responseData.setCode(200);
                responseData.setData(null);
                responseData.setMsg("登录成功");
                responseData.setToken(jwtToken);
            }else {
                responseData=new JWTResponseData();
                responseData.setCode(500);
                responseData.setData(null);
                responseData.setMsg("登录失败");
                responseData.setToken(null);
            }
            return responseData;
        }
    }

    7)Postman查看情况

  • 相关阅读:
    06-局部变量和全局变量
    05-python中函数的使用
    04-字典的常见操作
    04-python第四天学习
    Openstack认证过程
    03-字典的增删改查
    01-名字管理系统.py
    02-python中列表的增删改查
    01-python中字符串的常见操作
    ubuntu16.04卸载软件
  • 原文地址:https://www.cnblogs.com/it-taosir/p/10021811.html
Copyright © 2011-2022 走看看