法伯科技是一家以數據科技爲驅動, 專一於醫藥健康領域的循證諮詢公司. 以數據科學家身份, 賦能醫藥行業. 讓每位客戶都能享受數據帶來的價值, 洞察業務, 不止於數據, 讓決策更精彩。html
法伯擁有多套自主研發的數據分析工具, 爲企業帶來高效, 便捷, 實用的解決方案.前端
本篇文章, 全部實例代碼, 均爲Scala, 適用於以Scala系列技術棧和微服務架構的初期開發團隊. 其餘技術請自行斟酌修改.
原文地址http://www.javashuo.com/article/p-upfcxizd-ce.htmljava
公司的每一個產品都有各自應用的領域和範疇, 但都是醫藥業務擴展中的必經一換. 因此經常會有公司同時使用咱們多個產品的狀況. 而咱們每一個產品, 在一些數據和邏輯使用上, 有很大的相通性.
而如何更好的保護客戶數據, 怎樣提供更好的用戶體驗, 就是本篇文章的重點內容了.算法
爲了保障用戶帳號密碼的安全性, 在先後端交互中, 所傳輸的密碼, 均爲RSA規範的非對稱加密. 同時, 數據庫中存儲的用戶密碼, 也爲MD5序列化的密文形式.數據庫
爲每一個公司, 生成單獨的祕鑰對, 每對祕鑰有本身的過時時間, 過時時間爲公司購買產品的使用時間.後端
在用戶登陸成功後, 會將該用戶的全部權限信息存入Redis中, 同時生成一個ObjectId做爲token返回給前端. 同時, token有本身的有效時間.安全
用戶在任意位置登陸法伯帳號後, 在token有效期內, 能夠不用輸入帳號密碼, 直接登陸該帳號所擁有的其餘產品中.架構
用戶成功登陸, 會根據用戶當前的權限和角色, 前端決定渲染的組件和佈局dom
後端暴露給前端的接口(登陸, 註冊等無須登陸接口除外), 都須要有一個有效token才能夠正確調用.微服務
加密算法, 使用的類庫是java自帶的java.security
庫.
Base64庫使用的是commons-codec
, MVN以下:
<dependency> <groupId>commons-codec</groupId> <artifactId>commons-codec</artifactId> <version>1.10</version> </dependency>
公鑰祕鑰建立:
// create by ClockQ trait RSACryptogram extends PhCryptogram { val puk: String val prk: String val ALGORITHM_RSA: String val TRANSFORMS_RSA: String val CHARSET_NAME_UTF_8: String val KEY_SIZE: Int def createKey(): (String, String) = { val keyPairGenerator = KeyPairGenerator.getInstance(ALGORITHM_RSA) keyPairGenerator.initialize(KEY_SIZE, new SecureRandom()) val keyPair = keyPairGenerator.generateKeyPair() val publicKey = Base64.encodeBase64String(keyPair.getPublic.getEncoded) val privateKey = Base64.encodeBase64String(keyPair.getPrivate.getEncoded) (publicKey, privateKey) } }
這一段代碼很簡單:
上面的常量以下:
val ALGORITHM_RSA = "RSA" val TRANSFORMS_RSA = "RSA/ECB/PKCS1PADDING" val CHARSET_NAME_UTF_8 = "UTF-8" val KEY_SIZE = 512
加密流程:
代碼以下:
trait RSAEncryptTrait { this: RSACryptogram => def encrypt(cleartext: String): String = { if(puk.isEmpty) throw new Exception("public key is empty") val originKey = Base64.decodeBase64(puk) val keySpec = new X509EncodedKeySpec(originKey) val publicKey = KeyFactory.getInstance(ALGORITHM_RSA).generatePublic(keySpec) val cipher = Cipher.getInstance(TRANSFORMS_RSA) cipher.init(Cipher.ENCRYPT_MODE, publicKey) val inputBytes = URLEncoder.encode(cleartext, CHARSET_NAME_UTF_8).getBytes(CHARSET_NAME_UTF_8) val inputLength = inputBytes.length val MAX_ENCRYPT_BLOCK = (KEY_SIZE >> 3) - 11 var offset = 0 var cache: Array[Byte] = Array() while (inputLength - offset > 0) { val tmp = if (inputLength - offset > MAX_ENCRYPT_BLOCK) cipher.doFinal(inputBytes, offset, MAX_ENCRYPT_BLOCK) else cipher.doFinal(inputBytes, offset, inputLength - offset) cache ++= tmp offset += MAX_ENCRYPT_BLOCK } Base64.encodeBase64String(cache) } }
解密的過程與加密相反, 流程圖就不畫了, 代碼以下:
trait RSADecryptTrait { this: RSACryptogram => def decrypt(ciphertext: String): String = { if(prk.isEmpty) throw new Exception("private key is empty") val originKey = Base64.decodeBase64(prk) val keySpec = new PKCS8EncodedKeySpec(originKey) val privateKey = KeyFactory.getInstance(ALGORITHM_RSA).generatePrivate(keySpec) val cipher = Cipher.getInstance(TRANSFORMS_RSA) cipher.init(Cipher.DECRYPT_MODE, privateKey) val inputBytes = Base64.decodeBase64(ciphertext) val inputLength = inputBytes.length val MAX_DECRYPT_BLOCK = KEY_SIZE >> 3 var offset = 0 var cache: Array[Byte] = Array() while (inputLength - offset > 0) { val tmp = if (inputLength - offset > MAX_DECRYPT_BLOCK) cipher.doFinal(inputBytes, offset, MAX_DECRYPT_BLOCK) else cipher.doFinal(inputBytes, offset, inputLength - offset) cache ++= tmp offset += MAX_DECRYPT_BLOCK } URLDecoder.decode(new String(cache, CHARSET_NAME_UTF_8), CHARSET_NAME_UTF_8) } }
while
循環, 是用來處理加密解密的內容過長時, 用來分段加密的. 但請記住, 因爲RSA非對稱加密的效率問題, 不建議加密過長的內容, 能夠考慮採用對稱加密, 而後對於對稱加密的祕鑰, 使用RSA加密, 而後將密文和以前對稱加密的密文共同傳輸.KEY_SIZE = 512
, 而且採用RSA/ECB/PKCS1PADDING
協議加密, 則咱們的最大加密長度爲(KEY_SIZE >> 3) - 11
, 爲何減11呢? 由於RSA/ECB/PKCS1PADDING
是一種加密協議, 它爲了保證相同公鑰加密相同內容, 出現密文同樣, 因此在明文的中間部分, 加入了11位隨機的混淆碼.PKCS8
解密協議.關於PKCS
的信息, 能夠查看百度百科上面的代碼中, 已經寫了如何建立一對指定KEY_SIZE大小的祕鑰對, 咱們只須要將其存入MongoDB中, 並和Company關聯便可. 爲了實現每一個祕鑰對有本身的過時時間, 我想到了Redis的TTL, 慶幸MongoDB有相似的技術, 文章我就不抄了, 有興趣的朋友能夠查看這篇文章.
前端得到登陸token後, 對以後的每次請求, 都將如下面形式寫入Headers中,
{ "key":"Authorization", "value":"bearer 5bc58327c8f5e406a2b57394" }
後端驗證token是否過時, 以及該用戶token中所記錄的權限, 決定本次請求的合法性和返回內容.
以上就是咱們法伯科技的一個簡單的權限管理系統, 經過OAuth的特性, 能夠實現單點登陸, 利用token在Redis中存放用戶相關的角色和產品, 能夠決定用戶在登陸某一產品時, 是否有進入權限, 進入產品後, 決定能夠顯示哪些組件, 可使用哪些功能.