zoukankan      html  css  js  c++  java
  • JWT实现SSO

    1.JWT

    JWT是json web token缩写。它将用户信息加密到token里,服务器不保存任何用户信息。服务器通过使用保存的密钥验证token的正确性,只要正确即通过验证。JWT是一种紧凑且自包含的,用于在多方传递JSON对象的技术。传递的数据可以使用数字签名增加其安全行。可以使用HMAC加密算法或RSA公钥/私钥加密方式),且数据小代表传输速度快。JWT一般用于处理用户身份验证或数据信息交换。

    2.传统的利用cookie作为媒介存在的一些问题

    1)cookie不安全很容易被截取和伪造

    2)cookie自身存在跨域问题,之所以能够跨域是因为domain,以及Access-Control-Allow-Origin,根本原因是子域可以通过父域的拦截

    3)保存在cookie中的session  id 自身保存服务器内存中,自身存在跨服务器问题,所以需要保存在mysql中或者redies中,频繁的数据库操作给效率带来一定的影响

    4)上述问题可以解决,但是作用的是拓展性问题,往往需要做很大的更改。

    5)容易受到CSRF(跨站请求伪造)的攻击

    3.JWT机制

    优点:所有的数据不在由服务器保存,而是通过相应的私钥加密以后保存在头文件中,而服务器只保存秘钥即可,这样增加了相应的拓展性,无状态、可扩展,还可以防止CSRF(跨站请求伪造)

    缺点:当相应的json文件发送以后,我们便不再可以控制

    4.HTTP与HTTPS

    HTTP 是一种没有状态的协议,也就是它并不知道是谁是访问应用

    HTTPS是在建立连接的时候会接受服务器传过来的证书,基于SSL或者TLS,TLS是基于TLS的基础上发展来的,通过相应的证书完成相应的认证来识别对象

    5.Token认证的过程

    客户端使用用户名、密码请求登录,服务端收到请求,去验证用户名、密码,验证成功后,服务端会签发一个 Token,再把这个 Token 发送给客户端,客户端收到 Token 以后可以把它存储起来,比如放在 Cookie 里或者 Local Storage 里(cookie保存方式,可以实现跨域传递数据。localStorage是域私有的本地存储,无法实现跨域。webstorage可保存的数据容量为5M。且只能存储字符串数据),客户端每次向服务端请求资源的时候需要带着服务端签发的 Token,服务端收到请求,然后去验证客户端请求里面带着的 Token,如果验证成功,就向客户端返回请求的数据

    备注:

    webstorage分为localStorage和sessionStorage。

    localStorage的生命周期是永久的,关闭页面或浏览器之后localStorage中的数据也不会消失。localStorage除非主动删除数据,否则数据永远不会消失。

    sessionStorage是会话相关的本地存储单元,生命周期是在仅在当前会话下有效。sessionStorage引入了一个“浏览器窗口”的概念,sessionStorage是在同源的窗口中始终存在的数据。只要这个浏览器窗口没有关闭,即使刷新页面或者进入同源另一个页面,数据依然存在。但是sessionStorage在关闭了浏览器窗口后就会被销毁。同时独立的打开同一个窗口同一个页面,sessionStorage也是不一样的。

    6.JWT的数据结构是 : A.B.C

    A - header 头信息

    B - payload (有效荷载?)

    C - Signature 签名

    header

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

    alg是加密算法定义内容,如:HMAC SHA256 或 RSA

    typ是token类型,这里固定为JWT。

    payload

    在payload数据块中一般用于记录实体(通常为用户信息)或其他数据的。主要分为三个部分,分别是:已注册信息(registered claims),公开数据(public claims),私有数据(private claims)。

    payload中常用信息有:iss(发行者),exp(到期时间),sub(主题),aud(受众)等。前面列举的都是已注册信息。公开数据部分一般都会在JWT注册表中增加定义。避免和已注册信息冲突。

    公开数据和私有数据可以由程序员任意定义。

    注意:即使JWT有签名加密机制,但是payload内容都是明文记录,除非记录的是加密数据,否则不排除泄露隐私数据的可能。不推荐在payload中记录任何敏感数据。

    Signature

    签名信息。这是一个由开发者提供的信息。是服务器验证的传递的数据是否有效安全的标准。在生成JWT最终数据的之前。先使用header中定义的加密算法,将header和payload进行加密,并使用点进行连接。如:加密后的head.加密后的payload。再使用相同的加密算法,对加密后的数据和签名信息进行加密。得到最终结果。

    7.实例

    依赖

    <dependency>
        <groupId>com.auth0</groupId>
        <artifactId>java-jwt</artifactId>
        <version>3.4.1</version>
    </dependency>
    
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
        <version>0.9.1</version>
    </dependency>
    
    <dependency>
        <groupId>net.iharder</groupId>
        <artifactId>base64</artifactId>
        <version>2.3.9</version>
    </dependency>
    

     获取秘钥

    // 服务器的key。用于做加解密的key数据。
    private static final String JWT_SECERT = "test_jwt_secert" ;
    public static SecretKey generalKey() {
    		try {
                            // 不管哪种方式最终得到一个byte[]类型的key就行
    			byte[] encodedKey = Base64.decode(JWT_SECERT); 
    			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 ttlMillis 有效期,单位毫秒
    	 * @return token, token是一次性的。是为一个用户的有效登录周期准备的一个token。用户退出或超时,token失效。
    	 * @throws Exception
    	 */
    	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)  // 设置身份标志。就是一个客户端的唯一标记。 如:可以使用用户的主键,客户端的IP,服务器生成的随机数据。
    				.setIssuer(iss)
    				.setSubject(subject)
    				.setIssuedAt(now) // token生成的时间。
    				.signWith(signatureAlgorithm, secretKey); // 设定密匙和算法
    		if (ttlMillis >= 0) { 
    			long expMillis = nowMillis + ttlMillis;
    			Date expDate = new Date(expMillis); // token的失效时间。
    			builder.setExpiration(expDate);
    		}
    		return builder.compact(); // 生成token
    	}
    
    public class JWTResult {
    
    	/**
    	 * 错误编码。在JWTUtils中定义的常量。
    	 * 200为正确
    	 */
    	private int errCode;
    
    	/**
    	 * 是否成功,代表结果的状态。
    	 */
    	private boolean success;
    
    	/**
    	 * 验证过程中payload中的数据。
    	 */
    	private Claims claims;
    
    	public int getErrCode() {
    		return errCode;
    	}
    
    	public void setErrCode(int errCode) {
    		this.errCode = errCode;
    	}
    
    	public boolean isSuccess() {
    		return success;
    	}
    
    	public void setSuccess(boolean success) {
    		this.success = success;
    	}
    
    	public Claims getClaims() {
    		return claims;
    	}
    
    	public void setClaims(Claims claims) {
    		this.claims = claims;
    	}
    	
    }
    
    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;
    	}
    	
    }
    

    解析与验证

    /**
    	 * 验证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) { // token超时
    			checkResult.setErrCode(JWT_ERRCODE_EXPIRE);
    			checkResult.setSuccess(false);
    		} catch (SignatureException e) { // 校验失败
    			checkResult.setErrCode(JWT_ERRCODE_FAIL);
    			checkResult.setSuccess(false);
    		} catch (Exception e) {
    			checkResult.setErrCode(JWT_ERRCODE_FAIL);
    			checkResult.setSuccess(false);
    		}
    		return checkResult;
    	}
    	
    	/**
    	 * 
    	 * 解析JWT字符串
    	 * @param jwt 就是服务器为客户端生成的签名数据,就是token。
    	 * @return
    	 * @throws Exception
    	 */
    	public static Claims parseJWT(String jwt) throws Exception {
    		SecretKey secretKey = generalKey();
    		return Jwts.parser()
    			.setSigningKey(secretKey)
    			.parseClaimsJws(jwt)
    			.getBody(); // getBody获取的就是token中记录的payload数据。就是payload中保存的所有的claims。
    	}
    

    test

    登录验证的类

    public class JWTUsers {
    
    	private static final Map<String, String> USERS = new HashMap<>(16);
    	
    	static{
    		for(int i = 0; i < 10; i++){
    			USERS.put("admin"+i, "password"+1);
    		}
    	}
    	
    	// 是否可登录
    	public static boolean isLogin(String username, String password){
    		if(null == username || username.trim().length() == 0){
    			return false;
    		}
    		String obj = USERS.get(username);
    		if(null == obj || !obj.equals(password)){
    			return false;
    		}
    		
    		return true;
    	}
    	
    }
    

     返回的对象(后续经过json处理的对象)

    public class JWTResponseData {
    
    	private Integer code;// 返回码,类似HTTP响应码。如:200成功,500服务器错误,404资源不存在等。
    	
    	private Object data;// 业务数据
    	
    	private String msg;// 返回描述
    	
    	private String token;// 身份标识, JWT生成的令牌。
    
    	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;
    	}
    	
    }
    
    //产看是否存在token
    @RequestMapping("/testAll") @ResponseBody 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()); // 重新生成token,就是为了重置token的有效期。 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; } } //登录页面产生token @RequestMapping("/login") @ResponseBody 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(), "test_jwt_secert", 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; }
    
    
    
  • 相关阅读:
    Maven学习笔记
    [学习笔记] 网络流
    [Contest on 2021.11.3] 女子口阿
    [杂题合集] 25 岁小伙突然没了心跳,他的习惯很多年轻人都有!
    CSP 2021 提高组游记
    [题目小结] 可持久化数据结构
    [学习笔记] 无向图和有向图的连通分量
    [Contest on 2021.10.17] HustOJ 就是个 **
    [Contest on 2021.10.15] 细思极恐
    妖怪寺外,灯火通明
  • 原文地址:https://www.cnblogs.com/gg128/p/9905753.html
Copyright © 2011-2022 走看看