お仕事でSpringBootとFreeMarker使う事になったので、簡単なログインページを実装してみた。
また、フロントエンドにVueを使う事になりそうな感じなので、ついでにWebpackとGradle連携させて、Vueを読み込むところまでやってみた。
SpringBootのKotlinはまだ日本語のドキュメントが少ないので、ソースコード載せつつ要点かいつまんで説明していく。
ソースコード
https://github.com/renoinn/springboot-freemarker-vue-sample
dockerとかでDB用意すればそのまま動くはず。
build.gradle
buildscript {
ext {
kotlinVersion = '1.2.51'
springBootVersion = '2.0.5.RELEASE'
}
repositories {
mavenCentral()
maven {
url "https://plugins.gradle.org/m2/"
}
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}")
classpath("org.jetbrains.kotlin:kotlin-allopen:${kotlinVersion}")
classpath("org.jetbrains.kotlin:kotlin-noarg:${kotlinVersion}")
classpath "com.moowork.gradle:gradle-node-plugin:1.2.0"
}
}
apply plugin: 'kotlin'
apply plugin: 'kotlin-spring'
apply plugin: 'kotlin-allopen'
apply plugin: 'kotlin-jpa'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
apply plugin: 'com.moowork.node'
group = 'com.sample'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8
compileKotlin {
kotlinOptions {
freeCompilerArgs = ["-Xjsr305=strict"]
jvmTarget = "1.8"
}
}
compileTestKotlin {
kotlinOptions {
freeCompilerArgs = ["-Xjsr305=strict"]
jvmTarget = "1.8"
}
}
repositories {
mavenCentral()
}
dependencies {
// spring
compile('org.springframework.boot:spring-boot-starter-actuator')
compile('org.springframework.boot:spring-boot-starter-data-jpa')
compile('org.springframework.boot:spring-boot-starter-freemarker')
compile('org.springframework.boot:spring-boot-starter-security')
compile('org.springframework.boot:spring-boot-starter-web')
compile('org.springframework.session:spring-session-core')
runtime('org.springframework.boot:spring-boot-devtools')
compileOnly "org.springframework.boot:spring-boot-configuration-processor"
// etc
compile("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
compile("org.jetbrains.kotlin:kotlin-reflect")
compile('com.fasterxml.jackson.module:jackson-module-kotlin')
compile('no.api.freemarker:freemarker-java8:1.2.0')
// mysql
runtime('mysql:mysql-connector-java')
// test
testCompile('org.springframework.boot:spring-boot-starter-test')
testCompile('org.springframework.security:spring-security-test')
}
node {
version = '9.11.2'
npmVersion = '6.4.1'
download = true
}
// Webpackでのビルドを実行するタスク
task webpack(type: NodeTask, dependsOn: 'npmInstall') {
def osName = System.getProperty("os.name").toLowerCase()
if (osName.contains("windows")) {
script = project.file('node_modules/webpack/bin/webpack.js')
} else {
script = project.file('node_modules/.bin/webpack')
}
}
// processResourcesタスクの前に上述のwebpackタスクを実行する
processResources.dependsOn 'webpack'
clean.delete << file('node_modules')
まずはbuild.gradle。kotlin-allopen
とkotlin-noarg
、kotlin-jpa
はkotlinでSpringBoot開発する上で、少し面倒な部分を楽にしてくれる。
あと、webpackと連携させたいのでgradle-node-plugin
も入れておく。
WebSecurityConfig.kt
@Configuration
@EnableWebSecurity
class WebSecurityConfig : WebSecurityConfigurerAdapter() {
@Override
@Throws(Exception::class)
override fun configure(web: WebSecurity) {
// これらにマッチするURLへのアクセスは無視する
web.ignoring().antMatchers(
"/images/**",
"/css/**",
"/js/**",
"/webjars/**")
}
@Throws(Exception::class)
override fun configure(http: HttpSecurity) {
http.csrf().disable()
http.authorizeRequests()
.antMatchers("/").permitAll()
.anyRequest().authenticated()
.and()
.formLogin() // フォームでのログインをする宣言
.loginProcessingUrl("/login") // ログイン処理をするエンドポイント。formのactionでここを指定する
.loginPage("/") // ログインフォームのURL
.defaultSuccessUrl("/home") // ログイン成功後のURL
http.logout()
.logoutRequestMatcher(AntPathRequestMatcher("/logout**")) // マッチするリクエストがきたらログアウトする
.logoutSuccessUrl("/") // ログアウト後のURL
}
@Configuration
class AuthenticationConfiguration : GlobalAuthenticationConfigurerAdapter() {
@Autowired
var userDetailsService: UserDetailsServiceImpl = UserDetailsServiceImpl()
@Throws(Exception::class)
override fun init(auth: AuthenticationManagerBuilder) {
auth.userDetailsService<UserDetailsService>(userDetailsService)
.passwordEncoder(BCryptPasswordEncoder())
}
}
}
次にSpringSecurity用のConfig。WebSecurityConfigurerAdapter
のconfigure
をオーバーライドして設定を追加していく。
あと、GlobalAuthenticationConfigurerAdapter
のinit
をオーバーライドしている部分。AuthenticationManagerBuilder
にPasswordEncoder
を渡してやらないとSpringSecurityが有効にならないので注意。仮にパスワードを平文で保存する場合でもNoOpPasswordEncoder
を渡してやらないといけなかったりする。パスワード平文で保存とかほぼやらないと思うけど。
あとSpringSecurityを使う上で、UserDetails
を継承したエンティティクラスとUserDetailsService
の実装クラスを用意してやる必要がある。
## UserDetailsServiceImpl.kt
@Service
class UserDetailsServiceImpl : UserDetailsService {
@Autowired
lateinit var userRepository: UserRepository
override fun loadUserByUsername(username: String): UserDetails {
val user = findByUsername(username)
if (!user.isPresent)
throw UsernameNotFoundException("Username $username is not found")
if (!user.get().isAccountNonLocked)
throw UsernameNotFoundException("Username $username is locked")
return user.get().copy(authorities = getAuthorities())
}
private fun getAuthorities() = AuthorityUtils.createAuthorityList("ROLE_USER")
fun findByUsername(username: String): Optional<User> {
val user = userRepository.findByUsername(username)
return user
}
}
UserDetailsエンティティはリンク先を見てもらうとして、UserDetailsServiceImpl
の方はloadUserByUsername
をオーバーライドしてUserDetails
を返してやらなければならない。この時、返却するUserDetails
のauthorities
の中身の値はROLE_
のプレフィックスで始まる文字列じゃないといけないので注意。ROLE_USER
とかROLE_ADMIN
とか。
## webpack.config.js
const webpack = require("webpack");
const ExtractTextPlugin = require("extract-text-webpack-plugin");
module.exports = {
entry: './src/main/js/app.js',
output: {
filename: 'js/bundle.js',
path: __dirname + '/build/resources/main/static/'
},
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
options: {
loaders: {
css: ExtractTextPlugin.extract({
use: 'css-loader',
fallback: 'vue-style-loader'
})
}
}
},
{
test: /\.js$/,
use: 'babel-loader',
exclude: /node_modules/
},
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: "css-loader"
})
}
]
},
resolve: {
alias: {
'vue$': 'vue/dist/vue.esm.js'
}
},
plugins: [
new ExtractTextPlugin("css/styles.css"),
]
}
;
webpackは特に複雑な事はしてないはず。/build/resources/main/static/js
にファイルを置くとhttps://hoge.com/js
でアクセスできるようになるので、outputはそこに設定している。
まだまだ勉強中なので、こうした方が良いみたいなコメントとか、プルリクとか大歓迎です。
## 参考URL