法伯科技是一家以数据科技为驱动, 专一于医药健康领域的循证咨询公司. 以数据科学家身份, 赋能医药行业. 让每位客户都能享受数据带来的价值, 洞察业务, 不止于数据, 让决策更精彩。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中存放用户相关的角色和产品, 能够决定用户在登陆某一产品时, 是否有进入权限, 进入产品后, 决定能够显示哪些组件, 可使用哪些功能.