为什么要加密?
很多人可能都在使用nacos,但是在使用nacos的时候有没有留意到自己重要的密码都放到nacos配置中心管理了,觉得Jar包中没有数据库和各种中间件的密码,但是我今天告诉你一件细思极恐的事情,那就是你登录nacos的时候使用F12打开浏览器的开发者工具,你会发现登录接口/nacos/v1/auth/users/login,password居然是明文的????
是不是很不安全??虽然nacos大部分都是内网使用,但是安全团队那群人整天会说不能弱口令,安全第一。隔三差五给你来一波扫描,今天扫出来/nacos/v1/cs/ops/derby?sql=有安全漏洞,明天就说抓包发现你这个网址登录密码被我看到了。给你一两天时间要求整改
简直苦不堪言,茶饭不思,但是你看到了小明的这篇文章,瞬间活了过来,一顿CV,半天搞定~
如何加密?
首先要思考一个问题,造成password明文的地方是在F12网络看到的,那我们要修改的肯定是从前端代码入手,需要在输入密码后,将密码进行加密,然后后端拿到加密后的秘钥串构造token。
那么下一步当然是赶紧把源码下载下来,用idea打开,然后搜索/nacos/v1/auth/users/login定位代码。
package.json
#在console_fe目录执行
cd nacos-1.3.0\console\src\main\resources\static\console-fe
#添加crypto库
yarn add crypto
#安装前端依赖库
yarn
Login.jsx
import React from 'react';
import { Card, Form, Input, Message, ConfigProvider, Field } from '@alifd/next';
import { withRouter } from 'react-router-dom';
import './index.scss';
import Header from '../../layouts/Header';
import PropTypes from 'prop-types';
import { login } from '../../reducers/base';
// import CryptoJS from 'crypto-js';
//添加crypto库
const crypto = require('crypto');
function sha256Encrypt(password) {
// 创建SHA256哈希对象
const hash = crypto.createHash('sha256');
// 将要哈希的数据作为流传递给哈希对象
hash.update(password);
// 获取哈希结果
const hashValue = hash.digest('hex');
return hashValue.toString();
}
const FormItem = Form.Item;
@withRouter
@ConfigProvider.config
class Login extends React.Component {
static displayName = 'Login';
static propTypes = {
locale: PropTypes.object,
history: PropTypes.object,
};
constructor(props) {
super(props);
this.field = new Field(this);
}
componentDidMount() {
if (localStorage.getItem('token')) {
const [baseUrl] = location.href.split('#');
location.href = `${baseUrl}#/`;
}
}
handleSubmit = () => {
const { locale = {} } = this.props;
this.field.validate((errors, values) => {
if (errors) {
return;
}
//对密码进行SHA-256加密
values.password = sha256Encrypt(values.password);
login(values)
.then(res => {
localStorage.setItem('token', JSON.stringify(res));
this.props.history.push('/');
})
.catch(() => {
Message.error({
content: locale.invalidUsernameOrPassword,
});
});
});
};
onKeyDown = event => {
// 'keypress' event misbehaves on mobile so we track 'Enter' key via 'keydown' event
if (event.key === 'Enter') {
event.preventDefault();
event.stopPropagation();
this.handleSubmit();
}
};
render() {
const { locale = {} } = this.props;
return (
<div className="home-page">
<Header />
<section
className="top-section"
style={{
background: 'url(img/black_dot.png) repeat',
backgroundSize: '14px 14px',
}}
>
<div className="vertical-middle product-area">
<img className="product-logo" src="img/nacos.png" />
<p className="product-desc">
an easy-to-use dynamic service discovery, configuration and service management
platform for building cloud native applications
</p>
</div>
<div className="animation animation1" />
<div className="animation animation2" />
<div className="animation animation3" />
<div className="animation animation4" />
<div className="animation animation5" />
<Card className="login-panel" contentHeight="auto">
<div className="login-header">{locale.login}</div>
<Form className="login-form" field={this.field}>
<FormItem>
<Input
{...this.field.init('username', {
rules: [
{
required: true,
message: locale.usernameRequired,
},
],
})}
placeholder={locale.pleaseInputUsername}
onKeyDown={this.onKeyDown}
/>
</FormItem>
<FormItem>
<Input
htmlType="password"
placeholder={locale.pleaseInputPassword}
{...this.field.init('password', {
rules: [
{
required: true,
message: locale.passwordRequired,
},
],
})}
onKeyDown={this.onKeyDown}
/>
</FormItem>
<FormItem label=" ">
<Form.Submit onClick={this.handleSubmit}>{locale.submit}</Form.Submit>
</FormItem>
</Form>
</Card>
</section>
</div>
);
}
}
export default Login;
authority.js
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Message } from '@alifd/next';
import request from '../utils/request';
import { UPDATE_USER, SIGN_IN, USER_LIST, ROLE_LIST, PERMISSIONS_LIST } from '../constants';
const crypto = require('crypto');
function sha256Encrypt(password) {
// 创建SHA256哈希对象
const hash = crypto.createHash('sha256');
// 将要哈希的数据作为流传递给哈希对象
hash.update(password);
// 获取哈希结果
const hashValue = hash.digest('hex');
return hashValue.toString();
}
const initialState = {
users: {
totalCount: 0,
pageNumber: 1,
pagesAvailable: 1,
pageItems: [],
},
roles: {
totalCount: 0,
pageNumber: 1,
pagesAvailable: 1,
pageItems: [],
},
permissions: {
totalCount: 0,
pageNumber: 1,
pagesAvailable: 1,
pageItems: [],
},
};
const successMsg = res => {
if (res.code === 200) {
Message.success(res.message);
}
return res;
};
/**
* 用户列表
* @param {*} params
*/
const getUsers = params => dispatch =>
request.get('v1/auth/users', { params }).then(data => dispatch({ type: USER_LIST, data }));
/**
* 创建用户
* @param {*} param0
*/
const createUser = ([username, password]) => {
password = sha256Encrypt(password);
return request.post('v1/auth/users', { username, password }).then(res => successMsg(res));
};
/**
* 删除用户
* @param {*} username
*/
const deleteUser = username =>
request.delete('v1/auth/users', { params: { username } }).then(res => successMsg(res));
/**
* 重置密码
* @param {*} param0
*/
const passwordReset = ([username, newPassword]) => {
newPassword = sha256Encrypt(newPassword);
return request.put('v1/auth/users', { username, newPassword }).then(res => successMsg(res));
};
/**
* 角色列表
* @param {*} params
*/
const getRoles = params => dispatch =>
request.get('v1/auth/roles', { params }).then(data => dispatch({ type: ROLE_LIST, data }));
/**
* 创建角色
* @param {*} param0
*/
const createRole = ([role, username]) =>
request.post('v1/auth/roles', { role, username }).then(res => successMsg(res));
/**
* 删除角色
* @param {*} param0
*/
const deleteRole = role =>
request.delete('v1/auth/roles', { params: role }).then(res => successMsg(res));
/**
* 权限列表
* @param {*} params
*/
const getPermissions = params => dispatch =>
request
.get('v1/auth/permissions', { params })
.then(data => dispatch({ type: PERMISSIONS_LIST, data }));
/**
* 给角色添加权限
* @param {*} param0
*/
const createPermission = ([role, resource, action]) =>
request.post('v1/auth/permissions', { role, resource, action }).then(res => successMsg(res));
/**
* 删除权限
* @param {*} param0
*/
const deletePermission = permission =>
request.delete('v1/auth/permissions', { params: permission }).then(res => successMsg(res));
export default (state = initialState, action) => {
switch (action.type) {
case USER_LIST:
return { ...state, users: { ...action.data } };
case ROLE_LIST:
return { ...state, roles: { ...action.data } };
case PERMISSIONS_LIST:
return { ...state, permissions: { ...action.data } };
default:
return state;
}
};
export {
getUsers,
createUser,
deleteUser,
passwordReset,
getRoles,
createRole,
deleteRole,
getPermissions,
createPermission,
deletePermission,
};
如何安装部署
重新构建前端资源
#重新打包静态资源
yarn build
nacos打包
#将dist文件夹下静态资源拷到static目录
cp /nacos-1.3.0/console/src/main/resources/static/console-fe/dist/* /nacos-1.3.0/console/src/main/resources/static/console-fe/
#重新打包nacos
cd /nacos-1.3.0
mvn clean install -U -DskipTests=true
替换target中的nacos-server.jar
#拷贝targer下jar包覆盖原先的包
cp /nacos-1.3.0/distribution/target/nacos-server-1.3.0/nacos/target/nacos-server.jar /服务部署位置/nacos/target/
替换微服务中的密码
spring:
cloud:
nacos:
username: nacos
#更换密码
password: 569bf0af7a7562f31bbe4795656b6bdf307f7752163abc139157e3a3033b43ff
注意事项
线上是明文密码要提前更换成sha256加密后的密码,例如密码是nacos,提前通过sha256("nacos")得到569bf0af7a7562f31bbe4795656b6bdf307f7752163abc139157e3a3033b43ff,通过修改密码功能进行修改,然后再覆盖nacos-server.jar重启nacos完成升级。
Tips:如果你忘记了这一步,可以通过curl请求derby接口或直接操作数据库动态修改nacos密码
#password可以通过new BCryptPasswordEncoder().encode(sha256("nacos"))生成
#这里password是'nacos','$2a$10$mI4gqx8HCVxc3hozBgAM2uroyzZD7HSmxfQx9aCO4sUH0jjF7mce.'示例
#derby数据库可以通过下面curl进行修改
curl -X GET http://127.0.0.1:8848/nacos/v1/cs/ops/derby?sql=update users set password='$2a$10$mI4gqx8HCVxc3hozBgAM2uroyzZD7HSmxfQx9aCO4sUH0jjF7mce.' where username='nacos'