かんがるーさんの日記

最近自分が興味をもったものを調べた時の手順等を書いています。今は Spring Boot をいじっています。

Spring Boot で書籍の貸出状況確認・貸出申請する Web アプリケーションを作る ( その7 )( ログイン画面の作成 )

概要

Spring Boot で書籍の貸出状況確認・貸出申請する Web アプリケーションを作る ( その6 )( URL の決定 ) の続きです。

  • ログイン画面の作成は以下の手順で進める予定です。
    1. HTMLファイルの作成
    2. ログイン機能 ( 基本的な部分 ) の作成
    3. パスワードを5回間違えた場合には ID をロックする機能の作成
    4. ID の有効期限を過ぎている場合にはログインできないようにする機能の作成
    5. パスワードの有効期限を過ぎている場合にはログインできないようにする機能の作成
    6. 「次回から自動的にログインする」機能の作成
  • 今回の手順で確認できるのは以下の内容です。
    • HTMLファイルの作成

参照したサイト・書籍

  1. AdminLTE Dashboard & Control Panel Template
    https://almsaeedstudio.com/

  2. Font Awesome Icons
    http://fortawesome.github.io/Font-Awesome/

  3. Ionicons
    http://ionicons.com/

  4. gradle でファイルダウンロードを自動化する
    http://etc9.hatenablog.com/entry/2015/02/25/183902

  5. Gradle Download Task
    https://github.com/michel-kraemer/gradle-download-task

  6. Gradle 徹底入門

    Gradle徹底入門 次世代ビルドツールによる自動化基盤の構築

    Gradle徹底入門 次世代ビルドツールによる自動化基盤の構築

    • css/fonts/js ダウンロード・配置タスクを作成する際に「13.1.1 メソッド、クラスの抽出」を参照しました。

目次

  1. はじめに
  2. 1.0.x-make-login-html ブランチの作成
  3. static ディレクトリの作成
  4. AdminLTE Dashboard & Control Panel Template のダウンロード、ファイルの配置
  5. Font Awesome Icons, Ionicons, html5shiv.js, respond.min.js のダウンロード、ファイルの配置
  6. gradle で static ディレクトリの作成 ~ css/fonts/js のダウンロード、ファイルの配置を自動化できるか?
  7. AdminLTE Dashboard & Control Panel Template の表示の確認
  8. 画面作成用のテンプレートHTMLファイルの作成
  9. ログイン画面の作成
  10. commit、Push、Pull Request、マージ

手順

はじめに

ksbysample-webapp-email の時と同様に AdminLTE を利用して画面を作成します。ただしログイン画面のレイアウトは ksbysample-webapp-basic の時のものをベースに作成します。

1.0.x-make-login-html ブランチの作成

  1. IntelliJ IDEA で 1.0.x-make-login-html ブランチを作成します。

static ディレクトリの作成

  1. src/main/resources の下に static ディレクトリを作成します。

AdminLTE Dashboard & Control Panel Template のダウンロード、ファイルの配置

  1. AdminLTE Dashboard & Control Panel Template のホームページにアクセスします。 https://almsaeedstudio.com/

  2. 画面右側の画面キャプチャの下にある「DOWNLOAD(~7 Mb)」ボタンをクリックします。

  3. AdminLTE-2.2.0.zip がダウンロードされるので PC に保存します。

  4. AdminLTE-2.2.0.zip を解凍します。

  5. 解凍して作成された AdminLTE-2.2.0\bootstrap の下にある css, fonts, js ディレクトリを src/main/resources/static ディレクトリの下にコピーします。

  6. 解凍して作成された AdminLTE-2.2.0\dist の下にある css, js ディレクトリ ( img ディレクトリはコピーしません ) を src/main/resources/static ディレクトリの下にコピーします。ただし以下のディレクトリ、ファイルは削除します。

    • src/main/resources/static/js/pages/
    • src/main/resources/static/js/demo.js
  7. AdminLTE-2.2.0\plugins\jQuery の下にある jQuery-2.1.4.min.js を src/main/resources/static/js ディレクトリの下にコピーします。

Font Awesome Icons, Ionicons, html5shiv.js, respond.min.js のダウンロード、ファイルの配置

  1. 解凍して作成された AdminLTE-2.2.0 の下の starter.html ( このファイルが AdminLTE を使用する時のテンプレートのようです ) の中を見ると他にも必要なファイルがあるので、それもダウンロードします ( CDN は利用しません )。以下にダウンロード先の URL ( starter.html の中に書かれているものをそのまま使います ) と配置先を記載します。

  2. Font Awesome Icons のホームページにアクセスします。画面上部の「Download」ボタンをクリックします。
    http://fortawesome.github.io/Font-Awesome/

  3. font-awesome-4.3.0.zip がダウンロードされるので PC に保存します。解凍後、以下のファイルを src/main/resources/static の下へコピーします。

    • css/font-awesome.min.css
    • fonts/FontAwesome.otf
    • fonts/fontawesome-webfont.eot
    • fonts/fontawesome-webfont.svg
    • fonts/fontawesome-webfont.ttf
    • fonts/fontawesome-webfont.woff
    • fonts/fontawesome-webfont.woff2

    ※fonts ディレクトリの下は全てのファイルをコピーしています。

  4. Ionicons のホームページにアクセスします。画面上部の「Download」ボタンをクリックします。
    http://ionicons.com/

  5. ionicons-2.0.1.zip がダウンロードされるので PC に保存します。解凍後、以下のファイルを src/main/resources/static の下へコピーします。

    • css/ionicons.min.css
    • fonts/ionicons.eot
    • fonts/ionicons.svg
    • fonts/ionicons.ttf
    • fonts/ionicons.woff

    ※fonts ディレクトリの下は全てのファイルをコピーしています。

  6. 配置後は以下のディレクトリ・ファイル構成になります。

    f:id:ksby:20150711071439p:plain

    f:id:ksby:20150711071607p:plain

    f:id:ksby:20150711071751p:plain

    f:id:ksby:20150711072121p:plain

Gradle で static ディレクトリの作成 ~ css/fonts/js のダウンロード、ファイルの配置を自動化できるか?

  1. Gradle で自動化できるか調査してみたところ、以下のような感じでした。実現可能なようなので試してみたいと思います。

    • Groovy で書いてファイルをダウンロードすることも可能だが、Gradle Download Task という plugin を利用すると簡単にできるらしい。
    • zip の解凍及びファイルのコピーは Gradle の標準機能で対応可能。
  2. 一旦 commit します。「Code Analysis」ダイアログが出ますが、無視して「Commit」ボタンをクリックします。

  3. src/main/resources/static を削除します。

  4. build.gradle を リンク先の内容 に変更します。

  5. Gradle projects View の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。

  6. Gradle projects View の「ksbysample-webapp-lending」-「Tasks」-「other」の下に「downloadCssFontsJs」タスクが表示されますので、ダブルクリックして実行します。

    f:id:ksby:20150711185802p:plain

  7. Run View に "BUILD SUCCESSFUL" のログが表示されます。

    f:id:ksby:20150711190305p:plain

    src/main/resources の下を見ると static ディレクトリが作成され、その下に css, fonts, js ディレクトリとファイルが作成されていました。

    f:id:ksby:20150711191342p:plain

  8. 問題なく自動化できました。この時点で commit しておきます。

AdminLTE Dashboard & Control Panel Template の表示の確認

AdminLTE Dashboard & Control Panel Template を使用した画面が正常に表示されるかダウンロードした AdminLTE-2.2.0.zip の中にある starter.html を使用して確認します。

  1. src/main/resources/static の下に starter.html を配置した後、リンク先の内容 に変更します。

  2. Gradle projects View から bootRun タスクを実行して Tomcat を起動します。

  3. ブラウザを起動して http://localhost:8080/starter.html にアクセスしたところ、画面が表示されませんでした。ksbysample-webapp-email の時はこれで html ファイルの内容が表示されたのですが。。。 原因を調査してみます。

    f:id:ksby:20150711194605p:plain

  4. いろいろ試してみたところ以下の結果でした。

    • http://localhost:8080/css/AdminLTE.css にアクセスすると css ファイルの内容が表示されました。
    • starter.html を css ディレクトリの下に移動して http://localhost:8080/css/starter.html でアクセスすると今度は画面が表示されました。
    • /css の下では html が表示されて / の下だと表示されないことから、spring-boot-starter-security を build.gradle に書いていたことが原因であることに気づきました。spring-boot-starter-security を書いていると /css, /img, /js 以外には認証がかかることを忘れていました。
  5. 他にも html を作成して確認することはあるので、src/main/resources/static の下に html ディレクトリを作成し、その中の html は認証なしで表示されるよう設定を変更します。src/main/java/ksbysample/webapp/lending/config の下の WebSecurityConfig.javaリンク先の内容 に変更します。

  6. starter.html を src/main/resources/static/html の下へ移動します。

  7. Ctrl+F5 を押して Tomcat を再起動します。

  8. ブラウザから http://localhost:8080/html/starter.html にアクセスします。今度は画面が表示されました。

    f:id:ksby:20150711203715p:plain

画面作成用のテンプレートHTMLファイルの作成

starter.html は左側にメニューが出る構成ですが、IE11 の場合に画面を更新するたびにメニューの部分がひょこひょこ動く現象を解消する方法が分かりませんでした。

今回は Top Navigation の構成で画面を作成します。
https://almsaeedstudio.com/themes/AdminLTE/pages/layout/top-nav.html

画面はログイン画面以外にも作成しますので、まずはテンプレートとして使用できる HTML ファイルを作成します。

  1. src/main/resources/static/html の下に template.html を新規作成します。作成後、リンク先の内容 に変更します。

  2. ブラウザで http://localhost:8080/html/template.html にアクセスすると以下の画面が表示されます。

    f:id:ksby:20150712011743p:plain

ログイン画面の作成

  1. src/main/resources/static/html の template.html をコピー&ペーストして login.html を新規作成します。作成後、リンク先の内容 に変更します。

  2. ブラウザで http://localhost:8080/html/login.html にアクセスすると以下の画面が表示されます。

    f:id:ksby:20150712012830p:plain

commit、Push、Pull Request、マージ

  1. Ctrl+F2 を押して Tomcat を停止します。

  2. commit します。「Code Analysis」ダイアログが出ますが、無視して「Commit」ボタンをクリックします。

  3. 何回か commit しているので1つにまとめます。コマンドラインから以下のコマンドを実行します。

    > git log
    * まとめる対象の commit を確認します。
    > git rebase -i HEAD~3
    * 最初の行のみ pick のままにし、残りの2つを fixup に変更します。
    > git commit --amend -m "#3 ログイン画面のHTMLファイルを作成しました。"
    * commit のメッセージを変更します。
    * メッセージの先頭に # から始まる issue の番号を付けたいので、-m オプションを付けてメッセージを指定します。
    > git log
    * 1つにまとまっていることを確認します。

  4. GitHub へ Push、1.0.x-make-login-html -> 1.0.x へ Pull Request、1.0.x でマージ、1.0.x-make-login-html ブランチを削除、をします。

ソースコード

build.gradle

buildscript {
    ext {
        springBootVersion = '1.2.5.RELEASE'
    }
    repositories {
        jcenter()
        maven { url "http://repo.spring.io/repo/" }
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
        classpath("io.spring.gradle:dependency-management-plugin:0.5.1.RELEASE")
        // for Grgit
        classpath("org.ajoberstar:grgit:1.1.0")
        // Gradle Download Task
        classpath("de.undercouch:gradle-download-task:1.2")
    }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'spring-boot'
apply plugin: 'io.spring.dependency-management'
apply plugin: 'de.undercouch.download'

// for Doma 2
// JavaクラスとSQLファイルの出力先ディレクトリを同じにする
processResources.destinationDir = compileJava.destinationDir
// コンパイルより前にSQLファイルを出力先ディレクトリにコピーするために依存関係を逆転する
compileJava.dependsOn processResources

jar {
    baseName = 'ksbysample-webapp-lending'
    version = '1.0.0-SNAPSHOT'
}

sourceCompatibility = 1.8
targetCompatibility = 1.8

eclipse {
    classpath {
        containers.remove('org.eclipse.jdt.launching.JRE_CONTAINER')
        containers 'org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8'
    }
}

idea {
    module {
        inheritOutputDirs = false
        outputDir = file("$buildDir/classes/main/")
    }
}

configurations {
    domaGenRuntime
}

repositories {
    jcenter()
}

dependencies {
    def jdbcDriver = "org.postgresql:postgresql:9.4-1201-jdbc41"

    // spring-boot-gradle-plugin によりバージョン番号が自動で設定されるもの
    // Appendix E. Dependency versions ( http://docs.spring.io/spring-boot/docs/current/reference/html/appendix-dependency-versions.html ) 参照
    compile("org.springframework.boot:spring-boot-starter-web")
    compile("org.springframework.boot:spring-boot-starter-thymeleaf")
    compile("org.springframework.boot:spring-boot-starter-data-jpa")
    compile("org.springframework.boot:spring-boot-starter-velocity")
    compile("org.springframework.boot:spring-boot-starter-mail")
    compile("org.springframework.boot:spring-boot-starter-security")
    compile("org.codehaus.janino:janino")
    testCompile("org.springframework.boot:spring-boot-starter-test")
    testCompile("org.springframework.security:spring-security-test:4.0.1.RELEASE")
    testCompile("org.yaml:snakeyaml")

    // spring-boot-gradle-plugin によりバージョン番号が自動で設定されないもの
    compile("${jdbcDriver}")
    compile("org.seasar.doma:doma:2.3.1")
    compile("org.bgee.log4jdbc-log4j2:log4jdbc-log4j2-jdbc4.1:1.16")
    compile("org.apache.commons:commons-lang3:3.4")
    compile("org.projectlombok:lombok:1.16.4")
    compile("com.google.guava:guava:18.0")
    testCompile("org.dbunit:dbunit:2.5.1")
    testCompile("com.icegreen:greenmail:1.4.1")

    // for Doma-Gen
    domaGenRuntime("org.seasar.doma:doma-gen:2.3.1")
    domaGenRuntime("${jdbcDriver}")
}

bootRun {
    jvmArgs = ['-Dspring.profiles.active=develop']
}

test {
    jvmArgs = ['-Dspring.profiles.active=unittest']
}

// for Doma-Gen
task gen << {
    def rootPackageName = 'ksbysample.webapp.lending'
    def daoPackagePath  = 'src/main/java/ksbysample/webapp/lending/dao'
    def workDirPath     = 'work'
    def workDaoDirPath  = "${workDirPath}/dao"

    // 作業用ディレクトリを削除する
    clearDir("${workDirPath}")
    
    // 現在の Dao インターフェースのバックアップを取得する
    copy() {
        from "${daoPackagePath}"
        into "${workDaoDirPath}/org"
    }
    
    // Dao インターフェース、Entity クラスを生成する
    ant.taskdef(resource: 'domagentask.properties',
            classpath: configurations.domaGenRuntime.asPath)
    ant.gen(url: 'jdbc:postgresql://localhost/ksbylending'
                , user: 'ksbylending_user'
                , password: 'xxxxxxxx'
            ) {
        entityConfig(packageName: "${rootPackageName}.entity", useListener: false)
        daoConfig(packageName: "${rootPackageName}.dao")
        sqlConfig()
    }

    // 生成された Dao インターフェースを作業用ディレクトリにコピーし、
    // @ComponentAndAutowiredDomaConfig アノテーションを付加する
    copy() {
        from "${daoPackagePath}"
        into "${workDaoDirPath}/replace"
        filter {
            line -> line.replaceAll('import org.seasar.doma.Dao;', 'import ksbysample.webapp.lending.util.doma.ComponentAndAutowiredDomaConfig;\nimport org.seasar.doma.Dao;')
                        .replaceAll('@Dao', '@Dao\n@ComponentAndAutowiredDomaConfig')
        }
    }

    // @ComponentAndAutowiredDomaConfig アノテーションを付加した Dao インターフェースを
    // dao パッケージへ戻す
    copy() {
        from "${workDaoDirPath}/replace"
        into "${daoPackagePath}"
    }

    // 元々 dao パッケージ内にあったファイルを元に戻す
    copy() {
        from "${workDaoDirPath}/org"
        into "${daoPackagePath}"
    }

    // 作業用ディレクトリを削除する
    clearDir("${workDirPath}")
    
    // 自動生成したファイルを git add する
    addGit()
}

task downloadCssFontsJs << {
    def staticDirPath   = 'src/main/resources/static'
    def workDirPath     = 'work'
    def adminLTEVersion     = '2.2.0'
    def jQueryVersion       = '2.1.4'
    def fontAwesomeVersion  = '4.3.0'
    def ioniconsVersion     = '2.0.1'
    def html5shivJsVersion  = '3.7.2'
    def respondMinJsVersion = '1.4.2'

    // 作業用ディレクトリを削除する
    clearDir("${workDirPath}")
    
    // Bootstrap & AdminLTE Dashboard & Control Panel Template
    downloadAdminLTE("${adminLTEVersion}", "${jQueryVersion}", "${workDirPath}", "${staticDirPath}")
    
    // Font Awesome Icons
    downloadFontAwesome("${fontAwesomeVersion}", "${workDirPath}", "${staticDirPath}")

    // Ionicons
    downloadIonicons("${ioniconsVersion}", "${workDirPath}", "${staticDirPath}")
    
    // html5shiv.js
    downloadHtml5shivJs("${html5shivJsVersion}", "${workDirPath}", "${staticDirPath}")

    // respond.min.js
    downloadRespondMinJs("${respondMinJsVersion}", "${workDirPath}", "${staticDirPath}")

    // 作業用ディレクトリを削除する
    clearDir("${workDirPath}")
    
    // 追加したファイルを git add する
    addGit()
}

/* -----------------------------------------------------------------------------
 * メソッド定義部
 ---------------------------------------------------------------------------- */
void clearDir(String dirPath) {
    delete dirPath
}

void addGit() {
    def grgit = org.ajoberstar.grgit.Grgit.open(dir: project.projectDir)
    grgit.add(patterns: ['.'])
}

void downloadAdminLTE(String adminLTEVersion, String jQueryVersion, String workDirPath, String staticDirPath) {
    download {
        src "https://codeload.github.com/almasaeed2010/AdminLTE/zip/v${adminLTEVersion}"
        dest new File("${workDirPath}/download/AdminLTE-${adminLTEVersion}.zip")
    }
    copy {
        from zipTree("${workDirPath}/download/AdminLTE-${adminLTEVersion}.zip")
        into "${workDirPath}/unzip"
    }
    copy {
        from "${workDirPath}/unzip/AdminLTE-${adminLTEVersion}/bootstrap/css"
        into "${staticDirPath}/css"
    }
    copy {
        from "${workDirPath}/unzip/AdminLTE-${adminLTEVersion}/bootstrap/fonts"
        into "${staticDirPath}/fonts"
    }
    copy {
        from "${workDirPath}/unzip/AdminLTE-${adminLTEVersion}/bootstrap/js"
        into "${staticDirPath}/js"
    }
    copy {
        from "${workDirPath}/unzip/AdminLTE-${adminLTEVersion}/dist/css"
        into "${staticDirPath}/css"
    }
    copy {
        from "${workDirPath}/unzip/AdminLTE-${adminLTEVersion}/dist/js"
        into "${staticDirPath}/js"
    }
    copy {
        from "${workDirPath}/unzip/AdminLTE-${adminLTEVersion}/plugins/jQuery/jQuery-${jQueryVersion}.min.js"
        into "${staticDirPath}/js"
    }
    delete "${staticDirPath}/js/pages"
    delete "${staticDirPath}/js/demo.js"
}

void downloadFontAwesome(String fontAwesomeVersion, String workDirPath, String staticDirPath) {
    download {
        src "http://fortawesome.github.io/Font-Awesome/assets/font-awesome-${fontAwesomeVersion}.zip"
        dest new File("${workDirPath}/download/font-awesome-${fontAwesomeVersion}.zip")
    }
    copy {
        from zipTree("${workDirPath}/download/font-awesome-${fontAwesomeVersion}.zip")
        into "${workDirPath}/unzip"
    }
    copy {
        from "${workDirPath}/unzip/font-awesome-${fontAwesomeVersion}/css/font-awesome.min.css"
        into "${staticDirPath}/css"
    }
    copy {
        from "${workDirPath}/unzip/font-awesome-${fontAwesomeVersion}/fonts"
        into "${staticDirPath}/fonts"
    }
}

void downloadIonicons(String ioniconsVersion, String workDirPath, String staticDirPath) {
    download {
        src "https://codeload.github.com/driftyco/ionicons/zip/v${ioniconsVersion}"
        dest new File("${workDirPath}/download/ionicons-${ioniconsVersion}.zip")
    }
    copy {
        from zipTree("${workDirPath}/download/ionicons-${ioniconsVersion}.zip")
        into "${workDirPath}/unzip"
    }
    copy {
        from "${workDirPath}/unzip/ionicons-${ioniconsVersion}/css/ionicons.min.css"
        into "${staticDirPath}/css"
    }
    copy {
        from "${workDirPath}/unzip/ionicons-${ioniconsVersion}/fonts"
        into "${staticDirPath}/fonts"
    }
}

void downloadHtml5shivJs(String html5shivJsVersion, String workDirPath, String staticDirPath) {
    download {
        src "https://oss.maxcdn.com/html5shiv/${html5shivJsVersion}/html5shiv.min.js"
        dest new File("${workDirPath}/download/html5shiv.min.js")
    }
    copy {
        from "${workDirPath}/download/html5shiv.min.js"
        into "${staticDirPath}/js"
    }
}

void downloadRespondMinJs(String respondMinJsVersion, String workDirPath, String staticDirPath) {
    download {
        src "https://oss.maxcdn.com/respond/${respondMinJsVersion}/respond.min.js"
        dest new File("${workDirPath}/download/respond.min.js")
    }
    copy {
        from "${workDirPath}/download/respond.min.js"
        into "${staticDirPath}/js"
    }
}
  • メソッド定義部」のコメントとその下に記載しているメソッドを追加します。
  • downloadCssFontsJs タスクを追加します。
  • gen タスク内の処理を「メソッド定義部」に記載したメソッドに一部変更します。

starter.html

<!DOCTYPE html>
<!--
This is a starter template page. Use this page to start your new project from
scratch. This page gets rid of all links and provides the needed markup only.
-->
<html>
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>AdminLTE 2 | Dashboard</title>
    <!-- Tell the browser to be responsive to screen width -->
    <meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
    <!-- Bootstrap 3.3.4 -->
    <link href="/css/bootstrap.min.css" rel="stylesheet" type="text/css"/>
    <!-- Font Awesome Icons -->
    <link href="/css/font-awesome.min.css" rel="stylesheet" type="text/css"/>
    <!-- Ionicons -->
    <link href="/css/ionicons.min.css" rel="stylesheet" type="text/css"/>
    <!-- Theme style -->
    <link href="/css/AdminLTE.min.css" rel="stylesheet" type="text/css"/>
    <!-- AdminLTE Skins. We have chosen the skin-blue for this starter
          page. However, you can choose any other skin. Make sure you
          apply the skin class to the body tag so the changes take effect.
    -->
    <link href="/css/skins/skin-blue.min.css" rel="stylesheet" type="text/css"/>

    <!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
    <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
    <!--[if lt IE 9]>
    <script src="/js/html5shiv.min.js"></script>
    <script src="/js/respond.min.js"></script>
    <![endif]-->
</head>
<!--
BODY TAG OPTIONS:
=================
Apply one or more of the following classes to get the
desired effect
|---------------------------------------------------------|
| SKINS         | skin-blue                               |
|               | skin-black                              |
|               | skin-purple                             |
|               | skin-yellow                             |
|               | skin-red                                |
|               | skin-green                              |
|---------------------------------------------------------|
|LAYOUT OPTIONS | fixed                                   |
|               | layout-boxed                            |
|               | layout-top-nav                          |
|               | sidebar-collapse                        |
|               | sidebar-mini                            |
|---------------------------------------------------------|
-->
<body class="skin-blue sidebar-mini">
<div class="wrapper">

    <!-- Main Header -->
    <header class="main-header">

        <!-- Logo -->
        <a href="index2.html" class="logo">
            <!-- mini logo for sidebar mini 50x50 pixels -->
            <span class="logo-mini"><b>A</b>LT</span>
            <!-- logo for regular state and mobile devices -->
            <span class="logo-lg"><b>Admin</b>LTE</span>
        </a>

        <!-- Header Navbar -->
        <nav class="navbar navbar-static-top" role="navigation">
            <!-- Sidebar toggle button-->
            <a href="#" class="sidebar-toggle" data-toggle="offcanvas" role="button">
                <span class="sr-only">Toggle navigation</span>
            </a>
            <!-- Navbar Right Menu -->
            <div class="navbar-custom-menu">
                <ul class="nav navbar-nav">
                    <!-- Messages: style can be found in dropdown.less-->
                    <li class="dropdown messages-menu">
                        <!-- Menu toggle button -->
                        <a href="#" class="dropdown-toggle" data-toggle="dropdown">
                            <i class="fa fa-envelope-o"></i>
                            <span class="label label-success">4</span>
                        </a>
                        <ul class="dropdown-menu">
                            <li class="header">You have 4 messages</li>
                            <li>
                                <!-- inner menu: contains the messages -->
                                <ul class="menu">
                                    <li><!-- start message -->
                                        <a href="#">
                                            <div class="pull-left">
                                                <!-- User Image -->
                                                <img src="dist/img/user2-160x160.jpg" class="img-circle"
                                                     alt="User Image"/>
                                            </div>
                                            <!-- Message title and timestamp -->
                                            <h4>
                                                Support Team
                                                <small><i class="fa fa-clock-o"></i> 5 mins</small>
                                            </h4>
                                            <!-- The message -->
                                            <p>Why not buy a new awesome theme?</p>
                                        </a>
                                    </li>
                                    <!-- end message -->
                                </ul>
                                <!-- /.menu -->
                            </li>
                            <li class="footer"><a href="#">See All Messages</a></li>
                        </ul>
                    </li>
                    <!-- /.messages-menu -->

                    <!-- Notifications Menu -->
                    <li class="dropdown notifications-menu">
                        <!-- Menu toggle button -->
                        <a href="#" class="dropdown-toggle" data-toggle="dropdown">
                            <i class="fa fa-bell-o"></i>
                            <span class="label label-warning">10</span>
                        </a>
                        <ul class="dropdown-menu">
                            <li class="header">You have 10 notifications</li>
                            <li>
                                <!-- Inner Menu: contains the notifications -->
                                <ul class="menu">
                                    <li><!-- start notification -->
                                        <a href="#">
                                            <i class="fa fa-users text-aqua"></i> 5 new members joined today
                                        </a>
                                    </li>
                                    <!-- end notification -->
                                </ul>
                            </li>
                            <li class="footer"><a href="#">View all</a></li>
                        </ul>
                    </li>
                    <!-- Tasks Menu -->
                    <li class="dropdown tasks-menu">
                        <!-- Menu Toggle Button -->
                        <a href="#" class="dropdown-toggle" data-toggle="dropdown">
                            <i class="fa fa-flag-o"></i>
                            <span class="label label-danger">9</span>
                        </a>
                        <ul class="dropdown-menu">
                            <li class="header">You have 9 tasks</li>
                            <li>
                                <!-- Inner menu: contains the tasks -->
                                <ul class="menu">
                                    <li><!-- Task item -->
                                        <a href="#">
                                            <!-- Task title and progress text -->
                                            <h3>
                                                Design some buttons
                                                <small class="pull-right">20%</small>
                                            </h3>
                                            <!-- The progress bar -->
                                            <div class="progress xs">
                                                <!-- Change the css width attribute to simulate progress -->
                                                <div class="progress-bar progress-bar-aqua" style="width: 20%"
                                                     role="progressbar" aria-valuenow="20" aria-valuemin="0"
                                                     aria-valuemax="100">
                                                    <span class="sr-only">20% Complete</span>
                                                </div>
                                            </div>
                                        </a>
                                    </li>
                                    <!-- end task item -->
                                </ul>
                            </li>
                            <li class="footer">
                                <a href="#">View all tasks</a>
                            </li>
                        </ul>
                    </li>
                    <!-- User Account Menu -->
                    <li class="dropdown user user-menu">
                        <!-- Menu Toggle Button -->
                        <a href="#" class="dropdown-toggle" data-toggle="dropdown">
                            <!-- The user image in the navbar-->
                            <img src="dist/img/user2-160x160.jpg" class="user-image" alt="User Image"/>
                            <!-- hidden-xs hides the username on small devices so only the image appears. -->
                            <span class="hidden-xs">Alexander Pierce</span>
                        </a>
                        <ul class="dropdown-menu">
                            <!-- The user image in the menu -->
                            <li class="user-header">
                                <img src="dist/img/user2-160x160.jpg" class="img-circle" alt="User Image"/>

                                <p>
                                    Alexander Pierce - Web Developer
                                    <small>Member since Nov. 2012</small>
                                </p>
                            </li>
                            <!-- Menu Body -->
                            <li class="user-body">
                                <div class="col-xs-4 text-center">
                                    <a href="#">Followers</a>
                                </div>
                                <div class="col-xs-4 text-center">
                                    <a href="#">Sales</a>
                                </div>
                                <div class="col-xs-4 text-center">
                                    <a href="#">Friends</a>
                                </div>
                            </li>
                            <!-- Menu Footer-->
                            <li class="user-footer">
                                <div class="pull-left">
                                    <a href="#" class="btn btn-default btn-flat">Profile</a>
                                </div>
                                <div class="pull-right">
                                    <a href="#" class="btn btn-default btn-flat">Sign out</a>
                                </div>
                            </li>
                        </ul>
                    </li>
                    <!-- Control Sidebar Toggle Button -->
                    <li>
                        <a href="#" data-toggle="control-sidebar"><i class="fa fa-gears"></i></a>
                    </li>
                </ul>
            </div>
        </nav>
    </header>
    <!-- Left side column. contains the logo and sidebar -->
    <aside class="main-sidebar">

        <!-- sidebar: style can be found in sidebar.less -->
        <section class="sidebar">

            <!-- Sidebar user panel (optional) -->
            <div class="user-panel">
                <div class="pull-left image">
                    <img src="dist/img/user2-160x160.jpg" class="img-circle" alt="User Image"/>
                </div>
                <div class="pull-left info">
                    <p>Alexander Pierce</p>
                    <!-- Status -->
                    <a href="#"><i class="fa fa-circle text-success"></i> Online</a>
                </div>
            </div>

            <!-- search form (Optional) -->
            <form action="#" method="get" class="sidebar-form">
                <div class="input-group">
                    <input type="text" name="q" class="form-control" placeholder="Search..."/>
              <span class="input-group-btn">
                <button type="submit" name="search" id="search-btn" class="btn btn-flat"><i class="fa fa-search"></i>
                </button>
              </span>
                </div>
            </form>
            <!-- /.search form -->

            <!-- Sidebar Menu -->
            <ul class="sidebar-menu">
                <li class="header">HEADER</li>
                <!-- Optionally, you can add icons to the links -->
                <li class="active"><a href="#"><i class="fa fa-link"></i> <span>Link</span></a></li>
                <li><a href="#"><i class="fa fa-link"></i> <span>Another Link</span></a></li>
                <li class="treeview">
                    <a href="#"><i class="fa fa-link"></i> <span>Multilevel</span> <i
                            class="fa fa-angle-left pull-right"></i></a>
                    <ul class="treeview-menu">
                        <li><a href="#">Link in level 2</a></li>
                        <li><a href="#">Link in level 2</a></li>
                    </ul>
                </li>
            </ul>
            <!-- /.sidebar-menu -->
        </section>
        <!-- /.sidebar -->
    </aside>

    <!-- Content Wrapper. Contains page content -->
    <div class="content-wrapper">
        <!-- Content Header (Page header) -->
        <section class="content-header">
            <h1>
                Page Header
                <small>Optional description</small>
            </h1>
            <ol class="breadcrumb">
                <li><a href="#"><i class="fa fa-dashboard"></i> Level</a></li>
                <li class="active">Here</li>
            </ol>
        </section>

        <!-- Main content -->
        <section class="content">

            <!-- Your Page Content Here -->

        </section>
        <!-- /.content -->
    </div>
    <!-- /.content-wrapper -->

    <!-- Main Footer -->
    <footer class="main-footer">
        <!-- To the right -->
        <div class="pull-right hidden-xs">
            Anything you want
        </div>
        <!-- Default to the left -->
        <strong>Copyright &copy; 2015 <a href="#">Company</a>.</strong> All rights reserved.
    </footer>

    <!-- Control Sidebar -->
    <aside class="control-sidebar control-sidebar-dark">
        <!-- Create the tabs -->
        <ul class="nav nav-tabs nav-justified control-sidebar-tabs">
            <li class="active"><a href="#control-sidebar-home-tab" data-toggle="tab"><i class="fa fa-home"></i></a></li>
            <li><a href="#control-sidebar-settings-tab" data-toggle="tab"><i class="fa fa-gears"></i></a></li>
        </ul>
        <!-- Tab panes -->
        <div class="tab-content">
            <!-- Home tab content -->
            <div class="tab-pane active" id="control-sidebar-home-tab">
                <h3 class="control-sidebar-heading">Recent Activity</h3>
                <ul class="control-sidebar-menu">
                    <li>
                        <a href="javascript::;">
                            <i class="menu-icon fa fa-birthday-cake bg-red"></i>

                            <div class="menu-info">
                                <h4 class="control-sidebar-subheading">Langdon's Birthday</h4>

                                <p>Will be 23 on April 24th</p>
                            </div>
                        </a>
                    </li>
                </ul>
                <!-- /.control-sidebar-menu -->

                <h3 class="control-sidebar-heading">Tasks Progress</h3>
                <ul class="control-sidebar-menu">
                    <li>
                        <a href="javascript::;">
                            <h4 class="control-sidebar-subheading">
                                Custom Template Design
                                <span class="label label-danger pull-right">70%</span>
                            </h4>

                            <div class="progress progress-xxs">
                                <div class="progress-bar progress-bar-danger" style="width: 70%"></div>
                            </div>
                        </a>
                    </li>
                </ul>
                <!-- /.control-sidebar-menu -->

            </div>
            <!-- /.tab-pane -->
            <!-- Stats tab content -->
            <div class="tab-pane" id="control-sidebar-stats-tab">Stats Tab Content</div>
            <!-- /.tab-pane -->
            <!-- Settings tab content -->
            <div class="tab-pane" id="control-sidebar-settings-tab">
                <form method="post">
                    <h3 class="control-sidebar-heading">General Settings</h3>

                    <div class="form-group">
                        <label class="control-sidebar-subheading">
                            Report panel usage
                            <input type="checkbox" class="pull-right" checked/>
                        </label>

                        <p>
                            Some information about this general settings option
                        </p>
                    </div>
                    <!-- /.form-group -->
                </form>
            </div>
            <!-- /.tab-pane -->
        </div>
    </aside>
    <!-- /.control-sidebar -->
    <!-- Add the sidebar's background. This div must be placed
         immediately after the control sidebar -->
    <div class="control-sidebar-bg"></div>
</div>
<!-- ./wrapper -->

<!-- REQUIRED JS SCRIPTS -->

<!-- jQuery 2.1.4 -->
<script src="/js/jQuery-2.1.4.min.js" type="text/javascript"></script>
<!-- Bootstrap 3.3.2 JS -->
<script src="/js/bootstrap.min.js" type="text/javascript"></script>
<!-- AdminLTE App -->
<script src="/js/app.min.js" type="text/javascript"></script>

<!-- Optionally, you can add Slimscroll and FastClick plugins.
      Both of these plugins are recommended to enhance the
      user experience. Slimscroll is required when using the
      fixed layout. -->
</body>
</html>
  • Ctrl+Alt+L を押して HTML をフォーマットします。
  • <meta http-equiv="X-UA-Compatible" content="IE=edge"> を追加します。
  • css, js のパスを /css, /js に変更します。

WebSecurityConfig.java

package ksbysample.webapp.lending.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
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.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import javax.sql.DataSource;

@Configuration
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private DataSource dataSource;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                // 認証の対象外にしたいURLがある場合には、以下のような記述を追加します
                // 複数URLがある場合はantMatchersメソッドにカンマ区切りで対象URLを複数列挙します
                // .antMatchers("/country/**").permitAll()
                .antMatchers("/fonts/**").permitAll()
                .antMatchers("/html/**").permitAll()
                .anyRequest().authenticated();
        http.formLogin()
                .loginPage("/")
                .loginProcessingUrl("/login")
                .defaultSuccessUrl("/xxxxxxxx")
                .failureUrl("/")
                .usernameParameter("id")
                .passwordParameter("password")
                .permitAll()
                .and()
                .logout()
                .logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
                .logoutSuccessUrl("/")
                .deleteCookies("JSESSIONID")
                .invalidateHttpSession(true)
                .permitAll();
    }

    @Autowired
    public void configAuthentication(AuthenticationManagerBuilder auth) throws Exception {
        auth.jdbcAuthentication().dataSource(dataSource)
                .usersByUsernameQuery("select id, password, enabled from user where id = ?")
                .authoritiesByUsernameQuery("select id, role from user_role where id = ?")
                .passwordEncoder(new BCryptPasswordEncoder());
    }

}
  • .antMatchers("/fonts/**").permitAll().antMatchers("/html/**").permitAll() の2行を追加します。/fonts ディレクトリも認証対象になっていたので、認証不要に設定します。

template.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8"/>
    <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
    <title>ここに画面名を入れる</title>
    <!-- Tell the browser to be responsive to screen width -->
    <meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport"/>
    <!-- Bootstrap 3.3.4 -->
    <link href="/css/bootstrap.min.css" rel="stylesheet" type="text/css"/>
    <!-- Font Awesome Icons -->
    <link href="/css/font-awesome.min.css" rel="stylesheet" type="text/css"/>
    <!-- Ionicons -->
    <link href="/css/ionicons.min.css" rel="stylesheet" type="text/css"/>
    <!-- Theme style -->
    <link href="/css/AdminLTE.min.css" rel="stylesheet" type="text/css"/>
    <!-- AdminLTE Skins. Choose a skin from the css/skins
         folder instead of downloading all of them to reduce the load. -->
    <link href="/css/skins/_all-skins.min.css" rel="stylesheet" type="text/css"/>

    <!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
    <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
    <!--[if lt IE 9]>
    <script src="/js/html5shiv.min.js"></script>
    <script src="/js/respond.min.js"></script>
    <![endif]-->

    <style>
        .content-wrapper {
            background-color: #fffafa;
        }
    </style>
</head>

<!-- ADD THE CLASS layout-top-nav TO REMOVE THE SIDEBAR. -->
<body class="skin-blue layout-top-nav">
<div class="wrapper">

    <header class="main-header">
        <nav class="navbar navbar-static-top">
            <div class="container">
                <div class="navbar-header">
                    <a href="#" class="navbar-brand"><b>ksbysample-lending</b></a>
                    <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar-collapse">
                        <i class="fa fa-bars"></i>
                    </button>
                </div>

                <!-- Collect the nav links, forms, and other content for toggling -->
                <div class="collapse navbar-collapse pull-left" id="navbar-collapse">
                    <ul class="nav navbar-nav">
                        <li class="dropdown">
                            <a href="#" class="dropdown-toggle" data-toggle="dropdown">メニュー <span class="caret"></span></a>
                            <ul class="dropdown-menu" role="menu">
                                <li><a href="#">Action</a></li>
                                <li><a href="#">Another action</a></li>
                                <li><a href="#">Something else here</a></li>
                                <li class="divider"></li>
                                <li><a href="#">Separated link</a></li>
                                <li class="divider"></li>
                                <li><a href="#">One more separated link</a></li>
                            </ul>
                        </li>
                    </ul>
                </div>
                <!-- /.navbar-collapse -->

                <!-- Navbar Right Menu -->
                <div class="navbar-custom-menu">
                    <ul class="nav navbar-nav">
                        <li><a href="#">ログアウト</a></li>
                    </ul>
                </div>
                <!-- /.navbar-custom-menu -->
            </div>
            <!-- /.container-fluid -->
        </nav>
    </header>

    <!-- Full Width Column -->
    <div class="content-wrapper">
        <div class="container">
            <!-- Content Header (Page header) -->
            <section class="content-header">
                <h1>画面名</h1>
            </section>

            <!-- Main content -->
            <section class="content">
                ここに画面本体を入れる
            </section>
            <!-- /.content -->
        </div>
        <!-- /.container -->
    </div>

</div>
<!-- ./wrapper -->

<!-- jQuery 2.1.4 -->
<script src="/js/jQuery-2.1.4.min.js" type="text/javascript"></script>
<!-- Bootstrap 3.3.2 JS -->
<script src="/js/bootstrap.min.js" type="text/javascript"></script>
<!-- AdminLTE App -->
<script src="/js/app.min.js" type="text/javascript"></script>
</body>
</html>

login.html

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8"/>
    <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
    <title>ここに画面名を入れる</title>
    <!-- Tell the browser to be responsive to screen width -->
    <meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport"/>
    <!-- Bootstrap 3.3.4 -->
    <link href="/css/bootstrap.min.css" rel="stylesheet" type="text/css"/>
    <!-- Font Awesome Icons -->
    <link href="/css/font-awesome.min.css" rel="stylesheet" type="text/css"/>
    <!-- Ionicons -->
    <link href="/css/ionicons.min.css" rel="stylesheet" type="text/css"/>
    <!-- Theme style -->
    <link href="/css/AdminLTE.min.css" rel="stylesheet" type="text/css"/>
    <!-- AdminLTE Skins. Choose a skin from the css/skins
         folder instead of downloading all of them to reduce the load. -->
    <link href="/css/skins/_all-skins.min.css" rel="stylesheet" type="text/css"/>

    <!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
    <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
    <!--[if lt IE 9]>
    <script src="/js/html5shiv.min.js"></script>
    <script src="/js/respond.min.js"></script>
    <![endif]-->

    <style>
        <!--
        .content-wrapper {
            background-color: #fffafa;
            padding-top: 50px;
        }
        .form-group {
            margin-bottom: 5px;
        }
        -->
    </style>
</head>

<!-- ADD THE CLASS layout-top-nav TO REMOVE THE SIDEBAR. -->
<body class="skin-blue layout-top-nav">
<div class="wrapper">

    <!-- Full Width Column -->
    <div class="content-wrapper">
        <div class="container">
            <!-- Main content -->
            <section class="content">
                <div class="row">
                    <div class="col-xs-12 col-md-push-3 col-md-6">
                        <div class="form-wrap">
                            <div class="text-center"><h1>ksbysample-lending</h1></div>
                            <br/>
                            <form role="form" action="#" th:action="@{/login}" method="post" id="login-form" autocomplete="off">
                                <div class="form-group" th:if="${session['SPRING_SECURITY_LAST_EXCEPTION']} != null">
                                    <p class="form-control-static text-danger" th:text="${session['SPRING_SECURITY_LAST_EXCEPTION'].message}"></p>
                                </div>
                                <div class="form-group">
                                    <label for="id" class="sr-only">ID</label>
                                    <input type="text" name="id" id="id" class="form-control" placeholder="ID を入力して下さい"
                                           th:value="${session['SPRING_SECURITY_LAST_EXCEPTION']} != null ? ${session['SPRING_SECURITY_LAST_EXCEPTION'].authentication.principal} : ''"/>
                                </div>
                                <div class="form-group">
                                    <label for="password" class="sr-only">Password</label>
                                    <input type="password" name="password" id="password" class="form-control" placeholder="Password を入力して下さい"/>
                                </div>
                                <br/>
                                <button id="btn-login" class="btn btn-primary btn-block">ログイン</button>
                            </form>
                        </div>
                    </div>
                </div>
            </section>
            <!-- /.content -->
        </div>
        <!-- /.container -->
    </div>

</div>
<!-- ./wrapper -->

<!-- jQuery 2.1.4 -->
<script src="/js/jQuery-2.1.4.min.js" type="text/javascript"></script>
<!-- Bootstrap 3.3.2 JS -->
<script src="/js/bootstrap.min.js" type="text/javascript"></script>
<!-- AdminLTE App -->
<script src="/js/app.min.js" type="text/javascript"></script>
<script type="text/javascript">
    <!--
    $(document).ready(function() {
        $('#id').focus().select();
    });
    -->
</script>
</body>
</html>

履歴

2015/07/12
初版発行。