电子公文传输系统安全性设计与实现
一、系统概述
我们的电子公文传输系统一共由16个网页组成:
建议路径全英文,否则会出现错误。
1.login.jsp
login.jsp是公文传输系统的首页,这一部分主要以前端的设计为主,这里就不详细阐述了。最主要的是在这个页面要把用户输入的账号密码传入后端。
2.register.jsp
register.jsp是公文传输系统的注册界面,用户在login.jsp中点击注册后便跳转至该页面。用户在注册时需要选择自己注册的身份,并且注册完的用户必须要等待管理员审批,审批通过即可正常登录。
3.index.jsp、left.jsp、head.jsp
index.jsp是登陆的主界面,左边是left.jsp界面,有六个功能栏。每个角色仅能访问自己权限对应的功能,管理员仅能进行系统用户和学院部门的管理,院领导仅能进行院领导的审批功能,拟稿人可以进行公文管理,各个部门用户可以进行公文的审批和查看可下发的公文。head.jsp是顶上显示具体用户信息和提供修改密码跳转的界面。
对各个功能的访问控制如下:
相对应的角色type:
4.systemuser.jsp
systemuser.jsp对应着管理员身份才能访问的系统用户管理界面。在这里,管理员可以直接管理系统用户,也可以对注册的用户进行审核,审核通过后用户即可正常登录。
5.systemuser_add.jsp
systemuser_add.jsp就是相应的添加系统用户的界面。
6.dep.jsp
dep.jsp对应着管理员身份才能访问的学院部门管理界面。在这里,管理员可以直接管理学院的部门。
7.dep_add.jsp
dep_add.jsp就是相应的添加学院部门的界面。
8.document.jsp
document.jsp对应着拟稿人身份才能访问的公文管理管理界面。在该界面,拟稿人可以添加新的审批公文,并查看其他公文的状态。如公文审批未通过,需要删除后重新添加。
9.document_add.jsp
document_add.jsp就是相应的添加新的审批公文的界面。需要注意的是,一个公文写好后,需要指定该公文的审批部门和下发部门,只有当审批部门和院领导审批通过后,下发部门才会收到该公文。
10.audit1.jsp
audit1.jsp对应着部门成员身份才能访问的公文审批界面。在这里部门成员可以审批公文。
11.xiafa.jsp
xiafa.jsp对应着部门成员身份才能访问的可下发公文界面。在这里部门成员可以查看可以下发的公文。
12.document_show.jsp
document_show.jsp对应着部门成员身份才能访问的可下发公文界面。在这里部门成员可以查看可以下发的公文的具体内容。
13.password.jsp
password.jsp就是修改密码的界面。
14.audit.jsp
audit.jsp是院领导审批的界面,院领导可以在此对公文进行审批。
二、系统底层架构
1.访问控制
在后端设计中,我们经常会遇到账号密码不能为空、账号已存在的情况。为此,我们需要进行一定的控制措施。我们将这个措施封装到了admin.js中:
点击查看admin.js代码
//用户登录,js事件处理
//jQuery动态点击事件绑定
function login(path, closed) {
//#saveBtn绑定的是提交按钮
//on表示将事件绑定在body上,第一个参数click表示点击事件绑定到body对象,第二个表示冒泡到id=saveBtn则触发,第三个是触发的函数
$('body').on('click', "#saveBtn", function() {
var name = $("#name").val();// $("#name").val()是获取id为name绑定的值
var pwd = $("#pwd").val();
if (name == "" || pwd == "") {
alert("账号密码不能为空");
return false;
}
//jQuery AJAX 方法是一种与服务器交换数据的技术,可以在不重新载入整个页面的情况下更新网页的一部分。
//语法:$.ajax({name:value, name:value, ... })
$.ajax({
type : 'POST', //type 规定请求的类型(GET 或 POST)。
//path = /gwlzxt/admin/login.jsp
url : path + '/AdminLoginServlet?name=' + name + "&pwd=" + pwd,//url 规定发送请求的 URL。默认是当前页面。
success : function(data) {//success(result,status,xhr) 当请求成功时运行的函数。
if (data == "1") {
alert('用户名或密码错误或是未审核');
} else {
location.href = path+'/admin/index.jsp';
}
},
error : function(res) {//error(xhr,status,error) 如果请求失败要运行的函数。
},
});
});
$('body').on('click', "#resetBtn", function() {
$("#name").val("");
$("#pwd").val("");
});
}
// 修改密码
function editpass(path) {
$('#showModal').modal({
remote : path + '/admin/password.jsp',
backdrop : 'static', // 点击空白不关闭
keyboard : false,
});
$("#showModal").on("hidden.bs.modal", function() {
// 这个#showModal是模态框的id
$(this).removeData("bs.modal");
$(this).find(".modal-content").children().remove();
});
$('body').on('click', "#saveBtn1", function() {
var newpwd = $("#newpwd").val();
var repeatpwd = $("#repeatpwd").val();
if (newpwd == "" || repeatpwd == "") {
alert("密码不能为空");
return false;
}
if(newpwd!=repeatpwd){
alert("两次密码不一致");
return false;
}
$.ajax({
type : 'POST',
url : path + '/PwdUpdateServlet?newpwd=' + newpwd + "&repeatpwd=" + repeatpwd,
success : function(data) {
alert('修改成功');
location.reload();
},
error : function(res) {
},
});
});
$('body').on('click', "#resetBtn1", function() {
$("#newpwd").val("");
$("#repeatpwd").val("");
});
}
// 添加管理员
function adminAdd(path) {
$("body").append(
"<div id='dlg_systemuser_add' style='padding:20px;'></div>");
$('#dlg_systemuser_add')
.dialog(
{
href : path + '/admin/systemuser_add.jsp',
modal : true,
closed : false,
title : '添加系统用户',
width : 400,
height : 300,
buttons : [
{
text : '提交',
iconCls : 'icon-ok',
handler : function() {
$('#form_systemuser_add')
.form(
'submit',
{
url : path
+ '/AdminAddServlet',
onSubmit : function() {
return $(this)
.form(
'validate');
},
success : function(
data) {
if (data == "-1") {
$.messager
.alert(
'系统消息',
'用户名已存在',
'error');
} else {
$.messager
.alert(
'系统消息',
'添加成功',
'info',
function() {
$(
'#dlg_login')
.dialog(
'refresh');
$(
'#dlg_login')
.dialog(
'close');
location.href = path
+ '/admin/systemuser.jsp';
},
false);
}
}
});
}
},
{
text : '重置',
iconCls : 'icon-reload',
handler : function() {
$('#dlg_systemuser_add').dialog(
'refresh');
}
} ]
});
}
// 用户注销
function logout(path) {
$.ajax({
type : 'POST',
url : path + '/RemoveServlet',
data : 'mark=admin',
success : function(msg) {
window.location.href = path + '/admin/login.jsp';
} });
}
// 解析JSON
function parseJson(text) {
// extract JSON string
var match;
if ((match = /\{[\s\S]*\}|\[[\s\S]*\]/.exec(text))) {
text = match[0];
}
var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
cx.lastIndex = 0;
if (cx.test(text)) {
text = text.replace(cx, function(a) {
return '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
});
}
if (/^[\],:{}\s]*$/.test(text.replace(
/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').replace(
/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,
']').replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
return eval('(' + text + ')');
}
throw 'JSON parse error';
}
// 上传文件
function upload(path) {
$("body").append("<div id='dlg_upload' style='padding:20px;'></div>");
$('#dlg_upload')
.dialog(
{
href : path + '/admin/upload_file.jsp',
modal : true,
closed : false,
title : '上传文件',
width : 450,
height : 150,
closable : true,
buttons : [
{
text : '上传',
iconCls : 'icon-ok',
handler : function() {
$('#upload_file')
.form(
'submit',
{
url : path
+ '/admin/upload_json.jsp',
onSubmit : function() {
return $(this)
.form(
'validate');
},
success : function(
data) {
var json = parseJson(data);
if (json.error === 1) {
$.messager
.alert(
'系统消息',
json.message,
'error');
} else {
// window.navigate(path+'/index.jsp');
$.messager
.alert(
'系统消息',
'上传成功',
'info',
function() {
$(
'#dlg_upload')
.dialog(
'refresh');
$(
'#dlg_upload')
.dialog(
'close');
$(
'#paths')
.val(
json.url);
},
false);
}
}
});
}
}, {
text : '重置',
iconCls : 'icon-reload',
handler : function() {
$('#dlg_upload').dialog('refresh');
}
} ]
});
}
2.数据库操作相关方法
首先在SqlHelper中,我们封装了若干种方法,每一个方法都能返回数据库中某个表的所有值。因此我们如果想要进行库表操作时,使用该类内数据库表相对应的方法即可生成对应sql语句:
在DBHelper中,封装了执行sql查询语句后获取返回的结果集的方法,如果想要获取sql语句执行后返回的结果集,使用该类内相对应的方法即可。
在CRUDHelper中,封装了执行sql语句后获取返回值的方法,如果想要获取sql语句执行后返回的结果集,使用该类内相对应的方法即可。
需要注意的是,executeQuery() 用于执行查询语句,并返回一个 ResultSet 对象,可以通过这个对象来处理查询结果集。executeUpdate() 用于执行 INSERT、UPDATE、DELETE 等操作,返回一个整数值,表示受影响的行数。如果需要获取结果集,可以使用 ResultSet 对象处理。将数据从 ResultSet 中提取出来,并进行相应的处理和操作。
在SQLConnection中,定义了数据库的驱动和连接方法,通过该方法我们可以成功的连接到数据库:
3.功能操作相关方法
CheckerController类封装了部门审批方法,部门在审批公文时调用该方法即可。
DeleteController类封装了删除方法,管理员用户的学院部门管理的删除模块、管理员用户的系统用户管理的删除模块、拟稿人用户的公文管理的删除模块调用该方法即可。
DepartmentController类封装了添加部门方法,管理员添加部门时调用该方法即可。
FileController类封装了添加公文方法和更新检查表方法,拟稿人在添加公文时调用该方法即可。
InsertAdminController类封装了添加用户方法,需要添加用户时直接调用该方法即可
LeaderController类封装了院领导审批方法,需要院领导审批时直接调用该方法即可。
LoginController类封装了用户登录方法,用户登录时直接调用该方法即可。
PasswordController类封装了更新密码方法,更新密码时直接调用该方法即可。
RemoveController类封装了退出方法,退出时直接调用该方法即可。
UpdateController类封装了更新用户状态方法,更新用户状态方法时直接调用该方法即可。
3.安全性措施
- 加密算法的选择
为保证密码方案的安全性,应该使用符合国家密码管理局规定的加密算法。参考了《GMT 0007 2012 电子政务电子认证服务应用指南》、GMT 0054-2018 《信息系统密码应用基本要求》和国家标准GB/T 39786-2021《信息安全技术 信息系统密码应用基本要求》等相关标准,我们采用SM3和SM4进行数据加密。
在InsertAdminController类中,我们增加角色时需要将口令进行加密处理。由于密码长度通常不会很长,因此我们采用了SM3算法,对用户输入的口令进行哈希运算并存入数据库中。
在FileController类中,我们新建公文时需要将公文进行加密处理。由于公文长度通常不会很长,因此我们采用了SM4算法,对用户输入的口令进行加密并存入数据库中。
- 密码长度和复杂度
为防止破解,密码的长度和复杂度至关重要。密码长度越长、复杂度越高,则被破解的难度就越大,安全性也就越高。因此,建议设置较长、包括大小写字母、数字和特殊符号的复杂密码,并采取强制修改密码策略和不同用户不同密码的策略。
SM3 算法是国家密码管理局发布的一种哈希算法,其输出固定为 256 位(32 字节),长度固定,安全性高。在SM4加密过程中,使用了一个长度为 16 字节(128 位)的密钥。密钥的长度对 SM4 算法的安全性有直接影响。一般而言,密钥长度越长,破解难度越大,但同时也会带来更高的计算和存储成本。因此,在实际应用中,需要根据具体场景进行权衡和选择。
- 密码存储和传输
为了保证密码的安全性,应该采用不可逆加密的方式存储密码。同时,在密码传输过程中,也需要采用加密协议,我们使用GMSSL安全传输协议对传输进行保护。
三、实验感想
经过本次实验,我基本熟悉了web开发的流程,可以和团队一起开发web项目,基本上实现了数据的安全传输,可以通过国密算法保护用户数据的完整性、机密性、不可否认性,通过GMSSL保护传输过程,可以开发一个产品使其基本上又好用、又安全,了解了配置服务器的方法,申请域名的方法、申请国密SSL证书的方法。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具