搜索词>>细雨算法 耗时0.0050
  • 细雨算法b2b领域解读

    6月底我们发布了细雨算法的公告《百度搜索推出细雨算法 促进供求黄页类站点生态健康发展》。今天我们重点说b2b领域站点究竟应该怎么做。6月底我们发布了细雨算法的公告《百度搜索推出细雨算法 促进供求黄页类站点生态健康发展》。今天我们重点说b2b领域站点究竟应该怎么做。​​​​​​​
  • java c++通用DES/AES对称加密算法(包含源代码)

    java c++通用DES加密算法(包含源代码),本来觉得DES、AES这种流行加密算法,使用起来应该很简单。但研究后发现有两个变数:1分块的方式。加密是逐块进行的。2.padding的方式。当数据的位数不及块的大小时,需要填充。<h2>一说明</h2> 本来觉得DES、AES这种流行加密算法,使用起来应该很简单。但研究后发现有两个变数: <ul> <li>分块的方式。加密是逐块进行的。分块方法有:CBC、ECB、CFB……</li> <li>padding的方式。当数据的位数不及块的大小时,需要填充。填充方式有:NoPadding、PKCS5Padding……</li> </ul> 如果加解密端采用不同的分块方式或padding方式,即使都是采用DES/AES算法,同样无法解密成功。上次需要C端和Java端进行密文传输,就跪在这一点上(那时候没时间研究)。<br /> 参考文章:<a href="http://my.oschina.net/u/267094/blog/174035" rel="external nofollow" target="_blank">Java AES算法和openssl配对</a> ,主要通过其了解openssl里比较高级的EVP系列API(其默认padding和java一样都是PKCS5Padding),节约了搜索时间。<br /> 贴代码了,以下代码测试通过了。Java和C++均可以正确解密对方的密文。<br /> 约定:分块和padding采用Java默认的 ECB + PKCS5Padding。 <h2>二 DES加解密</h2> <h3>Java端DES加解密</h3> <pre> <code class="language-java">import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; public class DESUtil { /** 默认算法格式 **/ static String default_transformation = "DES/ECB/PKCS5Padding"; /** * 根据key获取加密器 * * @param keyData * key 8byte * @return 加密器 * @throws Exception * 异常 */ private static Cipher getEncryptCipher(byte[] keyData) throws Exception { if (keyData.length != 8) { throw new Exception("Key Data Must 8 byte!"); } SecretKeySpec key = new SecretKeySpec(keyData, "DES"); // 指定分块ECB模式,填充PKCS5Padding模式 Cipher encryptCipher = Cipher.getInstance(default_transformation); // 初始化加密的容器 encryptCipher.init(Cipher.ENCRYPT_MODE, key); return encryptCipher; } /** * 根据key获取解码器 * * @return 解码器 * @throws Exception * 异常 */ private static Cipher getDecryptCipher(byte[] keyData) throws Exception { if (keyData.length != 8) { throw new Exception("Key Data Must 8 byte!"); } SecretKeySpec key = new SecretKeySpec(keyData, "DES"); Cipher decryptCipher = Cipher.getInstance(default_transformation); // 初始化解密的容器 decryptCipher.init(Cipher.DECRYPT_MODE, key); return decryptCipher; } /** * DES加密 * * @param data * 待加密数据 * @param keyData * key值 * @return 加密后的数据 * @throws Exception * 异常 */ public static byte[] encrypt(byte[] data, byte[] keyData) throws Exception { return getEncryptCipher(keyData).doFinal(data); } /** * DES解密 * * @param data * 加密后的数据 * * @param keyData * key值 * @return 解密数据 * @throws Exception * 异常 */ public static byte[] decrypt(byte[] data, byte[] keyData) throws Exception { return getDecryptCipher(keyData).doFinal(data); } /** * 测试 * * @param args */ public static void main(String[] args) { try { byte[] data = "测试123456".getBytes(); byte[] keyData = "12345678".getBytes(); System.out.println("原文:" + new String(data)); byte[] enData = encrypt(data, keyData); System.out.println("加密后:" + new String(enData)); byte[] deData = decrypt(enData, keyData); System.out.println("解密后:" + new String(deData)); } catch (Exception e) { e.printStackTrace(); } } } </code></pre> <strong><span style="color:#27ae60">说明:Java必须指定填充模式和padding模式。DES/ECB/PKCS5Padding,好与C++同步</span></strong><br /> 测试执行结果: <pre> <code class="language-html">原文:测试123456 加密后:�a���O���?I�]�Y 解密后:测试123456</code></pre> <h3>C++端DES加解密</h3> c++DES加密代码 <pre> <code class="language-cpp">#include <string> #include <iostream> #include <stdio.h> #include <assert.h> #include <openssl/objects.h> #include <openssl/evp.h> // 注意:参数和返回值全部是二进制数据 std::string desEncrypt(const std::string& source, const std::string& key) {     EVP_CIPHER_CTX ctx;     EVP_CIPHER_CTX_init(&ctx);     int ret = EVP_EncryptInit_ex(&ctx, EVP_des_ecb(), NULL, (const unsigned char*)key.data(), NULL);     assert(ret == 1);     unsigned char* result = new unsigned char[source.length() + 64]; // 弄个足够大的空间     int len1 = 0;     ret = EVP_EncryptUpdate(&ctx, result, &len1, (const unsigned char*)source.data(), source.length());     assert(ret == 1);     int len2 = 0;     ret = EVP_EncryptFinal_ex(&ctx, result+len1, &len2);      assert(ret == 1);     std::cout << len1 << "," << len2 << std::endl;     ret = EVP_CIPHER_CTX_cleanup(&ctx);     assert(ret == 1);     std::string res((char*)result, len1+len2);     delete[] result;     return res; } int main() {     std::string key("hellodes", 8);    // 二进制数据,而不是以0结尾的字符串     // 读取文件内容(简单起见认为文件内容<100K)     char buf[1024*100];     FILE* fp = fopen("src.txt", "rb");     int bytes = fread(buf, 1, 1024*100, fp);     fclose(fp);     std::string source(buf, bytes); // 二进制数据     // 加密     std::string enc = desEncrypt(source, key);     std::cout << "desEncrypt:" << source.length() << "->" << enc.length() << std::endl;     // 输出到文件     fp =  fopen("enc.bin", "wb");     fwrite(enc.data(), 1, enc.length(), fp);     fclose(fp); }</code></pre> c++DES解密 <pre> <code class="language-cpp">#include <string> #include <iostream> #include <stdio.h> #include <assert.h> #include <openssl/objects.h> #include <openssl/evp.h> // 注意:参数和返回值全部是二进制数据 std::string desDecrypt(const std::string& ciphertext, const std::string& key) {     EVP_CIPHER_CTX ctx;     EVP_CIPHER_CTX_init(&ctx);     int ret = EVP_DecryptInit_ex(&ctx, EVP_des_ecb(), NULL, (const unsigned char*)key.data(), NULL);     assert(ret == 1);     unsigned char* result = new unsigned char[ciphertext.length() + 64]; // 弄个足够大的空间     int len1 = 0;     ret = EVP_DecryptUpdate(&ctx, result, &len1, (const unsigned char*)ciphertext.data(), ciphertext.length());     assert(ret == 1);     int len2 = 0;     ret = EVP_DecryptFinal_ex(&ctx, result+len1, &len2);      assert(ret == 1);     std::cout << len1 << "," << len2 << std::endl;     ret = EVP_CIPHER_CTX_cleanup(&ctx);     assert(ret == 1);     std::string res((char*)result, len1+len2);     delete[] result;     return res; } int main() {     std::string key("hellodes", 8);    // 二进制数据,而不是以0结尾的字符串     // 读取文件内容(简单起见认为文件内容<100K)     char buf[1024*100];     FILE* fp = fopen("enc.bin", "rb");     int bytes = fread(buf, 1, 1024*100, fp);     fclose(fp);     std::string data(buf, bytes); // 二进制数据     // 加密     std::string dec = desDecrypt(data, key);     std::cout << "desDecrypt:" << data.length() << "->" << dec.length() << std::endl;     // 输出到文件     fp =  fopen("dec.txt", "wb");     fwrite(dec.data(), 1, dec.length(), fp);     fclose(fp); }</code></pre> <br /> 说明:DES、AES加密算法都是针对数据块,Java加解密函数参数使用byte数组。C++用std::string,那是因为这是C++中使用byte数组的最简单方式(std::string可以存储二进制数据,很多人没想到吧),缺点是拷贝内存的次数可能会略多些。如果想要优化拷贝效率,可以使用自己封装的Buffer类来代替std::string。 <h2>三 AES加解密</h2> <h3>Java端AES加解密</h3> <pre> <code class="language-java">import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; /** * DES加解密算法工具类 * * @author xqlee * */ public class AESUtil { /** 默认算法格式 **/ static String default_transformation = "AES/ECB/PKCS5Padding"; /** * 根据key获取加密器 * * @param keyData * key 8byte * @return 加密器 * @throws Exception * 异常 */ private static Cipher getEncryptCipher(byte[] keyData) throws Exception { if (keyData.length != 16) { throw new Exception("Key Data Must 8 byte!"); } SecretKeySpec key = new SecretKeySpec(keyData, "AES"); // 指定分块ECB模式,填充PKCS5Padding模式 Cipher encryptCipher = Cipher.getInstance(default_transformation); // 初始化加密的容器 encryptCipher.init(Cipher.ENCRYPT_MODE, key); return encryptCipher; } /** * 根据key获取解码器 * * @return 解码器 * @throws Exception * 异常 */ private static Cipher getDecryptCipher(byte[] keyData) throws Exception { if (keyData.length != 16) { throw new Exception("Key Data Must 8 byte!"); } SecretKeySpec key = new SecretKeySpec(keyData, "AES"); Cipher decryptCipher = Cipher.getInstance(default_transformation); // 初始化解密的容器 decryptCipher.init(Cipher.DECRYPT_MODE, key); return decryptCipher; } /** * AES加密 * * @param data * 待加密数据 * @param keyData * key值 * @return 加密后的数据 * @throws Exception * 异常 */ public static byte[] encrypt(byte[] data, byte[] keyData) throws Exception { return getEncryptCipher(keyData).doFinal(data); } /** * AES解密 * * @param data * 加密后的数据 * * @param keyData * key值 * @return 解密数据 * @throws Exception * 异常 */ public static byte[] decrypt(byte[] data, byte[] keyData) throws Exception { return getDecryptCipher(keyData).doFinal(data); } /** * 测试 * * @param args */ public static void main(String[] args) { try { byte[] data = "测试123456".getBytes(); byte[] keyData = "1234567887654321".getBytes(); System.out.println("原文:" + new String(data)); byte[] enData = encrypt(data, keyData); System.out.println("加密后:" + new String(enData)); byte[] deData = decrypt(enData, keyData); System.out.println("解密后:" + new String(deData)); } catch (Exception e) { e.printStackTrace(); } } } </code></pre> <h3>C++端AES加解密</h3> 加密: <pre> <code class="language-cpp">#include <string> #include <iostream> #include <stdio.h> #include <assert.h> #include <openssl/objects.h> #include <openssl/evp.h> // 注意:参数和返回值全部是二进制数据 std::string aesEncrypt(const std::string& source, const std::string& key) {     EVP_CIPHER_CTX ctx;     EVP_CIPHER_CTX_init(&ctx);     int ret = EVP_EncryptInit_ex(&ctx, EVP_aes_128_ecb(), NULL, (const unsigned char*)key.data(), NULL);     assert(ret == 1);     unsigned char* result = new unsigned char[source.length() + 64]; // 弄个足够大的空间     int len1 = 0;     ret = EVP_EncryptUpdate(&ctx, result, &len1, (const unsigned char*)source.data(), source.length());     assert(ret == 1);     int len2 = 0;     ret = EVP_EncryptFinal_ex(&ctx, result+len1, &len2);      assert(ret == 1);     std::cout << len1 << "," << len2 << std::endl;     ret = EVP_CIPHER_CTX_cleanup(&ctx);     assert(ret == 1);     std::string res((char*)result, len1+len2);     delete[] result;     return res; } int main() {     std::string key("helloaeshelloaes", 16);    // 二进制数据,而不是以0结尾的字符串     // 读取文件内容(简单起见认为文件内容<100K)     char buf[1024*100];     FILE* fp = fopen("src.txt", "rb");     int bytes = fread(buf, 1, 1024*100, fp);     fclose(fp);     std::string source(buf, bytes); // 二进制数据     // 加密     std::string enc = aesEncrypt(source, key);     std::cout << "aesEncrypt:" << source.length() << "->" << enc.length() << std::endl;     // 输出到文件     fp =  fopen("enc.bin", "wb");     fwrite(enc.data(), 1, enc.length(), fp);     fclose(fp); }</code></pre> 解密: <pre> <code class="language-cpp">#include <string> #include <iostream> #include <stdio.h> #include <assert.h> #include <openssl/objects.h> #include <openssl/evp.h> // 注意:参数和返回值全部是二进制数据 std::string aesDecrypt(const std::string& ciphertext, const std::string& key) {     EVP_CIPHER_CTX ctx;     EVP_CIPHER_CTX_init(&ctx);     int ret = EVP_DecryptInit_ex(&ctx, EVP_aes_128_ecb(), NULL, (const unsigned char*)key.data(), NULL);     assert(ret == 1);     unsigned char* result = new unsigned char[ciphertext.length() + 64]; // 弄个足够大的空间     int len1 = 0;     ret = EVP_DecryptUpdate(&ctx, result, &len1, (const unsigned char*)ciphertext.data(), ciphertext.length());     assert(ret == 1);     int len2 = 0;     ret = EVP_DecryptFinal_ex(&ctx, result+len1, &len2);      assert(ret == 1);     std::cout << len1 << "," << len2 << std::endl;     ret = EVP_CIPHER_CTX_cleanup(&ctx);     assert(ret == 1);     std::string res((char*)result, len1+len2);     delete[] result;     return res; } int main() {     std::string key("helloaeshelloaes", 16);    // 二进制数据,而不是以0结尾的字符串     // 读取文件内容(简单起见认为文件内容<100K)     char buf[1024*100];     FILE* fp = fopen("enc.bin", "rb");     int bytes = fread(buf, 1, 1024*100, fp);     fclose(fp);     std::string data(buf, bytes); // 二进制数据     // 加密     std::string dec = aesDecrypt(data, key);     std::cout << "aesDecrypt:" << data.length() << "->" << dec.length() << std::endl;     // 输出到文件     fp =  fopen("dec.txt", "wb");     fwrite(dec.data(), 1, dec.length(), fp);     fclose(fp); }</code></pre>
  • Java策略模式应用实例

    策略模式简介策略设计模式是一种行为模式,其中我们有多种算法/策略来实现任务,以及使用哪种算法/策略供客户选择策略模式简介策略设计模式是一种行为模式,其中我们有多种算法/策略来实现任务,以及使用哪种算法/策略供客户选择。 各种算法选项封装在各个类中。在本教程中,我们将学习如何在Java中实现策略设计模式。UML表示:让我们首先看一下策略设计模式的UML表示:      ​在这里,我们有:Strategy:定义我们打算执行的常见操作的接口ConcreteStrategy:这些是使用不同算法来执行Strategy接口中定义的操作的实现类Context:任何需要改变行为并持有战略参考的东西JDK中策略模式的一个流行示例是在Collections.sort()方法中使用java.util.Comparator。 我们可以将Collections.sort()方法视为上下文和我们传入的java.util.Comparator实例作为排序对象的策略。实现Strategy众所周知,任何购物网站都提供多种付款方式。 所以,让我们用这个例子来实现策略模式。我们首先定义PaymentStrategy接口:public interface PaymentStrategy { void pay(Shopper shopper); } 现在,让我们定义两种最常见的支付方式,即货到付款和卡支付,作为两个具体的策略类:public class CashOnDeliveryStrategy implements PaymentStrategy { @Override public void pay(Shopper shopper) { double amount = shopper.getShoppingCart().getTotal(); System.out.println(shopper.getName() + " selected Cash On Delivery for Rs." + amount ); } } public class CardPaymentStrategy implements PaymentStrategy { @Override public void pay(Shopper shopper) { CardDetails cardDetails = shopper.getCardDetails(); double amount = shopper.getShoppingCart().getTotal(); completePayment(cardDetails, amount); System.out.println("Credit/Debit card Payment of Rs. " + amount + " made by " + shopper.getName()); } private void completePayment(CardDetails cardDetails, double amount) { ... } }实现Context 定义了我们的策略类后,现在让我们定义一个PaymentContext类:public class PaymentContext { private PaymentStrategy strategy; public PaymentContext(PaymentStratgey strategy) { this.strategy = strategy; } public void makePayment(Shopper shopper) { this.strategy.pay(shopper); } } 此外,我们的Shopper类看起来类似于:public class Shopper { private String name; private CardDetails cardDetails; private ShoppingCart shoppingCart; //suitable constructor , getters and setters public void addItemToCart(Item item) { this.shoppingCart.add(item); } public void payUsingCOD() { PaymentContext pymtContext = new PaymentContext(new CashOnDeliveryStrategy()); pymtContext.makePayment(this); } public void payUsingCard() { PaymentContext pymtContext = new PaymentContext(new CardPaymentStrategy()); pymtContext.makePayment(this); } } 我们系统中的购物者可以使用其中一种可用的购买策略进行支付。 对于我们的示例,我们的PaymentContext类接受所选的付款策略,然后为该策略调用pay()方法。策略模式对比状态模式 策略和状态设计模式都是基于接口的模式,可能看起来相似,但有一些重要的区别:状态设计模式定义了策略模式更多地讨论不同算法的各种状态在状态模式中,存在从一个状态到另一个状态的转换。 另一方面,策略模式中的所有策略类彼此独立
  • java编程之java jwt token什么是JWT?(一)

    本文讲解什么是JWT,JWT的构成和JWT算法?,Java编程,JWT<h2>一、什么是JWT?了解JWT,认知JWT</h2>   首先jwt其实是三个英语单词JSON Web Token的缩写。通过全名你可能就有一个基本的认知了。token一般都是用来认证的,比如我们系统中常用的用户登录token可以用来认证该用户是否登录。jwt也是经常作为一种安全的token使用。<br /> <br /> <strong>JWT的定义:</strong><br />   JWT是一种用于双方之间传递安全信息的简洁的、URL安全的表述性声明规范。JWT作为一个开放的标准(RFC 7519),定义了一种简洁的,自包含的方法用于通信双方之间以Json对象的形式安全的传递信息。因为数字签名的存在,这些信息是可信的,JWT可以使用HMAC算法或者是RSA的公私秘钥对进行签名。<br /> <strong>JWT特点:</strong> <ul> <li> <p><strong>简洁(Compact)</strong>: 可以通过URL,POST参数或者在HTTP header发送,因为数据量小,传输速度也很快</p> </li> <li> <p><strong>自包含(Self-contained)</strong>:负载中包含了所有用户所需要的信息,避免了多次查询数据库</p> </li> </ul> <h2>二、JWT构成或者说JWT是什么样的?</h2> <strong>2.1.JWT结构</strong><br /> JWT主要包含三个部分之间用英语句号'.'隔开 <ol> <li>Header 头部</li> <li>Payload 负载</li> <li>Signature 签名</li> </ol> <strong>注意,顺序是 header.payload.signature</strong><br /> 最终的结构有点像这样: <pre> <code>leftso.com.blog </code></pre> 当然真实的jwt不可能是这么简单的明文<br /> <br /> <strong>2.2.JWT的头部(Header)</strong><br /> 在header中通常包含了两部分:token类型和采用的加密算法。如下: <pre> <code class="language-json">{ "alg": "HS256", "typ": "JWT" } </code></pre> 上面的JSON内容指定了当前采用的加密方式为HS256,token的类型为jwt<br /> <br /> 将上面的内容进行base64编码,可以得到我们JWT的头部,编码后如下:<br /> (本站提供了在线的base64编码/解码的工具,可供读者测试) <pre> <code>ewogICJhbGciOiAiSFMyNTYiLAogICJ0eXAiOiAiSldUIgp9ICA=</code></pre> <br /> <strong>2.3.JWT的负载(Payload)</strong><br /> 负载(Payload)为JWT的第二部分。JWT的标准所定义了一下几个基本字段 <ol> <li><code>iss</code>: 该JWT的签发者</li> <li><code>sub</code>: 该JWT所面向的用户</li> <li><code>aud</code>: 接收该JWT的一方</li> <li><code>exp</code>(expires): 什么时候过期,这里是一个Unix时间戳</li> <li><code>iat</code>(issued at): 在什么时候签发的</li> </ol> <br /> 除了标准定义的字段外,我们还要定义一些我们在业务处理中需要用到的字段,例如用户token一般可以包含用户登录的token或者用户的id,一个简单的例子如下: <pre> <code class="language-json">{ "iss": "Lefto.com", "iat": 1500218077, "exp": 1500218077, "aud": "www.leftso.com", "sub": "leftso@qq.com", "user_id": "dc2c4eefe2d141490b6ca612e252f92e", "user_token": "09f7f25cdb003699cee05759e7934fb2" }</code></pre> 上面的user_id、user_token都是我们自己定义的字段<br /> <br /> 现在我们需要将负载这整个部分进行base64编码,编码后结果如下: <pre> <code>ewogICAgImlzcyI6ICJMZWZ0by5jb20iLAogICAgImlhdCI6IDE1MDAyMTgwNzcsCiAgICAiZXhwIjogMTUwMDIxODA3NywKICAgICJhdWQiOiAid3d3LmxlZnRzby5jb20iLAogICAgInN1YiI6ICJsZWZ0c29AcXEuY29tIiwKICAgICJ1c2VyX2lkIjogImRjMmM0ZWVmZTJkMTQxNDkwYjZjYTYxMmUyNTJmOTJlIiwKICAgICJ1c2VyX3Rva2VuIjogIjA5ZjdmMjVjZGIwMDM2OTljZWUwNTc1OWU3OTM0ZmIyIgp9</code></pre> <br /> <strong>2.4.</strong><strong>Signature(签名)</strong><br /> 签名其实是对JWT的头部和负载整合的一个签名验证<br /> 首先需要将头部和负载通过.链接起来就像这样:header.Payload,上述的例子链接起来之后就是这样的: <pre> <code>ewogICJhbGciOiAiSFMyNTYiLAogICJ0eXAiOiAiSldUIgp9ICA=.ewogICAgImlzcyI6ICJMZWZ0by5jb20iLAogICAgImlhdCI6IDE1MDAyMTgwNzcsCiAgICAiZXhwIjogMTUwMDIxODA3NywKICAgICJhdWQiOiAid3d3LmxlZnRzby5jb20iLAogICAgInN1YiI6ICJsZWZ0c29AcXEuY29tIiwKICAgICJ1c2VyX2lkIjogImRjMmM0ZWVmZTJkMTQxNDkwYjZjYTYxMmUyNTJmOTJlIiwKICAgICJ1c2VyX3Rva2VuIjogIjA5ZjdmMjVjZGIwMDM2OTljZWUwNTc1OWU3OTM0ZmIyIgp9</code></pre> <br /> 由于HMacSHA256加密算法需要一个key,我们这里key暂时用leftso吧<br /> <br /> 加密后的内容为: <pre> <code>686855c578362e762248f22e2cc1213dc7a6aff8ebda52247780eb6b5ae91877</code></pre> <br /> 其实加密的内容也就是JWT的签名,类似我们对某个文件进行MD5加密然后接收到文件进行md5对比一样.只是这里的HMacSHA256算法需要一个key,当然这个key应该是使用者和接收者都知道的。<br /> <br /> 对上面的签名内容进行base64编码得到最终的签名 <pre> <code>Njg2ODU1YzU3ODM2MmU3NjIyNDhmMjJlMmNjMTIxM2RjN2E2YWZmOGViZGE1MjI0Nzc4MGViNmI1YWU5MTg3Nw==</code></pre> <br /> <strong>2.5最终的JWT</strong> <pre> <code>ewogICJhbGciOiAiSFMyNTYiLAogICJ0eXAiOiAiSldUIgp9ICA=.ewogICAgImlzcyI6ICJMZWZ0by5jb20iLAogICAgImlhdCI6IDE1MDAyMTgwNzcsCiAgICAiZXhwIjogMTUwMDIxODA3NywKICAgICJhdWQiOiAid3d3LmxlZnRzby5jb20iLAogICAgInN1YiI6ICJsZWZ0c29AcXEuY29tIiwKICAgICJ1c2VyX2lkIjogImRjMmM0ZWVmZTJkMTQxNDkwYjZjYTYxMmUyNTJmOTJlIiwKICAgICJ1c2VyX3Rva2VuIjogIjA5ZjdmMjVjZGIwMDM2OTljZWUwNTc1OWU3OTM0ZmIyIgp9.Njg2ODU1YzU3ODM2MmU3NjIyNDhmMjJlMmNjMTIxM2RjN2E2YWZmOGViZGE1MjI0Nzc4MGViNmI1YWU5MTg3Nw==</code></pre> <br /> 通过上面的一个简单的说明您是否对JWT有一个简单额认知了呢?接下来我将讲解JWT在Java编程中的使用<br /> <br /> 未完待续中...<br /> <br />  
  • AES 使用JavaScript加密然后用Java解密

    引言AES代表高级加密系统,它是一种对称加密算法引言AES代表高级加密系统,它是一种对称加密算法。很多时候我们需要在客户端加密一些明文,例如密码,并将其发送到服务器,然后服务器将其解密以进一步处理.AES加密和解密更容易在Android客户端和Java服务器等相同的平台上实现,但有时在跨平台环境(如Javascript客户端和Java Server,如Spring mvc框架)中解密AES加密密码变得非常具有挑战性,因为如果任何系统默认值不匹配解密将失败。    在本文中,我们将使用spring mvc和angular js客户端创建一个应用程序。我们将有一个带有用户名和密码的表单输入的登录页面。在将密码发送到服务器之前,密码将使用CryptoJS在JavaScript中加密,并且相同的加密密码将在java中解密,并且会进行比较以匹配密码。我们将在javascript中生成salt和IV,然后生成从密码,salt和密钥大小中使用PBKDF2函数的密钥。之后,我们将使用密钥和IV对明文进行加密,并且这些密钥将在Java中进行解密。因此,基本上我们将开发一种可与Java进行可互操作的AES加密的机制,的JavaScript。    在继续进行之前,让我们明确一点,该机制仅在数据的有线传输期间(最有可能)增加了一种额外的安全性,但并未提供充分的证明安全性。如果您不使用SSL,则攻击者可以执行中间人攻击并通过为用户提供不同的密钥来窃取数据。项目结构我们这里有个spring  boot和angular js webapp项目。项目结构如下:​JavaScript中的Aes加密    对于JavaScript中的AES加密,我们已经导入了两个js文件 - crypto.js并且pbkdf2.js我们有AesUtil.js用于执行加密和解密的通用代码。这里this.keySize是4字节块的密钥大小。因此,要使用128位密钥,我们将位数除以32得到用于CryptoJS的密钥大小。 AesUtil.js:var AesUtil = function(keySize, iterationCount) { this.keySize = keySize / 32; this.iterationCount = iterationCount; }; AesUtil.prototype.generateKey = function(salt, passPhrase) { var key = CryptoJS.PBKDF2( passPhrase, CryptoJS.enc.Hex.parse(salt), { keySize: this.keySize, iterations: this.iterationCount }); return key; } AesUtil.prototype.encrypt = function(salt, iv, passPhrase, plainText) { var key = this.generateKey(salt, passPhrase); var encrypted = CryptoJS.AES.encrypt( plainText, key, { iv: CryptoJS.enc.Hex.parse(iv) }); return encrypted.ciphertext.toString(CryptoJS.enc.Base64); } AesUtil.prototype.decrypt = function(salt, iv, passPhrase, cipherText) { var key = this.generateKey(salt, passPhrase); var cipherParams = CryptoJS.lib.CipherParams.create({ ciphertext: CryptoJS.enc.Base64.parse(cipherText) }); var decrypted = CryptoJS.AES.decrypt( cipherParams, key, { iv: CryptoJS.enc.Hex.parse(iv) }); return decrypted.toString(CryptoJS.enc.Utf8); }密码加密在JavaScript中    该方法logMeIn()将在点击提交按钮后被调用。此方法将使用定义的通用代码AesUtil.js对密码进行加密并使POST请求验证密码。发送的密码将采用以下格式:iv::salt::ciphertext在服务器端,java将解密密码并在响应中发送解密密码显示在警告框中。var app = angular.module('demoApp', []); app.controller('loginController', ['$scope', '$rootScope', '$http', function ($scope, $rootScope, $http) { $scope.logMeIn = function(){ if(!$scope.userName || !$scope.password){ $scope.showMessage("Missing required fields.", false); return; } var iv = CryptoJS.lib.WordArray.random(128/8).toString(CryptoJS.enc.Hex); var salt = CryptoJS.lib.WordArray.random(128/8).toString(CryptoJS.enc.Hex); var aesUtil = new AesUtil(128, 1000); var ciphertext = aesUtil.encrypt(salt, iv, $('#key').text(), $scope.password); var aesPassword = (iv + "::" + salt + "::" + ciphertext); var password = btoa(aesPassword); var data = { userName: $scope.userName, password: password } $http.post('/login',data).then(function (response){ if(response.status === 200){ alert("Password is " + response.data.password); }else { alert("Error occurred"); } }) }; }]);Java中的AES解密首先让我们实现将拦截登录请求的控制器类。在这里,我们对密钥进行了硬编码。此密钥将由服务器唯一地生成并发送给客户端以用于每个登录请求。客户端将使用相同的密钥,而加密和服务器将使用相同的密钥进行解密。确保密钥长度为16,因为我们使用的是128位加密。请记住我们从客户端发送的加密文本的格式iv::salt::ciphertext。文本以相同的格式解密。我们已经有IV,盐和密文。import com.example.demo.model.Credentials; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import javax.servlet.http.HttpServletRequest; import java.util.HashMap; import java.util.Map; import java.util.UUID; @Controller public class WelcomeController { private static final Logger LOGGER = LoggerFactory.getLogger(WelcomeController.class); @RequestMapping(value={"/login"},method = RequestMethod.GET) public String loginPage(HttpServletRequest request){ LOGGER.info("Received request for login page with id - " + request.getSession().getId()); String randomKey = UUID.randomUUID().toString(); //String uniqueKey = randomKey.substring(randomKey.length()-17, randomKey.length() -1); String uniqueKey = "1234567891234567"; request.getSession().setAttribute("key", uniqueKey); return "index"; } @RequestMapping(value={"/login"},method = RequestMethod.POST) public @ResponseBody ResponseEntity login(@RequestBody Credentials credentials, HttpServletRequest request) { String decryptedPassword = new String(java.util.Base64.getDecoder().decode(credentials.getPassword())); AesUtil aesUtil = new AesUtil(128, 1000); Map map = new HashMap<>(); if (decryptedPassword != null && decryptedPassword.split("::").length == 3) { LOGGER.info("Password decrypted successfully for username - " + credentials.getUserName()); String password = aesUtil.decrypt(decryptedPassword.split("::")[1], decryptedPassword.split("::")[0], "1234567891234567", decryptedPassword.split("::")[2]); map.put("password", password); } return new ResponseEntity<>(map, HttpStatus.OK); } } 以下是用于AES加密和解密的java util类。您可以按照java中的AES加密和解密获得关于以下实现的更多详细说明。import org.apache.commons.codec.DecoderException; import org.apache.commons.codec.binary.Base64; import org.apache.commons.codec.binary.Hex; import javax.crypto.*; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.SecretKeySpec; import java.io.UnsupportedEncodingException; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.spec.InvalidKeySpecException; import java.security.spec.KeySpec; public class AesUtil { private final int keySize; private final int iterationCount; private final Cipher cipher; public AesUtil(int keySize, int iterationCount) { this.keySize = keySize; this.iterationCount = iterationCount; try { cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { throw fail(e); } } public String decrypt(String salt, String iv, String passphrase, String ciphertext) { try { SecretKey key = generateKey(salt, passphrase); byte[] decrypted = doFinal(Cipher.DECRYPT_MODE, key, iv, base64(ciphertext)); return new String(decrypted, "UTF-8"); } catch (UnsupportedEncodingException e) { return null; }catch (Exception e){ return null; } } private byte[] doFinal(int encryptMode, SecretKey key, String iv, byte[] bytes) { try { cipher.init(encryptMode, key, new IvParameterSpec(hex(iv))); return cipher.doFinal(bytes); } catch (InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) { return null; } } private SecretKey generateKey(String salt, String passphrase) { try { SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); KeySpec spec = new PBEKeySpec(passphrase.toCharArray(), hex(salt), iterationCount, keySize); SecretKey key = new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES"); return key; } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { return null; } } public static byte[] base64(String str) { return Base64.decodeBase64(str); } public static byte[] hex(String str) { try { return Hex.decodeHex(str.toCharArray()); } catch (DecoderException e) { throw new IllegalStateException(e); } } private IllegalStateException fail(Exception e) { return null; } }测试AES加密和解密     DemoApplication.java作为Java应用程序运行,然后打到http:// localhost:8080。一旦出现登录页面,您可以输入用户名和密码,然后单击提交按钮,您可以在警报中看到解密的密码。​总结在这篇文章中,我们讨论了使用Java和Javascript进行可互操作的AES加密。我们使用Crypto.js库在javascript中执行此加密。提示:项目源码下载aes-encryption-javascript-java-master.zip
  • Java 垃圾回收详细讲解

    之前上学的时候有这个一个梗,说在食堂里吃饭,吃完把餐盘端走清理的,是 C++ 程序员,吃完直接就走的,是 Java 程序员之前上学的时候有这个一个梗,说在食堂里吃饭,吃完把餐盘端走清理的,是 C++ 程序员,吃完直接就走的,是 Java 程序员。​确实,在 Java 的世界里,似乎我们不用对垃圾回收那么的专注,很多初学者不懂 GC,也依然能写出一个能用甚至还不错的程序或系统。但其实这并不代表 Java 的 GC 就不重要。相反,它是那么的重要和复杂,以至于出了问题,那些初学者除了打开 GC 日志,看着一堆0101的天文,啥也做不了。​今天我们就从头到尾完整地聊一聊 Java 的垃圾回收。什么是垃圾回收垃圾回收(Garbage Collection,GC),顾名思义就是释放垃圾占用的空间,防止内存泄露。有效的使用可以使用的内存,对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收。Java 语言出来之前,大家都在拼命的写 C 或者 C++ 的程序,而此时存在一个很大的矛盾,C++ 等语言创建对象要不断的去开辟空间,不用的时候又需要不断的去释放控件,既要写构造函数,又要写析构函数,很多时候都在重复的 allocated,然后不停的析构。于是,有人就提出,能不能写一段程序实现这块功能,每次创建,释放控件的时候复用这段代码,而无需重复的书写呢?1960年,基于 MIT 的 Lisp 首先提出了垃圾回收的概念,而这时 Java 还没有出世呢!所以实际上 GC 并不是Java的专利,GC 的历史远远大于 Java 的历史!怎么定义垃圾既然我们要做垃圾回收,首先我们得搞清楚垃圾的定义是什么,哪些内存是需要回收的。引用计数算法引用计数算法(Reference Counting)是通过在对象头中分配一个空间来保存该对象被引用的次数(Reference Count)。如果该对象被其它对象引用,则它的引用计数加1,如果删除对该对象的引用,那么它的引用计数就减1,当该对象的引用计数为0时,那么该对象就会被回收。String m = new String("jack");先创建一个字符串,这时候"jack"有一个引用,就是 m。​然后将 m 设置为 null,这时候"jack"的引用次数就等于0了,在引用计数算法中,意味着这块内容就需要被回收了。m = null;​引用计数算法是将垃圾回收分摊到整个应用程序的运行当中了,而不是在进行垃圾收集时,要挂起整个应用的运行,直到对堆中所有对象的处理都结束。因此,采用引用计数的垃圾收集不属于严格意义上的"Stop-The-World"的垃圾收集机制。看似很美好,但我们知道JVM的垃圾回收就是"Stop-The-World"的,那是什么原因导致我们最终放弃了引用计数算法呢?看下面的例子。1. 定义2个对象2. 相互引用3. 置空各自的声明引用​我们可以看到,最后这2个对象已经不可能再被访问了,但由于他们相互引用着对方,导致它们的引用计数永远都不会为0,通过引用计数算法,也就永远无法通知GC收集器回收它们。可达性分析算法可达性分析算法(Reachability Analysis)的基本思路是,通过一些被称为引用链(GC Roots)的对象作为起点,从这些节点开始向下搜索,搜索走过的路径被称为(Reference Chain),当一个对象到 GC Roots 没有任何引用链相连时(即从 GC Roots 节点到该节点不可达),则证明该对象是不可用的。 ​通过可达性算法,成功解决了引用计数所无法解决的问题-“循环依赖”,只要你无法与 GC Root 建立直接或间接的连接,系统就会判定你为可回收对象。那这样就引申出了另一个问题,哪些属于 GC Root。Java 内存区域在 Java 语言中,可作为 GC Root 的对象包括以下4种:虚拟机栈(栈帧中的本地变量表)中引用的对象方法区中类静态属性引用的对象方法区中常量引用的对象本地方法栈中 JNI(即一般说的 Native 方法)引用的对象 ​虚拟机栈(栈帧中的本地变量表)中引用的对象此时的 s,即为 GC Root,当s置空时,localParameter 对象也断掉了与 GC Root 的引用链,将被回收。public class StackLocalParameter { public StackLocalParameter(String name){} } public static void testGC(){ StackLocalParameter s = new StackLocalParameter("localParameter"); s = null; }方法区中类静态属性引用的对象s 为 GC Root,s 置为 null,经过 GC 后,s 所指向的 properties 对象由于无法与 GC Root 建立关系被回收。而 m 作为类的静态属性,也属于 GC Root,parameter 对象依然与 GC root 建立着连接,所以此时 parameter 对象并不会被回收。public class MethodAreaStaicProperties { public static MethodAreaStaicProperties m; public MethodAreaStaicProperties(String name){} } public static void testGC(){ MethodAreaStaicProperties s = new MethodAreaStaicProperties("properties"); s.m = new MethodAreaStaicProperties("parameter"); s = null; } 方法区中常量引用的对象m 即为方法区中的常量引用,也为 GC Root,s 置为 null 后,final 对象也不会因没有与 GC Root 建立联系而被回收。public class MethodAreaStaicProperties { public static final MethodAreaStaicProperties m = MethodAreaStaicProperties("final"); public MethodAreaStaicProperties(String name){} } public static void testGC(){ MethodAreaStaicProperties s = new MethodAreaStaicProperties("staticProperties"); s = null; } 本地方法栈中引用的对象任何 Native 接口都会使用某种本地方法栈,实现的本地方法接口是使用 C 连接模型的话,那么它的本地方法栈就是 C 栈。当线程调用 Java 方法时,虚拟机会创建一个新的栈帧并压入 Java 栈。然而当它调用的是本地方法时,虚拟机会保持 Java 栈不变,不再在线程的 Java 栈中压入新的帧,虚拟机只是简单地动态连接并直接调用指定的本地方法。​怎么回收垃圾 在确定了哪些垃圾可以被回收后,垃圾收集器要做的事情就是开始进行垃圾回收,但是这里面涉及到一个问题是:如何高效地进行垃圾回收。由于Java虚拟机规范并没有对如何实现垃圾收集器做出明确的规定,因此各个厂商的虚拟机可以采用不同的方式来实现垃圾收集器,这里我们讨论几种常见的垃圾收集算法的核心思想。标记 --- 清除算法​标记清除算法(Mark-Sweep)是最基础的一种垃圾回收算法,它分为2部分,先把内存区域中的这些对象进行标记,哪些属于可回收标记出来,然后把这些垃圾拎出来清理掉。就像上图一样,清理掉的垃圾就变成未使用的内存区域,等待被再次使用。这逻辑再清晰不过了,并且也很好操作,但它存在一个很大的问题,那就是内存碎片。上图中等方块的假设是 2M,小一些的是 1M,大一些的是 4M。等我们回收完,内存就会切成了很多段。我们知道开辟内存空间时,需要的是连续的内存区域,这时候我们需要一个 2M的内存区域,其中有2个 1M 是没法用的。这样就导致,其实我们本身还有这么多的内存的,但却用不了。复制算法​复制算法(Copying)是在标记清除算法上演化而来,解决标记清除算法的内存碎片问题。它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。保证了内存的连续可用,内存分配时也就不用考虑内存碎片等复杂情况,逻辑清晰,运行高效。上面的图很清楚,也很明显的暴露了另一个问题,合着我这140平的大三房,只能当70平米的小两房来使?代价实在太高。标记整理算法​标记整理算法(Mark-Compact)标记过程仍然与标记 --- 清除算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,再清理掉端边界以外的内存区域。标记整理算法一方面在标记-清除算法上做了升级,解决了内存碎片的问题,也规避了复制算法只能利用一半内存区域的弊端。看起来很美好,但从上图可以看到,它对内存变动更频繁,需要整理所有存活对象的引用地址,在效率上比复制算法要差很多。分代收集算法分代收集算法(Generational Collection)严格来说并不是一种思想或理论,而是融合上述3种基础的算法思想,而产生的针对不同情况所采用不同算法的一套组合拳。对象存活周期的不同将内存划分为几块。一般是把 Java 堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用标记-清理或者标记 --- 整理算法来进行回收。so,另一个问题来了,那内存区域到底被分为哪几块,每一块又有什么特别适合什么算法呢?内存模型与回收策略 ​Java 堆(Java Heap)是JVM所管理的内存中最大的一块,堆又是垃圾收集器管理的主要区域,这里我们主要分析一下 Java 堆的结构。Java 堆主要分为2个区域-年轻代与老年代,其中年轻代又分 Eden 区和 Survivor 区,其中 Survivor 区又分 From 和 To 2个区。可能这时候大家会有疑问,为什么需要 Survivor 区,为什么Survivor 还要分2个区。不着急,我们从头到尾,看看对象到底是怎么来的,而它又是怎么没的。Eden 区IBM 公司的专业研究表明,有将近98%的对象是朝生夕死,所以针对这一现状,大多数情况下,对象会在新生代 Eden 区中进行分配,当 Eden 区没有足够空间进行分配时,虚拟机会发起一次 Minor GC,Minor GC 相比 Major GC 更频繁,回收速度也更快。通过 Minor GC 之后,Eden 会被清空,Eden 区中绝大部分对象会被回收,而那些无需回收的存活对象,将会进到 Survivor 的 From 区(若 From 区不够,则直接进入 Old 区)。Survivor 区Survivor 区相当于是 Eden 区和 Old 区的一个缓冲,类似于我们交通灯中的黄灯。Survivor 又分为2个区,一个是 From 区,一个是 To 区。每次执行 Minor GC,会将 Eden 区和 From 存活的对象放到 Survivor 的 To 区(如果 To 区不够,则直接进入 Old 区)。为啥需要?不就是新生代到老年代么,直接 Eden 到 Old 不好了吗,为啥要这么复杂。想想如果没有 Survivor 区,Eden 区每进行一次 Minor GC,存活的对象就会被送到老年代,老年代很快就会被填满。而有很多对象虽然一次 Minor GC 没有消灭,但其实也并不会蹦跶多久,或许第二次,第三次就需要被清除。这时候移入老年区,很明显不是一个明智的决定。所以,Survivor 的存在意义就是减少被送到老年代的对象,进而减少 Major GC 的发生。Survivor 的预筛选保证,只有经历16次 Minor GC 还能在新生代中存活的对象,才会被送到老年代。为啥需要俩?设置两个 Survivor 区最大的好处就是解决内存碎片化。我们先假设一下,Survivor 如果只有一个区域会怎样。Minor GC 执行后,Eden 区被清空了,存活的对象放到了 Survivor 区,而之前 Survivor 区中的对象,可能也有一些是需要被清除的。问题来了,这时候我们怎么清除它们?在这种场景下,我们只能标记清除,而我们知道标记清除最大的问题就是内存碎片,在新生代这种经常会消亡的区域,采用标记清除必然会让内存产生严重的碎片化。因为 Survivor 有2个区域,所以每次 Minor GC,会将之前 Eden 区和 From 区中的存活对象复制到 To 区域。第二次 Minor GC 时,From 与 To 职责兑换,这时候会将 Eden 区和 To 区中的存活对象再复制到 From 区域,以此反复。这种机制最大的好处就是,整个过程中,永远有一个 Survivor space 是空的,另一个非空的 Survivor space 是无碎片的。那么,Survivor 为什么不分更多块呢?比方说分成三个、四个、五个?显然,如果 Survivor 区再细分下去,每一块的空间就会比较小,容易导致 Survivor 区满,两块 Survivor 区可能是经过权衡之后的最佳方案。Old 区老年代占据着2/3的堆内存空间,只有在 Major GC 的时候才会进行清理,每次 GC 都会触发“Stop-The-World”。内存越大,STW 的时间也越长,所以内存也不仅仅是越大就越好。由于复制算法在对象存活率较高的老年代会进行很多次的复制操作,效率很低,所以老年代这里采用的是标记 --- 整理算法。除了上述所说,在内存担保机制下,无法安置的对象会直接进到老年代,以下几种情况也会进入老年代。大对象大对象指需要大量连续内存空间的对象,这部分对象不管是不是“朝生夕死”,都会直接进到老年代。这样做主要是为了避免在 Eden 区及2个 Survivor 区之间发生大量的内存复制。当你的系统有非常多“朝生夕死”的大对象时,得注意了。长期存活对象虚拟机给每个对象定义了一个对象年龄(Age)计数器。正常情况下对象会不断的在 Survivor 的 From 区与 To 区之间移动,对象在 Survivor 区中每经历一次 Minor GC,年龄就增加1岁。当年龄增加到15岁时,这时候就会被转移到老年代。当然,这里的15,JVM 也支持进行特殊设置。动态对象年龄虚拟机并不重视要求对象年龄必须到15岁,才会放入老年区,如果 Survivor 空间中相同年龄所有对象大小的总合大于 Survivor 空间的一半,年龄大于等于该年龄的对象就可以直接进去老年区,无需等你“成年”。这其实有点类似于负载均衡,轮询是负载均衡的一种,保证每台机器都分得同样的请求。看似很均衡,但每台机的硬件不通,健康状况不同,我们还可以基于每台机接受的请求数,或每台机的响应时间等,来调整我们的负载均衡算法。
  • java处理RSA非对称加解密

    java处理RSA非对称加解密 <pre> <code class="language-java">package org.xqlee.utils.security; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.security.InvalidKeyException; import java.security.Key; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import java.util.HashMap; import java.util.Map; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import org.apache.commons.codec.binary.Base64; /** * * * <pre> * _________________________INFO_____________________________ * | Description : [RAS加密/解密工具类|其他说明,非对称加密速度非常慢,内容过大一般使用对称加密,然后对对称加密的密文进行非对称加密] * | Encoding : [UTF-8] * | Package : [org.xqlee.utils.security] * | Project : [utils] * | Author : [LXQ] * | CreateDate : [] * | Updater : [] * | UpdateDate : [] * | UpdateRemark: [] * | Company : [www.zhljc.com] * | Version : [v 1.0] * | Libs : [commons-codec-1.x.jar] * __________________________________________________________ * </pre> */ public class RSAUtil { /** 换行符 **/ private final static String lineSeparator = System.getProperty("line.separator", "\n"); /** 加解密算法键值-这里RSA **/ private final static String KEY_ALGORITHM = "RSA"; /** 获取公钥的KEY **/ public final static String PUBLIC_KEY = "RSAPublicKey"; /** 获取私钥的KEY **/ public final static String PRIVATE_KEY = "RSAPrivateKey"; /** * 生成RSA算法的密钥对 * * @return 密钥对,Map-> key=RSAPublicKey|KEY=RSAPrivateKey * @throws NoSuchAlgorithmException * 获取密钥对可能发生的异常 */ public static Map<String, Object> genKeyPair() throws NoSuchAlgorithmException { KeyPairGenerator kGenerator = KeyPairGenerator.getInstance(KEY_ALGORITHM); // init the keyPair KeyPair keyPair = kGenerator.generateKeyPair(); // get public key RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); // get private key RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); Map<String, Object> keys = new HashMap<String, Object>(); keys.put(PUBLIC_KEY, publicKey); keys.put(PRIVATE_KEY, privateKey); return keys; } /** * 加密 * * @param key * 加密使用的[公钥/私钥] * @param input * 需要加密的明文 * @return 加密后的密文 * @throws Exception * 加密过程中可能发生的异常 */ public static byte[] encrypt(Key key, byte[] input) throws Exception { if (key != null) { // Cipher负责完成加密或解密工作,基于RSA Cipher cipher; try { cipher = Cipher.getInstance(KEY_ALGORITHM); // 根据公钥,对Cipher对象进行初始化 cipher.init(Cipher.ENCRYPT_MODE, key); byte[] resultBytes = cipher.doFinal(input); return resultBytes; } catch (NoSuchAlgorithmException e) { throw new Exception("无此解密算法:" + e.getMessage()); } catch (NoSuchPaddingException e) { throw new Exception("加密过程中发生异常:" + e.getMessage()); } catch (InvalidKeyException e) { throw new Exception("解密私钥非法,请检查:" + e.getMessage()); } catch (IllegalBlockSizeException e) { throw new Exception("密文长度非法:" + e.getMessage()); } catch (BadPaddingException e) { throw new Exception("密文数据已损坏:" + e.getMessage()); } } else { throw new Exception("解密私钥为空, 请设置"); } } /** * 解密 * * @param key * 对应解密的[公钥或私钥] * @param input * 需要解密的加密信息 * @return 解密后的明文信息 * @throws Exception * 解密过程中可能发生的异常 */ public static byte[] decrypt(Key key, byte[] input) throws Exception { if (key == null) { throw new Exception("解密私钥为空, 请设置"); } Cipher cipher = null; try { // Cipher负责完成加密或解密工作,基于RSA cipher = Cipher.getInstance(KEY_ALGORITHM); // 根据私钥,对Cipher对象进行初始化 cipher.init(Cipher.DECRYPT_MODE, key); byte[] output = cipher.doFinal(input); return output; } catch (NoSuchAlgorithmException e) { throw new Exception("无此解密算法:" + e.getMessage()); } catch (NoSuchPaddingException e) { throw new Exception("解密过程中发生异常:" + e.getMessage()); } catch (InvalidKeyException e) { throw new Exception("解密私钥非法,请检查:" + e.getMessage()); } catch (IllegalBlockSizeException e) { throw new Exception("密文长度非法:" + e.getMessage()); } catch (BadPaddingException e) { throw new Exception("密文数据已损坏:" + e.getMessage()); } } /** * 通过文件获取私钥对象 * * @param file * 私钥文件 * @return 私钥对象 * @throws Exception * 从文件私钥获取私钥Java对象过程中可能发生的异常 */ public static PrivateKey getPrivateKey(File file) throws Exception { String strPrivateKey = null; try { BufferedReader br = new BufferedReader(new FileReader(file)); String readLine = null; StringBuilder sb = new StringBuilder(); while ((readLine = br.readLine()) != null) { sb.append(readLine); sb.append(lineSeparator); } br.close(); // 字符 strPrivateKey = sb.toString().substring(0, sb.toString().length() - lineSeparator.length()); } catch (IOException e) { throw new Exception("私钥数据流读取错误:" + e.getMessage()); } try { byte[] buffer = Base64.decodeBase64(strPrivateKey.getBytes()); PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(buffer); KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); return keyFactory.generatePrivate(keySpec); } catch (NoSuchAlgorithmException e) { throw new Exception("=====无此算法====="); } catch (InvalidKeySpecException e) { throw new Exception("=====密钥非法====="); } catch (NullPointerException e) { throw new Exception("=====私钥数据为空====="); } } /** * 通过字符串获取私钥对象 * * @param strPrivateKey * 字符串的私钥 * @return 私钥对象 * @throws Exception * 从字符串私钥获取私钥Java对象过程中可能发生的异常 */ public static PrivateKey getPrivateKey(String strPrivateKey) throws Exception { try { byte[] buffer = Base64.decodeBase64(strPrivateKey.getBytes()); PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(buffer); KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); return keyFactory.generatePrivate(keySpec); } catch (NoSuchAlgorithmException e) { throw new Exception("=====无此算法====="); } catch (InvalidKeySpecException e) { throw new Exception("=====密钥非法====="); } catch (NullPointerException e) { throw new Exception("=====私钥数据为空====="); } } /** * 从公钥文件中加载获取公钥Java对象 * * @param file * 公钥文件 * @return Java公钥对象 * @throws Exception * 获取过程中可能发生的异常 */ public static PublicKey getPublicKey(File file) throws Exception { String strRSAPublicKey = null; try { BufferedReader br = new BufferedReader(new FileReader(file)); String readLine = null; StringBuilder sb = new StringBuilder(); while ((readLine = br.readLine()) != null) { sb.append(readLine); sb.append(lineSeparator); } br.close(); strRSAPublicKey = sb.toString().substring(0, sb.toString().length() - lineSeparator.length()); } catch (IOException e) { throw new Exception("公钥数据流读取错误"); } catch (NullPointerException e) { throw new Exception("公钥输入流为空"); } try { byte[] buffer = Base64.decodeBase64(strRSAPublicKey.getBytes()); KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); X509EncodedKeySpec keySpec = new X509EncodedKeySpec(buffer); return keyFactory.generatePublic(keySpec); } catch (NoSuchAlgorithmException e) { throw new Exception("=====无此算法====="); } catch (InvalidKeySpecException e) { throw new Exception("=====公钥非法====="); } catch (NullPointerException e) { throw new Exception("=====公钥数据为空====="); } } /** * 从公钥字符串加载获取公钥Java对象 * * @param strPublicKey * 公钥字符串 * @return Java公钥对象 * @throws Exception * 获取过程中可能发生的异常 */ public static PublicKey getPublicKey(String strPublicKey) throws Exception { try { byte[] buffer = Base64.decodeBase64(strPublicKey.getBytes()); KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); X509EncodedKeySpec keySpec = new X509EncodedKeySpec(buffer); return keyFactory.generatePublic(keySpec); } catch (NoSuchAlgorithmException e) { throw new Exception("=====无此算法====="); } catch (InvalidKeySpecException e) { throw new Exception("=====公钥非法====="); } catch (NullPointerException e) { throw new Exception("=====公钥数据为空====="); } } /** * 将Java对象的公钥或者私钥转换为字符串 * * @param key * 公钥/私钥 * @return 秘钥的字符串 */ public static String key2String(Key key) { byte[] keyBytes = key.getEncoded(); String result = new String(Base64.encodeBase64(keyBytes)); return result; } } </code></pre>  
  • java编程之java jwt token使用autho0-jwt框架使用(二)

    java编程之java jwt token使用,autho0的Java-jwt框架使用,java编程,java-jwt<h2>一、前言</h2> Java编程中使用jwt,首先你必须了解jwt是什么,长什么样子。如果你不了解可以先去本站另外一篇博客<a href="http://www.leftso.com/blog/220.html" rel="" target="_blank" >什么是JWT?</a><br />   <h2>二、Java编程中jwt框架选择</h2> 在Java编程中,实现jwt标准的有很多框架,本博客采用的框架是auth0的java-jwt版本为3.2.0 <h2>三、什么是Java-JWT</h2> auth0的java-jwt是一个JSON WEB TOKEN(JWT)的一个实现。 <h2>四、安装下载相关依赖</h2> 如果你是采用<strong>maven</strong>的方式,在你的项目pom.xml文件中添加以下java-jwt的依赖片段: <pre> <code class="language-xml"><dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.2.0</version> </dependency></code></pre> 如果你是采用<strong>Gradle</strong>的方式,则添加以下内容: <pre> <code>compile 'com.auth0:java-jwt:3.2.0'</code></pre> <h2>五、java-jwt已经实现的算法</h2> 该库使用以下算法实现JWT验证和签名: <table class="table table-bordered table-hover" > <thead> <tr> <th>JWS</th> <th>算法</th> <th>介绍</th> </tr> </thead> <tbody> <tr> <td>HS256</td> <td>HMAC256</td> <td>HMAC with SHA-256</td> </tr> <tr> <td>HS384</td> <td>HMAC384</td> <td>HMAC with SHA-384</td> </tr> <tr> <td>HS512</td> <td>HMAC512</td> <td>HMAC with SHA-512</td> </tr> <tr> <td>RS256</td> <td>RSA256</td> <td>RSASSA-PKCS1-v1_5 with SHA-256</td> </tr> <tr> <td>RS384</td> <td>RSA384</td> <td>RSASSA-PKCS1-v1_5 with SHA-384</td> </tr> <tr> <td>RS512</td> <td>RSA512</td> <td>RSASSA-PKCS1-v1_5 with SHA-512</td> </tr> <tr> <td>ES256</td> <td>ECDSA256</td> <td>ECDSA with curve P-256 and SHA-256</td> </tr> <tr> <td>ES384</td> <td>ECDSA384</td> <td>ECDSA with curve P-384 and SHA-384</td> </tr> <tr> <td>ES512</td> <td>ECDSA512</td> <td>ECDSA with curve P-521 and SHA-512</td> </tr> </tbody> </table> <h2>六、如何使用java-jwt</h2> <strong>6.1.选择一种算法</strong> <p>  算法定义了一个令牌是如何被签名和验证的。它可以用HMAC算法的原始值来实例化,也可以在RSA和ECDSA算法的情况下对密钥对或密钥提供程序进行实例化。创建后,该实例可用于令牌签名和验证操作。</p> <p>在使用RSA或ECDSA算法时,只需要签署JWTs,就可以通过传递null值来避免指定公钥。当您需要验证JWTs时,也可以使用私钥进行操作<br /> <br /> 使用静态的字符密文或者key来获取算法器:<br />  </p> <pre> <code class="language-java">//HMAC Algorithm algorithmHS = Algorithm.HMAC256("secret"); //RSA RSAPublicKey publicKey = //Get the key instance RSAPrivateKey privateKey = //Get the key instance Algorithm algorithmRS = Algorithm.RSA256(publicKey, privateKey);</code></pre> 使用一个key提供者来获取算法:<br />   通过使用KeyProvider,您可以在运行时更改密钥,用于验证令牌签名或为RSA或ECDSA算法签署一个新的令牌。这是通过实现RSAKeyProvider或ECDSAKeyProvider方法实现的: <ul> <li><code>getPublicKeyById(String kid)</code>: 它在令牌签名验证中调用,它应该返回用于验证令牌的密钥。如果使用了关键的轮换,例如JWK,它可以使用id来获取正确的轮换键(或者只是一直返回相同的键)。</li> <li><code>getPrivateKey()</code>: 在令牌签名期间调用它,它应该返回用于签署JWT的密钥。</li> <li><code>getPrivateKeyId()</code>:在令牌签名期间调用它,它应该返回标识由getPrivateKey()返回的键的id的id。这个值比JWTCreator.Builder和keyid(String)方法中的值更受欢迎。如果您不需要设置孩子的值,就避免使用KeyProvider实例化算法。</li> </ul> 下面的代码片段将展示如何使用: <pre> <code class="language-java">final JwkStore jwkStore = new JwkStore("{JWKS_FILE_HOST}"); final RSAPrivateKey privateKey = //Get the key instance final String privateKeyId = //Create an Id for the above key RSAKeyProvider keyProvider = new RSAKeyProvider() { @Override public RSAPublicKey getPublicKeyById(String kid) { //Received 'kid' value might be null if it wasn't defined in the Token's header RSAPublicKey publicKey = jwkStore.get(kid); return (RSAPublicKey) publicKey; } @Override public RSAPrivateKey getPrivateKey() { return privateKey; } @Override public String getPrivateKeyId() { return privateKeyId; } }; Algorithm algorithm = Algorithm.RSA256(keyProvider); //Use the Algorithm to create and verify JWTs.</code></pre> <br /> <em>提示:对于使用JWKs的简单的键轮换,可以尝试JWKs-rsa-java库。</em><br /> <br /> <strong>6.2.创建一个签名的JWT token</strong><br /> 首先需要通过调用<code>jwt.create()</code>创建一个<code>JWTCreator</code>实例 <ul> <li>例如使用 <code>HS256算法:</code></li> </ul> <pre> <code class="language-java">try { Algorithm algorithm = Algorithm.HMAC256("secret"); String token = JWT.create() .withIssuer("auth0") .sign(algorithm); } catch (UnsupportedEncodingException exception){ //UTF-8 encoding not supported } catch (JWTCreationException exception){ //Invalid Signing configuration / Couldn't convert Claims. }</code></pre> <ul> <li>例如使用<code>RS256算法:</code></li> </ul> <pre> <code class="language-java">RSAPublicKey publicKey = //Get the key instance RSAPrivateKey privateKey = //Get the key instance try { Algorithm algorithm = Algorithm.RSA256(publicKey, privateKey); String token = JWT.create() .withIssuer("auth0") .sign(algorithm); } catch (JWTCreationException exception){ //Invalid Signing configuration / Couldn't convert Claims. }</code></pre> 如果Claim不能转换为JSON,或者在签名过程中使用的密钥无效,那么将会抛出<code>JWTCreationException</code>异常。<br /> <br /> <strong>6.3.验证令牌</strong><br />   首先需要通过调用jwt.require()和传递算法实例来创建一个JWTVerifier实例。如果您要求令牌具有特定的Claim值,请使用构建器来定义它们。方法build()返回的实例是可重用的,因此您可以定义一次,并使用它来验证不同的标记。最后调用verifier.verify()来验证token <ul> <li>例如使用 <code>HS256算法的时候:</code></li> </ul> <pre> <code class="language-java">String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; try { Algorithm algorithm = Algorithm.HMAC256("secret"); JWTVerifier verifier = JWT.require(algorithm) .withIssuer("auth0") .build(); //Reusable verifier instance DecodedJWT jwt = verifier.verify(token); } catch (UnsupportedEncodingException exception){ //UTF-8 encoding not supported } catch (JWTVerificationException exception){ //Invalid signature/claims }</code></pre> <ul> <li>例如使用 <code>RS256算法的时候:</code></li> </ul> <pre> <code class="language-java">String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; RSAPublicKey publicKey = //Get the key instance RSAPrivateKey privateKey = //Get the key instance try { Algorithm algorithm = Algorithm.RSA256(publicKey, privateKey); JWTVerifier verifier = JWT.require(algorithm) .withIssuer("auth0") .build(); //Reusable verifier instance DecodedJWT jwt = verifier.verify(token); } catch (JWTVerificationException exception){ //Invalid signature/claims }</code></pre> 如果令牌有一个无效的签名,或者没有满足Claim要求,那么将会抛出JWTVerificationException异常<br /> <br /> <strong>6.4.jwt时间的验证</strong><br /> JWT令牌可能包括可用于验证的DateNumber字段: <ul> <li>这个令牌发布了一个过期的时间 <code>"iat" < TODAY</code></li> <li>这个令牌还没过期 <code>"exp" > TODAY</code> and</li> <li>这个令牌已经被使用了. <code>"nbf" > TODAY</code></li> </ul> 当验证一个令牌时,时间验证会自动发生,导致在值无效时抛出一个JWTVerificationException。如果前面的任何一个字段都丢失了,那么在这个验证中就不会考虑这些字段。<br /> 要指定令牌仍然被认为有效的余地窗口,在JWTVerifier builder中使用accept回旋()方法,并传递一个正值的秒值。这适用于上面列出的每一项。 <pre> <code class="language-java">JWTVerifier verifier = JWT.require(algorithm) .acceptLeeway(1) // 1 sec for nbf, iat and exp .build();</code></pre> 您还可以为给定的日期声明指定一个自定义值,并为该声明覆盖缺省值。 <pre> <code class="language-java">JWTVerifier verifier = JWT.require(algorithm) .acceptLeeway(1) //1 sec for nbf and iat .acceptExpiresAt(5) //5 secs for exp .build();</code></pre> 如果您需要在您的lib/app中测试此行为,将验证实例转换为basever可视化,以获得verific.build()方法的可见性,该方法可以接受定制的时钟。例如: <pre> <code class="language-java">BaseVerification verification = (BaseVerification) JWT.require(algorithm) .acceptLeeway(1) .acceptExpiresAt(5); Clock clock = new CustomClock(); //Must implement Clock interface JWTVerifier verifier = verification.build(clock);</code></pre> <br /> <strong>6.5解码一个jwt令牌</strong> <pre> <code class="language-java">String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; try { DecodedJWT jwt = JWT.decode(token); } catch (JWTDecodeException exception){ //Invalid token }</code></pre> 如果令牌有无效的语法,或者消息头或有效负载不是JSONs,那么将会抛出JWTDecodeException异常。<br /> <br /> <strong>6.6JWT头部解析</strong> <h4><br /> Algorithm ("alg")</h4> 返回jwt的算法值或,如果没有定义则返回null <pre> <code class="language-java">String algorithm = jwt.getAlgorithm();</code></pre> <p> </p> <p>Type ("typ")</p> <p>返回jwt的类型值,如果没有定义则返回null(多数情况类型值为jwt)</p> <pre> <code class="language-java">String type = jwt.getType();</code></pre> <p> </p> <p>Content Type ("cty")</p> <p>返回内容的类型,如果没有定义则返回null</p> <pre> <code class="language-java">String contentType = jwt.getContentType();</code></pre> <p><br /> Key Id ("kid")<br /> 返回key的id值,如果没有定义则返回null<br />  </p> <pre> <code class="language-java">String keyId = jwt.getKeyId();</code></pre> <h4>私有的Claims,即自定义字段</h4> 在令牌的头部中定义的附加声明可以通过调用getHeaderClaim() 获取,即使无法找到,也会返回。您可以通过调用claim.isNull()来检查声明的值是否为null。 <pre> <code class="language-java">Claim claim = jwt.getHeaderClaim("owner");</code></pre> 当使用jwt.create()创建一个令牌时,您可以通过调用withHeader()来指定头声明,并同时传递声明的映射。 <pre> <code class="language-java">Map<String, Object> headerClaims = new HashMap(); headerClaims.put("owner", "auth0"); String token = JWT.create() .withHeader(headerClaims) .sign(algorithm); </code></pre> <em>提示:在签名过程之后,alg和typ值将始终包含在Header中。</em><br /> <br /> <strong>6.7JWT的负载(Payload)声明</strong> <h4>Issuer ("iss")</h4> 返回签发者的名称值,如果没有在负载中定义则返回null <pre> <code class="language-java">String issuer = jwt.getIssuer();</code></pre> <h4>Subject ("sub")</h4> 返回jwt所面向的用户的值,如果没有在负载中定义则返回null <pre> <code class="language-java">String subject = jwt.getSubject();</code></pre> <h4>Audience ("aud")</h4> 返回该jwt由谁接收,如果没有在负载中定义则返回null <pre> <code class="language-java">List<String> audience = jwt.getAudience();</code></pre> <h4>Expiration Time ("exp")</h4> 返回该jwt的过期时间,如果在负载中没有定义则返回null <pre> <code class="language-java">Date expiresAt = jwt.getExpiresAt();</code></pre> <h4>Not Before ("nbf")</h4> Returns the Not Before value or null if it's not defined in the Payload. <pre> <code class="language-java">Date notBefore = jwt.getNotBefore();</code></pre> <h4>Issued At ("iat")</h4> 返回在什么时候签发的,如果在负载中没有定义则返回null <pre> <code class="language-java">Date issuedAt = jwt.getIssuedAt();</code></pre> <h4>JWT ID ("jti")</h4> 返回该jwt的唯一标志,如果在负载中没有定义则返回null <pre> <code class="language-java">String id = jwt.getId();</code></pre> <strong>自定义声明</strong><br /> 在令牌有效负载中定义的附加声明可以通过调用getClaims()或 getClaim()和传递声明名来获得。即使无法找到声明,也将会有返回值。您可以通过调用claim.isNull()来检查声明的值是否为null。 <pre> <code class="language-java">Map<String, Claim> claims = jwt.getClaims(); //Key is the Claim name Claim claim = claims.get("isAdmin");</code></pre> 或者: <pre> <code class="language-java">Claim claim = jwt.getClaim("isAdmin");</code></pre> 当使用jwt.create()创建一个令牌时,您可以通过调用withClaim()来指定自定义声明,并同时传递名称和值。 <pre> <code class="language-java">String token = JWT.create() .withClaim("name", 123) .withArrayClaim("array", new Integer[]{1, 2, 3}) .sign(algorithm);</code></pre> 您还可以通过调用withClaim()来验证jwt.require()的自定义声明,并传递该名称和所需的值。 <pre> <code class="language-java">JWTVerifier verifier = JWT.require(algorithm) .withClaim("name", 123) .withArrayClaim("array", 1, 2, 3) .build(); DecodedJWT jwt = verifier.verify("my.jwt.token");</code></pre> <em>提示:当前支持的自定义JWT声明创建和验证的类型是:Boolean, Integer, Double, String, Date 和Arrays。</em><br /> <br /> <strong>6.8Claim Class</strong><br /> 索赔类是索赔值的包装器。它允许您将索赔作为不同的类类型。可用的工具方法:<br /> 原始的: <ul> <li><strong>asBoolean()</strong>: 返回布尔值,如果不能转换返回null。</li> <li><strong>asInt()</strong>: 返回整数值,如果不能转换返回null。</li> <li><strong>asDouble()</strong>: 返回 Double 值,如果不能转换则返回null。</li> <li><strong>asLong()</strong>: 返回Long 值,如果不能转换则返回null。</li> <li><strong>asString()</strong>: 返回String值,如果不能转换则返回null。</li> <li><strong>asDate()</strong>: 返回 Date值,如果不能转换则返回null。 必须是一个数字日期 (Unix 系统时间戳). 注意,JWT标准指定所有的数字日期值必须以秒为单位。</li> </ul> <h4>自定义类型和集合:</h4> 要获得作为集合的声明,您需要提供要转换的内容的类类型 <ul> <li>as(class): 返回 Class Type 的解析值. 对于集合,您应该使用asArray和asList方法。</li> <li>asMap(): 返回被转换为 Map<String, Object>的值</li> <li>asArray(class): 返回被转换成元素类型的 Class Type, or null if the value isn't a JSON Array.</li> <li>asList(class): 返回集合元素的 Class Type, or null if the value isn't a JSON Array.</li> </ul> <em>如果不能将值转换为给定的类类型,则会抛出JWTDecodeException异常</em><br /> <br /> <em>翻译的不标准的后续更近,欢迎提供宝贵意见或者翻译,联系leftso@qq.com</em>
  • Java编程使用zip打包文件(压缩文件)和解包(加压文件) 工具实现

    这里主要讲解在Java编程中如何使用zip算法来打包文件、文件集合和解压一个zip包的工具类。该工具类主要通过Apache的compress项目中衍生出来的。比Java原生的压缩方式灵活和方便。<ul> <li> <h2>1.说明</h2> zip的文件压缩和解压在文件的存放用的还是比较多的。这里写了一个zip的工具类通过Apache的compress依赖实现。方便移植。</li> <li> <h2>2.依赖</h2> <pre> <code class="language-xml"> <!-- 压缩 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-compress</artifactId> <version>1.14</version> </dependency></code></pre> </li> </ul> <h2> </h2> <ul> <li> <h2>3.工具类实现</h2> <pre> <code class="language-java">package net.xqlee.project.utils.compress; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.compress.archivers.zip.Zip64Mode; import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream; import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream; import org.apache.commons.compress.utils.IOUtils; import org.apache.commons.lang.StringUtils; import net.xqlee.project.utils.exception.UtilException; /** * zip压缩算法工具类 * * @author xq * @JDK 1.7 */ public class ZipCompressUtils { private ZipCompressUtils() { // 禁止构造 } /** 缓冲大小 **/ public static final int BUFFER_SIZE = 1024; /** 系统默认字符集 **/ public static final String SYSTEM_ENCODING = System.getProperty("file.encoding"); /** * 解压zip文件,默认操作系统字符集编码 * * @param zipFile * @param destDir * @return * @throws UtilException */ public static List<String> unZip(final File zipFile, final String targetPath) throws UtilException { try { return unZip(new FileInputStream(zipFile), targetPath, SYSTEM_ENCODING); } catch (FileNotFoundException e) { throw new UtilException(e); } } /** * 指定zip字符集解压文件 * * @param zipFile * @param targetPath * @param encoding * @return * @throws UtilException */ public static List<String> unZip(final File zipFile, final String targetPath, String encoding) throws UtilException { try { return unZip(new FileInputStream(zipFile), targetPath, encoding); } catch (FileNotFoundException e) { throw new UtilException(e); } } /** * 解压zip文件流 * * @param zipFileInputStream * @param targetPath * @return * @throws UtilException */ public static List<String> unZip(final InputStream zipFileInputStream, final String targetPath) throws UtilException { return unZip(zipFileInputStream, targetPath, SYSTEM_ENCODING); } /** * 解压 zip 文件 * * @param zipFileInputStream * zip 压缩文件流 * @param destDir * zip 压缩文件解压后保存的目录 * @return 返回 zip 压缩文件里的文件名的 list * @throws UtilException * 工具类处理的异常 */ public static List<String> unZip(final InputStream zipFileInputStream, final String destDir, final String encoding) throws UtilException { // 如果 destDir 为 null, 空字符串, 或者全是空格, 则解压到压缩文件所在目录 String targetDir = ""; if (StringUtils.isBlank(destDir)) { throw new UtilException("Target Path Can't Be Empty!"); } else { targetDir = destDir.endsWith(File.separator) ? destDir : destDir + File.separator; } // 字符集处理 String fileEncoding = encoding; if (StringUtils.isBlank(encoding)) { fileEncoding = SYSTEM_ENCODING; } List<String> fileNames = new ArrayList<>(); try (ZipArchiveInputStream is = new ZipArchiveInputStream( new BufferedInputStream(zipFileInputStream, BUFFER_SIZE), fileEncoding);) { ZipArchiveEntry entry = null; while ((entry = is.getNextZipEntry()) != null) { String fileName = entry.getName(); fileNames.add(fileName); if (entry.isDirectory()) { File directory = new File(targetDir, fileName); directory.mkdirs(); } else { try (OutputStream os = new BufferedOutputStream(new FileOutputStream(new File(targetDir, fileName)), BUFFER_SIZE);) { IOUtils.copy(is, os); } } } } catch (Exception e) { throw new UtilException(e); } return fileNames; } /** * zip打包 * * @param files * @param zipFilePath * @throws UtilException */ public static void zip(File[] files, String zipFilePath,String encoding) throws UtilException { if (files == null || files.length == 0) { throw new UtilException("Files Is Null Or Empty!"); } try { File zipFile = new File(zipFilePath); try (ZipArchiveOutputStream zipos = new ZipArchiveOutputStream(zipFile)) { //字符集 zipos.setEncoding(encoding); // 启用64模式 zipos.setUseZip64(Zip64Mode.AsNeeded); // 将每个文件用ZipArchiveEntry封装 // 再用ZipArchiveOutputStream写到压缩文件中 for (File file : files) { if (file != null && file.exists() && file.isFile()) { ZipArchiveEntry archiveEntry = new ZipArchiveEntry(file, file.getName()); zipos.putArchiveEntry(archiveEntry); try (InputStream is = new BufferedInputStream(new FileInputStream(file));) { byte[] buffer = new byte[BUFFER_SIZE * 4]; int len = -1; while ((len = is.read(buffer)) != -1) { // 把缓冲区的字节写入到ZipArchiveEntry zipos.write(buffer, 0, len); } // Writes all necessary data for this entry. zipos.closeArchiveEntry(); } } } zipos.finish(); } } catch (Exception e) { throw new UtilException(e); } } /** * 压缩某个目录 * * @param dir * @param zipFilePath * @throws UtilException */ public static void zip(String dir, String zipFilePath,String encoding) throws UtilException { if (StringUtils.isBlank(dir) || StringUtils.isBlank(zipFilePath)) { throw new UtilException("Dir To Package Or Zip File Can't Be Empty"); } Map<String, File> filesInfo = filesName(dir, dir); Set<String> keys = filesInfo.keySet(); // try { File zipFile = new File(zipFilePath); try (ZipArchiveOutputStream zipos = new ZipArchiveOutputStream(zipFile)) { if (!StringUtils.isBlank(encoding)) { zipos.setEncoding(encoding); } // 启用64模式 zipos.setUseZip64(Zip64Mode.AsNeeded); // 将每个文件用ZipArchiveEntry封装 // 再用ZipArchiveOutputStream写到压缩文件中 Iterator<String> iterator = keys.iterator(); while (iterator.hasNext()) { String string = (String) iterator.next(); File file = filesInfo.get(string); ZipArchiveEntry archiveEntry = new ZipArchiveEntry(file, string); zipos.putArchiveEntry(archiveEntry); try (InputStream is = new BufferedInputStream(new FileInputStream(file));) { byte[] buffer = new byte[BUFFER_SIZE * 4]; int len = -1; while ((len = is.read(buffer)) != -1) { // 把缓冲区的字节写入到ZipArchiveEntry zipos.write(buffer, 0, len); } // Writes all necessary data for this entry. zipos.closeArchiveEntry(); } } zipos.finish(); } } catch (Exception e) { throw new UtilException(e); } } public static Map<String, File> filesName(String dirPath, String rootPath) { Map<String, File> fileInfo = new HashMap<>(); File dir = new File(dirPath); if (dir.exists() && dir.isDirectory()) { File[] files = dir.listFiles(); for (File file : files) { if (file.isDirectory()) { Map<String, File> fs = filesName(file.getPath(), rootPath); fileInfo.putAll(fs); } else { fileInfo.put(file.getPath().replace(rootPath, ""), file); } } } return fileInfo; } public static void main(String[] args) throws UtilException { // String zipPath = "C:\\Users\\xq\\Desktop\\李小强工作交接内容.zip"; // String destDir = "C:\\Users\\xq\\Desktop\\zip"; // unZip(new File(zipPath), destDir); // 打包 // File dir = new File(destDir); // zip(dir.listFiles(), destDir + "\\test.zip"); zip("C:\\Users\\xq\\Desktop\\zip", "C:\\Users\\xq\\Desktop\\test.zip",null); } } </code></pre> 工具类最后有调用方式。</li> </ul> <br />  
  • 常用JVM内存设置以及调优

    常用JVM内存设置以及调优常用JVM内存设置以及调优 <h1>JVM内存简介</h1> JVM占用的内存称为堆(heap),<br /> 他被分成三个区:<br /> 1>年轻(young,又称为new)<br /> 2>老(tenred,又称为old)<br /> 3>永生(perm)<br /> 这里的三个区按照java的生命周期进行划分,在new区的对象生存期最短,很快就会被gc回收;perm区的对象生存期最长,与JVM同生同死,perm区的对象不会被gc回收<br /> New区又被分为三个部分:<br /> 1>伊甸园(eden)<br /> 2>幸存者1(survivor)<br /> 3>幸存者2(survivor)<br /> 对象的创建总是在eden部分.两个survivor中总有一个是空的,他作为另外一个survivor的缓冲区.当发生gc时,所有的eden和survivor中活下来的对象被移动到另一个survivor中.对象会在两个survivor直接不断移动,直到获得足够久然后移动到old区<br />   <h1>GC回收算法</h1> 除了默认的垃圾回收算法外,JVM还提供了两个 <ol> <li>并行(parallel)</li> <li>并发(concurrent)</li> </ol> 前者作用于new区,后者作用于old区,两者可以同时使用.并行算法会产生多个线程以提高执行效率.当有多个CPU内核的时候,会显著的缩短gc的工作时间<br />   <h1>性能参数说明</h1> <table border="1" cellpadding="0" cellspacing="0"> <tbody> <tr> <td style="width:249px">参数</td> <td style="width:249px">含义</td> <td style="width:249px">说明</td> </tr> <tr> <td style="width:249px">-Xms</td> <td style="width:249px">Heap的最小值</td> <td style="width:249px">默认为系统物理内存的1/64</td> </tr> <tr> <td style="width:249px">-Xmx</td> <td style="width:249px">Heap 的最大值</td> <td style="width:249px">默认为系统物理内存的1/4<br /> ,作为同行的标准设置Xms和Xmx的大小一样,可以减少GC次数</td> </tr> <tr> <td style="width:249px">-Xmn</td> <td style="width:249px">New的大小</td> <td style="width:249px"> </td> </tr> <tr> <td style="width:249px">-XX:PermSize</td> <td style="width:249px">Perm的最小值</td> <td style="width:249px"> </td> </tr> <tr> <td style="width:249px">-XX:MaxPermSize</td> <td style="width:249px">Perm 的最大值</td> <td style="width:249px">类似heap的设置,应该讲perm设置为固定大小.</td> </tr> <tr> <td style="width:249px">-XX:SurvivorRatio</td> <td style="width:249px">New区中eden与Survivor区的比值</td> <td style="width:249px"> </td> </tr> <tr> <td style="width:249px">-XX:+UseParallelGC</td> <td style="width:249px">使用parallelGC</td> <td style="width:249px"> </td> </tr> <tr> <td style="width:249px">-XX:ParallelGCThreads</td> <td style="width:249px">Parallel gc的线程个数</td> <td style="width:249px">与CPU核心数相同,使得所有CPU都参与GC工作</td> </tr> </tbody> </table>   <h1>常见工具JVM设置</h1> <h2>1.eclipse</h2> 方法1:<br /> 进入eclipse的安装存放目录<br /> <img alt="1" src="/assets/upload/blog/thumbnail/2017-01/117ff6c1-abe3-4c16-b725-257675d2a1aa.png" style="height:344px; width:599px" /><br /> 找到eclipse.ini文件.打开<br /> <img alt="2" src="/assets/upload/blog/thumbnail/2017-01/be454c1e-2a68-4ed7-b19f-a9dbe755bda5.png" style="height:397px; width:711px" /> <h2>2.tomcat</h2> Linux系统:<br /> 修改TOMCAT_HOME/bin/catalina.sh<br /> 在第一行的后面添加一句 <pre> <code>JAVA-OPTS=’-server –Xms256m –Xmx512m –XX:PermSize=128M –XX:MaxPermSize=256M’</code></pre> <br /> 注意Linux必须有单引号<br /> Windows系统<br /> 修改TOMCAT_HOME/bin/catalina.bat<br /> 在第一行后面添加 <pre> <code>set JAVA-OPTS=-server –Xms256m –Xmx512m –XX:PermSize=128M –XX:MaxPermSize=256M</code></pre> <br /> <span style="color:#FF0000">注意:windows没有引号<br /> 注意:java options中每一行不能有空格</span><br />  <br />