読者です 読者をやめる 読者になる 読者になる

Webサービスで起業を目指すプログラマーblog

仕事で使ったプログラミング、サーバー周りで役に立つこと、Webサービス開発に必要な技術情報、モバイル情報を書いてます。わかりやすく見やすくをモットーにしています。

さくらレンタルサーバーにMercurialをインストールする

f:id:shinsuke789:20141003110840p:plain

さくらのレンタルサーバーMercurialをインストールした時の手順です。


1.ホームディレクトリ内に「local」ディレクトリを作成する

$ mkdir ~/local


2.ホームディレクトリ内に「tmp」ディレクトリを作成する

$ mkdir ~/tmp


3.Mercurialのソースを公式サイトよりダウンロードし、「tmp」ディレクトリに置き、解凍する

公式サイトでは、「Mercurial 3.7.2 source release」の記載欄にあるリンクからダウンロードします。

$ cd ~/tmp
$ wget http://mercurial.selenic.com/release/mercurial-3.7.2.tar.gz
$ tar xvzf mercurial-3.7.2.tar.gz


4.Mercurialをインストールする

さくらレンタルサーバーでは、Pythonがデフォルトでインストールされています。

$ cd mercurial-3.7.2
$ python setup.py installhome=$HOME/local —force


5.インストールされたか確認する

$ hg version

Mercurial Distributed SCM (version 3.7.2)
(see https://mercurial-scm.org for more information)

Copyright (C) 2005-2016 Matt Mackall and others
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

VMware Fusionでサブネットアドレスと固定IPアドレスを画面から設定する

以前に設定ファイルを修正してゲストOSのIPアドレスを固定する方法を紹介しました。

shinsuke789.hatenablog.jp

MacのOSをクリーンインストールでバージョンアップして仮想環境を移行しました。
その時にIPアドレスの構成が変わってしまったので変更する必要がありました。
設定ファイルの修正では上手くいかなかったので、画面の設定から変更する手順で設定する方法を紹介します。
こちらの方が簡単に設定できるのでおすすめです。

参考サイト

調べていると公式サイトにヒントとなる記事がありました。
kb.vmware.com

最後辺りに次のような記述があり画面から設定できるようなことが書いてありました。

To access the network editor:

1. Click VMware Fusion in the menu bar.
2. Click Preferences.
3. Click Network.
4. Click the + sign located at the bottom right to add a custom network connection.

環境

仮想環境 VMware Fusion 8.1.0
ゲストOS CentOS 6.4 (x64)
ホストOS Mac OS X 10.11.2

設定の流れ

1.VMware Fusionの環境設定でネットワーク構成を追加する
2.VMware FusionでのゲストOSのネットワーク設定で1のネットワーク構成を選択する
3.ゲストOS内のネットワーク設定で固定IPアドレスを設定する

手順

VMware Fusionの設定

1.VMware Fusionを起動しファイルメニューの「VMware Fusion」→「環境設定」をクリックします
f:id:shinsuke789:20151231205248p:plain

2.ネットワークをクリックし、画面下部の鍵アイコンをクリックします
f:id:shinsuke789:20151231205218p:plain

3.パスワードを聞かれるのでMacの管理者パスワードを入力します
f:id:shinsuke789:20151231205142p:plain

4.「+」ボタンをクリックして構成を追加します(vmnet2が追加される)
f:id:shinsuke789:20151231205231p:plain

5.1つ目と3つ目にチェックを入れ、サブネットIP、サブネットマスクに任意の値を入力し適用します
f:id:shinsuke789:20151231205238p:plain

VMware FusionでのゲストOSの設定

1.仮想マシン一覧画面でゲストOSを選択し設定ボタンをクリックします
f:id:shinsuke789:20151231205300p:plain

2.「ネットワークアダプタ」をクリックします
f:id:shinsuke789:20151231205308p:plain

3.VMware Fusionの設定で追加した構成(vmnet2)を選択します
f:id:shinsuke789:20151231205320p:plain

ゲストOS内での設定

1.ネットワーク接続を起動します
f:id:shinsuke789:20151231205101p:plain

2.「Auto eth1」を選択し「編集」をクリックします
f:id:shinsuke789:20151231205115p:plain

3.「IPv4のセッティング」で方法を「手動」、アドレスに固定IPアドレスDNSDNSサーバーアドレスを入力します
ゲートウェイDNSの末尾は設定ファイルを見る限り「2」が使用されています。
f:id:shinsuke789:20151231205124p:plain

4.ゲストOSを再起動します

疎通確認

1.ホストOSからゲストOSにpingを実行し疎通を確認します

2.ゲストOSからホストOSにpingを実行し疎通を確認します

まとめ

この方法だと仮想環境上に全て設定がされているので、設定ファイルのバックアップを忘れても再設定が簡単です。
画面上での設定なのでやりやすいのも良いところです。


プログラマのためのDocker教科書 インフラの基礎知識&コードによる環境構築の自動化

プログラマのためのDocker教科書 インフラの基礎知識&コードによる環境構築の自動化

プログラミング初心者から上達するための4つの大事なこと

ネットではプログラミングを学ぶためのサービスが増えてきました。
プログラムを動かすだけであれば、誰でも学習するだけで習得可能です。


いくらプログラミングができても中にはずっと初心者のままで一向に上達しない人もいます。
仕事でプログラミングをしていますが、毎回同じこともいってもプログラミングレベルが上がらない人がいます。
なぜ上がらないのか、どうやったら上がるのか考えてみました。

後で読んだ時に理解できるコードを書く

初心者の方は、プログラムの文法等は一通り覚えて、コードを書けるようにはなっていると思います。
ただ書くだけなら簡単です。
でも、そのコードを後で読んだとき理解できるでしょうか?


コードを書いていると夢中になって、同じメソッドにダラダラコードを書いていることが多いと思います。
そのコードは、if文やfor文などがぎっしり詰まって、階層が深くなっていませんか?
そのようなコードを後で読んだとき、すぐに理解できますか?


経験を積んでいくと分かるのですが、そのような長くてif文が多くてfor文が多くて階層が深いコードは理解に非常に時間がかかります。


1人だけで書くなら良いですが、上達させるために1人で書いてても他の人が見た時に分かるように書くようにしましょう。
わかりやすいコードというのは、1つの機能を1つのメソッドで定義しているものです。


悪いコードを良いコードに変える技術として「リファクタリング」というものがあります。
名書があるので一度は読むことをおすすめします。

新装版 リファクタリング―既存のコードを安全に改善する― (OBJECT TECHNOLOGY SERIES)

新装版 リファクタリング―既存のコードを安全に改善する― (OBJECT TECHNOLOGY SERIES)


他人のコードをメンテナンスする

プログラミングを上達させるために「他人のコードを読む」というものがあります。
これでも上達に効果はありますが、さらに理解を深めるために他人のコードをメンテナンスする方法があります。


読むだけならプログラミングに入り込む感じが薄いので、自分がプログラミングしているという感じを深めるために実際に他人のコードを修正していきます。
なるべく、悪いコードを書く人のものをメンテナンスした方が効果が高いです。


悪いコードというのは、グローバル変数や先ほど書いた1メソッドが長いコードのことを言います。


そのようなコードを実際に触ってみると分かるのですが、どこに何が書いてあるのか、この変数には何が入っているのかわかりにくいと感じます。
どのようなものが悪いコードなのか理解しつつ、それをどうしたら良いコードにするのか考えることが学習に繋がります。


悪いコードを理解することで、書いてはいけないコードというのが分かるようになり良いコードを書くことに繋がります。

テストコードで手を抜かない

テストコードだからといって、後で読めなくなるような適当なコードを書いていませんか?


このようなことをしていると、それが習慣となり一向に上達しません。
お試しでも本番でも常に同じ気持ちでコードを書くようにしましょう。


練習でサボっていたら本番でうまくいくはずがありません。

学習し続ける

プログラミングの世界は、次から次へと新しい技術が出てきます。
全てを追いかけることは不可能ですが、興味を持った新しい技術は少しでもいいので触れてみましょう。
新しい技術に触れることで、今持っている技術や考え方に活かされることが多々あります。


技術以外にも他に良いコードの書き方がないか試行錯誤したりすることも学習になります。
長いコードをもっとシンプルに書く方法はないのか考えるたり調べたりするのも良いでしょう。


学習をやめるとプログラミングが上達することはほぼなくなります。
先輩の中にはCOBOL時代の知識でJavaのコードを書いたり設計する人もいます。
Javaを理解して一緒にプログラミングする人にとっては迷惑な話です。


大事なのは最初に学んだことを捨ててでも新しいことを取り入れれる勇気です。

Macをちょこっと快適にする4つの無料アプリ

Alfred

f:id:shinsuke789:20151122065456p:plain:w300

コマンドラインランチャーです。
Dockにアイコンをおいてそこからアプリを起動させるのもいいですが、数が多いとDockの見栄えが悪くなったり、クリックしにくくなります。
Alfredを使えば、アプリ名を入力することで素早くアプリを起動させることができます。
アプリの起動以外にもちょっとした計算も行えます。

Alfred - Productivity App for Mac OS X

karabiner

f:id:shinsuke789:20151122065501p:plain:w300

キーボードのカスタマイズを行うフリーソフトです。
キーボード以外にも4ボタン以上のマウスのボタンを有効にしてくれます。

Karabiner - OS X用のソフトウェア

XtraFinder

f:id:shinsuke789:20151122065511p:plain:w300

Finderに機能を追加するアプリです。
Finderをタブ化するのに使っています。

XtraFinder adds Tabs and features to Mac Finder.

ScrollReverser

f:id:shinsuke789:20151122065506p:plain:w300

マウスとトラックパッドを使い分けていると、スクロール設定が逆になり使い勝手が悪くなります。
マウスとトラックパッドの利用時に、自動的に設定したスクロール設定に変更してくれるアプリです。

Scroll Reverser for Mac OS X

Rails4でBootstrapのGlyphicon(アイコン)を表示させる方法

RailsにBootstrapを導入するとGlyphicon(アイコン)が表示されません。

ネットで調べてみるとGemをインストールしたり、application.rbに設定を追加したりしているのが多かったです。
個人的には単純に追加の処理は行いたくなかったのでいろいろ試してみました。

結果的に、CSSで使用してるフォントのパスを変更するだけで表示されるようになりました。

環境

Rails 4.2.4
Bootstrap 3.3.5

変更箇所

bootstrap.cssを「app/assets/stylesheets/bootstrap.css」に設置した場合、このCSS内にある「@font-face」の定義のurlにある「../fonts/」を削除するだけです。(263行目あたり)

パス変更前

パス変更前のアイコンは次のように表示されます。
f:id:shinsuke789:20151027062934p:plain

app/assets/stylesheets/bootstrap.css

@font-face {
  font-family: 'Glyphicons Halflings';

  src: url('../fonts/glyphicons-halflings-regular.eot');
  src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url(../fonts/'glyphicons-halflings-regular.woff2') format('woff2'), url('../fonts/glyphicons-halflings-regular.woff') format('woff'), url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg');
}

パス変更後

パス変更後は正常にアイコンが表示されます。
f:id:shinsuke789:20151027063304p:plain

app/assets/stylesheets/bootstrap.css

@font-face {
  font-family: 'Glyphicons Halflings';

  src: url('glyphicons-halflings-regular.eot');
  src: url('glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('glyphicons-halflings-regular.woff2') format('woff2'), url('glyphicons-halflings-regular.woff') format('woff'), url('glyphicons-halflings-regular.ttf') format('truetype'), url('glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg');
}

さくらレンタルサーバーにRubyをインストールする

さくらレンタルサーバーにはデフォルトでRubyがインストールされていますが、バージョンが古いので最新版をインストールする手順を書いてみました。

環境

さくらレンタルサーバースタンダード

手順

sshでサーバーにログインする

$ ssh account@account.sakura.ne.jp
Password:

cshからbashに変更する

使い勝手の良いbashを使えるようにします。


bashのパスを確認するためにシェルのパス一覧を確認します。

$ cat /etc/shells
# $FreeBSD: release/9.1.0/etc/shells 59717 2000-04-27 21:58:46Z ache $
#
# List of acceptable shells for chpass(1).
# Ftpd will not allow users to connect who are not using
# one of these shells.

/bin/sh
/bin/csh
/bin/tcsh
/usr/local/bin/zsh
/usr/local/bin/rzsh
/usr/bin/passwd
/usr/local/bin/bash
/usr/local/bin/rbash


シェルのパス一覧からbashのパスを確認しそれに変更します。

$ chsh -s /usr/local/bin/bash


ログインし直します。


再ログインしたらシェルが変更されているか確認します。

$ echo $SHELL
/usr/local/bin/bash

インストールに必要なディレクトリの作成

レンタルサーバーなのでroot権限がありません。
ホームディレクトリにインストールに必要なディレクトリを作成しパスを通します。


ホームディレクトリに「local」「local/src」「local/bin」の3つのディレクトリを作成します。

$ mkdir ~/local
$ mkdir ~/local/src
$ mkdir ~/local/bin


ユーザー設定にパスを追記し有効にします。

$ vi ~/.bashrc
export PATH=$HOME/local/bin:$PATH
$ source ~/.bashrc

gitがインストールされているか確認する

さくらレンタルサーバーは、デフォルトでgitがインストールされているようなのでこれを使います。


gitのバージョンを確認します。

$ git --version
git version 1.9.3

rbenvをインストールする

ホームディレクトリにrbenvをインストールします。

$ cd ~/local
$ git clone git://github.com/sstephenson/rbenv.git rbenv


ruby-buildをrbenv のプラグインとして入れます。

$ mkdir ~/local/rbenv/plugins
$ cd  ~/local/rbenv/plugins
$ git clone git://github.com/sstephenson/ruby-build.git ruby-build


~/.bashrcの先頭にrbenvのパスを設定します。

$ vi ~/.bashrc
export RBENV_ROOT=$HOME/local/rbenv
export PATH=$RBENV_ROOT/bin:$HOME/local/bin:$PATH
eval "$(rbenv init -)"
$ source ~/.bashrc


tmpディレクトリの定義を追加します。

$ vi ~/.bashrc
export TMPDIR=$HOME/tmp
$ source ~/.bashrc


rbenvでインストール可能なrubyのバージョンを確認します。

$ rbenv install -l


現時点での最新(2.2.3)のrubyをインストールします。
インストールには、約5分程かかりますので待ちます。

$ rbenv install 2.2.3
Downloading ruby-2.2.3.tar.gz...
-> https://dqw8nmjcqpjn7.cloudfront.net/df795f2f99860745a416092a4004b016ccf77e8b82dec956b120f18bdc71edce
Installing ruby-2.2.3...
Installed ruby-2.2.3 to /home/user/local/rbenv/versions/2.2.3

$ rbenv rehash


先ほどインストールしたバージョンをデフォルトにします。

$ rbenv global 2.2.3


デフォルトになっているか確認します。

$ ruby -v
ruby 2.2.3p173 (2015-08-18 revision 51636) [x86_64-freebsd9.1]

bundlerをインストールする

現在のgemを確認します。

$ gem --version
2.4.5.1

$ gem list

*** LOCAL GEMS ***

bigdecimal (1.2.6)
io-console (0.4.3)
json (1.8.1)
minitest (5.4.3)
power_assert (0.2.2)
psych (2.0.8)
rake (10.4.2)
rdoc (4.2.0)
test-unit (3.0.8)


bundlerをインストールします。

$ rbenv exec gem install bundler
Fetching: bundler-1.10.6.gem (100%)
Successfully installed bundler-1.10.6
Parsing documentation for bundler-1.10.6
Installing ri documentation for bundler-1.10.6
Done installing documentation for bundler after 5 seconds
1 gem installed

$ rbenv rehash


bundlerがインストールされたか確認します。

$ gem list

*** LOCAL GEMS ***

bigdecimal (1.2.6)
bundler (1.10.6)       <- インストールされている
io-console (0.4.3)
json (1.8.1)
minitest (5.4.3)
power_assert (0.2.2)
psych (2.0.8)
rake (10.4.2)
rdoc (4.2.0)
test-unit (3.0.8)

最終の.bashrc

$ cat ~/.bashrc
export RBENV_ROOT=$HOME/local/rbenv
export PATH=$RBENV_ROOT/bin:$HOME/local/bin:$PATH
eval "$(rbenv init -)"
export TMPDIR=$HOME/tmp
export PATH=$HOME/local/bin:$PATH

SpringSecurityで独自テーブルを使って認証を行う

f:id:shinsuke789:20150804182444j:plain

SpringBootでSpringSecurityを使って独自認証でログイン機能を実装してみました。

ドキュメントを参考にやってみたけど、詳しく書いてなくて理解に苦しみ結構ハマりました。
最終的にSpringSecurityのソースを見ることで認証オブジェクトの仕組みを理解しました。

概要

SpringSecurityでDBを使用して認証する場合、SpringSecurity付属のテーブル定義を行う必要があります。
今回は、独自テーブルを使った認証を行い、ロールは使用しません。

SpringSecurityの設定

SecurityConfigクラスを作成し、アクセス制限、ログイン処理、ログアウト処理等を定義します。

認証チェック処理クラス

AuthenticationProviderインターフェースを継承して独自クラスを作成します。
このクラスで独自テーブルを参照して認証チェック処理を定義します。

認証オブジェクト作成サービスクラス

UserDetailsServiceインターフェースを継承し独自クラスを作成します。
このクラスで認証後にシステム内で使用する認証オブジェクトを生成します。

認証オブジェクトクラス

Userクラスを継承したクラスを作成し、username、passwordフィールドを定義します。
username、passwordフィールドを定義することで、システム内で認証オブジェクトをオブジェクトで持つことができ、他に必要な情報を持たせることができるようになります。

環境

Eclipse 4.3
Java 1.7
SpringBoot 1.2.4
SpringSecurity 3.2.7
Thymeleaf 2.1.4
Doma 1.0.38
Gradle 2.3.10

構成

  • src/main/java
    • demo
      • SecurityConfig.java
    • demo.dao(省略)
    • demo.dto
    • demo.entity(省略)
    • demo.impl
      • AuthenticationProviderImpl.java
      • UserDetailsServiceImpl.java
    • demo.web
      • LoginController.java
      • MenuController.java
  • src/main/resources
    • tempaltes
      • login.html
      • menu.html

build.gradle

buildscript {
    ext {
        springBootVersion = '1.2.4.RELEASE'
    }
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") 
        classpath("io.spring.gradle:dependency-management-plugin:0.5.1.RELEASE")
    }
}

apply plugin: 'java'
apply plugin: 'eclipse-wtp'
apply plugin: 'idea'
apply plugin: 'spring-boot' 
apply plugin: 'io.spring.dependency-management' 
apply plugin: 'war'


war {
    baseName = 'demo'
    version = '1.0'
}
sourceCompatibility = 1.7
targetCompatibility = 1.7

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

repositories {
    maven {url 'http://maven.seasar.org/maven2'}
    mavenCentral()
}

configurations {
    providedRuntime
}

dependencies {
    compile("org.springframework.boot:spring-boot-starter-aop")
    // SpringSecurityの依存
    compile("org.springframework.boot:spring-boot-starter-security")
    compile("org.springframework.boot:spring-boot-starter-thymeleaf")
    compile("org.springframework.boot:spring-boot-starter-web")
    compile("org.springframework.boot:spring-boot-starter-jdbc")
    // htmlでThymeleaf用のSpringSecurityタグ使うためのもの
    compile("org.thymeleaf.extras:thymeleaf-extras-springsecurity3")
    compile("org.hibernate:hibernate-validator")
    compile("org.seasar.doma:doma:1.38.0")
    compile("org.projectlombok:lombok:1.16.4")
    compile files("C:/app/lib/jdbc/ojdbc7.jar")
    providedRuntime("org.springframework.boot:spring-boot-starter-tomcat")
}


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.7'
    }
}

task wrapper(type: Wrapper) {
    gradleVersion = '2.3'
}

demo/SecurityConfig.java

package demo;

import org.springframework.beans.factory.annotation.Autowired;
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.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import demo.impl.AuthenticationProviderImpl;
import demo.impl.UserDetailsServiceImpl;

@Configuration
@EnableWebMvcSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsServiceImpl userDetailsService;

    @Autowired
    private AuthenticationProviderImpl authenticationProvider;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .headers()
                .xssProtection()
                .frameOptions()
                .contentTypeOptions()
                .cacheControl()
                .and()
            .authorizeRequests()
                // 認証対象外のパスを設定する
                .antMatchers("/", "/login", "/registration/**", "/css/**", "/js/**", "/img/**")
                // 上記パスへのアクセスを許可する
                .permitAll()
                // その他のリクエストは認証が必要
                .anyRequest().authenticated()
                .and()
            .formLogin()
                // ログインフォームのパス
                .loginPage("/")
                // ログイン処理のパス
                .loginProcessingUrl("/login")
                // ログイン成功時の遷移先
                .defaultSuccessUrl("/menu")
                // ログイン失敗時の遷移先
                .failureUrl("/login-error")
                // ログインフォームで使用するユーザー名のinput name
                .usernameParameter("empNo")
                // ログインフォームで使用するパスワードのinput name
                .passwordParameter("password")
                .permitAll()
                .and()
            .rememberMe()
                .tokenValiditySeconds(86400) // 1ヶ月(秒)
                .and()
            .logout()
                // ログアウトがパス(GET)の場合設定する(CSRF対応)
                .logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
                // ログアウトがPOSTの場合設定する
                //.logoutUrl("/logout")
                // ログアウト後の遷移先
                .logoutSuccessUrl("/")
                // セッションを破棄する
                .invalidateHttpSession(true)
                // ログアウト時に削除するクッキー名
                .deleteCookies("JSESSIONID", "remember-me")
                .permitAll();
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        // 独自認証クラスを設定する
        auth
            .authenticationProvider(authenticationProvider)
            .userDetailsService(userDetailsService);
    }
}

demo/dto/LoginUser.java

package demo.dto

import java.util.ArrayList;

import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import demo.entity.Emp;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;

@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class LoginUser extends User {

    private static final long serialVersionUID = 1L;

    // 追加する(テーブルでユーザーのキーとなる値を設定する)
    public String username;

    // 追加する
    public String password;

    // 独自で必要な項目
    public String empNm;

    public LoginUser(Emp emp) {
        super(emp.empNo, emp.password, true, true, true, true, new ArrayList<GrantedAuthority>());
        username = emp.empNo;
        password = emp.password;
        empNm = emp.empNm;
    }
}

demo/impl/AuthenticationProviderImpl.java

package demo.impl;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Component;

import demo.dao.EmpDao;
import demo.entity.Emp;

@Component
public class AuthenticationProviderImpl implements AuthenticationProvider {

    private static final Logger log = LoggerFactory.getLogger(AuthenticationProviderImpl.class);

    @Autowired
    private EmpDao empDao;

    @Override
    public Authentication authenticate(Authentication auth)
            throws AuthenticationException {

        String id = auth.getName();
        String password = auth.getCredentials().toString();

        if ("".equals(id) || "".equals(password) {
            // 例外はSpringSecurityにあったものを適当に使用
            throw new AuthenticationCredentialsNotFoundException("ログイン情報に不備があります。");
        }

        Emp emp = empDao.authEmp(id, password);
        if (emp == null) {
            // 例外はSpringSecurityにあったものを適当に使用
            throw new AuthenticationCredentialsNotFoundException("ログイン情報が存在しません。");
        }

        return new UsernamePasswordAuthenticationToken(new LoginUser(emp), password, auth.getAuthorities());
    }

    @Override
    public boolean supports(Class<?> token) {
        return UsernamePasswordAuthenticationToken.class.isAssignableFrom(token);
    }
}

demo/impl/UserDetailsServiceImpl.java

package demo.impl;

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.Component;

import demo.dao.EmpDao;
import demo.dto.LoginUser;
import demo.entity.Emp;

@Component
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private EmpDao empDao;

    @Override
    public UserDetails loadUserByUsername(String empNo)
            throws UsernameNotFoundException {

        Emp emp = empDao.findByNo(empNo);
        if (emp == null) {
            throw new UsernameNotFoundException("ユーザーが見つかりませんでした。");
        }

        return new LoginUser(emp);
    }
}

demo/web/LoginController.java

package demo.web;

import javax.validation.Valid;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestMapping;

import demo.form.LoginForm;

@Controller
public class LoginController {

    @RequestMapping(value = "/")
    public String index(Model model) {
        model.addAttribute(new LoginForm());
        return "login/login";
    }
/* ログイン処理は実装しない。SpringSecurityの処理で行われる。
    @RequestMapping(value = "/login")
    public String login(@Valid LoginForm form, BindingResult result, Model model) {

        if (result.hasErrors()) {
            return "login/login";
        }

        return "redirect:/menu";
    }
*/

    // SpringConfigで設定したログインできなかった場合の処理を定義する
    @RequestMapping(value = "/login-error")
    public String loginError(Model model) {
        model.addAttribute("loginError", true);
        return "login/login";
    }
}

demo/web/MenuController.java

package demo.web;

import org.springframework.security.web.bind.annotation.AuthenticationPrincipal;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

import demo.dto.LoginUser;

@Controller
public class MenuController {

    @RequestMapping(value = "/menu")
    public String index(@AuthenticationPrincipal LoginUser loginUser, Model model) {
        // @AuthenticationPrincipalを使うと認証オブジェクトを参照できる。

        return "menu/menu";
    }
}

templates/login.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
</head>
<body>
    <h1>login</h1>
    
    <!-- ログインできなかった時のエラーメッセージ -->
    <p th:if="${loginError}">Login Error!!</p>
    
    <form th:action="@{/login}" method="post">
        <table>
            <tr>
                <td>社員番号</td>
                <td>
                    <input type="text" name="empNo" />
                </td>
            </tr>
            <tr>
                <td>パスワード</td>
                <td>
                    <input type="password" name="password" />
                </td>
            </tr>
        </table>
        <input name="remember-me" type="checkbox" />ログインしたままにする
        <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
        <input type="submit" name="login" value="ログイン" />
    </form>
</body>
</html>

templates/menu.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
</head>
<body>
    <h1>menu</h1>
    <!-- 認証されているか -->
    <p th:if="${#authorization.expression('isAuthenticated()')}">認証済み</p>

    <!-- 認証オブジェクトの参照 -->
    <p th:text="${#authentication.principal.empNm}"></p>
</body>
</html>