jks
java keystore
作用:保证应用的唯一性
简介:可以理解为java的密钥库,是一个用来存放密钥和证书的仓库。
(而keytool就是密钥和证书的管理工具,它把key(密钥)和certificate(证书)存放在一个叫keystore的文件中)
jks可以同时容纳n个公钥或私钥,后缀一般是.jks或者.keystore或.truststore等,千奇百怪
用jdk\bin目录下的keytool.exe对其进行查看,导入,导出,删除,修改密码等各种操作。
使用举例:Android Studio生成.jks文件
使用举例:生成jks证书文件:确保安装了JDK,在命令行中输入如下命令
keytool.exe -genkeypair -alias filename -keyalg RSA -keypass 501937 -storepass 501937 -keyalg RSA -keysize 2048 -validity 3650 -keystore filename.jks
您的名字与姓氏是什么?
[Unknown]: cheng
您的组织单位名称是什么?
[Unknown]: wang
您的组织名称是什么?
[Unknown]: qiu
您所在的城市或区域名称是什么?
[Unknown]: shanghai
您所在的省/市/自治区名称是什么?
[Unknown]: shanghai
该单位的双字母国家/地区代码是什么?
[Unknown]: CN
CN=cheng, OU=wang, O=qiu, L=shanghai, ST=shanghai, C=CN是否正确?
[否]: y
Warning:
JKS 密钥库使用专用格式。建议使用 "keytool -importkeystore -srckeystore filename.jks -destkeystore filename.jks -deststoretype pkcs12" 迁移到行业标准格式 PKCS12。
keytool -importkeystore -srckeystore filename.jks -destkeystore filename.jks -deststoretype pkcs12
输入源密钥库口令:
已成功导入别名 filename 的条目。
已完成导入命令: 1 个条目成功导入, 0 个条目失败或取消
Warning:
已将 "filename.jks" 迁移到 Non JKS/JCEKS。将 JKS 密钥库作为 "filename.jks.old" 进行了备份。
注:
storepass keystore 文件存储密码
keypass 私钥加解密密码
PS: 上面两个密码要一致
keyalt 采用公钥算法,默认是DSA
validity 有效期 单位是天
keysize 密钥长度(DSA算法对应的默认算法是sha1withDSA,不支持2048长度,此时需指定RSA)
keystore 指定keystore文件 如上面命令中filename.jks
签名文件kestore和jks:
在作用上基本上没有太大区别,主要是生成来源不一样,它们是由不同的IDE生成,
keystore文件一般是由Eclipce或dos命令行生成,而jks一般是在Android studio上自动生成。
要实现这个两种签名文件的相互转化,需要用到一个工具:keytool。
注意到这个知识点是因为,我在build.gradle
文件中看到
signingConfigs {
release {
storeFile file("../myapp.jks")
storePassword"mypassword"
keyAlias"My_App"
keyPassword"mykeypassword"
}
debug {
storeFile file('../debug.keystore')
storePassword 'android'
keyAlias 'androiddebugkey'
keyPassword 'android'
}
}
类似这种,我以为是硬编码
关于编码:保护Android Java代码中的硬编码值(API密钥):https://www.codenong.com/35378295/
但其实将密钥添加到build.gradle文件中。 这些文件不包含在您的apk中。 实际上,这些只是用来对您的应用程序进行签名并创建发行版本。
不算硬编码。
那可以算弱口令吗?
APP客户端与后台服务端通信
https://www.bbsmax.com/A/x9J2kbXZ56/
AIDL(Android Interface definition language-“接口定义语言”) 是 Android 提供的一种进程间通信 (IPC:Inter-Process Communication) 机制,支持的数据类型:
- Java 的原生类型;
- String 和CharSequence;
- List 和 Map ,List和Map 对象的元素必须是AIDL支持的数据类型; 以上三种类型都不需要导入(import);
- AIDL 自动生成的接口 需要导入(import);
- 实现android.os.Parcelable 接口的类. 需要导入(import)。
加密
加密算法:https://www.pianshen.com/article/4442439864/
https://blog.csdn.net/xihuailu3244/article/details/109769037
https://www.cnblogs.com/whoislcj/p/5580950.html
http://www.360doc.com/content/23/0210/12/81615618_1067007071.shtml
密钥常量:https://www.jianshu.com/p/f366b7c115a5
为什么我们应该编写所有这些步骤来使用java生成DES算法的密钥https://www.orcode.com/question/1320303_k56849.html
https://blog.csdn.net/mlymark/article/details/49175789
https://www.orcode.com/question/1320303_k56849.html
Android应用安全开发之浅谈硬编码: https://www.pianshen.com/article/9026439286/
https://blog.csdn.net/mlymark/article/details/49175789
Intent Scheme URLs攻击
Environment.getExternalStorageDirectory()
Android SDK 版本超过29编译的时候,Android Studio会提示Environment.getExternalStorageDirectory()过时了,要用Context#getExternalFilesDir代替,
Android Q以后Environment.getExternalStorageDirectory()返回的路径可能无法直接访问,所以改成了Context#getExternalFilesDir
android:exported=“true”
android:exported
是描述四大组件的参数,表示当前组件能否被其他应用程序的组件调用或与之交互。
android:exported 为true,表示可以和其他应用程序的组件发生调用和交互。
android:exported 为false,表示只能当前应用程序组件或者带有相同的用户ID(shareUserId)的应用程序调用和交互。
exported属性的默认值四大组件略有不同:
- Activity/Service/Receiver:当没有设置
intent-filter
时 ,默认false。 - Provider:当Android sdk版本为16或更低版本时,默认值为true,如果是17及以上版本则默认为false。
设置intent-filter的场景
<service
android:name="com.alibaba.sdk.android.push.MsgService">
<intent-filter>
<action android:name="com.alibaba.sdk.android.push.NOTIFY_ACTION" />
</intent-filter>
</service>
此情况下说明,除了“内部使用”外,组件需要被部分“特定”App调用的,推荐
1、调用的App与当前暴露组件的App使用同一uid:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
...
android:sharedUserId="xxx.xxx.xxx">
2、或通过对暴露的组件设置permission:
<activity android:name=".xxxActivity"
android:label="自定义permission"
android:permission="com.xxx.permission" >
<intent-filter>
<action android:name="android.xxx.action" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
既可满足暴露组件的需求,又可以保护数据安全。
https://blog.csdn.net/weixin_35691921/article/details/120430987
Intent(意图)
负责完成Android应用、组件之间的交互与通信。
常见的Activity的调用、Receiver的发送、Service的启动都需离不开Intent。
Intent通常包含的信息:
Categpry:种类、归类。
Action:表明要做什么?通常代表了一个组件具有的能力。
Data/Extras:通信的数据。
Flags:规定了系统如何去启动一个Activity。
Activity(活动)
通常应用里一个显示界面就是一个Activity,Activity负责显示界面的元素和与用户之间的交互。
https://blog.csdn.net/wsq_tomato/article/details/83349812
https://blog.csdn.net/qy1387/article/details/73740438
https://zhuanlan.zhihu.com/p/554069141
WebView
https://blog.csdn.net/qq_35114086/article/details/88796144/
https://blog.csdn.net/weixin_35691921/article/details/120430987
https://code84.com/750978.html
https://www.jianshu.com/p/6e9695fdedca
Android WebView在Android平台上是一个特殊的View,它能用来显示网页
,这个WebView类可以被用来在app中仅仅显示一张在线的网页,还可以用来开发浏览器。
WebView内部实现是采用渲染引擎(WebKit)来展示view的内容,提供网页前进后退、网页放大、缩小、搜索等功能。Android WebView 在低版本和高版本采用了不同的 webkit 版本内核,在 4.4 版本后使用 Chrome 内核。
作用:
- 显示和渲染Web页面
- 直接使用html文件(网络上或本地assets中)作布局
- 可和JavaScript交互调用
WebView控件功能强大,除了具有一般View的属性和设置外,还可以对url请求、页面加载、渲染、页面交互进行强大的处理。
使用:
1、本地加载
在布局文件中来添加WebView控件
在代码中让WebView控件加载显示网页
在配置文件中添加网络权限:
<!-- 添加网络权限 -->
<uses-permission android:name="android.permission.INTERNET" />
运行效果
2、远程加载
在本地桌面新建js文件attack.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Carson</title>
<script>
function callAndroid(){
//由于对象映射,所以调用test对象等于调用Android映射的对象
test.hello("WindXaa!");
}
</script>
</head>
<body>
<!--点击按钮则调用callAndroid函数-->
<button type="button" id="button1" onclick="callAndroid()">Internet Click connect</button>
</body>
</html>
开启一个简易的http_server的监听
编写代码:
{
WebView mWebView = (WebView) findViewById(R.id.Wind_webview1);
WebSettings webSettings = mWebView.getSettings();
// 设置与Js交互的权限
webSettings.setJavaScriptEnabled(true);
// 通过addJavascriptInterface()将Java对象映射到JS对象
//参数1:Javascript对象名
//参数2:Java对象名
mWebView.addJavascriptInterface(new AndroidtoJs(), "test");//AndroidtoJS类对象映射到js的test对象
mWebView.loadData("","text/html",null);
// 加载JS代码
// 格式规定为:file:///android_asset/文件名.html
// mWebView.loadUrl("file:///android_asset/javascript.html");
mWebView.loadUrl("http://ip地址填自己的/attack.html");
}
/**
* 提供接口在Webview中供JS调用
*/
public class AndroidtoJs {
// 定义JS需要调用的方法,被JS调用的方法必须加入@JavascriptInterface注解
@JavascriptInterface
public void hello(String msg) {
Log.e("WindXaa","Hello," + msg);
}
}
运行效果:
点击按钮,成功的通过JS调用Android代码
WebView常用方法
webView.onResume();// 激活WebView为活跃状态,能正常执行网页的响应
webView.onPause();// 当页面被失去焦点被切换到后台不可见状态,需要执行onPause
// 通过onPause动作通知内核暂停所有的动作,比如DOM的解析、plugin的执行、JavaScript执行。
webView.pauseTimers()// 当应用程序(存在webview)被切换到后台时,这个方法不仅仅针对当前的webview而是全局的全应用程序的webview
// 它会暂停所有webview的layout,parsing,javascripttimer。降低CPU功耗。
webView.resumeTimers()// 恢复pauseTimers状态
rootLayout.removeView(webView)
webView.destory()
// webview调用destory时,webview仍绑定在Activity上
// 需要先从父容器中移除webview,然后再销毁webview
前进、后退网页
Webview.canGoBack()//是否可以后退
Webview.goBack()//后退网页
Webview.canGoForward()//是否可以前进
Webview.goForward()//前进网页
//以当前的index为起始点前进或者后退到历史记录中指定的steps
//如果steps为负数则为后退,正数则为前进
Webview.goBackOrForward(intsteps)
在不做任何处理前提下,浏览网页时点击系统的“Back”键时,整个 Browser 会调用 finish()而结束自身,因此需要在当前Activity中处理并消费掉该 Back 事件,当按下返回键时,调用goBack方法。
我们可以做一些处理,让点击“Back”键后,让网页返回上一页而不是直接退出浏览器,此时我们可以在当前的Activity中处理Back事件,如下:
public boolean onKeyDown(int keyCode, KeyEvent event) {
if ((keyCode == KEYCODE_BACK) && mWebView.canGoBack()) {
mWebView.goBack();
return true;
}
return super.onKeyDown(keyCode, event);
}
常用类
WebSettings类:对WebView进行配置和管理
配置步骤:
第一步:添加访问网络权限(AndroidManifest.xml)
<uses-permission android:name="android.permission.INTERNET"/>
注意:从Android 9.0(API级别28)开始,默认情况下禁用明文支持,会显示 ERR_CLEARTEXT_NOT_PERMITTED。
因此http的url均无法在webview中加载,可以在manifest中application节点添加android:usesCleartextTraffic="true"。
第二步:生成一个WebView组件(有两种方式)
//方式1:直接在在Activity中生成
WebView webView = new WebView(this)
//方法2:在Activity的layout文件里添加webview控件:
WebView webview = (WebView) findViewById(R.id.webView1);
第三步:进行配置-利用WebSettings子类(常见方法)
WebSettings webSettings = webView.getSettings();//声明WebSettings子类
//如果访问的页面中要与Javascript交互,则webview必须设置支持Javascript
webSettings.setJavaScriptEnabled(true);
//支持插件
webSettings.setPluginsEnabled(true);
//设置自适应屏幕,两者合用
webSettings.setUseWideViewPort(true); //将图片调整到适合webview的大小
webSettings.setLoadWithOverviewMode(true); // 缩放至屏幕的大小
//缩放操作
webSettings.setSupportZoom(true); //支持缩放,默认为true。是下面那个的前提。
webSettings.setBuiltInZoomControls(true); //设置内置的缩放控件。若为false,则该WebView不可缩放
webSettings.setDisplayZoomControls(false); //隐藏原生的缩放控件
//其他细节操作
webSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK); //关闭webview中缓存
webSettings.setAllowFileAccess(true); //设置可以访问文件
webSettings.setJavaScriptCanOpenWindowsAutomatically(true); //支持通过JS打开新窗口
webSettings.setLoadsImagesAutomatically(true); //支持自动加载图片
webSettings.setDefaultTextEncodingName("utf-8");//设置编码格式
常见方法:设置WebView缓存
当加载 html 页面时,WebView会在/data/data/包名目录下生成 database 与 cache 两个文件夹
请求的 URL记录保存在 WebViewCache.db,而 URL的内容是保存在 WebViewCache 文件夹下
是否启用缓存:
//优先使用缓存
WebView.getSettings().setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);
//缓存模式如下:
//LOAD_CACHE_ONLY: 不使用网络,只读取本地缓存数据
//LOAD_DEFAULT: (默认)根据cache-control决定是否从网络上取数据。
//LOAD_NO_CACHE: 不使用缓存,只从网络获取数据.
//LOAD_CACHE_ELSE_NETWORK,只要本地有,无论是否过期,或者no-cache,都使用缓存中的数据
//不使用缓存
WebView.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);
WebViewClient类:用来处理各种通知和请求事件
shouldOverrideUrlLoading():打开网页时不调用系统浏览器, 而是在本WebView中显示;在网页上的所有加载都经过这个方法,这个函数我们可以做很多操作。
//Webview控件
Webview webview = (WebView) findViewById(R.id.webView);
//加载一个网页
webView.loadUrl("http://www.google.com/");
//重写shouldOverrideUrlLoading()方法,使得打开网页时不调用系统浏览器, 而是在本WebView中显示
webView.setWebViewClient(new WebViewClient(){
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
});
onPageStarted()作用:开始载入页面调用的,我们可以设定一个loading的页面,告诉用户程序在等待网络响应。
webView.setWebViewClient(new WebViewClient(){
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
//设定加载开始的操作
}
});
onLoadResource()作用:在加载页面资源时会调用,每一个资源(比如图片)的加载都会调用一次。
webView.setWebViewClient(new WebViewClient(){
@Override
public boolean onLoadResource(WebView view, String url) {
//设定加载资源的操作
}
});
onReceivedError()作用:加载页面的服务器出现错误时(如404)调用。
//App里面使用webview控件的时候遇到了诸如404这类的错误的时候,若也显示浏览器里面的那种错误提示页面就显得很丑陋了,那么这个时候我们的app就需要加载一个本地的错误提示页面,即webview如何加载一个本地的页面。
//步骤1:写一个html文件(error_handle.html),用于出错时展示给用户看的提示页面
//步骤2:将该html文件放置到代码根目录的assets文件夹下
//步骤3:复写WebViewClient的onRecievedError方法
//该方法传回了错误码,根据错误类型可以进行不同的错误分类处理
webView.setWebViewClient(new WebViewClient(){
@Override
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl){
switch(errorCode)
{
case HttpStatus.SC_NOT_FOUND:
view.loadUrl("file:///android_assets/error_handle.html");
break;
}
}
});
onReceivedSslError():处理https请求
webView默认是不处理https请求的,页面显示空白,需要进行如下设置:
webView.setWebViewClient(new WebViewClient() {
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
handler.proceed(); //表示等待证书响应
// handler.cancel(); //表示挂起连接,为默认方式
// handler.handleMessage(null); //可做其他处理
}
});
c.WebChromeClient:辅助 WebView 处理 Javascript 的对话框,网站图标,网站标题等等。
onProgressChanged():获得网页的加载进度并显示
webview.setWebChromeClient(new WebChromeClient(){
@Override
public void onProgressChanged(WebView view, int newProgress) {
if (newProgress < 100) {
String progress = newProgress + "%";
progress.setText(progress);
} else {
}
});
onReceivedTitle():获取Web页中的标题每个网页的页面都有一个标题,比如http://www.baidu.com这个页面的标题即“百度一下,你就知道”,那么如何知道当前webview正在加载的页面的title并进行设置呢?
webview.setWebChromeClient(new WebChromeClient(){
@Override
public void onReceivedTitle(WebView view, String title) {
titleview.setText(title);
}
Android WebView与JS的交互
https://mp.weixin.qq.com/s/KcmhXCIE8cr32OVBVkc4Lg
安全风险
webview.getSettings().setJavaScriptEnabled
(true)
设置WebView是否允许执行JavaScript脚本,默认false。
当targetSdkVersion小于17时,攻击者可以利用addJavascriptInterface
这个接口添加的函数远程执行任意代码
解决办法:
- 在
API17
(Android 4.2) 版本之后,需要在被调用的地方加上@addJavascriptInterface
约束注解,因为不加上注解的方法是没有办法被调用的。进而避免漏洞攻击。 - API < 17:需要采用拦截prompt()的方式进行漏洞修复。
https://blog.csdn.net/weixin_32534669/article/details/117833703
https
https://blog.51cto.com/u_4029519/5425558
https://blog.csdn.net/caizehui/article/details/112464577
https://mp.weixin.qq.com/s?__biz=MzIyNTgwOTIyOQ==&mid=2247489201&idx=1&sn=3a93ec381c40e7497bed2ad5196bcf10&chksm=e87b5093df0cd9858866724530f94427a653f1921b951fac36104352764f9d912b079e289a3d&scene=27
存储
https://blog.csdn.net/libo407/article/details/129862032?ydreferer=aHR0cHM6Ly93d3cuYmFpZHUuY29tL2xpbms%2FdXJsPTYzU2RQOU03ejRSS0xWSWpueE5NY185RS1WZzFmMUlQUExfQWUzUVBiUFBNQU9FOTBkSDFERGN3Qkp3bWduaHlaa1B6S1NITHpqcFN4d1R3Q2pKZlhxNHByTDFEbUlZN1JYWUczaTZrcHZPJndkPSZlcWlkPWFiNjhjZmM0MDAwMDQxNDkwMDAwMDAwNDY0MzY1YTYz
https://blog.csdn.net/qq_20466945/article/details/80199252
https://blog.csdn.net/Ananas_Orangey/article/details/126219957
https://www.jb51.net/article/220703.htm
审计参考
https://blog.51cto.com/u_14397532/3000922
https://mp.weixin.qq.com/s?__biz=MzIyNTgwOTIyOQ==&mid=2247489201&idx=1&sn=3a93ec381c40e7497bed2ad5196bcf10&chksm=e87b5093df0cd9858866724530f94427a653f1921b951fac36104352764f9d912b079e289a3d&scene=27
https://www.wangan.com/p/7fy7f882627f2fdb#8.1谨慎使用高风险函数
代码审计2-java
@PreAuthorize
@PreAuthorize("@ss.hasPermi('show:type:list')")
:用于权限控制,只有拥有’show:type:list’权限的用户才能访问这个接口
Java 注解
(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制。Java 定义了一套注解,共有 7 个,3 个在 java.lang 中,剩下 4 个在 java.lang.annotation 中。
作用在代码的注解是:
@Override - 检查该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。
@Deprecated - 标记过时方法。如果使用该方法,会报编译警告。
@SuppressWarnings - 指示编译器去忽略注解中声明的警告。
作用在其他注解的注解(或者说 元注解)是:
@Retention - 标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问。
@Documented - 标记这些注解是否包含在用户文档中。
@Target - 标记这个注解应该是哪种 Java 成员。
@Inherited - 标记这个注解是继承于哪个注解类(默认 注解并没有继承于任何子类)
Annotation通用定义
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation1 {
}
(1)@interface
定义 Annotation 时,@interface 是必须的。
使用 @interface 定义注解时,意味着它实现了 java.lang.annotation.Annotation 接口,即该注解就是一个Annotation。
(2)@Documented
类和方法的 Annotation 在缺省情况下是不出现在 javadoc 中的。如果使用 @Documented 修饰该 Annotation,则表示它可以出现在 javadoc 中。
定义 Annotation 时,@Documented 可有可无;若没有定义,则 Annotation 不会出现在 javadoc 中。
(3)@Target(ElementType.TYPE)
ElementType 是 Annotation 的类型属性。而 @Target 的作用,就是来指定 Annotation 的类型属性。
ElementType.METHOD:该注解只能声明在一个类的方法前。
ElementType.TYPE:该注解只能声明在一个类前。
(4)@Retention(RetentionPolicy.RUNTIME)
@Retention(RetentionPolicy.RUNTIME) 的意思就是指定该 Annotation 的策略是 RetentionPolicy.RUNTIME。这就意味着,编译器会将该 Annotation 信息保留在 .class 文件中,并且能被虚拟机读取。
总结: @interface 用来声明 Annotation,@Documented 用来表示该 Annotation 是否会出现在 javadoc 中, @Target 用来指定 Annotation 的类型,@Retention 用来指定 Annotation 的策略。
Spring Security
Spring Security提供了Spring EL表达式,允许我们在定义接口访问的方法上面添加注解,来控制访问权限。
@PreAuthorize
注解用于配置接口,要求用户拥有某些权限才可访问,它拥有如下方法
1、数据权限
整体逻辑:每个用户分配角色之后,都会有一定的权限,在用户访问某个接口时,会判断用户所拥有的权限是否包含该接口需要的权限,然后将判断的结果(“true"或者"fales”)传入@PreAuthorize注解实现数据权限的控制。当Spring EL 表达式返回TRUE,则权限校验通过。
示例:
@PreAuthorize
注解括号里面接收的是String类型的参数,且值为 “true” 或者 “false”
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface PreAuthorize {
String value();
}
ss.hasPermi()
方法:
ss:ss是PermissionService类实例化后起的的对象名
hasPermi() 方法接收一个 String类型的参数,值为’system:user:add’,返回值为布尔类型,首先进行校验,判断参数是否为空,然后拿到当前登录系统的用户
,判断当前用户是否为空以及用户拥有的权限是否为空(调用 loginUser.getPermissions() 方法,返回一个Set集合的权限列表),最后调用 hasPermissions(loginUser.getPermissions(), permission) 方法判断用户是否有该权限,如果包含就返回 true。
2、角色权限
角色权限时根据用户所属的角色来进行权限控制,实际原理和数据权限一样。
// 属于user角色
@PreAuthorize("@ss.hasRole('user')")
// 不属于user角色
@PreAuthorize("@ss.lacksRole('user')")
// 属于user或者admin之一
@PreAuthorize("@ss.hasAnyRoles('user,admin')")
@Validated
@Valid和@Validated是Spring Validation框架提供的参数验证功能
二者都作为标准JSR-303规范。在检验Controller的入参是否符合规范时,使用@Validated或者@Valid在基本验证功能上没有太多区别。但是在分组、注解地方、嵌套验证等功能上两个有所不同:
@Valid:@Valid注解用于校验,所属包为:javax.validation.Valid。
用在方法入参上无法单独提供嵌套验证功能。能够用在成员属性
(字段)上,提示验证框架进行嵌套验证。能配合嵌套验证注解@Valid进行嵌套验证。
@Validated:@Validated是@Valid 的一次封装,是Spring提供的校验机制使用。
用在方法入参上无法单独提供嵌套验证功能。不能用在成员属性(字段)上,也无法提示框架进行嵌套验证。能配合嵌套验证注解@Valid进行嵌套验证。
-----引入并使用@Validated参数验证:
1、引入校验的依赖包
<!--第一种方式导入校验依赖:使用springboot时,在org\springframework\spring-context\5.2.1.RELEASE\spring-context-5.2.1.RELEASE.jar-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!--第二种方式导入校验依赖-->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
<!--第三种方式导入校验依赖-->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
2、在实体类定义要校验的参数
3、
javax.validation.constraints下参数条件注解详解
实现参数验证功能,我们需要@Validated注解配合 在实体类的的参数加上条件验证注解
(设置具体的条件限制规则)一起实现参数验证功能。
而这些参数条件注解是由javax.validation.constraints包下提供,主要如下:
@NotNull :被注解的元素必须不为null
@NotBlank注解 :验证注解的元素值不为空(不为null、去除首位空格后长度为0) ,并且类型为String。
@NotEmpty注解 :验证注解的元素值不为null且不为空(字符串长度不为0、集合大小不为0) ,并且类型为String。
@AssertTrue注解 :被注解的元素必须为true,并且类型为boolean。
@AssertFalse注解 :被注解的元素必须为false,并且类型为boolean。
@Min注解 :被注解的元素其值必须大于等于最小值,并且类型为int,long,float,double。
@Max注解:被注解的元素其值必须小于等于最小值,并且类型为int,long,float,double。
@DecimalMin注解 :验证注解的元素值大于等于@DecimalMin指定的value值,并且类型为BigDecimal。
@DecimalMax注解 :验证注解的元素值小于等于@DecimalMax指定的value值 ,并且类型为BigDecimal。
@Range注解 :验证注解的元素值在最小值和最大值之间,并且类型为BigDecimal,BigInteger,CharSequence,byte,short,int,long。
@Past注解 :被注解的元素必须为过去的一个时间,并且类型为java.util.Date。
@Future注解 :被注解的元素必须为未来的一个时间,并且类型为java.util.Date。
@Size注解 :被注解的元素的长度必须在指定范围内,并且类型为String,Array,List,Map。
@Length注解 :验证注解的元素值长度在min和max区间内 ,并且类型为String。
@Digits注解 :验证注解的元素值的整数位数和小数位数上限 ,并且类型为float,double,BigDecimal。
@Pattern注解 :被注解的元素必须符合指定的正则表达式,并且类型为String。
@Email注解: 验证注解的元素值是Email,也可以通过regexp和flag指定自定义的email格式,类型为String。
自定义条件注解
如果在写项目的过程中,参数需要的条件注解满足不上,则我们需要自定义注解来完成
步骤:
1.创建一个自定义的注解类
//自定义条件注解
@Target({ElementType.FIELD,ElementType.METHOD,ElementType.ANNOTATION_TYPE,ElementType.CONSTRUCTOR,ElementType.PARAMETER,ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = {ListValueConstraintValidator.class})
public @interface ListValue {
//配置路径,后端传递信息
String message() default "{com.itfuture.e.valid.ListValue.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
//自定义一个类型来存放数据(数组)
int[] values() default {};
}
String message() default "{com.itfuture.e.valid.ListValue.message}";
也可以通过配置文件去配置:
2、创建一个逻辑处理数据的方法
//自定义显示状态
public class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer> {
//set存储
private Set<Integer> set = new HashSet<>();
//初始化数据
//listValue拿到的是注解中的数据
@Override
public void initialize(ListValue constraintAnnotation) {
//拿到注解中自定义的数据,且是数组型的
int[] values = constraintAnnotation.values();
//放在数组里,遍历判断
for(int value:values){
set.add(value);
}
}
//判断数据是否相同
@Override
public boolean isValid(Integer integer, ConstraintValidatorContext constraintValidatorContext) {
if(set.contains(integer)){
return true;
}
return false;
}
}
3、在实体类的参数条件中来调用
spring-boot-devtools热部署
- 是一个为开发者服务的模块,可以实现 Spring Boot 热部署,其中最重要的功能就是自动将应用代码更改到最新的 App 上面去。
- 可以实现页面热部署,实现类文件热部署(类文件修改后不会立即生效),实现对属性配置文件的热部署。其原理是 spring-boot-devtools 会监听 Classpath 下的文件变动,并且会立即重启应用(发生在保存时机)。
- 由于采用的虚拟机机制,重启的时候只是加载了在开发的 Class,没有重新加载第三方的 JAR 包,所以重启是很快的。
其深层原理是使用了两个ClassLoader,一个Classloader加载那些不会改变的类(第三方Jar包),另一个ClassLoader加载会更改的类,称为 restart ClassLoader ,这样在有代码更改的时候,原来的restart ClassLoader 被丢弃,重新创建一个restart ClassLoader,由于需要加载的类相比较少,所以实现了较快的重启时间
idea使用示例:
由于idea 没有保存修改,也就是说在idea中并不会因为ctrl+s 就重新编译代码。那么就需要额外的配置
1、在pom文件中,增加编译插件,让代码有变动的时候也编译
<!--热部署依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<!--没有该项配置,热部署不会起作用-->
<configuration>
<fork>true</fork>
</configuration>
</plugin>
2、application.properties
# 页面修改后立即生效,关闭缓存,立即刷新
spring.thymeleaf.cache=false
# 热部署生效
spring.devtools.restart.enabled=true
# 设置需要重启的目录
spring.devtools.restart.additional-paths=src/main/java
# 设置不需要重启的目录
spring.devtools.restart.exclude=static/**,public/**,WEB-INF/**
# 为 mybatis 设置,生产环境可删除
# restart.include.mapper=/mapper-[\\w-\\.]+jar
# restart.include.pagehelper=/pagehelper-[\\w-\\.]+jar
3、设置 IDEA 环境自动编译
在编译器选项中勾选 Build project automatically 选项
FreeMarker模板注入
https://zhuanlan.zhihu.com/p/432361789
FreeMarker 是一款模板引擎,即一种基于模板和需要改变的数据, 并用来生成输出文本( HTML 网页,电子邮件,配置文件,源代码等)的通用工具,其模板语言为 FreeMarker Template Language (FTL),模板文件简称为FTL(后缀可能也为这个)。
它不是面向最终用户的,而是一个Java类库,是一款程序员可以嵌入他们所开发产品的组件,适用于Web开发框架生成html页面。所以此类库经常应用于MVC开发模式的Java Web程序。
FreeMarker 模板文件与 HTML 一样都是静态页面,当用户访问页面时,FreeMarker 引擎会进行解析并动态替换模板中的内容进行渲染,然后将渲染后的结果返回到浏览器中。
FreeMarker 模板语言(FreeMarker Template Language,FTL)由 4 个部分组成:
- 文本:包括HTML标签与静态文本等静态内容,会原样输出;
- 插值:这部分的输出会被计算的数据来替换,使用${}这种语法;
- 标签:给FreeMarker的指示,可以简单与指令等同,不会打印在内容中,比如<#assign name=‘bob’>;
- 注释:由<#–和–>表示,不会被freemarker处理。
FreeMarker模版内容示例:
<html>
<head>
<title>Welcome TeamsSix!</title>
</head>
<body> <#-- 这是注释 -->
<h1>Welcome !</h1>
<p>Our latest product:
<a href="${latestProduct.url}">${latestProduct.name}</a>!
</body>
</html>
1、漏洞原理:
服务端接收了用户的恶意输入以后,未经任何处理就将其作为 Web 应用模板内容的一部分,模板引擎在进行目标编译渲染的过程中,执行了用户插入的可以破坏模板的语句,因而可能导致了敏感信息泄露、代码执行、GetShell 等问题。其影响范围主要取决于模版引擎的复杂性。
springboot防止XSS攻击和sql注入
https://blog.csdn.net/china_coding/article/details/129680240
①:创建Xss请求过滤类XssHttpServletRequestWraper
public class XssHttpServletRequestWraper extends HttpServletRequestWrapper {
Logger log = LoggerFactory.getLogger(this.getClass());
public XssHttpServletRequestWraper() {
super(null);
}
public XssHttpServletRequestWraper(HttpServletRequest httpservletrequest) {
super(httpservletrequest);
}
//过滤springmvc中的 @RequestParam 注解中的参数
public String[] getParameterValues(String s) {
String str[] = super.getParameterValues(s);
if (str == null) {
return null;
}
int i = str.length;
String as1[] = new String[i];
for (int j = 0; j < i; j++) {
//System.out.println("getParameterValues:"+str[j]);
as1[j] = cleanXSS(cleanSQLInject(str[j]));
}
log.info("XssHttpServletRequestWraper净化后的请求为:==========" + as1);
return as1;
}
//过滤request.getParameter的参数
public String getParameter(String s) {
String s1 = super.getParameter(s);
if (s1 == null) {
return null;
} else {
String s2 = cleanXSS(cleanSQLInject(s1));
log.info("XssHttpServletRequestWraper净化后的请求为:==========" + s2);
return s2;
}
}
//过滤请求体 json 格式的
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream bais = new ByteArrayInputStream(inputHandlers(super.getInputStream ()).getBytes ());
return new ServletInputStream() {
@Override
public int read() throws IOException {
return bais.read();
}
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) { }
};
}
public String inputHandlers(ServletInputStream servletInputStream){
StringBuilder sb = new StringBuilder();
BufferedReader reader = null;
try {
reader = new BufferedReader(new InputStreamReader(servletInputStream, Charset.forName("UTF-8")));
String line = "";
while ((line = reader.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (servletInputStream != null) {
try {
servletInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return cleanXSS(sb.toString ());
}
public String cleanXSS(String src) {
String temp = src;
src = src.replaceAll("<", "<").replaceAll(">", ">");
src = src.replaceAll("\\(", "(").replaceAll("\\)", ")");
src = src.replaceAll("'", "'");
src = src.replaceAll(";", ";");
//bgh 2018/05/30 新增
/**-----------------------start--------------------------*/
src = src.replaceAll("<", "& lt;").replaceAll(">", "& gt;");
src = src.replaceAll("\\(", "& #40;").replaceAll("\\)", "& #41");
src = src.replaceAll("eval\\((.*)\\)", "");
src = src.replaceAll("[\\\"\\\'][\\s]*javascript:(.*)[\\\"\\\']", "\"\"");
src = src.replaceAll("script", "");
src = src.replaceAll("link", "");
src = src.replaceAll("frame", "");
/**-----------------------end--------------------------*/
Pattern pattern = Pattern.compile("(eval\\((.*)\\)|script)",
Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(src);
src = matcher.replaceAll("");
pattern = Pattern.compile("[\\\"\\'][\\s]*javascript:(.*)[\\\"\\']",
Pattern.CASE_INSENSITIVE);
matcher = pattern.matcher(src);
src = matcher.replaceAll("\"\"");
// 增加脚本
src = src.replaceAll("script", "").replaceAll(";", "")
/*.replaceAll("\"", "").replaceAll("@", "")*/
.replaceAll("0x0d", "").replaceAll("0x0a", "");
if (!temp.equals(src)) {
// System.out.println("输入信息存在xss攻击!");
// System.out.println("原始输入信息-->" + temp);
// System.out.println("处理后信息-->" + src);
log.error("xss攻击检查:参数含有非法攻击字符,已禁止继续访问!!");
log.error("原始输入信息-->" + temp);
throw new CustomerException("xss攻击检查:参数含有非法攻击字符,已禁止继续访问!!");
}
return src;
}
//输出
public void outputMsgByOutputStream(HttpServletResponse response, String msg) throws IOException {
ServletOutputStream outputStream = response.getOutputStream(); //获取输出流
response.setHeader("content-type", "text/html;charset=UTF-8"); //通过设置响应头控制浏览器以UTF-8的编码显示数据,如果不加这句话,那么浏览器显示的将是乱码
byte[] dataByteArr = msg.getBytes("UTF-8");// 将字符转换成字节数组,指定以UTF-8编码进行转换
outputStream.write(dataByteArr);// 使用OutputStream流向客户端输出字节数组
}
// 需要增加通配,过滤大小写组合
public String cleanSQLInject(String src) {
String lowSrc = src.toLowerCase();
String temp = src;
String lowSrcAfter = lowSrc.replaceAll("insert", "forbidI")
.replaceAll("select", "forbidS")
.replaceAll("update", "forbidU")
.replaceAll("delete", "forbidD").replaceAll("and", "forbidA")
.replaceAll("or", "forbidO");
if (!lowSrcAfter.equals(lowSrc)) {
log.error("sql注入检查:输入信息存在SQL攻击!");
log.error("原始输入信息-->" + temp);
log.error("处理后信息-->" + lowSrc);
throw new CustomerException("sql注入检查:参数含有非法攻击字符,已禁止继续访问!!");
}
return src;
}
}
②:把请求过滤类XssHttpServletRequestWraper添加到Filter中,注入容器
@Component
public class XssFilter implements Filter {
Logger log = LoggerFactory.getLogger(this.getClass());
// 忽略权限检查的url地址
private final String[] excludeUrls = new String[]{
"null"
};
public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) arg0;
HttpServletResponse response = (HttpServletResponse) arg1;
String pathInfo = req.getPathInfo() == null ? "" : req.getPathInfo();
//获取请求url的后两层
String url = req.getServletPath() + pathInfo;
//获取请求你ip后的全部路径
String uri = req.getRequestURI();
//注入xss过滤器实例
XssHttpServletRequestWraper reqW = new XssHttpServletRequestWraper(req);
//过滤掉不需要的Xss校验的地址
for (String str : excludeUrls) {
if (uri.indexOf(str) >= 0) {
arg2.doFilter(arg0, response);
return;
}
}
//过滤
arg2.doFilter(reqW, response);
}
public void destroy() {
}
public void init(FilterConfig filterconfig1) throws ServletException {
}
}
上述代码已经可以完成请求参数、JSON请求体的过滤,但对于json请求体还有其他的方式实现。
扩展:
还可以重写spring中的MappingJackson2HttpMessageConverter来过滤Json请求体
因为请求体在进出Contoroller时,会经过MappingJackson2HttpMessageConverter的一个转换,把请求体转换成我们需要的json格式,所以可以在这里边做一些修改!
@Configuration
public class MyConfiguration {
@Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(){
//自定义转换器
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
//转换器日期格式设置
ObjectMapper objectMapper = new ObjectMapper();
SimpleDateFormat smt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
objectMapper.setDateFormat(smt);
converter.setObjectMapper(objectMapper);
//转换器添加自定义Module扩展,主要是在这里做XSS过滤的!!,其他的是其他业务,不用看
SimpleModule simpleModule = new SimpleModule();
//添加过滤逻辑类!
simpleModule.addDeserializer(String.class,new StringDeserializer());
converter.getObjectMapper().registerModule(simpleModule);
//设置中文编码格式
List<MediaType> list = new ArrayList<>();
list.add(MediaType.APPLICATION_JSON_UTF8);
converter.setSupportedMediaTypes(list);
return converter;
}
}
真正的过滤逻辑类StringDeserializer:
//检验请求体的参数
@Component
public class StringDeserializer extends JsonDeserializer<String> {
@Override
public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
String str = jsonParser.getText().trim();
//sql注入拦截
if (sqlInject(str)) {
throw new CustomerException("参数含有非法攻击字符,已禁止继续访问!");
}
return xssClean(str);
}
public boolean sqlInject(String str) {
if (StringUtils.isEmpty(str)) {
return false;
}
//去掉'|"|;|\字符
str = org.apache.commons.lang3.StringUtils.replace(str, "'", "");
str = org.apache.commons.lang3.StringUtils.replace(str, "\"", "");
str = org.apache.commons.lang3.StringUtils.replace(str, ";", "");
str = org.apache.commons.lang3.StringUtils.replace(str, "\\", "");
//转换成小写
str = str.toLowerCase();
//非法字符
String[] keywords = {"master", "truncate", "insert", "select", "delete", "update", "declare", "alert","alter", "drop"};
//判断是否包含非法字符
for (String keyword : keywords) {
if (str.indexOf(keyword) != -1) {
return true;
}
}
return false;
}
//xss攻击拦截
public String xssClean(String value) {
if (value == null || "".equals(value)) {
return value;
}
//非法字符
String[] keywords = {"<", ">", "<>", "()", ")", "(", "javascript:", "script","alter", "''","'"};
//判断是否包含非法字符
for (String keyword : keywords) {
if (value.indexOf(keyword) != -1) {
throw new CustomerException("参数含有非法攻击字符,已禁止继续访问!");
}
}
return value;
}
}
使用这种形式也可以完成json请求体的过滤,但个人更推荐使用XssHttpServletRequestWraper的形式来完成xss过滤
Mybatis 在 Mapper.xml 文件中的转义字符处理方式
在mybatis的映射文件写sql时,很多时候都需要写一些特殊的字符。例如:“<” 字符 “>” 字符 “>=” 字符 “<=” 字符,但是在xml文件中并不能直接写上述列举的字符,否则就会报错。
1、使用<![CDATA[ ]]>
来解决
<select id = "selectUserByAge" resultType="com.test.hiioc.model.UserTable" >
select
id,userName,age
from
userTable
<where>
IS_DELETE = 1
/*时间段查询*/
<if test = "userTable.startDate != null">
and SIGNING_DATE <![CDATA[>=]]> #{userTable.startDate}
</if>
<if test = "userTable.endDate!=null">
and SIGNING_DATE <![CDATA[<=]]> #{userTable.endDate}
</if>
</where>
</select>
2、
<select id = "selectUserByAge" resultType="com.test.hiioc.model.UserTable" >
select
id,userName,age
from
userTable
<where>
IS_DELETE = 1
/*时间段查询*/
<if test = "userTable.startDate!=null">
and SIGNING_DATE >= #{userTable.startDate}
</if>
<if test = "userTable.endDate != null">
and SIGNING_DATE <= #{userTable.endDate}
</if>
</where>
</select>