RSA加密登录时账号和密码
This commit is contained in:
parent
612c4293d1
commit
94c81296d0
@ -1,7 +1,15 @@
|
||||
package com.ruoyi.web.controller.system;
|
||||
|
||||
import java.security.*;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.ruoyi.common.annotation.DecryptLogin;
|
||||
import com.ruoyi.common.annotation.RateLimiter;
|
||||
import com.ruoyi.common.core.redis.RedisCache;
|
||||
import com.ruoyi.common.enums.LimitType;
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
@ -34,12 +42,33 @@ public class SysLoginController
|
||||
@Autowired
|
||||
private SysPermissionService permissionService;
|
||||
|
||||
/**
|
||||
* 登录前先生成一个RSA密钥,LoginBody中的账号密码进行加密。
|
||||
* 公钥返回给前端加密使用;
|
||||
* 私钥存入redis,等待登陆时解密使用。
|
||||
*
|
||||
* 这个接口需要加入白名单,所以使用限流器 RateLimiter 对IP限制每秒请求次数
|
||||
* @return public key
|
||||
*/
|
||||
@RateLimiter(time = 1, count = 5, limitType = LimitType.IP)
|
||||
@GetMapping("/preLogin")
|
||||
public AjaxResult preLogin() {
|
||||
String publicKey;
|
||||
try {
|
||||
publicKey = loginService.generateRSA();
|
||||
} catch (NoSuchAlgorithmException | NoSuchProviderException e) {
|
||||
return AjaxResult.error("生成RSA密钥对失败");
|
||||
}
|
||||
return AjaxResult.success("", publicKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录方法
|
||||
*
|
||||
* @param loginBody 登录信息
|
||||
* @return 结果
|
||||
*/
|
||||
@DecryptLogin
|
||||
@PostMapping("/login")
|
||||
public AjaxResult login(@RequestBody LoginBody loginBody)
|
||||
{
|
||||
|
@ -0,0 +1,16 @@
|
||||
package com.ruoyi.common.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* RSA解密注解
|
||||
* 使用该注解,可以对已经加密的参数进行解密
|
||||
* @author wrw
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface DecryptLogin {
|
||||
}
|
@ -39,6 +39,11 @@ public class Constants
|
||||
*/
|
||||
public static final String FAIL = "1";
|
||||
|
||||
/**
|
||||
* 预登录 redis key
|
||||
*/
|
||||
public static final String PRE_LOGIN_KEY = "pre_login_key:";
|
||||
|
||||
/**
|
||||
* 登录成功
|
||||
*/
|
||||
|
@ -27,6 +27,11 @@ public class LoginBody
|
||||
*/
|
||||
private String uuid = "";
|
||||
|
||||
/**
|
||||
* RSA公钥
|
||||
*/
|
||||
private String publicKey;
|
||||
|
||||
public String getUsername()
|
||||
{
|
||||
return username;
|
||||
@ -66,4 +71,8 @@ public class LoginBody
|
||||
{
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
public String getPublicKey() {
|
||||
return publicKey;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,61 @@
|
||||
package com.ruoyi.framework.aspectj;
|
||||
|
||||
import com.ruoyi.common.annotation.DecryptLogin;
|
||||
import com.ruoyi.common.constant.Constants;
|
||||
import com.ruoyi.common.core.domain.model.LoginBody;
|
||||
import com.ruoyi.common.core.redis.RedisCache;
|
||||
import com.ruoyi.common.exception.user.UserException;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Before;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
|
||||
/**
|
||||
* RSA解密
|
||||
*
|
||||
* @author wrw
|
||||
*/
|
||||
@Aspect
|
||||
@Component
|
||||
public class DecryptParameters {
|
||||
|
||||
@Autowired
|
||||
private RedisCache redisCache;
|
||||
|
||||
@Before("@annotation(decrypt)")
|
||||
public void doBefore(JoinPoint point, DecryptLogin decrypt) throws IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, NoSuchPaddingException {
|
||||
LoginBody pointArg = (LoginBody) point.getArgs()[0];
|
||||
//从缓存里获取私钥
|
||||
String loginPrivateKey = redisCache.getCacheObject(Constants.PRE_LOGIN_KEY + pointArg.getPublicKey());
|
||||
if (StringUtils.isEmpty(loginPrivateKey)) {
|
||||
throw new UserException("RSA密钥对已过期!", point.getArgs());
|
||||
}
|
||||
//初始化解密密钥
|
||||
PKCS8EncodedKeySpec pkcs8EncodedKeySpec5 = new PKCS8EncodedKeySpec(Base64.decodeBase64(loginPrivateKey));
|
||||
//创建密钥工厂
|
||||
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
|
||||
//解析密钥
|
||||
PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec5);
|
||||
Cipher cipher = Cipher.getInstance("RSA");
|
||||
cipher.init(Cipher.DECRYPT_MODE, privateKey);
|
||||
//解密
|
||||
byte[] username = cipher.doFinal(Base64.decodeBase64(pointArg.getUsername()));
|
||||
byte[] password = cipher.doFinal(Base64.decodeBase64(pointArg.getPassword()));
|
||||
pointArg.setUsername(new String(username));
|
||||
pointArg.setPassword(new String(password));
|
||||
}
|
||||
}
|
@ -97,7 +97,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter
|
||||
// 过滤请求
|
||||
.authorizeRequests()
|
||||
// 对于登录login 注册register 验证码captchaImage 允许匿名访问
|
||||
.antMatchers("/login", "/register", "/captchaImage").anonymous()
|
||||
.antMatchers("/preLogin", "/login", "/register", "/captchaImage").anonymous()
|
||||
.antMatchers(
|
||||
HttpMethod.GET,
|
||||
"/",
|
||||
|
@ -1,6 +1,8 @@
|
||||
package com.ruoyi.framework.web.service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
@ -24,6 +26,9 @@ import com.ruoyi.framework.manager.factory.AsyncFactory;
|
||||
import com.ruoyi.system.service.ISysConfigService;
|
||||
import com.ruoyi.system.service.ISysUserService;
|
||||
|
||||
import java.security.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 登录校验方法
|
||||
*
|
||||
@ -130,4 +135,25 @@ public class SysLoginService
|
||||
sysUser.setLoginDate(DateUtils.getNowDate());
|
||||
userService.updateUserProfile(sysUser);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成RSA密钥对
|
||||
* @return 公钥
|
||||
*/
|
||||
public String generateRSA() throws NoSuchAlgorithmException, NoSuchProviderException {
|
||||
KeyPairGenerator gen = KeyPairGenerator.getInstance("RSA", "SunRsaSign");
|
||||
gen.initialize(512, new SecureRandom());
|
||||
KeyPair pair = gen.generateKeyPair();
|
||||
|
||||
byte[] privateKey = pair.getPrivate().getEncoded();
|
||||
byte[] publicKey = pair.getPublic().getEncoded();
|
||||
|
||||
privateKey = Base64.encodeBase64(privateKey);
|
||||
publicKey = Base64.encodeBase64(publicKey);
|
||||
|
||||
//私钥存入缓存
|
||||
redisCache.setCacheObject(Constants.PRE_LOGIN_KEY + new String(publicKey), new String(privateKey), 30, TimeUnit.SECONDS);
|
||||
//返回公钥
|
||||
return new String(publicKey);
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,28 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 预登陆,获取RSA密钥
|
||||
export function preLogin() {
|
||||
return new Promise(resolve => {
|
||||
request({
|
||||
url: '/preLogin',
|
||||
headers: {
|
||||
isToken: false
|
||||
},
|
||||
method: 'get'
|
||||
}).then(res =>{
|
||||
resolve(res.data)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 登录方法
|
||||
export function login(username, password, code, uuid) {
|
||||
export function login(username, password, code, uuid, publicKey) {
|
||||
const data = {
|
||||
username,
|
||||
password,
|
||||
code,
|
||||
uuid
|
||||
uuid,
|
||||
publicKey
|
||||
}
|
||||
return request({
|
||||
url: '/login',
|
||||
|
@ -35,8 +35,9 @@ const user = {
|
||||
const password = userInfo.password
|
||||
const code = userInfo.code
|
||||
const uuid = userInfo.uuid
|
||||
const publicKey = userInfo.publicKey
|
||||
return new Promise((resolve, reject) => {
|
||||
login(username, password, code, uuid).then(res => {
|
||||
login(username, password, code, uuid, publicKey).then(res => {
|
||||
setToken(res.token)
|
||||
commit('SET_TOKEN', res.token)
|
||||
resolve()
|
||||
|
@ -28,3 +28,15 @@ export function decrypt(txt) {
|
||||
return encryptor.decrypt(txt) // 对数据进行解密
|
||||
}
|
||||
|
||||
/**
|
||||
* 对登录的用户名密码加密
|
||||
* @param loginPublicKey
|
||||
* @param txt 待加密文本
|
||||
* @returns {Promise<ArrayBuffer>}
|
||||
*/
|
||||
export function encryptLogin(loginPublicKey, txt) {
|
||||
const encrypt = new JSEncrypt();
|
||||
encrypt.setPublicKey(loginPublicKey);
|
||||
return encrypt.encrypt(txt);
|
||||
}
|
||||
|
||||
|
@ -62,9 +62,10 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { preLogin } from "@/api/login";
|
||||
import { getCodeImg } from "@/api/login";
|
||||
import Cookies from "js-cookie";
|
||||
import { encrypt, decrypt } from '@/utils/jsencrypt'
|
||||
import { encrypt, decrypt, encryptLogin } from '@/utils/jsencrypt'
|
||||
|
||||
export default {
|
||||
name: "Login",
|
||||
@ -127,7 +128,8 @@ export default {
|
||||
rememberMe: rememberMe === undefined ? false : Boolean(rememberMe)
|
||||
};
|
||||
},
|
||||
handleLogin() {
|
||||
async handleLogin() {
|
||||
const login_public_key = await preLogin();
|
||||
this.$refs.loginForm.validate(valid => {
|
||||
if (valid) {
|
||||
this.loading = true;
|
||||
@ -140,7 +142,15 @@ export default {
|
||||
Cookies.remove("password");
|
||||
Cookies.remove('rememberMe');
|
||||
}
|
||||
this.$store.dispatch("Login", this.loginForm).then(() => {
|
||||
let authLoginForm = {};
|
||||
// encrypt 加密
|
||||
authLoginForm.username = encryptLogin(login_public_key, this.loginForm.username);
|
||||
authLoginForm.password = encryptLogin(login_public_key, this.loginForm.password);
|
||||
authLoginForm.code = this.loginForm.code;
|
||||
authLoginForm.uuid = this.loginForm.uuid;
|
||||
authLoginForm.publicKey = login_public_key;
|
||||
console.log(authLoginForm);
|
||||
this.$store.dispatch("Login", authLoginForm).then(() => {
|
||||
this.$router.push({ path: this.redirect || "/" }).catch(()=>{});
|
||||
}).catch(() => {
|
||||
this.loading = false;
|
||||
|
Loading…
x
Reference in New Issue
Block a user