20200529のJavaに関する記事は8件です。

Javaのメリット・デメリット

はじめに

JavaというAndroidやゲームに使われたりする素晴らしい言語のメリット・デメリットをまとめました。

※これはJavaについての記事です。JavaScriptは全く別の言語であるので注意してください。

メリット

オブジェクト指向

オブジェクト指向とは、プログラムの可読性をあげるものです。リストに追加するにはaddメソッド(処理のまとまり)を呼び出すという直感的なプログラミングができます。Javaにはクラスなどでオブジェクト指向のプログラミングができます。

リストにアイテムを追加する
list.add(item);

型とは変数の値のタイプ(文字列、数値など)のことです。例えば変数numに数値が入っていると仮定して作られたプログラムがあるとします。しかしそのプログラムを使用する際に間違えて文字列を渡してしまうと実行時に数値でないためエラーがでます。Javaならコンパイルという文法チェックで型が違うと実行できません。面倒かと思いますが、実行前に気づけることはメリットです。

型を使った例
int num;// 数値(Integer)しか入れることができない。また変数は宣言しないと使えない
num = "文字列";// "で囲むと文字列を使える。また整数でないのでエラーを出す

int num2;
num2 = num + 1;// 整数でないと計算できない

一つのコードで複数のOSで動かせる

OSに処理を依存することなくプログラミングができます。

デメリット

記述が長い

記述が長く、少し読みにくいです。学習の場合はjshell(Javaのツール)をつかうことで簡単に学習できると思います。以下は画面にHello!と出力するプログラムです。jsellを実行する場合はターミナルでjshellと入力して開始できます。

ファイルに書いた場合
public class Main {
    public static void main(String[] args) {
        System.out.println("Hello!");
    }
}
jshellの場合
System.out.println("Hello, World");

環境構築が大変

windowsの場合、JavaをインストールするにはOracleのホームページでダウンロードし、インストーラーをし、環境変数に追加しないといけません。初心者には大変な作業です。Ubuntuだとsudo apt install openjdk-jdk-11-headlessで完了できるのでおすすめです。

おわりに

Javaだけに邪魔な言語ではなく、習得することでオブジェクト指向という本質を理解し、プログラミングが簡単だ楽しいことだと気づけるはずです。すでに他の言語を学んでいても試してみてください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Java14×Spring Boot2.3×JUnit5でHelloWorldしたよ〜

はじめに

こんにちは!macでSTSの環境構築を行った者です。
今回は「HelloWorldファイルとHelloWorldのテストコードファイルを作成し、jarに保存してmacで実行してみる」ところまで実施しました。
前回同様、Spring徹底入門を参考に進めました。簡単な手順ですが、バージョンのせいかJUnitで実行する辺りやjarにする辺りで思いの外詰まったので記事にしました。
ぜひ俺の屍を越えていってください。

使用環境とバージョン

  • macOS Catalina
  • jdk14.0.1
  • JUnit5
  • Maven 3.6.3_1
  • STS 4.6.1
  • Spring Boot 2.3

Spring Initializrでプロジェクトの雛形を作る

まずSpring Initializrでプロジェクトの雛形を作成します。
Spring InitializrはSpring Bootが提供しているWebサービスです。必要事項をチェックするだけで、Spring Bootプロジェクト作成の基礎となるディレクトリがzip形式でダウンロードできます。また、メインメソッドの雛形があるDemoApplication.javaと、テストコードの雛形があるDemoApplicationTests.javaが、あらかじめ適切なディレクトリ配下に配置されています。

つまり、ダウンロードするだけで今回の作成物の8割は完成します笑

スクリーンショット 2020-05-20 22.49.42.png

前回Mavenで環境構築を行ったので、ProjectはMavenを選びます。
その他、言語やSpringBootのバージョン、Javaのバージョンなど、自分に合ったものを選択し、GENERATEボタンをクリックすると、demo.zipのダウンロードが始まります。

demo.zipを解凍すると、以下のようなMavenプロジェクトが既に作成されています。すごい!めちゃめちゃ簡単じゃないですか!!

スクリーンショット 2020-05-26 0.47.04.png

解凍したdemoファイルは、環境構築した際に作成したworkspaceディレクトリ配下に移動させておくと進めやすいです。
STSを開き、File > Import > Existing Maven Projectsで、先ほど解凍したdemoフォルダを選択すれば、インポートを行えます。

自動的にビルドが走るので、Package Explorerが以下画像のようにパッケージ表示になるまでしばし待ちます。自動で以下のようにならなかったら、メニューバーからProject > Cleanを押下してください。

スクリーンショット 2020-05-26 1.14.17.png

DemoApplication.javaの編集

ダウンロードしたばかりのDemoApplication.javaの中身は以下のようになっているはずです。

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

}

SpringはMVCモデルのフレームワークです。HelloWorldプロジェクトは最低限の実装で良いので、クライアントからのリクエストを受け取ってレスポンスを返す1、Controller部分のみを実装していきたいと思います。

■DemoApplicationクラスに@Controllerをつける

言わずもがなですが、@Controllerアノテーションをつければ、そのクラスをコントローラーとすることができます。

@RequestMapping@ResponseBodyを付与したメソッドhelloWorld()を作成する

@RequestMapping@ResponseBody、二つのアノテーションを付与することで、戻り値をレスポンスのコンテンツとすることができます。
参考:Spring MVCのコントローラでの戻り値いろいろ

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@SpringBootApplication
@Controller
public class DemoApplication {

    @RequestMapping("/")
    @ResponseBody
    String helloWorld() {
        return "Hello World!";
    }

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

}

保存して、DemoApplicationを右クリック > Run As > Java Applicationと選択してメインメソッドを実行すると、http://localhost:8080 でハロワできるはずです!やったね!

スクリーンショット 2020-05-26 2.12.34.png

上記以外の書き方でも、コントローラーのアノテーションを@Controllerから@RestControllerに変更すれば、helloWorld()のアノテーションは@RequestMappingのみで動きます。

ただし、アノテーションの組み合わせを間違えるとエラーが発生します(やらかしました)。どうやらHTTPリクエストがアプリケーション側で受け取れていないとこの画面になるようで、DemoApplication.javaをダウンロードしたまんまで実行しても同じ画面になるそうです。

スクリーンショット 2020-05-26 1.38.40.png

DemoApplicationTests.javaの編集

DemoApplication.javaの実装に合わせて、テストコードも編集していきます。以下がダウンロードしたまんまのテストコードです。これに手を加えていきます。

package com.example.demo;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class DemoApplicationTests {

    @Test
    void contextLoads() {
    }

}

@SpringApplicationConfigration@WebIntegrationTestの代わりに@SpringBootTestを付与する

前者2つのアノテーションはSpring Boot 1.4から非推奨になったため、@SpringBootTestに置き換えます。
@SpringBootTest@SpringBootApplicationがついているクラスをテスト用のコンフィグレーションクラスとして認識します。

@RunWith(SpringRunner.class)の代わりに@ExtendWith(SpringExtension.class)を使用する

SpringRunnerはJUnit4のものなので、JUnit5で使用するとその後のjar作成時にビルドがうまく通らなくなりました…
JUnit5では@RunWithアノテーション自体が@ExtendWithに置き換えられていたので、そちらに変更しました。JUnit5 @RunWith

@LocalServerPortでポート番号を取得する

Spring Boot 1.3以前では@Value(“${local.server.port}”)で取得していたポート番号ですが、1.4以降@LocalServerPortがショートカットとして追加されています。2.3だと@Valueでポート番号を取得しようとすると以下のエラーが発生して、テスト実行ができませんでした。。

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'com.example.demo.DemoApplicationTests': Unsatisfied dependency expressed through field 'port';
nested exception is org.springframework.beans.TypeMismatchException: Failed to convert value of type 'java.lang.String' to required type 'int'; 
nested exception is java.lang.NumberFormatException: For input string: "${local.server.port"

■assertThatメソッドはorg.junit.Assert.assertThatではなくorg.hamcrest.MatcherAssert.assertThatを使用する

JUnitのassertThatは非推奨と出て、deprecatedを許容するアノテーション@SuppressWarnings(“deprecated”)を付与してもうまくjarのビルドができませんでした…(上記以外が原因かもです)
代わりに、hamcrestのassertThatを使用しました。

最終的なテストコードは以下のようになりました。

package com.example.demo;

import static org.hamcrest.CoreMatchers.is;

import static org.hamcrest.MatcherAssert.assertThat;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.test.context.junit.jupiter.SpringExtension;

@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment=SpringBootTest.WebEnvironment.RANDOM_PORT)
public class DemoApplicationTests {

    TestRestTemplate restTemplate = new TestRestTemplate();

    @LocalServerPort
    int port;

    @Test
    public void testHello() {
        assertThat(restTemplate.getForObject("http://localhost:" + port, String.class), is("Hello World!"));
    }

}

DemoApplicationTests.javaを右クリック > Run As > JUnit Testでテストコードが実行できます。JUnitパネルに以下のように表示されれば、テスト成功です!

スクリーンショット 2020-05-26 3.26.19.png

上記テストコードかDemoApplication.javaのどちらかの"Hello World!"部分を別の言葉に変えて、ちゃんとテストが失敗するかも確認してみてください。

スクリーンショット 2020-05-26 3.28.54.png

実行可能jarを作成する

ここまでの工程で、既にSpring プロジェクトのHelloWorldは出来上がっています。しかし、実際にjarにしてみると、コンパイルやビルドがうまくいかないことに気づけるので、やっておくと吉です!

jarを作成する手順を説明していきます。

①macのターミナルから、ワークスペース配下のdemoディレクトリまで移動する

cd ~/Users/xxx/workspace/demo

②実行可能jarを作成するMavenコマンドを実行する

./mvnw clean package

STS上ではHelloWorldもテストもうまく行っていたとしても、ここでエラーが出ることもあります(自分です)
自分はここで@RunWithが使えないことを知りました。

スクリーンショット 2020-05-26 3.40.11.png

うまくいった場合は、下図のようにBUILD SUCCESSが表示されます。

スクリーンショット 2020-05-26 3.46.31.png

③jarを実行する

無事にビルドが成功したら、demo/target 配下に「demo-0.0.1-SNAPSHOT.jar」ができているはずです。末尾にoriginalとついていない方を実行します。

java -jar /Users/xxx/workspace/demo/target/demo-0.0.1-SNAPSHOT.jar

以下のようにSpringが立ち上がれば成功です! http://localhost:8080 で同じようにHello World!が表示できます。

スクリーンショット 2020-05-26 3.52.32.png

おわりに

冒頭に記した通り、Spring徹底入門を読みながら実施しましたが、ただのHelloWorldを作る際に、同じJavaやIDEでも、バージョンが違うだけでこんなに違うんだなと驚きました…バージョン大事と言われる理由が分かりました…
せっかく雛形を作成したので、次回は簡単にアプリを作成して、テストコードも書いてみたいと思います!

お読みいただきありがとうございました!
ここ違うかもよ〜、という箇所ありましたら、例の如く、そ〜っと教えてください…!


  1. Spring MVCの細かな説明は省きますが、正確にはコントローラーの前にあるフロントコントローラーというサーブレットが、リクエストを受け取ってレスポンスを返しています。ここはフレームワークに任せるため、開発者はコントローラーのみ実装します。 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Minecraft サーバを AWS で作る

Minecraft のサーバを作る。いろんな方が既に記事を書いているので目新しさはない。この組み合わせで動作したという確認。

Minecraft サーバとは

Minecraft はネットワークで通信してマルチプレイができる。マルチプレイをするにはネットワーク内でMinecraftをプレイしているマシンを1台公開してそこに他のプレーヤがつなげるやりかたと、ネットワーク内でサーバ専用サービスを実行するホストを作りサーバにするやりかたの2つがある。

Minecraft でマルチプレイをするには Minecraft の系統と、バージョンを合わせる必要がある。

Minecraft の系統

マルチプレイをするときには、系統を合わせないといけない。今の主な系統は2つ

  • Java 版
  • 統合版 (Bedrock Edition)

系統は、プレイする全員、および、サーバその系統に合わせる必要がある。
なお、現在公開されているサーバのソフトウェアは Java 版系統。
統合版のサーバも公開されている( https://help.minecraft.net/hc/en-us/articles/360035131651-Dedicated-Servers-for-Minecraft-on-Bedrock- ) が、アルファ版。

ちなみに、上記以外の系統も存在する。サーバソフトウェアは公開されていないが、ピア2ピア的にマルチプレイができる。その場合はそれぞれの系統同士でしかマルチプレイできない。

  • ブラウザ版
  • Raspberry Pi edition
  • Earth edition
  • Education edition
  • Legacy console editon (現在、PS4,Nintendo Switch等は統合版にアップデート、それ以外の旧いプラットフォームではLegacy conosole edition 同士でも異機種だとマルチプレイできないぽい)
  • New Nintendo 3DS Edition

  • 他に Minecraft China というのがあるがナンダ? https://minecraft-ja.gamepedia.com/Minecraft_China

バージョン

サーバのバージョンと、マルチプレイの参加者の Minecraft のバージョンを合わせる必要がある。
バージョンはJava版において、2020/5/29現在最新のものは以下の2つ。

  • 1.15.2 (正式版)
  • 20w21a (開発版1.16のスナップショット)

であるが、こちらからダウンロード

https://www.minecraft.net/ja-jp/download/server/

できるサーバのバージョンは正式版の最新のものである 1.15.2。

それ以外の old バージョンや、開発版はこちらから。

https://mcversions.net/

サーバの動作環境

java で動作するので Windows / Mac / Linux で動作する。もちろん、Raspsberry Piも!

サーバを動かすのは難しい?

https://minecraft.gamepedia.com/Tutorials/Setting_up_a_server
によると、

Setting up a server takes some time, and some technical knowledge. Don't try to set up a server unless you have some basic computer and networking abilities.

とあるけれども、試してみたらあっさり動いた。

AWS サーバを起動

まずは TOKYOリージョン、t2.midium を利用して、Ubuntu 20.04を起動する。

ログイン後、

$ sudo apt update
$ sudo apt upgrade 

をしたあと、Java だけインストール。
とりあえずUbuntu20.04で最新のJREを入れてみた。

$ sudo apt install openjdk-14-jre

https://www.minecraft.net/ja-jp/download/server/
からサーバをダウンロード。

image.png

リンクをマウスで右クリックしてアドレスを取得、ssh で以下に貼り付けた。

$ wget https://launcher.mojang.com/v1/objects/bb2b6b1aefcd70dfd1892149ac3a215f6c636b07/server.jar

起動してみる

$ java -Xmx1024M -Xms1024M -jar server.jar nogui
[08:18:47] [main/ERROR]: Failed to load properties from file: server.properties
[08:18:47] [main/WARN]: Failed to load eula.txt
[08:18:47] [main/INFO]: You need to agree to the EULA in order to run the server. Go to eula.txt for more info.

という結果になった。

$ ls -alh

としてみたら、

total 35M
drwxr-xr-x 5 ubuntu ubuntu 4.0K May 28 08:18 .
drwxr-xr-x 3 root   root   4.0K May 28 07:51 ..
-rw-r--r-- 1 ubuntu ubuntu  220 Feb 25 12:03 .bash_logout
-rw-r--r-- 1 ubuntu ubuntu 3.7K Feb 25 12:03 .bashrc
drwx------ 2 ubuntu ubuntu 4.0K May 28 07:56 .cache
-rw-r--r-- 1 ubuntu ubuntu  807 Feb 25 12:03 .profile
drwx------ 2 ubuntu ubuntu 4.0K May 28 07:51 .ssh
-rw-r--r-- 1 ubuntu ubuntu    0 May 28 08:01 .sudo_as_admin_successful
-rw-rw-r-- 1 ubuntu ubuntu  181 May 28 08:18 eula.txt
drwxrwxr-x 2 ubuntu ubuntu 4.0K May 28 08:18 logs
-rw-rw-r-- 1 ubuntu ubuntu  35M Jan 17 10:06 server.jar
-rw-rw-r-- 1 ubuntu ubuntu  940 May 28 08:18 server.properties

とあって、eula.txt ができているようだ。

$ vim eula.txt 

として、中身の

#By changing the setting below to TRUE you are indicating your agreement to our EULA (https://account.mojang.com/documents/minecraft_eula).
#Fri May 29 19:21:06 JST 2020
eula=false

false を true に書き換えて保存。

もう一度起動

$ java -Xmx1024M -Xms1024M -jar server.jar nogui
[08:20:14] [main/WARN]: Ambiguity between arguments [teleport, destination] and [teleport, targets] with inputs: [Player, 0123, @e, dd12be42-52a9-4a91-a8a1-11c01849e498]
[08:20:14] [main/WARN]: Ambiguity between arguments [teleport, location] and [teleport, destination] with inputs: [0.1 -0.5 .9, 0 0 0]
[08:20:14] [main/WARN]: Ambiguity between arguments [teleport, location] and [teleport, targets] with inputs: [0.1 -0.5 .9, 0 0 0]
[08:20:14] [main/WARN]: Ambiguity between arguments [teleport, targets] and [teleport, destination] with inputs: [Player, 0123, dd12be42-52a9-4a91-a8a1-11c01849e498]
[08:20:14] [main/WARN]: Ambiguity between arguments [teleport, targets, location] and [teleport, targets, destination] with inputs: [0.1 -0.5 .9, 0 0 0]
[08:20:14] [Server thread/INFO]: Starting minecraft server version 1.15.2
[08:20:14] [Server thread/INFO]: Loading properties
[08:20:14] [Server thread/INFO]: Default game type: SURVIVAL
[08:20:14] [Server thread/INFO]: Generating keypair
[08:20:15] [Server thread/INFO]: Starting Minecraft server on *:25565
[08:20:15] [Server thread/INFO]: Using epoll channel type
[08:20:15] [Server thread/INFO]: Preparing level "world"
[08:20:15] [Server thread/INFO]: Found new data pack vanilla, loading it automatically
[08:20:15] [Server thread/INFO]: Reloading ResourceManager: Default
[08:20:47] [Server thread/INFO]: Loaded 6 recipes
[08:20:47] [Server thread/INFO]: Loaded 825 advancements
[08:20:53] [Server thread/INFO]: Preparing start region for dimension minecraft:overworld
[08:20:53] [Server-Worker-1/INFO]: Preparing spawn area: 0%
[08:20:53] [Server-Worker-1/INFO]: Preparing spawn area: 0%
[08:20:54] [Server-Worker-1/INFO]: Preparing spawn area: 0%
[08:20:54] [Server-Worker-1/INFO]: Preparing spawn area: 0%
[08:20:55] [Server-Worker-1/INFO]: Preparing spawn area: 1%
[08:20:55] [Server-Worker-1/INFO]: Preparing spawn area: 2%
[08:20:56] [Server-Worker-1/INFO]: Preparing spawn area: 4%
[08:20:56] [Server-Worker-1/INFO]: Preparing spawn area: 5%
[08:20:57] [Server-Worker-1/INFO]: Preparing spawn area: 7%
[08:20:57] [Server-Worker-1/INFO]: Preparing spawn area: 10%
[08:20:58] [Server-Worker-1/INFO]: Preparing spawn area: 12%
[08:20:58] [Server-Worker-1/INFO]: Preparing spawn area: 14%
[08:20:59] [Server-Worker-1/INFO]: Preparing spawn area: 17%
[08:20:59] [Server-Worker-1/INFO]: Preparing spawn area: 19%
[08:21:00] [Server-Worker-1/INFO]: Preparing spawn area: 22%
[08:21:00] [Server-Worker-1/INFO]: Preparing spawn area: 24%
[08:21:01] [Server-Worker-1/INFO]: Preparing spawn area: 26%
[08:21:01] [Server-Worker-1/INFO]: Preparing spawn area: 29%
[08:21:02] [Server-Worker-1/INFO]: Preparing spawn area: 32%
[08:21:02] [Server-Worker-1/INFO]: Preparing spawn area: 33%
[08:21:03] [Server-Worker-1/INFO]: Preparing spawn area: 36%
[08:21:03] [Server-Worker-1/INFO]: Preparing spawn area: 38%
[08:21:04] [Server-Worker-1/INFO]: Preparing spawn area: 41%
.
.
.

起動したようだ。

ポート公開

TCP25565 と UDP25565 を開放したら、外部から接続し、プレイできた。

image.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PDFテキストコンテンツを抽出するJava

日常の作業では、巨大なPDFドキュメントに含まれているテキストコンテンツを抽出する必要がある場合があります。そして、Free Spire.PDF for Javaは、便利で高速なテキスト抽出方法を提供します、次に、プロセスで使用されるJavaコードを紹介します。

基本的な手順:
1. Free Spire.PDF for Javaパッケージをダウンロードして解凍します。
2. libフォルダーのSpire.Pdf.jarパッケージを依存関係としてJavaアプリケーションにインポートするか、MavenリポジトリーからJARパッケージをインストールします(pom.xmlファイルを構成するコードについては、以下を参照してください)。
3. Javaアプリケーションで、新しいJava Class(ここではExtractTextという名前)を作成し、対応するJavaコードを入力して実行します。

pom.xmlファイルを構成します:

<repositories>
   <repository>
      <id>com.e-iceblue</id>
      <name>e-iceblue</name>
      <url>http://repo.e-iceblue.com/nexus/content/groups/public/</url>
   </repository>
</repositories>
<dependencies>
   <dependency>
      <groupId>e-iceblue</groupId>
      <artifactId>spire.pdf.free</artifactId>
      <version>2.6.3</version>
   </dependency>
</dependencies>

PDFソースドキュメントは次のとおりです:
sample.jpg

Javaコード:

import com.spire.pdf.PdfDocument;
import com.spire.pdf.PdfPageBase;
import java.io.*;

public class ExtractText {

    public static void main(String[] args) {

        //PdfDocumentインスタンスを作成する
        PdfDocument doc = new PdfDocument();
        //PDFファイルをロード
        doc.loadFromFile("雪.pdf");

        //StringBuilderインスタンスを作成する
        StringBuilder sb = new StringBuilder();

        PdfPageBase page;
        //PDFページをトラバースし、各ページのテキストを取得して、StringBuilderオブジェクトに追加します
        for(int i= 0;i<doc.getPages().getCount();i++){
            page = doc.getPages().get(i);
            sb.append(page.extractText(true));
        }
        FileWriter writer;
        try {
            //StringBuilderオブジェクトのテキストをテキストファイルに書き込みます
            writer = new FileWriter("ExtractText.txt");
            writer.write(sb.toString());
            writer.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }

        doc.close();
    }
}

結果を抽出する:
text.jpg

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Angular+Rest(Java)でCORS対応

Angularクライアントからドメインの異なるRestサーバー(Java)へアクセスしようとするとCORS問題によってエラーとなります。
その対策を以下に記述します。
(サーバー側のRest API処理は通常と変わらないため省略します)

Angular側のサーバーへリクエストする処理

import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root',
})
export class ClientService {
  url = "http://XXXXX:XXXX/XXXXX"; // アクセス先

  returnModel : ReturnModel; // サーバーからの戻り値

  // コンストラクタ
  constructor(private http: HttpClient) {}

  // 送信メソッド
  request(): void {
    // Httpヘッダー(CORS対応でAccess-Control-Allow-Originを指定する)
    const httpOptions = {
      headers: new HttpHeaders({
        'Access-Control-Allow-Origin': '*',
      })
     };

    // 送信データ
    const data = new HttpParam({
      fromObject: {
        value: '1000"
      }
    });

    // サーバーへPOST
    this.http.post<ReturnModel>(this.url , data, httpOptions).subscribe(h => this.returnModel = h);
  }
}

サーバー側のServlet Filter

package sample.filter;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletResponse;

// サーブレットフィルタ
@WebFilter(filterName = "CorsFilter", urlPatterns = { "/*" })
public class CorsFilter implements Filter {
  @Override
  public void init(FilterConfig config) {
  }

  // フィルター処理
  @Override
  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    if (response instanceof HttpServletResponse) {
      // 以下の通りCORS対応のためHeaderwo指定する
      HttpServletResponse http = (HttpServletResponse) response;
      http.addHeader("Access-Control-Allow-Origin", "*");
      http.addHeader("Access-Control-Allow-Headers", "*");
      http.addHeader("Access-Control-Allow-Credentials", "true");
      http.addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, HEAD");
    }
    chain.doFilter(request, response);
  }

  @Override
  public void destroy() {
  }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

eclipseショートカット表

はじめに

OSはWindows10
基本はsts4で動作確認していますが、EclipseやPleiadesでも動くはず。
ショートカットは調べればいくらでも出てくるけど、個人的にパッと見られるいい感じのサイトがなかったので雑に表を作っただけ。
確証はないけどmacOSならCtrlをcommand、Altをoptionに換えればほとんどは動くはず。

そもそも自分が使ってよかったものを載せているけど、個人的に特に使い勝手が良かったもの、使用頻度が高いものは★つけたので参考になれば嬉しいです。

エディタ内

コピペと削除

 ショートカット     キー       補足   
全選択 Ctrl + A
コピー Ctrl + C 任意の箇所を選択
ペースト Ctrl + V 任意の箇所を選択
カット Ctrl + X 任意の箇所を選択
★行コピペ Ctrl + Alt + ↓ 1行下にペーストされる
★行削除 Ctrl + D
キャレットのから行末まで削除 Ctrl + Shift + Delete

コードの見た目を整える

 ショートカット     キー       補足   
行ごと上/下移動 Alt + ↑/↓
★インデント整理 Ctrl + Shift + F
右にインデント tab 複数行選択も可能
左にインデント Shift + tab 複数行選択も可能

作業効率向上

 ショートカット     キー       補足   
テンプレート・プロポーザル Ctrl + space コード記述補助。ググるの推奨
直近に記述したクラスをimport Ctrl + Shift + M
import編成 Ctrl + Shift + O 複数クラスをimport、不要import削除
★quick fixes表示 Ctrl + 1 コンパイルエラーの修正
★次の警告・エラーに移動 Ctrl + .
メソッドの型変更 Shift + Alt + C 参照元なども勝手に変わる
メソッドの呼び出し階層を開く Ctrl + Alt + H 定義の中や宣言の上で押す。コードの解読に◎
名前変更 Shift + Alt + R 変数・メソッド両方可。呼び出し先も変わる
★キャレット移動 Ctrl + ←/→ 単語ごと等に移動。Shiftと組み合わせると◎
★コメントにする/戻す Ctrl + / 任意の箇所を選択
★対応する括弧行き来 Ctrl + Shift + P
★選択範囲の拡大/縮小 Shift + Alt + ↑/↓ まとまりごとに自動選択してくれる

エディタ外

ファイル・タブ

 ショートカット     キー       補足   
★元に戻す Ctrl + Z
やり直し Ctrl + Y 戻しすぎたものを進める
★ファイル保存 Ctrl + S
全ファイルを保存 Ctrl + Shift + S
ファイルを閉じる Ctrl + W
全ファイルを閉じる Ctrl + Shift + W
★編集タブを戻る/進む Alt + ←/→

検索

 ショートカット     キー       補足   
ファイル検索 Alt + Aを押してF
Javaファイル検索 Alt + H
★編集タブを検索して移動 Ctrl + E タブのリストが出てくるので選択して移動
文字列検索 Shift + F
型検索 Ctrl + Shift + F
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Bypass JEP290

关于JEP290

JEP290是Java底层为了缓解反序列化攻击提出的一种解决方案,主要做了以下几件事

  1. 提供一个限制反序列化类的机制,白名单或者黑名单。
  2. 限制反序列化的深度和复杂度。
  3. 为RMI远程调用对象提供了一个验证类的机制。
  4. 定义一个可配置的过滤机制,比如可以通过配置properties文件的形式来定义过滤器。

JEP290的实际限制

写一个RMIServer

RMIServer.java

package org.chabug.rmi.server;

import java.rmi.Naming;
import java.rmi.registry.LocateRegistry;

public class RMIServer {
    public static String HOST = "127.0.0.1";
    public static int PORT = 1099;
    public static String RMI_PATH = "/hello";
    public static final String RMI_NAME = "rmi://" + HOST + ":" + PORT + RMI_PATH;

    public static void main(String[] args) {
        try {
            // 注册RMI端口
            LocateRegistry.createRegistry(PORT);
            // 创建一个服务
            Hello hello = new HelloImpl();
            // 服务命名绑定
            Naming.rebind(RMI_NAME, hello);

            System.out.println("启动RMI服务在" + RMI_NAME);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

HelloImpl.java

package org.chabug.rmi.server;

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

public class HelloImpl extends UnicastRemoteObject implements Hello {
    protected HelloImpl() throws RemoteException {
    }

    public String hello() throws RemoteException {
        return "hello world";
    }

    public String hello(String name) throws RemoteException {
        return "hello" + name;
    }

    public String hello(Object object) throws RemoteException {
        System.out.println(object);
        return "hello "+object.toString();
    }
}

Hello.java

package org.chabug.rmi.server;


import java.rmi.Remote;
import java.rmi.RemoteException;

public interface Hello extends Remote {
    String hello() throws RemoteException;
    String hello(String name) throws RemoteException;
    String hello(Object object) throws RemoteException;
}

使用JDK7u21打Commonscollections1,成功弹出calc。
image.png

使用JDK8u221启动RMIServer攻击失败
image.png
报错显示

ObjectInputFilter REJECTED: class sun.reflect.annotation.AnnotationInvocationHandler, array length: -1, nRefs: 8, depth: 2, bytes: 298, ex: n/a

JEP290的过滤机制

在上文的报错中可见sun.reflect.annotation.AnnotationInvocationHandler被拒绝,跟一下RMI的过程,看下在哪里过滤了,JEP290又是怎么实现的。以下使用JDK8U221调试

首先我们要清楚RMI的实现流程
image.png
在远程引用层中客户端服务端两个交互的类分别是RegistryImpl_StubRegistryImpl_Skel,在服务端的RegistryImpl_Skel类中,向注册中心进行bind、rebind操作时均进行了readObject操作以此拿到Remote远程对象引用。
image.png

跟进63行进入到java.io.ObjectInputStream#readObject,然后进入readObject0()
image.png
在readObject0()之中进入readOrdinaryObject()
image.png
继续进入readClassDesc()
image.png
进入readProxyDesc()
image.png

在readProxyDesc()中有filterCheck
image.png

先检查其所有接口,然后检查对象自身。进入filterCheck()之后
image.png
调用了serialFilter.checkInput(),最终来到sun.rmi.registry.RegistryImpl#registryFilter
image.png

return String.class != var2 && !Number.class.isAssignableFrom(var2) && !Remote.class.isAssignableFrom(var2) && !Proxy.class.isAssignableFrom(var2) && !UnicastRef.class.isAssignableFrom(var2) && !RMIClientSocketFactory.class.isAssignableFrom(var2) && !RMIServerSocketFactory.class.isAssignableFrom(var2) && !ActivationID.class.isAssignableFrom(var2) && !UID.class.isAssignableFrom(var2) ? Status.REJECTED : Status.ALLOWED;

image.png
没有给AnnotationInvocationHandler白名单,所以返回REJECTED。

绕过JEP290

在RMI远程方法调用过程中,方法参数需要先序列化,从本地JVM发送到远程JVM,然后在远程JVM上反序列化,执行完后,将结果序列化,发送回本地JVM,而在本地的参数是我们可以控制的,如果向参数中注入gadget会怎么样?

我在HelloImpl实现了三个hello()方法,分别是void、string、Object类型的参数
image.png

在客户端我向Object参数类型注入cc5的gadget
image.png

运行成功弹出calc
image.png

也就是说:如果目标的RMI服务暴漏了Object参数类型的方法,我们就可以注入payload进去。

那么别的参数类型呢?在sun.rmi.server.UnicastRef#unmarshalValue中判断了远程调用方法的参数类型
image.png
如果不是基本类型,就进入readObject,之后的流程也走了filterCheck过滤
image.png
不过在sun.rmi.transport.DGCImpl#checkInput这里ObjID是在白名单中的,所以可以被反序列化。

那这个只是object类型的参数可以,其他的参数类型呢?

由于攻击者可以完全控制客户端,因此他可以用恶意对象替换从Object类派生的参数(例如String)有几种方法:

  1. 将java.rmi软件包的代码复制到新软件包,然后在其中更改代码
  2. 将调试器附加到正在运行的客户端,并在序列化对象之前替换对象
  3. 使用Javassist之类的工具更改字节码
  4. 通过实现代理来替换网络流上已经序列化的对象

afanti师傅用的是通过RASP hook住java.rmi.server.RemoteObjectInvocationHandler类的InvokeRemoteMethod方法的第三个参数非Object的改为Object的gadget。他的项目地址在RemoteObjectInvocationHandler

修改src\main\java\afanti\rasp\visitor\RemoteObjectInvocationHandlerHookVisitor.java的dnslog地址,然后打包出来在RMIClient运行前加上-javaagent:e:/rasp-1.0-SNAPSHOT.jar

虽然报错参数类型不匹配
image.png

但是dnslog已经收到请求了。

image.png

参考

  1. https://mogwailabs.de/blog/2019/03/attacking-java-rmi-services-after-jep-290/
  2. https://www.anquanke.com/post/id/200860
  3. https://github.com/Afant1/RemoteObjectInvocationHandler
  4. https://paper.seebug.org/454/
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SpringBootでStrategyパターンを適用する

はじめに

SpringBootでStrategy パターンを適用したサンプルコードを書いてみました。
Strategy パターンについては IT専科さんの解説記事 などを参照してください。
サンプルコードは GitHub に公開しています。

動作環境

  • Java 11
  • SpringBoot 2.3.0

題材

大学の図書館を題材にしています。ユーザは本の貸出、返却ができます。またユーザは役職によって借りられる本の数が変化することとします。今回、Strategyにあてはめるのはユーザの役職によって変化する処理の部分です。サンプルコードでは教授、院生、学部生の場合を実装しています。
Strategyを利用する(呼び出す)側はFactoryに依頼をして目的にあったStrategyを取得します。

クラス図

クラス図

インターフェイスの定義

インターフェイスでは、各役職に依存した処理を定義しています。ユーザごとに借りれる本の上限が決まるわけではなく、役職ごとに決定するので上限冊数を返却するメソッドも定義します。また、役職を識別するEnumである PositionName を返却するメソッドも定義します。
borrowBookreturnBook で引数としている UserBook は単なるPOJOです。詳しくは GitHub を参照してください。

BorrowerStrategy.java
public interface BorrowerStrategy {

    int getMaxBorrowNum();

    User borrowBook(User user, Book book);

    User returnBook(User user, Book book);

    PositionName getPositionName();
}
PositionName.java
public enum PositionName {
    PROFESSOR,
    GRADUATE_STUDENT,
    COLLEGE_STUDENT
}

Strategyの実装

先程定義したインターフェイスを実装します。教授と院生の場合を例として挙げます。今回は実装が同じメソッドが目立ちますが、権限によって借りられる本の種類が制限されたりすると更にありがたみを感じることでしょう。
後述しますが、SpringによるDIを利用したいので @Component アノテーションをクラスに付与します。

ProfessorStrategy.java
@Component
public class ProfessorStrategy implements BorrowerStrategy {

    @Override
    public int getMaxBorrowNum() {
        return 50;
    }

    @Override
    public User borrowBook(User user, Book book) {
        if (user.getCurrentNum() >= getMaxBorrowNum()) {
            throw new CannotBorrowBookException();
        }
        final var books = new ArrayList<>(user.getBooks());
        books.add(book);
        return new User(user.getName(), user.getPositionName(), books);
    }

    @Override
    public User returnBook(User user, Book book) {
        if (user.getCurrentNum() == 0 || !user.getBooks().contains(book)) {
            throw new CannotReturnBookException(book.getName());
        }
        final var books = new ArrayList<>(user.getBooks());
        books.remove(book);
        return new User(user.getName(), user.getPositionName(), books);
    }


    @Override
    public PositionName getPositionName() {
        return PositionName.PROFESSOR;
    }
}
GraduateStudentStrategy.java
@Component
public class GraduateStudentStrategy implements BorrowerStrategy {
    @Override
    public int getMaxBorrowNum() {
        return 30;
    }

    @Override
    public User borrowBook(User user, Book book) {
        if (user.getCurrentNum() >= getMaxBorrowNum()) {
            throw new CannotBorrowBookException();
        }
        final var books = new ArrayList<>(user.getBooks());
        books.add(book);
        return new User(user.getName(), user.getPositionName(), books);
    }

    @Override
    public User returnBook(User user, Book book) {
        if (user.getCurrentNum() == 0 || !user.getBooks().contains(book)) {
            throw new CannotReturnBookException(book.getName());
        }
        final var books = new ArrayList<>(user.getBooks());
        books.remove(book);
        return new User(user.getName(), user.getPositionName(), books);
    }


    @Override
    public PositionName getPositionName() {
        return PositionName.GRADUATE_STUDENT;
    }
}

Factoryの実装

Factoryは主にサービスから呼ばれることを想定しているので、 @Component アノテーションをクラスに付与します。このクラスは Set<BorrowStrategy> brrorwerStrategySet をAutowireしているのですが、これを記述することで BrrorwerStrategyを継承したBeanのSetを注入することができます

BorrowerStrategyFactory.java
@Component
public class BorrowerStrategyFactory {

    private Map<PositionName, BorrowerStrategy> borrowerStrategyMap;

    @Autowired
    public BorrowerStrategyFactory(Set<BorrowerStrategy> borrowerStrategySet) {
        createStrategy(borrowerStrategySet);
    }

    public BorrowerStrategy findStrategy(PositionName positionName) {
        return borrowerStrategyMap.get(positionName);
    }

    private void createStrategy(Set<BorrowerStrategy> borrowerStrategySet) {
        borrowerStrategyMap = borrowerStrategySet.stream()
                .collect(Collectors.toMap(BorrowerStrategy::getPositionName, borrowerStrategy -> borrowerStrategy));
    }
}

実装の確認

今回の実装をユニットテストで確認していきます。ここでは一部をご紹介します。

BorrowerStrategyFactoryTest.java
@SpringBootTest
public class BorrowerStrategyFactoryTest {

    static final String PROFESSOR = "Professor";
    static final String GRADUATE_STUDENT = "GraduateStudent";
    static final String COLLEGE_STUDENT = "CollegeStudent";

    @Autowired
    BorrowerStrategyFactory borrowerStrategyFactory;

    @Test
    void canGetAllStrategy() {
        final var professorStrategy = borrowerStrategyFactory.findStrategy(PositionName.PROFESSOR);
        assertThat(professorStrategy).isInstanceOf(ProfessorStrategy.class);
        final var graduateStudentStrategy = borrowerStrategyFactory.findStrategy(PositionName.GRADUATE_STUDENT);
        assertThat(graduateStudentStrategy).isInstanceOf(GraduateStudentStrategy.class);
        final var collegeStudentStrategy = borrowerStrategyFactory.findStrategy(PositionName.COLLEGE_STUDENT);
        assertThat(collegeStudentStrategy).isInstanceOf(CollegeStudentStrategy.class);
    }

    @ParameterizedTest
    @MethodSource
    void successBorrowBook(User user) {
        final var borrowerStrategy = borrowerStrategyFactory.findStrategy(user.getPositionName());
        user = borrowerStrategy.borrowBook(user, new Book("Book1"));
        assertThat(user.getCurrentNum()).isEqualTo(1);
    }

    static Stream<Arguments> successBorrowBook() {
        return Stream.of(
            Arguments.arguments(new User(PROFESSOR, PositionName.PROFESSOR)),
            Arguments.arguments(new User(GRADUATE_STUDENT, PositionName.GRADUATE_STUDENT)),
            Arguments.arguments(new User(COLLEGE_STUDENT, PositionName.COLLEGE_STUDENT))
        );
    }

    // 中略

    @ParameterizedTest
    @MethodSource
    void failedReturnBookWhenNotFoundBook(User user) {
        final var borrowerStrategy = borrowerStrategyFactory.findStrategy(user.getPositionName());
        final var failedUser = borrowerStrategy.borrowBook(user, new Book("Book1"));
        assertThrows(CannotReturnBookException.class, () -> borrowerStrategy.returnBook(failedUser, new Book("Book2")));
        assertThat(failedUser.getCurrentNum()).isEqualTo(1);
    }

    static Stream<Arguments> failedReturnBookWhenNotFoundBook() {
        return Stream.of(
            Arguments.arguments(new User(PROFESSOR, PositionName.PROFESSOR)),
            Arguments.arguments(new User(GRADUATE_STUDENT, PositionName.GRADUATE_STUDENT)),
            Arguments.arguments(new User(COLLEGE_STUDENT, PositionName.COLLEGE_STUDENT))
        );
    }

}

結果

???
テスト結果

Strategy パターンを適用するメリット

  • 同じようなswitch文が乱立しない
  • パターンの増減にも対応しやすい

2つ目のメリットに関しては、いらなくなったらそのクラスを削除することで対応できます。今回のケースでは「期間限定で市民が本を借りれるようにしたい」といった要望に答えることができます。

さいごに

Strategy パターンを適用するとswitch文の多用で煩雑になりそうなケースが回避できる場合があります。Strategy パターンに似たデザインパターンであるState パターンもあるので、気になる方は調べてみてください。

ソースコード

GitHub

参考サイト

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む