前言
JFinal-layui 极速开发企业应用管理系统,是以 JFinal+layui 为核心的企业应用项目架构,利用 JFinal 的特性与 layui 完美结合,达到快速启动项目的目的。让开发更简单高效,C , | 8 J即使- = Y你不会前端layui1 W R O y p m O Q,也能轻松掌握使用。
JFinal-layui v1.4.2 新增XSS、CSRF防御和代码生成器,加强web安全和提升开发效率!在工作中发现,一些公司在给客户开发系统的时候,都很容易忽略了w5 v 3 { I ! w Jeb安全的内容,A | r - P U ! w或者根本不考虑web安全漏洞,所以这样开发出来的系. e ] j 0 统本身就是存在很大的安全f ` i a X 6 N b隐患,只要稍微有点技术的人员就能进行XSS攻击和CSRF跨站请求伪造!因为本人有过一段难忘的web安全漏洞修复的经历,所以把积累的一些经验应用到JFinal-layui中,让系统更加安全可靠!
v1.4.2更新内容详情:
一、XSS攻击防御
JFinal-layui主要是对XSS的存储型攻击进行防御,把用户输入的数据都进行XSS过! Q p K 5 & V k滤,避免对系统造成不良影响。利用J- + W aFInal的Handler来实现,重写Htt! 2 j MpServletRequestWrapper非常简单。
1、自定义Xs+ a !sHttpServletRequestWrapper类,重写getParameter:
/**
* xss过滤处理
* @author L | C;QinHaiLin
&nbf M o 1 p B [ n gsp;* @date&nbsO O R / P Gp;2020-02-1W M U [3
*l g $ x ~ Z/
pw v Vublic class XssHttpServlg ] _ ] s S uetRequestWrapper extends HttpServletRequestWrapper{
&nbsf $ ? e m | i p;&nbsF ~ 6 C wp; public XssHttpServletRequestWrapp2 q F } y - l q Cer(HttpServletRequest request) {
&3 k ~ H ` 0nbsp; super(request);
 / l T l p 0; }
Q 5 6 H | l U | ` /**
* 重写并过滤getParameter方法
*/
@Override
public Strit U v { =ng getParameter(String namX # N r I g *e) {
 6 % [ = T ` 6 b t; return getBasicC l { s 9 & q AHtmlandimage(suV 4 H gper.getP! ^ H U m ]arameter(name));
&nr ` nbsp;&nbs? C x 5 $ - Up;
&% L E c # 0 2nbsp; }
/**
&nb$ r ,sp; * 重写并过滤getParameterValues方法
*/
@Override
 k O c r - %;pub` E # Blic&n& A e Y ( L Rbsp. - t x J P;String[] getParameterValues(String name) {
&nbsb @ f U B 4p;&nbh a ! ? 3 9sp; String[]&n. p C ? Z bbsp;values = super.getParameterValues(name);
if&nbs; % l + A K wp;(null == values){
 h m k 1 d w x W; &nb@ 7 v l J ) sp; return null;
 ? C R R b p 5 m w;&n| E w obsp; &nf L d U d e } ebsp; }
for (int i = 0; i < values.length0 $ 4; i++) {
&nf | T # j ] 8 _bsp; i - l , / [ ; ; &nbs? S r Ip; values[i] = getBasicHtmlandimage(value@ Q E K I ( *s[i]);
 c # H e 6 I;&] = q s D 0 h }nbsp; &nR ) y 8bsp; }
&nb/ v b c $ s s A ?sp; return&nbb | 0 @ T T Jsp;values;
}
&n~ w rbsp;
/**
* 重写并过滤getPaY C xrameterMap方法
*/
@Override
q } * X pu: ~ A l c +blG G % 6 eicR C } u & 0 N Map<String,String[]> getPars ^ 3ameterMap() {
Map<String,String[]> paraMap = super.getParameterMap();
&nbsI 8 L ] bp;// 对于paraMap为空的直接return
&nbsV K r c * a lp; if (null == paraMap || pa0 q d S xraMap.isEmpty()) {
return&nb. { n t e 4 } usp;paraMap;
 A ) | a V / [;&nbsm V c Yp; }
&nbs_ i Q v g dp;
&nbsw K Op; , | n //super.getPT ` s / @ 3 ( z DarameterMap()不y t O ^ | c允许任何修改,所以只能做深拷贝
&nj q X E y f c bsp; | f 6 b d ^ W Map<String, String[]> p] K g % l _ q l *araMapCopy = new HashMap<String, String[]>();
 _ X % * i L E;//实际上putAll只对基本类型深拷贝有效,如果是自定义类型,则要找其他办法
&n1 : 2 - m ! o n Cbsp; 9 1 [ ; f = r z D G t d i d O K R paraMapCopy.putAll(paraMap);
&n8 ] 8 t _ N F e 1bsp;
&nba I * i / , * Msp;&ni d Y v F ) Z *bsp;  m ( Q;&n_ Y dbsp; fR y - M O s 4or (Map.Entry<String,&n) u M ) (bsp;St* S 8 @ 8 I ) t )ring[]> entry : q G -paraMapCopy.entrySet()) {
7 6 * z ( r z K &nbs% # C E t x t hp;&nbs# A _ Ip; ST D x L { U ytring[] i # t * ; ? 4 W [;values &ns , . y ! ybsp; ] m G G 4 I x; = entry.getValue();
&nbs3 c V 5 ] { vp; if (null == val? & 4ues) {
&f 0 R L Enbsp; &u O !nbsp;&{ O | 3nbsp; continue;
&nba z # Bsp; l - b 8 J 9 }
&nb7 Q ~ #sp; &n] g Gbsp; String[] newValues = ne0 G 1 V Xw String[values.. V 4 } L ; + [length];
 ( 8 ] j Q s; &nbsf 9 l S I i op; for (int i = 0; i < values.length; i++) {
 k A 8 T;  c R ~ 8 4; newValues[i] = C ( sgetBasicHtmlandimage(values[i]);
&nbf | y S e Xsp; }
 j G ! . K . $ # ,; &nbs! % ! . m T 1p; &nl g 6 ] G T v % ibsp; entry.setValue(newValues);
}
&nbse L z I ? q * Hp; &nb^ f ] @ W * B 7sp;^ P ureturn par# l l A : @ +aMapCopy;
&nbsD 6 3p; }
 u w * 5 c ; private static StK % qring getBasicHtmlandimage(String html)b h p i _ x B {
h p ` f # Y , 6 } if (html == null)
rx A 6eturn&n| 0 e bsp;null;
return| 1 G k H H J ~ {&nbsi Y 9 g 1 j - .p;Jso& u ^ o K a {up.cleM A } w p 7 aan(html, Whitelist.basicWithImages());
}
}
2、新建XssHandl] % ; t { C U Der拦截器:
/**
* xsx u !s拦截j J x u器
* @author QinHaiL{ ) p s j cin
*
&n} 6 J | 2 ^bsp;*/
public clas} s : } m {s XssHan6 ] 8 z z | Q k Edler extends HandlerB e ^ s - Y ^ N 8 {
// 排除的url,使用的target.startsWith匹配的
private String excludePattern;
/**
* 忽略列表,使用正则排除url
* @param&nbh p e H R ! Dsp;exclude
*/
public XssHax ( ! u y sndler(String excludePattern) {
&s % unbsp; &n8 K p t Pbsp;&] ? B g !nbsp;this.excludePattern/ I t w Z & ? : v # p N m= excludePattern;
&nbs| ` U 7 Lp; }
@Oveq ] O ` * o ~ )rride
public void handle(String target,&nbs0 + ; x N { ) ?p;HttpServletRequest request, u m 4 / h B s b l;Htk g L w 1 i : D ;tpServleJ G Z d s , q M LtResponK ; w ise respox H H D e { S v anse,&nbsB ? ` | H d ^ mp;boolean[] isHandled) {
java.util.regex.Pattern&X _ V W y y anbsp;pattern = Pattern.compile(excludePattern);
//带.表2 W g示非action请求,忽略(其实不太严谨,如果是伪静态,比如.html会被错误地排除);b { ] /匹配excludePattern的,忽略
&nbl , h 1 9 /sp;8 j F l Iif&nbsd { x T @ i 9 } Vp;(tr : Y rarget.indexOf(\".\") == -1 &: G $ l& !(!StriE o L N , / F l RngUtil.isBlank(excludePattern) &; t ^ f i& pattern.matcher(target).find() ) ){
_ Y c L j o f ; o request = new Xj 2 m GssHttpServletRequestWrapper(request);
&4 ( Unbsp; }
&n) v 2 q } 2 U TbspB Y 5 l T v B 9; next.handle(targe~ # X ! 6t, m T x K ) Q $ C;requeV = Vst, response, isHandled);
 8 l w 9 V r 9 u; &ng ) S Z I ibsp;
}
}
3、在MaG - X E k 2inConfig里面配置XssHandler拦截器,那么对XSS的存储型攻击就可以进行有效的防御了。
/**
* 配置全局处理器
*/
@Override
public void configHandler(Handlers me) {
/** 配置druid监控&nbn r k usp;**/
me.add(DruidKit.getDruidStatViewHandler())( i , ! , 1 v;
// 路由处c z M K B s e理
me.add(new CommonHandler());
// XL 5 * 4SS过滤
me.add(new XssHandler(\"^\\\\/portal/form/vi{ } ` ] q +ew.*\"));
}
二、CSRF跨站请求伪造防御
针对CSRF跨站请求伪造,JFinal-layui主要是对添加、修改的业务表单加入token验证机制,这样就可以解决重要业务操作的安= H X / D k全性。我们的拦截是全局验证拦截,在? t | R开发功能过程中就是顺手做的事2 @ o 9情,简单高效。
1、利用JFu 2 0 O 4 (inal0 v }的token机制,创建自己的TokenService:
/**
&e Q V T 0 K 0 ~ $nbsp;*&nR z Dbsp;token
* @authc A v z ~ 0 $ Mor QinHaiLin
* @date 2020-02-14
*/
public class&n( : . ? {bsp;TokenService&nb[ a a 7 o C Qsp;{
/**
* 创建token
*&nbY M B 6 K - / msp;@param c
*/
public vo( n #id createToken(Controller&nby @ ~ ` Vsp;o B P : { t @ M Cc){
TokenManager.createToken(c, Const.^ = s { T A 5 9 zDEFAULT_TOKEN_NAME, Const.DEM K QFAULT_SECx bONDS_OF_TOKEN_TIME_OUT);
}
/**
* 验证tokD T j t A len
* @param c
*/
public boolean validateToken(Co% 8 ! c 6ntroller c){
return TokenManager.validateToken(c, Const.DEFAULT_TOKEN_NAu . b ] ~ME);
}
}
2、再创建TokenInterceptor的拦截器进行全局拦截验证:
/**
* L Q d /token拦截器
* @author QC v PinHaiLin
&n$ ~ u nbsp;* @date ? , ~ I G 9 q;2020-02-1W g Y : )3
*/
public class TokenInt[ 0 $ N t erceptor&nbN B @ N w x 2 psp;implements Interceptor {
@Inject
TokenService tokenServ3 J ^ Q , W sice;
@Override
public$ d 1 Y b void intercept(Invocation&nb( } r asp;in9 ; = 3 ( P x f Dv) {
Controller Q 2 f O B T v F $;c=inv.getController();
String methName=inv.getMethod().getName();
//给默认的添加、修改方法添加tokeJ n * Dn
iw f D Y ` , d s Sf(methName.equals(\"add\")||methName.equals(\"e] ; Fdit\")){
tokenService.createToken(c);
}
//验证token
if(methName.equals(\"save\")|$ % M|methName.equals(\"update\")){
boolean b=tokenService.validateToken(c);
if(!b){
boolean isAjax=\"XMLHttpRequest\".equalsIgnoreCase(c.getHeader(\"X-X # ; O D s ;Requested-With\"));
if(isAjax){
c.renderJson(Ret.fa~ a 7 A ` ~ | Zil(\"mv 2 R l 4sg\", \"token验证不通过,请刷新页面\"));
}else{
c.Q ~ ? s vsetAttr(\"msg\", \"token验证不通j f D % Q G过\");
c.renderError(403);
}
ree R 2 hturn;
}
//添加修改成功后,返回对的页{ ^ p q ? K J E面,此处是解决业务验证不通过的情况,如:添加用s _ W & & E o S户时,如果存在用户编号,那么需要重新填写,此时就要重新赋值新的token
tt ` F z w ;okenService.createToken(c);
}
inv.invoke();
}
}
3、在MainConfig配置成全局拦截器即可:
/**
* 配置全局拦截器
*/
@Override
public void 0 8 + K v U S;configInterceptor(Interceptors me) {
me.addGlobalActionInterceptor(new&nbsm j Lp;SessionInViep B S / n L ( LwInterceptor());
me.addGlobalActionInterceptor(new SesS ) / q +si# P ] a X H AonInterceptor());
me.addGlobalActionInterceptor(new&nbs$ u ( 0 C tp;ExceptionInterceptor());
//表单token验证拦截器1 N U P | =
me.addGlobc W 6 ! % / ; v salActionInterceptor(new TokenInter { | T { ! L 0rceptor()) p y;
mQ . c R L &e.addGloW R R l N |balActionInterceptor(new LoggerInterceptor());
}
4、在添加修改的操作表单输出token:#(tokel & D } V bn)
三、代码生成器
为满足广大用户1 k 0 X U m {要求,还是决定把代码生成器集成进来,那么有了这一神器,u s s x l就可以把那些繁琐重复的开发工作交给代码器了,一键多表生成代码文件,页面操作简单,直接生成代码文件到项目当中,刷新重启F S G $ }项目即可。
1、代码生成器操作页面,选择需要的表,可多选,点击选择按钮即可:
2、点击生成代码,这是预览代码,还没有真正创建代码文件,点击下载代码才是最终在项目中创建:
3、点击下载代码,创建文件:
4、刷新项目,就能在预先设置的package里面创建对应的Java文件了,html目录也是按照相应规4 4 D h 0 q则创建对应的目录:
java文件: html文件:
5、此时代码文件已经创J - m x M d 3 J P建好了,还需要最关键的一步,那就是把_MappingKit.java里面在数据库表映射关系配置到主配置文件中:
6、最后就是启动项目,H o l 8 R S A r s配置菜单权限即可访问e S ( 8 o了。