SPA アプリそろそろ作って見たいんじゃい(3)
とりあえず SpringBootSecurity を設定してみた回
まずは build.gradle に依存を追加する。
dependencirs{ compile('org.springframework.boot:spring-boot-starter-security') // あとはおすきに
そしてログインに使用する DB からデータを取得するためのインターフェースを追加する。
基本的に DB にアカウントとパスワードを格納しているので、それを取得する。
interface AccountRepository : JpaRepository<Account, Long> { fun findOneByName(name: String): Optional<Account> }
次に、Spring がログインの際に使用するユーザ情報のデータ定義を作る。
package net.white_azalea.todo_demo.service.secure import net.white_azalea.todo_demo.repositories.Account import org.springframework.security.core.authority.AuthorityUtils import org.springframework.security.core.userdetails.User class LoginAccount(account: Account) : User(account.name, account.password, AuthorityUtils.createAuthorityList("LOGIN_ROLE")) { }
中身はあってもなくてもいい。
要するにアカウント ID と、ハッシュ化されたパスワードがあり、その時の権限を用意する。
次は Spring がアカウント ID を受け付けたときにログイン情報を検索するロジックの実装。
package net.white_azalea.todo_demo.service.secure import net.white_azalea.todo_demo.repositories.AccountRepository 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 @Component class AccountDetailService( val accountRepository: AccountRepository ) : UserDetailsService { override fun loadUserByUsername(username: String?): UserDetails { val acc = accountRepository.findOneByName(username.orEmpty()) if (acc.isPresent) { return LoginAccount(acc.get()) } else { throw UsernameNotFoundException("Account does not exists.") } } }
パスワードのエンクリプトと、チェックを行うためのクラスも作成。
package net.white_azalea.todo_demo.service.secure import org.springframework.security.crypto.password.PasswordEncoder import org.springframework.stereotype.Component import java.util.* import javax.crypto.Mac import javax.crypto.spec.SecretKeySpec @Component class Crypto : PasswordEncoder { override fun encode(rawPassword: CharSequence?): String { if (rawPassword == null) { return "" } return this.sign("SecretKey", rawPassword.toString()) } /** * Check password matching. */ override fun matches(rawPassword: CharSequence?, encodedPassword: String?): Boolean { if (rawPassword == null || encodedPassword == null) { return false } return this.sign("SecretKey", rawPassword.toString()).equals(encodedPassword) } /** * Sign with key and value. * * Not good to use password hash. * TODO: Have to use bcrypt to password. * https://docs.spring.io/spring-security/site/docs/current/apidocs/org/springframework/security/crypto/bcrypt/BCrypt.html */ fun sign(key: String, value: String): String { val secretKey = SecretKeySpec(key.toByteArray(), "HmacSHA1") val mac = Mac.getInstance("HmacSHA1") mac.init(secretKey) val bytes = mac.doFinal(value.toByteArray()) return Base64.getEncoder().encodeToString(bytes) } }
そしてこれらを使ってログインをするための設定クラス
package net.white_azalea.todo_demo.service.secure import org.springframework.context.annotation.Configuration import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder import org.springframework.security.config.annotation.authentication.configurers.GlobalAuthenticationConfigurerAdapter import org.springframework.security.core.userdetails.UserDetailsService @Configuration class AuthenticationConfiguration( val userDetailsService: UserDetailsService, val crypto: Crypto ) : GlobalAuthenticationConfigurerAdapter() { override fun init(auth: AuthenticationManagerBuilder?) { auth?.userDetailsService(userDetailsService) ?.passwordEncoder(crypto) } }
最後に設定これがあれば /login
に account
, password
を POST すれば認証処理が走る。
package net.white_azalea.todo_demo import org.springframework.context.annotation.Configuration 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.web.authentication.AuthenticationFailureHandler import org.springframework.security.web.util.matcher.AntPathRequestMatcher @Configuration @EnableWebSecurity class SecurityConfig : WebSecurityConfigurerAdapter(false) { override fun configure(web: WebSecurity?) { web?.ignoring()?.antMatchers( "/images/**", "/css/**", "/javascript/**", "/webjars/**") } fun authFailure(): AuthenticationFailureHandler { return AuthenticationFailureHandler { request, response, exception -> response.status = 200 response.sendRedirect("/?error=Failed to access.") } } override fun configure(http: HttpSecurity?) { // 認可設定 http?.authorizeRequests() ?.antMatchers("/", "/login")?.permitAll() // index は全ユーザアクセス許可 ?.anyRequest()?.authenticated() // それ以外は認証要求 // ログイン認定 http?.formLogin() ?.loginProcessingUrl("/login") // login url ?.loginPage("/") // ログインフォームのパス ?.failureHandler(authFailure()) // ログイン失敗時のハンドラ ?.defaultSuccessUrl("/menu") // ログイン結果 ?.usernameParameter("account") // ユーザ名パラメータ ?.passwordParameter("password") // パスワードのパラメータ名 ?.and() // ログアウト設定 http?.logout() ?.logoutRequestMatcher(AntPathRequestMatcher("/logout**")) // ログアウト処理のパス ?.logoutSuccessUrl("/") // ログアウト完了時のパス } }
実際試して見たが、Cookie 嬢の JSESSION_ID なんかもきちんと更新されていたし、セッションハイジャック対策もそこそこされているのだろう。
送信フォームもこんなカンジ
<!DOCTYPE HTML> <html lang="ja" xmlns:th="http://www.thymeleaf.org"> <head> <title>top page</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> </head> <body> <h1>Login form!</h1> <form action="/login" method="post" th:action="@{'/login'}"> <div>Account:</div> <input type="text" name="account" /> <div>Pass:</div> <input type="password" name="password" /> <button type="submit">Do login</button> </form> </body> </html>
気をつけなければならないのは @{'/login'}
の部分。
これで作成しないと、URL に CSRF ヘッダも生成されない。つまり POST が蹴られる。
やっぱ大分考慮されてるのねーってカンジである。
SPA アプリそろそろ作って見たいんじゃい(2)
次に DB 接続と Migration してみる。
SpringBoot では公式に Flyway 書いてあるので、そっちを使う。
build.gradle
にcompile("org.flywaydb:flyway-core")
追加。application.properties
に下記を記載
spring.datasource.url=jdbc:h2:./db/example;MODE=MySQL spring.jpa.hibernate.ddl-auto=validate flyway.locations=db/migration flyway.schemas=PUBLIC
src/main/resources
配下にdb/migration
ディレクトリを作って、SQL ツッコミ
注意点は、flyway はスキーマバージョン管理テーブルを作るのに、デフォルトスキーマ「PUBLIC」にアクセスするべき所を、小文字で「public」アクセスしようとして失敗する∑(゚Д゚)
-- see: http://qiita.com/niwashun/items/dc71dfba4cbb9e9eef98 CREATE SCHEMA IF NOT EXISTS "public"; CREATE TABLE `accounts` ( `id` BIGINT AUTO_INCREMENT NOT NULL PRIMARY KEY, `name` VARCHAR(255) NOT NULL, `password` VARCHAR(127) NOT NULL ); -- account and password. INSERT INTO `accounts` (`name`, `password`) VALUES ('account', '8RSglSZ8nqBtmR07hEM3E0Hdslo=');
ここまで来たら、あとは Entity とか作るだけ。
package net.white_azalea.todo_demo.repositories import javax.persistence.Entity import javax.persistence.GeneratedValue import javax.persistence.Id @Entity(name = "accounts") data class Account( @Id @GeneratedValue val id: Long, val name: String, val password: String ) { /** * for Spring Data JPA */ constructor() : this(0, "", "") { // Nothing to do. } }
package net.white_azalea.todo_demo.repositories import org.springframework.data.jpa.repository.JpaRepository import java.util.* interface AccountRepository : JpaRepository<Account, Long> { fun findOneByName(name: String): Optional<Account> }
SPA アプリそろそろ作って見たいんじゃい(1)
話としてはこれだけ。
仕様としては、ログインと TODO だけで、特にセキュリティも考えない。
SPAの構成は、バックエンドに SpringBootWeb + Thymeleaf + SpringDataJPA + SQLite3 構成。
フロントに riot + riot-control でもしようかなと考えてる。
まずは SpringBoot を落としてくる。コレ自体は、サイト で設定してダウンロードすればよい。
選択したのは Web + JPA + Thymeleaf ここまではテンプレ作ってくれる。
で、とりあえずエンティティ作って、HelloWorld まで書いたものをぽとり。
正味 3 時間くらいかかった。
正直たかがこれだけでと思わなくない。
- Kotlin ほとんど書いてないから戸惑った
- Java と違って、
build.gradle
にspringBoot
とかいうセクションを追加しないと、ComponentScan がうまく走らないという状況に気づかなかった
いやー知らないものを詰め込みすぎた感満載。
とはいえ、Kotlin でとりあえず書けるようにはなってきたかな。
「面倒なことは Python にやらせよう」の実習課題(3章以降)
課題図書はこれ
退屈なことはPythonにやらせよう ―ノンプログラマーにもできる自動化処理プログラミング
- 作者: Al Sweigart,相川愛三
- 出版社/メーカー: オライリージャパン
- 発売日: 2017/06/03
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (3件) を見る
gist ではじめてみた。
これも1日1課題以上かなー
プログラム脳を鍛える数学パズル on python3
追記: とりま gist に置いてみた (2017/06/27
プログラマ脳を鍛える数学パズル シンプルで高速なコードが書けるようになる70問
- 作者: 増井敏克
- 出版社/メーカー: 翔泳社
- 発売日: 2015/10/16
- メディア: Kindle版
- この商品を含むブログ (7件) を見る
お題はこれで、目標は1日1個以上。
…ダレ無いといいけど…。
続きを読む超久々に Python 弄ってた
AI 関連が実質 Python 一択で、数学系ライブラリもその表示も Python だとかなり揃ってたので、久々にやってみようとしてどハマりした記録。
何をしようとしたかというと、下記を Python で実装すればいいかなーとかとか漠然と考えてどハマりした。
プログラマ脳を鍛える数学パズル シンプルで高速なコードが書けるようになる70問
- 作者: 増井敏克
- 出版社/メーカー: 翔泳社
- 発売日: 2015/10/14
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (8件) を見る
どこにはまったかというと問題2。
問題内容は書籍を買うか、下記を見て欲しい。
- 「Python だし、eval あるよねー使い方調べるついでに使って見るかー」
→ 9009 に*
突っ込むとしても90*09
とかになるとダウンする。09 ってなんぞやー - 「諦めて、
*
が必要な箇所で文字列区切って掛け算するか」
→ 変数タイポで 1h どハマり(実行時評価だから実行するまでわからないので単純なミスが見つからない) - 「計算できたし正解を比較するか」
→ 久々にやってて問題文間違って覚えてて、「計算の前後で出た四桁数字が順不同で含まれてることじゃね?」と勘違いして実装。
→ 片方の文字列を1時づつ取り出して、もう片方に含まれるか?という判定をするが、122 - 124 でも true になる。
→ なら逆も比較したらええやんと思いきや、122 - 12 で結局OK判定になってしまう。
→ なので、片方の文字列を1時づつ取り出して、もう片方から1字づつ削除(immutable じゃなくて背中が痒く…)してみるも、 Java の String.replace 同様の replace メソッドを見つけたが、第3引数に指定がないと replaceAll 同等動作とか知らずにハマる。
→ 突破したら突破したで仕様違いに気づいて脱力…回文かよ…
で、色々やった挙句、こんな単純な問題に 3 時間も悩まされたという。
途中途中で Syntax error に悩まされたのもでかい。
TextMate2 を使っているが、やっぱ IDE なんかでリアルタイムに syntax チェックしてないとわからん。
加えて言えば、実行字型の為に、動かしてから死んでデバッグの後戻りがひどかった…
慣れの問題なのだろうか、例えば代入時に変数名が一時違った位でも、「新しい変数ができた」と認識されて平然と動いてしまう為、何が悪いか超追いかけづらかった…。
結局最後は全部関数にした…もう…関数型で…イイヨ。
たとえ…Python に… tail call recursive が実装されてなかったとしても…!
positions = [ [1], [2], [3], [2, 1], [3, 2], [3, 1], [3, 2, 1] ] def split(str, pos, stack): if len(pos) == 0: stack.append(str) return stack else: i = pos[0] stack.append(str[i:]) return split(str[:i], pos[1:], stack) from functools import reduce def multiple(list): return reduce(lambda a,b: a * b, list) def is_kaibun(left, right): return left == right[::-1] for cur in range(1000, 9999): strCur = str(cur) for pos in positions: splitted = split(strCur, pos, []) result = multiple(map(lambda v: int(v), splitted)) if is_kaibun(strCur, str(result)): print(str(splitted) + " = " + str(result)) print("success: " + strCur)