- 投稿日:2021-03-21T21:44:16+09:00
Javaで地図を書く時に使うGeoToolsのリポジトリが変わっていました(←勘違い)
Javaで地図を書きたくてネット上のサンプルを写経したらGeoToolsライブラリがダウンロードできない問題に遭遇しました。
末尾に挙げた「Gradleを介してGeoToolsdependencisを解決するための正しいURLは何ですか」に記されているとおり依存ライブラリのダウンロード先を変更したら解決しました。遭遇した問題
$ ./gradlew build > Task :compileJava FAILED FAILURE: Build failed with an exception. * What went wrong: Execution failed for task ':compileJava'. > Could not resolve all files for configuration ':compileClasspath'. > Could not find org.geotools:gt-shapefile:22.2. Searched in the following locations: - https://repo.maven.apache.org/maven2/org/geotools/gt-shapefile/22.2/gt-shapefile-22.2.pom (以下省略)解決方法
GeoToolsを使うために、
https://repo.osgeo.org/repository/release
をリポジトリに追加しました。
後からわかったことですが、GeoTools公式HPをよく見たら、Mavenの例ですが先のリポジトリが必要であると書いてありました。plugins { id 'java' } group 'org.example' version '1.0-SNAPSHOT' repositories { // このリポジトリを追加したら解決しました。 maven {url "https://repo.osgeo.org/repository/release"} mavenCentral() } dependencies { compile 'org.geotools:gt-shapefile:22.2' compile 'org.geotools:gt-swing:24.2' testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.0' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine' } test { useJUnitPlatform() }写経したJavaプログラム
package hellogeotools; import java.io.File; import java.io.IOException; //import org.geotools.data.CachingFeatureSource; import org.geotools.data.FileDataStore; import org.geotools.data.FileDataStoreFinder; import org.geotools.data.simple.SimpleFeatureSource; import org.geotools.map.FeatureLayer; import org.geotools.map.Layer; import org.geotools.map.MapContent; import org.geotools.styling.SLD; import org.geotools.styling.Style; import org.geotools.swing.JMapFrame; import org.geotools.swing.data.JFileDataStoreChooser; public class HelloGeoTools { public static void main(String[] args) throws IOException { System.out.print("Javaバージョン(java.version):"); System.out.println(System.getProperty("java.version")); System.out.print("オペレーティングシステム名(os.name):"); System.out.println(System.getProperty("os.name")); File file = JFileDataStoreChooser.showOpenFile("shp", null); if (file == null) { System.err.println("No such file."); return; } FileDataStore store = FileDataStoreFinder.getDataStore(file); SimpleFeatureSource featureSource = store.getFeatureSource(); MapContent map = new MapContent(); map.setTitle("Quickstart"); Style style = SLD.createSimpleStyle(featureSource.getSchema()); Layer layer = new FeatureLayer(featureSource, style); map.addLayer(layer); JMapFrame.showMap(map); } }参考
環境
- Ubuntu 18.04
- Java 11.0.10
- 投稿日:2021-03-21T19:43:13+09:00
改めてpure javaでHello World
はじめに
今やどこの開発現場でも何かしらのフレームワーク・ビルドツールを使うのが主流になっているかと思います。が、javacとかいうコマンドを使ってコンパイルする方法、みなさん覚えていますか?(私は覚えておりませんでした)
先日ふと「研修で習ったjavacコマンドってどうやって使うんだっけ?」と思い復習しましたので記録しておきます。基本の流れ
- javaファイルを作成
- コンパイル
- 実行
さっそくHello World
javaファイルを作成
Main.javaというファイルを作成しました。
標準出力するだけの簡単な内容です。
コンパイル
先ほど作成したMain.javaをコンパイルします。
コンパイルとは、ソースコードの内容をコンピューターが実行できる形式に変換することです。
Javaにおいてはクラスファイル(バイナリコードのファイル)に変換することを指します。実行コマンド.javac ソースファイル名.java今回はこんな感じ.javac src/main/Main.javaそうすると、同じディレクトリに.classファイルが作成されます
$ ls src/main/ Main.class Main.java実行
コンパイルしたプログラムをコンピューターに解釈させ、動作させます。
実行コマンド.java クラス名今回はこんな感じ.java src/main/Main # 実行結果 Hello World
- 投稿日:2021-03-21T17:21:09+09:00
EC2にJavaWebアプリ環境を構築 #2
EC2にJavaWebアプリ環境を構築 #1
EC2にJavaWebアプリ環境を構築 #2
EC2にJavaWebアプリ環境を構築 #3
EC2にJavaWebアプリ環境を構築 #4目次
1.はじめに
2.インストールはじめに
ざっくり説明するとeclipseで作ったアプリをEC2で動かす
- 前回
- インスタンスの作成が完了
インストール
JDK
開発環境と同じjdk11を使用する
インストールはamazonが提供しているパッケージからおこなうインストール
sudo amazon-linux-extras install java-openjdk11 -y-y というコマンドは全部yesと回答するオプションみたいなやつなのでコマンドを打てばインストールが完了する
java -version
でバージョン確認が可能
nginx
WEBサーバはnginxを使用
Apacheでも可能だが私は、
nginxのロゴがカッコイイ
という理由からnginxを採用小規模なアプリを稼働させるくらいなので好みで選んで良いと思う
インストール
sudo amazon-linux-extras install nginx1 -ynginxの起動と自動起動の設定も行う
#起動 sudo systemctl start nginx #自動起動 sudo systemctl enable nginx基本的には最新版がインストールされる(多分)
ステータス、バージョン確認は下記コマンド
#ステータス確認 systemctl status nginx #バージョン確認 nginx -vnginxをインストールできたらアクセスしてみる
インスタンス画面から
パブリックIPv4 DNSをコピーしアクセス
Tomcat
記事では最新のver.10を使用する
ver.9などでも基本的にやり方は変わらないので真似してOK(だと思う)雑だが10と9の違いは
//Tomcat 10 import jakarta.servlet //Tomcat 9 import javax.servletインポートの記述が違うこと
あとファイルの配置が違うくらい公式サイトより
- Core
- tar.gz
のリンクをコピー
ダウンロード
wget https://downloads.apache.org/tomcat/tomcat-10/v10.0.4/bin/apache-tomcat-10.0.4.tar.gzURLはコピーを貼り付ける
下記コマンド入力
ver.9をダウンロードした場合は
tomcat-10を
tomcat-9に変更するのが良いと思う#解凍 tar -xzvf ~/apache-tomcat-10.0.4.tar.gz #ファイル移動&リネーム sudo mv ~/apache-tomcat-10.0.4 /opt/tomcat-10 #Windowsでいうショートカット作成 sudo ln -s /opt/tomcat-10 /opt/tomcat #ユーザー追加 sudo useradd -s /sbin/nologin tomcat #所有権変更 sudo chown -R tomcat:tomcat /opt/tomcat-10 #viが起動する sudo vi /etc/systemd/system/tomcat.service【vi操作(適当)】
i
→貼り付け
→ESC
→:wq
→エンター
Ver.10
[Unit] Description=Apache Tomcat 10 After=network.target [Service] User=tomcat Group=tomcat Type=oneshot PIDFile=/opt/tomcat-10/tomcat.pid RemainAfterExit=yes ExecStart=/opt/tomcat-10/bin/startup.sh ExecStop=/opt/tomcat-10/bin/shutdown.sh ExecReStart=/opt/tomcat-10/bin/shutdown.sh;/opt/tomcat-10/bin/startup.sh [Install] WantedBy=multi-user.targetVer.9
[Unit] Description=Apache Tomcat 9 After=network.target [Service] User=tomcat Group=tomcat Type=oneshot PIDFile=/opt/tomcat-9/tomcat.pid RemainAfterExit=yes ExecStart=/opt/tomcat-9/bin/startup.sh ExecStop=/opt/tomcat-9/bin/shutdown.sh ExecReStart=/opt/tomcat-9/bin/shutdown.sh;/opt/tomcat-9/bin/startup.sh [Install] WantedBy=multi-user.targetvi終了後
#実行権限付与 sudo chmod 755 /etc/systemd/system/tomcat.service #起動 sudo systemctl start tomcat #自動起動 sudo systemctl enable tomcatステータス確認は下記コマンド
systemctl status tomcatTomcatをインストールできたらアクセスしてみる
nginxと同様、パブリックIPv4 DNSアドレスを入力し、
末尾に「:8080」を追加するイメージ
http://amazonaws.com:8080
長くなってきたのでここまで
参考サイト
- 投稿日:2021-03-21T15:41:02+09:00
public static void mainとは何なのか(Java学習1日目)
はじめに
java初学者による備忘録になります。
なにか間違っている事などございましたらご指導して頂けると幸いです。エントリーポイントとは
プログラムを開始する場所をエントリーポイント(開始点)と呼びます。
javaのエントリーポイントはmainメソッドです。
mainメソッド=開始点ですから、これが存在しない場合や正しく記述されていない場合は
他のメソッドを使用する事が出来ません。
さて。このmainメソッドですが、定義する為のルールがいくつか存在します。mainメソッドを定義する上でのルール
ルールは5つあります。
1.アクセス修飾子はpublic
2.staticメソッド
3.メソッドの戻り値はvoid
4.メソッド名はmain(小文字)
5.メソッドの引数はStringの配列(またはStringの可変長引数1)のみ上記のルールを守った記述をした場合、以下の様なコードになるでしょう。
class Main { public static void main (String[] args) { // 処理 } }アクセス修飾子とpublic
ルールの1つ目に出てきたアクセス修飾子とは、(Mainメソッドを)利用できるプログラムの範囲を指定する修飾子です。publicを指定した場合、他の全クラスが(Mainメソッドを)利用できるようになります。このような設定をする理由は、メソッドを利用時に、開始点であるmainメソッドにアクセスする必要があるからです。
staticメソッド
staticメソッドとは、インスタンスが存在しなくても使用できるメソッドです。エントリーポイントは、最初にプログラムを起動する場所です。なので、起動した時点ではインスタンスが存在しない可能性があります。その為、このような設定が必要になるわけです。
メソッドの戻り値はvoid
voidとは空を意味する英単語です。戻り値はvoidとは戻り値が無いことを意味します。ただ、なぜこのような規約を設けたのか調べてもわからなかったので、ご存じの方がいましたら是非教えてください。
4.メソッド名はmain(小文字)
javaはC言語の後継言語なので、C言語と同様の規約を流用したという説が
あります。メソッドの引数はStringの配列のみ
mainメソッドは最初に実行されるメソッドです。したがって、他のクラスからメソッドを呼び引数を渡すと2重で呼び出されることになってしまいエラーが出てしまいます。そこで、特別な方法を使用して引数を渡す必要があります。それがコマンドライン引数です。コマンドラインに入力した情報を引数として渡します。そして、コマンドラインには人間が打ち込んだ情報が表示され、人間が打ち込む情報は文字列であるという考えからstringの配列を使用しなければならないと定められたようです。
最後に
これから可能な限り備忘録として記事投稿をしていきたいと思います。未熟者ではありますがよろしくお願いします。
- 投稿日:2021-03-21T15:07:53+09:00
実はJrubyはgradleだけで管理できる
はじめに
Jruby覚えてますか?JRubyはまだ現役です。こういう言語を扱うと必ずと言っていいほど、「javaでいいじゃん」と言われます。確かにJRubyでできることはJVMでできることです。
現状ではRubyが好きな人がたまに使うといった感覚でしょうか。ですが、私はJrubyでJava開発のテストを書いてます。理由は生産性が高いからです。
この記事では普段Java使いのみなさんがJrubyで楽できるtipsを紹介します。
依存環境
- gradle
- Jruby
- jruby-gradle-plugin
- http://jruby-gradle.org/ これだけです。
ですが、全て意識なくていいです。管理はすべてgradleで行います。ここでは一旦、jrubyのコマンドも忘れてしまいましょう。
初期プロジェクトを立ち上げる
必要なのは2つのファイルだけです。
terminalmkdir sample cd sample nvim build.gradle #neovimはいいぞ nvim main.rbbuild.gradle/* * This is sample programs under the jruby. *@ak.f */ buildscript { repositories { jcenter() } dependencies { classpath 'com.github.jruby-gradle:jruby-gradle-plugin:2.0.0' classpath 'com.github.jruby-gradle:jruby-gradle-jar-plugin:2.0.0' classpath 'com.github.jengelman.gradle.plugins:shadow:5.0.0' } } apply plugin: 'com.github.jruby-gradle.jar' repositories { jcenter() ruby.gems() } dependencies { jrubyJar 'org.slf4j:slf4j-simple:1.7.12' jrubyJar "rubygems:colorize:0.7.7+" } jrubyJar { initScript "./main.rb" } task jrubyRun(type: Exec){ dependsOn jrubyJar workingDir "./build/libs" commandLine 'java', '-jar', jrubyJar.outputs.files.singleFile.absolutePath }main.rbrequire 'colorize' java_import 'org.slf4j.Logger' java_import 'org.slf4j.LoggerFactory' logger = LoggerFactory.getLogger('demo') puts "-" * 20 logger.info "Ruby version: #{RUBY_VERSION}" logger.info "Ruby platform: #{RUBY_PLATFORM}" logger.info "Current file: #{__FILE__}" puts "-" * 20 puts "Roses are red".red puts "Violets are blue".blue puts "I can use JRuby/Gradle".green puts "And now you can too!".yellow以上がサンプルプロジェクトになります。
自分の管理するJavaライブラリを呼び出す場合は、
repositories {
jcenter()
ruby.gems()
maven {url 'mavenリポジトリのURL'}}
dependencies {
jrubyJar 'xxx.org:name:version'
}
として参照してやればgradleが全部やってくれます。コマンドチートシート
- とりあえず実行する
gradle jrubyRun
- jarファイルにビルドする
gradle jrubyJar
- ビルドファイルを削除する
gradle clean
- ビルドしたものを実行する
java -jar build/libs/sample-jruby.jar
最後に
今どきのJRubyでした。
最近は日本語に記事であまり話題に上がらないですが、実務ではjavaライブラリのテストケースや、使い捨てのメインクラスを作成するぐらいなら私はJrubyで書くようになりました。
gradleで管理できるのが最高です。ほとんど、http://jruby-gradle.org/ で紹介されているとおりですが、
build.gradleにはjrubyRunのタスクを定義してあります。
- 投稿日:2021-03-21T14:30:12+09:00
[Android] Jetpack DataBinding
Android Studio 4.1.3(windows版) での流れとなります
Jetpack DataBindingでボタンイベントを設定する方法を例にして説明します
Android DeveloperのJetpack DataBindingの説明は以下です
準備
以下を参考にJetpack Navigation + ViewModelを作成してください
DataBindingを使用するための設定
build.gradleに以下を追加します
DataBindingを有効にするとBindingクラスが自動生成されますbuild.gradleandroid { dataBinding { enabled = true } }ViewModelでの準備
ボタンイベントを受けるメソッドを用意します
MainViewModel.javapublic void onClickButton() { }FragmentのLayoutの準備
ここではFrameLayoutをConstraintLayoutにしてid:buttonのボタンを追加します
※ Convert FraneLayout to ConstraintLayoutでも変更できますCodeを開きConstraintLayoutタグの箇所でwindowsの場合はAlt+Enterなどで
Convert to data bindinglayout を選択します
追加されたdataタグにViewModelを追加します
main_fragment.xml<data> <variable name="viewModel" type="com.xxx.sample.MainViewModel" /> </data>ボタンのonClickに以下を追加します
※DesignまたはCodeで直接追加してください@{() -> viewModel.onClickButton()}Fragmentでの準備
DataBindingの準備とDataBindingにViewModelをセットします
MainFragment.java// ViewModelはFragment作成時のテンプレートで自動生成されたものです private MainViewModel mViewModel; // 自動生成されます(main_fragment.xml) private MainFragmentBinding binding; @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { binding = DataBindingUtil.inflate(inflater, R.layout.main_fragment, container, false); final View view = binding.getRoot(); return view; } @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); mViewModel = new ViewModelProvider(this).get(MainViewModel.class); // TODO: Use the ViewModel binding.setViewModel(mViewModel); }この記事は以下の記事の補足です
- 投稿日:2021-03-21T14:28:52+09:00
仮引数と実引数
- 投稿日:2021-03-21T14:22:25+09:00
参照渡し+値渡し+String+オブジェクトの場合+List
Stringクラスのオブジェクトの場合
Stringクラスのオブジェクトをメソッドに渡した場合、メソッド側の引数には同じオブジェクトの位置が渡されるのは配列と同じですが、文字列変数に新しい文字列を代入するとまったく別の場所にオブジェクトが新しく作成され、その新しい場所の位置がメソッド内の変数に代入されます。その為、元の場所には元の文字列が格納されたままなのでメソッド呼び出し元の文字列には何も影響を与えません。
String.javapublic static void main(String[] args) { String str = "abc"; test(str);//先にここのtext()が実行される System.out.println(str); } private static void test(String str) { str = "def"; System.out.println(str); } 実行 def abcList
参照型の変数の場合は、final修飾子をつけても値の変更が可能になってしまいます。
このように参照型の変数を引数に使う場合は、呼び出し元の値が変わるため注意してください。List.javapublic static void main(String[] args) { List<String> list = new ArrayList<>(); list.add("a"); list.add("b"); list.add("c"); System.out.println(list); finalSample(list); System.out.println(list); } public static void finalSample(final List<String> list) { // public static void finalSample(List<String> list) list.set(0, "A"); list.set(1, "B"); list.set(2, "C"); } 実行 [a, b, c] [A, B, C]
- 投稿日:2021-03-21T14:02:06+09:00
参照渡し+Method
参照渡し(引数にアドレスを渡す)
基本型変数などではなく配列など引数としてアドレスを渡していることを参照渡し、参照渡しを行うと呼び出し先で加えた変更が呼び出し元にも影響を与える
『配列をメソッド呼び出しで渡すと』
.呼び出し元の配列のアドレスが、呼び出し先の引数にコピーされる
.呼び出し先で配列の実体を書き換えると、呼び出し元にも影響を与える値渡し(値そのものが渡されること)
『基本型の変数をメソッド呼び出しで渡すと』
.呼び出し元の変数の内容が、呼び出し先の引数にコピーされる
.呼び出し先で引数の内容を書き換えても呼び出し元の変数は変化しない参照渡しと値渡しの例
MethodArray.javapublic static void main(String[] args) { int num = 8; int array[] = {10, 4}; System.out.println(num); System.out.println(array[0]); test(num, array); System.out.println(num); System.out.println(array[0]); } private static void test(int num, int array[]){ num = 5;//ここで代入しても変わらない値渡し array[0] = 12;//アドレスを渡すので変わる 実行 8 10 8 12
- 投稿日:2021-03-21T12:57:12+09:00
Javaアクセス修飾子表
- 投稿日:2021-03-21T11:42:39+09:00
血圧測定結果管理プログラムのソースコードを公開いたします
血圧測定結果管理プログラムのソースコードを公開いたします
// // // 血圧記録プログラム // 新規作成日 2021/03/19(Ver1.0) // 作成者 乃木坂46好きのITエンジニア // // // // // // //パッケージの定義 package Ketsuatsu; //importするAPIを定義 import java.io.FileWriter; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Scanner; //標準入力クラス class top_ketsuatsudata{ //日付オブジェクトを呼び出す //Date d = new Date(); //日付フォーマットオブジェクトを呼び出す //SimpleDateFormat d1 = new SimpleDateFormat("yyyy/MM/dd"); //文字列型のデータに変換する //private String hiduke = d1.format(d); //上の血圧 private int t_blood; //下の血圧 //private int u_blood; //ループフラグ private boolean flag = false; //Scannerオブジェクトを呼び出す Scanner sc = new Scanner(System.in); //上の血圧を入力するメソッド public int t_blood_data() { while ( flag == false) { System.out.println("上の血圧を入力してください"); String line1 = sc.nextLine(); //例外処理判定 try { t_blood = Integer.parseInt(line1); flag = true; //文字列を入力された場合の処理 } catch (NumberFormatException e){ System.out.println("数値を入力してください"); } } return t_blood; } } class under_ketsuatsudata{ //下の血圧フィールド private int u_blood; //ループフラグ private boolean flag = false; //Scannerオブジェクトを呼び出す Scanner sc = new Scanner(System.in); //上の血圧を入力するメソッド public int u_blood_data() { while ( flag == false) { System.out.println("下の血圧を入力してください"); String line2 = sc.nextLine(); //例外処理判定 try { u_blood = Integer.parseInt(line2); flag = true; //文字列を入力された場合の処理 } catch (NumberFormatException e){ System.out.println("数値を入力してください"); } } return u_blood; } } //出力結果クラス class kekka{ public static void kekka(int a,int b) { //日付オブジェクトを呼び出す Date d = new Date(); //日付フォーマットオブジェクトを呼び出す SimpleDateFormat d1 = new SimpleDateFormat("yyyy/MM/dd"); String c = d1.format(d); String msg = ""; //System.out.println(c); //厚生労働省のBMI値の基準でやせすぎ、普通、肥満度を判定する。 if ((a < 120) && (b < 80)) { msg = "Low-blood"; //System.out.println("低血圧です"); } else if (((a>=120) && (a<140)) || ((b >= 80) &&( b < 90))) { msg = "Normal-Blood"; //System.out.println("正常血圧です"); } else if (((a>=140) && (a<160)) || ((b >= 90) &&( b < 100))) { msg = "High-blood(First)"; //System.out.println("高血圧(1度)です"); }else if (((a>=160) && (a<180)) || ((b >= 100) &&( b < 110))) { msg = "High-blood(Second)"; //System.out.println("高血圧(2度)です"); } else { msg = "High-blood(Critical)"; //System.out.println("高血圧(3度)です"); } //ファイルの書き込み処理 FileWriter fw = null; //書き込みエラーが出た場合、書き込みエラーをメッセージ表示する try { fw = new FileWriter("ketsuatsu.csv",true); fw.write(c + "," + a + "," + b + "," + msg + "\n"); fw.flush(); } catch (IOException e) { System.out.println("ファイル書き込みエラーです"); } finally { //ファイルを閉じる処理。閉じるのに失敗した場合、エラーメッセージを表示する。 if (fw !=null) { try { if (fw != null) { fw.close(); } } catch(IOException e2) { System.out.println("ファイルを閉じるのに失敗しました"); } System.out.println("ファイルの書き込みに成功しました"); } } } } //メインクラス public class Ketsuatsu { public static void main(String[] args) { //上の血圧データオブジェクトを呼び出す top_ketsuatsudata t = new top_ketsuatsudata(); //下の血圧データオブジェクトを呼び出す under_ketsuatsudata u = new under_ketsuatsudata(); //戻り値フィールドを定義する。 int top_blood; int under_blood; //上の血圧を返す。 top_blood = t.t_blood_data(); //下の血圧を返す。 under_blood = u.u_blood_data(); //結果表示メソッドを呼び出す //KEKKAオブジェクトを呼び出す。 kekka k = new kekka(); k.kekka(top_blood,under_blood); } }
- 投稿日:2021-03-21T01:54:41+09:00
Java: EasyBuggy - 30分でわかる!SQLインジェクションの脆弱性
概要
EasyBuggyとEasyBuggy Bootを利用して、SQLインジェクションの脆弱性をシミュレートし、その修正を試みます。
環境構築はこちらの記事から。
注意事項
本記事では脆弱性をついた攻撃手法について解説しています。
しかし、実際に第三者が運用しているWebサービスなどに対して、勝手に脆弱性の検査をおこなうのは違法行為となる可能性が非常に高いです。無害な検査用文字列を送信しているだけのつもりであっても、意図しない破壊を招いたり、監視システムによって攻撃と勘違いされる可能性があります。
絶対にやめましょう。
参考事例: 脆弱性検査について
SQLインジェクションの怖さ
SQLインジェクションの怖さを理解するには、下記の動画がおすすめです。
これは、実際に経済産業省からも注意喚起がされている脆弱性ですね。
脆弱性を確認する
EasyBuggyを起動し、
脆弱性 > SQLインジェクション
を選択します。画面の指示に従い、パスワードに
' OR '1'='1
を入力します。
すると、ほかのユーザーすべての情報が表示できてしまいます。脆弱性を修正する
XSS脆弱性の根本的解決のためには、いくつかの方法があります。
そのうち、今回は「SQL文の組み立ては全てプレースホルダで実装する」という方法での修正を試みます。
ただ、上記のページの文章を読むだけでは、ちょっと意味がつかみにくいかと思います。
実際のコードで見てみましょう。EasyBuggyの場合
SQLInjectionServletクラスに問題となる実装があります。
SQLInjectionServletimport java.sql.Statement; // (中略) Statement stmt = null; // (中略) stmt = conn.createStatement(); rs = stmt.executeQuery("SELECT name, secret FROM users WHERE ispublic = 'true' AND name='" + name + "' AND password='" + password + "'");SQLインジェクションの原因
さきほど確認したSQLインジェクションの原因ですが、このSQL文にパスワードを渡すときの実装に問題があります。
プログラムが実行されると、下記のようなSQL文が実行されます。SELECT name, secret FROM users WHERE ispublic = 'true' AND name='[入力された名前]' AND password='[入力されたパスワード]'このSQL文の
[入力した名前]
をMark
、[入力されたパスワード]
を' OR '1'='1
で置き換えてみます。SELECT name, secret FROM users WHERE ispublic = 'true' AND name='Mark' AND password='' OR '1'='1'SQLの演算子はANDが先に処理されます。
このため、WHERE句のispublic = 'true' AND name='Mark' AND password=''
の部分が先に処理されます。
name='Mark' AND password=''
を満たすレコードはないため、結果はFALSE
となります。SELECT name, secret FROM users WHERE FALSE OR '1'='1'
'1'='1'
の結果はTRUE
です。SELECT name, secret FROM users WHERE FALSE OR TRUE
FALSE OR TRUE
の結果はTRUE
です。SELECT name, secret FROM users WHERE TRUEつまり、すべてのレコード行を返すSQL文になってしまいました。
順を追って修正していきましょう。
1. Statement から PreparedStatementへの変更
SQL文を組み立てるときは、java.sql.Statementではなくjava.sql.PreparedStatementを使いましょう。
StatementからPreparedStatementへの変更import java.sql.PreparedStatement; // import java.sql.Statement; から変更 // (中略) PreparedStatement stmt = null; // Statement stmt = null; から変更 // (中略) stmt = conn.createStatement(); rs = stmt.executeQuery("SELECT name, secret FROM users WHERE ispublic = 'true' AND name='" + name + "' AND password='" + password + "'");2. Connection#createStatement から Connection#prepareStatement への変更
先ほどの変更により、コンパイルエラーとなる箇所を修正します。
Connection#prepareStatementの引数にはとりあえず空文字を入れ、コンパイルエラーが解消されることを確認します。StatementからPreparedStatementへの変更import java.sql.PreparedStatement; // (中略) PreparedStatement stmt = null; // (中略) stmt = conn.prepareStatement(""); // stmt = conn.createStatement(); から変更 rs = stmt.executeQuery("SELECT name, secret FROM users WHERE ispublic = 'true' AND name='" + name + "' AND password='" + password + "'");3. Connection#prepareStatement の引数にSQLを渡す
とりあえずコンパイルエラーは解消しました。
しかし、JavaDocに書いてある通り、
Connection#prepareStatement
の引数にはSQLを渡す必要があります。このため、Statement#executeQuery の引数に渡しているSQLを移動させます。Connection#prepareStatementの引数にSQLを渡すimport java.sql.PreparedStatement; // (中略) PreparedStatement stmt = null; // (中略) stmt = conn.prepareStatement("SELECT name, secret FROM users WHERE ispublic = 'true' AND name='" + name + "' AND password='" + password + "'"); rs = stmt.executeQuery();ここまでは、脆弱性を修正するまでの準備段階です。
4. パラメータ・プレースホルダーを設定する
ここからが、実際に脆弱性を修正する部分になります。
パラメータ・プレースホルダーを設定することで、機械的な処理でSQL文が組み立てられます。これにより、SQLインジェクションの脆弱性を解消できます。
まず、パラメータ部分を
?
に置換します。パラメータ・プレースホルダーを設定するimport java.sql.PreparedStatement; // (中略) PreparedStatement stmt = null; // (中略) stmt = conn.prepareStatement("SELECT name, secret FROM users WHERE ispublic = 'true' AND name=? AND password=?"); rs = stmt.executeQuery();5. 設定したプレースホルダーにパラメータを割り当てる
PreparedStatement#setStringを呼び出して、さきほど設定したプレースホルダーにパラメータを割り当てます。
パラメータの数値は1から始まります。
1番目のプレースホルダーに変数name
の値を、2番目のプレースホルダーに変数password
の値を割り当てます。設定したプレースホルダーにパラメータを割り当てるimport java.sql.PreparedStatement; // (中略) PreparedStatement stmt = null; // (中略) stmt = conn.prepareStatement("SELECT name, secret FROM users WHERE ispublic = 'true' AND name=? AND password=?"); stmt.setString(1, name); stmt.setString(2, password); rs = stmt.executeQuery();ここまできたら、再度、動作を確認してみましょう。
修正方法が正しければ、ほかのユーザーの情報が表示されることはなくなったはずです。EasyBuggy Bootの場合
SQLInjectionServletクラスの実装は、SQLInjectionControllerクラスに移植されています。
Spring Frameworkを利用しているため、PreparedStatementではなくJdbcTemplateを利用してSQLを発行しています。こちらのほうが、簡単に修正することができます。
1. パラメータ・プレースホルダーを設定する
EasyBuggyと同じように、パラメータ・プレースホルダーを設定したSQL文字列に置き換えましょう。
2. 設定したプレースホルダーにパラメータを割り当てる
JdbcTemplate#queryでは、第3引数以降にパラメータを指定できます。
メソッドを呼び出す際の引数として、パラメータを順番に渡しましょう。おつかれさまでした。