LoginSignup
1
2

More than 5 years have passed since last update.

SpringBoot(Kotlin)とFreemarkerでログインするサンプル作った

Last updated at Posted at 2018-09-27

お仕事で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-allopenkotlin-noargkotlin-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。WebSecurityConfigurerAdapterconfigureをオーバーライドして設定を追加していく。
あと、GlobalAuthenticationConfigurerAdapterinitをオーバーライドしている部分。AuthenticationManagerBuilderPasswordEncoderを渡してやらないとSpringSecurityが有効にならないので注意。仮にパスワードを平文で保存する場合でもNoOpPasswordEncoderを渡してやらないといけなかったりする。パスワード平文で保存とかほぼやらないと思うけど。

あとSpringSecurityを使う上で、UserDetailsを継承したエンティティクラスとUserDetailsServiceの実装クラスを用意してやる必要がある。

 User.kt

 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を返してやらなければならない。この時、返却するUserDetailsauthoritiesの中身の値は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

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2