-token登录

-token登录

一、密码

密码的演变
  1. 什么叫密码

密码是一种用来混淆的技术,使用者希望将正常的(可识别的)信息转变为无法识别的信息。但这种无法识别的信息部分是可以再加工并恢复和破解的。密码在中文里是“口令”(password)的通称。
  1. 什么是明文密码?

“明文密码”(Cleartext Password),即传输或保存为明文的密码。具体是指 保存密码或网络传送密码的时候,用的是没有隐藏、直接显示的明文字符,而不是经过加密后的密文。如密码为123,那么密文密码是***,明文密码则是123。从信息安全的角度出发,任何网络服务都不应该保存或发送明文密码。
  1.    什么是密文?

         密文是加了密的的文字,明文是加密之前的文字。密文是对明文进行加密后的报文。
  1. 为什么不允许在数据库里明文保存密码?

用明文保存密码有很大的信息安全隐患。
一般数据库里还存有用户的姓名、手机号、用户名等信息,一旦数据库发生泄漏,再加上用户的明文密码,攻击者就可以用用户名和密码去其他网站尝试登陆(因为往往用户会将多个网站的密码根据习惯设成一样的),一旦登陆成功,就会造成很严重的后果。
  1. 常见的加密算法 (数学里的函数方程)

在密码学中,加密算法分为 双向加密单向加密。单向加密包括MD5、SHA等摘要算法,它们是不可逆的。 双向加密包括对称加密和非对称加密,对称加密包括AES加密、DES加密等。非对称加密包括RSA加密等, 双向加密是可逆的存在密文的密钥
  1. MD5

 
   
public static void main(String[] args) { //明文密码 String password = "123456"; //md5加密后的密码 String md5Password = Md5Utils.getMD5String(password); System.out.println(md5Password); //e10adc3949ba59abbe56e057f20f883e //随机一个8位数 加盐(salt) String salt = UUID.randomUUID().toString().substring(0, 8); System.out.println(salt); //d1a3a984 // 先加盐,再经过md5加密后的密码 String md5SaltPassword = Md5Utils.getMD5String(password + salt); System.out.println(md5SaltPassword);//fe685f5a90a1cfd4b3f2aca7eb2cc02d }
事实上MD5并不是加密算法,它只是消息摘要(安全散列)算法,只能正向加密,无法逆向解密;
md5不可逆的原因是因为它是一种散列函数,使用的是hash算法,在计算过程中原文的部分信息是丢失了的。也就是说,MD5的运算过程存在信息丢失。由于不知道运算过程中会有多少个进位在哪一步被丢弃,因而仅仅根据MD5的计算过程和得到的最终结果,是无法逆向计算出明文的。这才是MD5不可逆的真正原因。
  1. AES对称加密

加密和解密使用同一个密钥
 
   
import cn.hutool.crypto.Mode; import cn.hutool.crypto.Padding; import cn.hutool.crypto.symmetric.AES; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.StandardCharsets; /** * @author gogo */ public class AESUtil { /** * 密钥 16字节 */ private static final String ENCODE_KEY = "1234567812345678"; private static final String IV_KEY = "0000000000000000"; public static void main(String[] args) { String encryptData = encryptFromString("zdm321123.", Mode.CBC, Padding.ZeroPadding); System.out.println("加密:" + encryptData); String decryptData = decryptFromString(encryptData, Mode.CBC, Padding.ZeroPadding); System.out.println("解密:" + decryptData); } public static String encryptFromString(String data, Mode mode, Padding padding) { AES aes; if (Mode.CBC == mode) { aes = new AES(mode, padding, new SecretKeySpec(ENCODE_KEY.getBytes(), "AES"), new IvParameterSpec(IV_KEY.getBytes())); } else { aes = new AES(mode, padding, new SecretKeySpec(ENCODE_KEY.getBytes(), "AES")); } return aes.encryptBase64(data, StandardCharsets.UTF_8); } public static String decryptFromString(String data, Mode mode, Padding padding) { AES aes; if (Mode.CBC == mode) { aes = new AES(mode, padding, new SecretKeySpec(ENCODE_KEY.getBytes(), "AES"), new IvParameterSpec(IV_KEY.getBytes())); } else { aes = new AES(mode, padding, new SecretKeySpec(ENCODE_KEY.getBytes(), "AES")); } byte[] decryptDataBase64 = aes.decrypt(data); return new String(decryptDataBase64, StandardCharsets.UTF_8); } }
  1. RSA非对称加密

非对称加密有两个密钥: 公钥和私钥
使用公钥加密,只能用私钥解密
使用私钥加密,只能用公钥解密
暂时无法在飞书文档外展示此内容
 
   
import cn.hutool.crypto.SecureUtil; import cn.hutool.crypto.asymmetric.KeyType; import cn.hutool.crypto.asymmetric.RSA; import java.security.KeyPair; import java.security.PrivateKey; import java.security.PublicKey; import java.util.Base64; import java.util.HashMap; import java.util.Map; /** */ public class RsaUtils { /** * 类型 */ public static final String ENCRYPT_TYPE = "RSA"; /** * 获取公钥的key */ private static final String PUBLIC_KEY = "RSAPublicKey"; /** * 获取私钥的key */ private static final String PRIVATE_KEY = "RSAPrivateKey"; public static void main(String[] args) { Map stringStringMap = RsaUtils.generateKeyPair(); String RSAPublicKey = stringStringMap.get(PUBLIC_KEY); String RSAPrivateKey = stringStringMap.get(PRIVATE_KEY); System.out.println(RSAPublicKey); System.out.println("**************************"); System.out.println(RSAPrivateKey); System.out.println("##########################"); String content = "湾湾是中国的"; //公钥加密 String encrypt = RsaUtils.encrypt(content, RSAPublicKey); System.out.println(encrypt); //私钥解密 String decrypt = RsaUtils.decrypt(encrypt, RSAPrivateKey); System.out.println(decrypt); } public static Map generateKeyPair() { try { KeyPair pair = SecureUtil.generateKeyPair(ENCRYPT_TYPE); PrivateKey privateKey = pair.getPrivate(); PublicKey publicKey = pair.getPublic(); // 获取 公钥和私钥 的 编码格式(通过该 编码格式 可以反过来 生成公钥和私钥对象) byte[] pubEncBytes = publicKey.getEncoded(); byte[] priEncBytes = privateKey.getEncoded(); // 把 公钥和私钥 的 编码格式 转换为 Base64文本 方便保存 String pubEncBase64 = Base64.getEncoder().encodeToString(pubEncBytes); String priEncBase64 = Base64.getEncoder().encodeToString(priEncBytes); Map map = new HashMap(2); map.put(PUBLIC_KEY, pubEncBase64); map.put(PRIVATE_KEY, priEncBase64); return map; } catch (Exception e) { e.printStackTrace(); } return null; } /** * 公钥加密 * * @param content 要加密的内容 * @param publicKey 公钥 */ public static String encrypt(String content, String publicKey) { try { RSA rsa = new RSA(null, publicKey); return rsa.encryptBase64(content, KeyType.PublicKey); } catch (Exception e) { e.printStackTrace(); } return null; } /** * 私钥解密 * * @param content 要解密的内容 * @param privateKey 私钥 */ public static String decrypt(String content, String privateKey) { try { RSA rsa = new RSA(privateKey, null); return rsa.decryptStr(content, KeyType.PrivateKey); } catch (Exception e) { e.printStackTrace(); } return null; } }

二、cookie & session

2.1 Cookie

Cookie其实是浏览器保存在电脑中的一些 key-value结构形式的文本数据,其中包含了我们以及服务器的一些信息, 当我们向服务器发送请求的时候,这些cookie数据会随着请求(request)报文一起发送服务器,服务器识别当前的cookie,同时服务器也可以在响应(response)报文的头部加上set-cookie希望浏览器可以缓存cookie,浏览器收到响应报文,发现了set-cookie字段就会将其数据保在浏览器
   cookie由服务器创建,保存在浏览器中(标记浏览器),当浏览器下一次访问该服务器时,会自动携带该cookie
暂时无法在飞书文档外展示此内容
由于Cookie是保存在本地文件中,所以是能够长期保存,只需要将过期时间设置的长一些。由于cookie是存储在本地,所以它其实是 不安全 的,并且Cookie能够 保存的数据大小有限 ,单个Cookie保存的数据不能超过4K

2.2 Session

session从字面上讲就是会话,表明客户端与服务器的一次会话,与Cookie不一样, Session是由服务器进行维护的(服务器创建,由服务器保存的)。当客户端向服务发送一个请求时,服务器会为发起这个请求的客户端创建一个对象并存储在服务器的一个集合中,同时生成一个唯一的SessionId来标识这个对象,而有关这个请求的客户端信息就保存在这个session中。当服务器给用户发送响应报文时,也会将sessionid放入set-cookie中,浏览器接收到响应报文后,保存这个cookie,当下一次向服务器发送请求的时候会带上这个cookie然后服务端进行验证
服务端不会一直维护session,它会在用户退出浏览器、或者在一段时间没有接收到这个用户的请求后将Session清除
暂时无法在飞书文档外展示此内容
Session的缺点,对于每一个用户服务器都需要维护一个或多个Session,若一段时间内访问服务器的用户数量庞大,将导致服务器需要维护大量的Session对象, 严重占用资源 ,除此之外,当今的web服务器,都采用了 集群的技术 集群中的每一台服务器的Session都是独立的,要实现Session共享比较麻烦
暂时无法在飞书文档外展示此内容

三、token(令牌)

Token的引入
  Token是在客户端频繁向服务端请求数据,服务端频繁的去数据库查询用户名和密码并进行对比,判断用户名和密码正确与否,并作出相应提示,在这样的背景下,Token便应运而生。
Token的定义
Token是 服务端生成的一串字符串,以作客户端进行请求的一个令牌, 当第一次登录后,服务器生成一个Token便将此Token返回给客户端,以后客户端只需带上这个Token前来请求数据即可,无需再次带上用户名和密码。
使用Token的目的
Token的目的是为了减轻服务器的压力,减少频繁的查询数据库,使服务器更加健壮。
Token又叫令牌,不仅可以在登录场景中使用, 在涉及到多系统互信的场景下都可以使用;算是一种轻量化的加密传输手段 3.1 Token 在登录中的应用
暂时无法在飞书文档外展示此内容

四、JWT-生成token

1. 什么是JWT

JWT 即Json Web Token,将用户登录状态以及数据用加密的json格式存储在客户端,服务端可以完全依靠这个字符串认定用户身份。简单来说, 这是一种用户身份认证的解决方案

2. JWT的构成

一个JWT实际上就是一个字符串,由三部分组成分别是:
header(头部)
payload(载荷)
signature(签名)
结构如下: header.payload.signature

第一部分:header(头部)

header(头部)的信息指定了其Token类型和所使用的加密算法。
{
"typ": "JWT",
"alg": "HS256"
}
然后将头部进行base64编码(该加密是可以解码的),构成了第一部分eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

第二部分:payload(载荷)

payload(载荷)信息存放的是Claims声明信息。载荷其实就是自定义的数据, 一般存储用户Id,过期时间等信息 也就是 JWT的核心所在 ,因为这些数据就是使后端知道此token是哪个用户已经登录的凭证。而且这些数据是存在token里面的,由前端携带,所以后端几乎不需要保存任何数据
{
'id': user.id,
'username': user.username,
‘exp’: time.time() + 300, #过期时间
}
然后将其进行base64编码,得到Jwt的第二部分:eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZvaG4gRGdWV9

第三部分:signature(签名)

signature(签名)需要使用编码后的header和payload以及一个密钥,使用header中声明的编码方式进行加盐secret组合加密,然后就构成了jwt的第三部分:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZvaG4gRGdWV9
最后将这三部分拼接起来: token = header + '.'+ payload +'.'+ signature,生成token
  1. jwt工具类的使用

 
   
com.auth0 java-jwt 3.2.0 io.jsonwebtoken jjwt 0.6.0
 
   
package com.bw.common; import cn.hutool.core.util.StrUtil; import cn.hutool.json.JSONUtil; import io.jsonwebtoken.*; import java.util.Date; public class JwtUtils { //密钥 public static final String SECRET = "zjKkye4PN59B2wriTjtVCo3BOYoD1B"; //过期时间 public static final long EXPIRE = 1000 * 60 * 60 * 24; /** * 根据入参进行一系列的加密,生成一个token * @param payload 入参是一个对象,一般传入用户的基本信息 * @return 返回一个token */ public static String createToken(Object payload) { String jwtToken = Jwts.builder() .setHeaderParam("typ", "JWT") .setHeaderParam("alg", "HS256")//使用hs256算法进行加密 此算法是对称性加密 .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + EXPIRE))//设置token的有效截止时间 .claim("payload", JSONUtil.toJsonStr(payload))//存放载荷(用户信息) .signWith(SignatureAlgorithm.HS256, SECRET) .compact(); return jwtToken; } /** * 验证token是否有效 * @param jwtToken * @return */ public static boolean validToken(String jwtToken) { if (StrUtil.isEmpty(jwtToken)) { return false; } try { Jwts.parser().setSigningKey(SECRET).parseClaimsJws(jwtToken); } catch (Exception e) { e.printStackTrace(); return false; } return true; } /** * 获取token里的载荷(用户信息) * @param jwtToken * @return */ public static String parseToken(String jwtToken) { if (StrUtil.isEmpty(jwtToken)) { return null; } try { Jws claimsJws = Jwts.parser().setSigningKey(SECRET).parseClaimsJws(jwtToken); Claims claims = claimsJws.getBody(); return (String) claims.get("payload"); } catch (Exception e) { return null; } } }

五、登录实战开发

  1. 构建ssm框架项目

  1. 搭建好三层架构

  1. 编写登录功能

     

你可能感兴趣的:(java)