1.前端获取SM2公钥
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| mounted() { this.axios.get('/getPublicKey').then((resp) =>{ let data = resp.data; if(data.code==200){ sessionStorage.setItem('publicKey', data.data.publicKey); } else{ this.$message({ message: "获取公钥失败", type:'error' }); } }).catch(error => { }); }
|
1 2 3 4
| @RequestMapping("/getPublicKey") public ResponseResult getPublicKey(){ return secretKeyService.getPublicKey(); }
|
`com/gaomu/server/impl/SecretKeyServiceImpl.java`
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @Service public class SecretKeyServiceImpl implements SecretKeyService {
@Value("${secretKey.publicKey}") private String publicKey;
@Override public ResponseResult getPublicKey() { System.out.println("publicKey :" + publicKey ); Map<String, String > map = new HashMap<>(); map.put("publicKey", publicKey); return new ResponseResult(200, "获取成功", map); } }
|
1 2
| secretKey: publicKey: 04ac65e37746267acd937a6532be574cf48e678f33f535b045baa92c3533892f8d66bff57cad3fe8e37a61a2693ca49c21c933c57b211e04bf104dc4a5d46aa3dc
|
2.前端生成SM4密钥
1 2 3
| SM4Data = generateSM4KeyPair(SM4Data) sessionStorage.setItem('secretKey', SM4Data.key) sessionStorage.setItem('iv', SM4Data.iv)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| let SM4Data = { key: '', iv: '', originalData: '', encryptedData: '', decryptedData: '' }
function generateSM4KeyPair (SM4Data) { let SM2Pair = SM2.generateKeyPair() SM4Data.key = SM2Pair.privateKey.substring(0, 32) SM4Data.iv = SM2Pair.privateKey.substring(32, 64) return SM4Data }
|
3.前端用户认证
- 将用户在页面中输入的用户名和密码存入表单
- 添加一个tempPassword为了暂存用户输入的密码,如果直接双向绑定表单中的password,当password加密后页面中的密码字符会变得特别长,不友好
src\views\Login.vue
1 2 3 4 5 6
| <el-form-item label=""> <el-input type="text" v-model="loginForm.userName" autocomplete="off" placeholder="账号"></el-input> </el-form-item> <el-form-item label=""> <el-input type="password" v-model="tempPassword" autocomplete="off" placeholder="密码"></el-input> </el-form-item>
|
1 2 3 4 5 6 7 8 9 10 11
| data() { return { loginForm: { userName: '', password: '', secretKey: '', iv: '' }, tempPassword: '' } },
|
4.前端使用SM2公钥加密
- 前端使用SM2公钥加密(password、key、iv)
src\views\Login.vue
1 2 3
| this.loginForm.secretKey = encryptSM2(SM4Data.key, publicKey) this.loginForm.iv = encryptSM2(SM4Data.iv, publicKey) this.loginForm.password = encryptSM2(this.loginForm.password, publicKey)
|
发送登陆请求包
src\views\Login.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| this.axios.post('/user/login', this.loginForm).then((resp) =>{ let data = resp.data; if(data.code==200){ this.loginForm = data.data; sessionStorage.setItem('token', data.data.token) console.log(data.data) this.$message({ message: '登陆成功', type:'success' }); this.$router.push({path:'/Home'}) } else{ sessionStorage.removeItem('secretKey') sessionStorage.removeItem('iv') this.$message({ message: '登陆失败', type:'error' }); } }).catch(error => { });
|
5.后端使用SM2私钥解密
1 2 3 4 5 6 7 8 9
| @Autowired private LoginService loginService;
@PostMapping("/user/login") public ResponseResult login(@RequestBody User user){ System.out.println(user); return loginService.login(user); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| @Service public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired private UserMapper userMapper;
@Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<User>(); queryWrapper.eq(User::getUserName, username); User user = userMapper.selectOne(queryWrapper); if(Objects.isNull(user)){ throw new RuntimeException("用户名或密码错误!"); } return new LoginUser(user); } }
|
1 2
| @Value("${secretKey.privateKey}") private String privateKey;
|
1 2 3
| user.setPassword(SM2Util.sm2Decrypt(user.getPassword(), privateKey)); user.setSecretKey(SM2Util.sm2Decrypt(user.getSecretKey(), privateKey)); user.setIv(SM2Util.sm2Decrypt(user.getIv(), privateKey));
|
6.将key、iv存入loginUser并存入redis
1 2 3 4 5 6 7
| UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserName(), user.getPassword()); Authentication authenticate = authenticationManager.authenticate(authenticationToken);
if(Objects.isNull(authenticate)){ throw new RuntimeException("authenticate 为空,登陆失败!"); }
|
1 2 3 4 5 6
| LoginUser loginUser = (LoginUser) authenticate.getPrincipal(); loginUser.getUser().setSecretKey(user.getSecretKey()); loginUser.getUser().setIv(user.getIv()); String loginUserKey = JwtUtil.getUUID(); String userid = loginUser.getUser().getId().toString();
|
7.生成JWT
- 使用JWT工具类生成jwt token,并将token封装返回前端😶
com/gaomu/server/impl/LoginServiceImpl.java
1 2 3 4 5 6 7
| String jwt = JwtUtil.createJWT(loginUserKey, userid, null); Map<String, String > map = new HashMap<>(); map.put("token", jwt);
redisCache.setCacheObject("LOGIN_USER_KEY:"+loginUserKey, loginUser); return new ResponseResult(200, "登陆成功", map);
|
8.前端存储token
1 2 3 4 5 6 7 8
| if(data.code==200){ this.loginForm = data.data; sessionStorage.setItem('token', data.data.token) console.log(data.data) this.$message({ message: '登陆成功', type:'success' });
|
至此密钥交换完成,前后端就拥有了共同的SM4公钥,这样前后端就能愉快的加密业务数据了。
项目前后端均有生产SM2密钥对的方法
后端:com/gaomu/utils/crypto/SM2Util.java/generateECSM2HexKey()
前端:src\utils\encrypto\sm2.js\SM2.generateKeyPair()