More Related Content
Similar to Spring bootでweb セキュリティ(ログイン認証)編
Similar to Spring bootでweb セキュリティ(ログイン認証)編 (20)
Spring bootでweb セキュリティ(ログイン認証)編
- 2. アジェンダ
はじめに
準備
認証方式
ユーザー認証処理
ユーザー認証処理のカスタマイズ
パスワードエンコード
BCryptを使ったパスワードのハッシュ化
ロール
ユニットテスト
静的コンテンツの配置
まとめ
- 10. 認証方式
アクセス出来るURLの範囲を決める
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/").permitAll()
.anyRequest().authenticated();
}
HttpSecurityから、
アクセス範囲を決めるクラスを取得
ルート「/」は全ユーザーが
アクセス可能
他のURLは認証が必要
- 16. 認証方式
ホーム画面を作成する
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Spring Security Example</title>
</head>
<body>
<h1>Welcome!</h1>
<p>Click <a th:href="@{/hello}">here</a> to see a greeting.</p>
</body>
</html>
home.html
- 18. 認証方式
挨拶画面を作成する
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Hello World!</title>
</head>
<body>
<h1 th:inline="text">Hello [[${#httpServletRequest.remoteUser}]]!</h1>
<form th:action="@{/logout}" method="post">
<input type="submit" value="Sign Out"/>
</form>
</body>
</html>
hello.html
ログインしたユーザー名が
埋め込まれる
- 20. 認証方式
URLとビューを関連付ける
@Configuration
public class MvcConfig extends WebMvcConfigurerAdapter {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/home").setViewName("home");
registry.addViewController("/").setViewName("home");
registry.addViewController("/hello").setViewName("hello");
}
}
本来はコントローラを作成してビューを制御するが、
今回は簡易版としてWebMvcConfigurerAdapterを
拡張してURLとビューのテンプレートをお手軽に関連付ける
/home もしくは / は home.html を
/hello は hello.html を表示する
- 21. 認証方式
設定メソッドを変更する
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/").permitAll()
.anyRequest().authenticated()
.and()
// .httpBasic();
.formLogin()
.loginPage("/login_page")
.usernameParameter("username")
.passwordParameter("password")
.permitAll();
}
BASIC認証をやめて
Form認証を行う 認証のページは /login_page
ユーザー名とパスワードの項目は
username と password
全ユーザーのアクセスを許可する
- 22. 認証方式
URLとビューを関連付ける
@Configuration
public class MvcConfig extends WebMvcConfigurerAdapter {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/home").setViewName("home");
registry.addViewController("/").setViewName("home");
registry.addViewController("/hello").setViewName("hello");
registry.addViewController("/login_page").setViewName("login_page");
}
}
ログインページ用の関連付けを追加
- 23. 認証方式
ログイン画面を作成する
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Spring Security Example </title>
</head>
<body>
<form th:action="@{/login_page}" method="post">
<div><label> User Name : <input type="text" name="username"/>
</label></div>
<div><label> Password: <input type="password" name="password"/>
</label></div>
<div><input type="submit" value="Sign In"/></div>
</form></body></html>
login_page.html
ユーザー名とパスワードの項目は
username と password
- 25. 認証方式
ユーザー認証処理に、インメモリ認証を使用する
@Configuration
@EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
~~ 中略 ~~
}
@Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
}
}
HttpSecurityを引数に受けるconfigureメソッドの他に
AuthenticationManagerBuilderを受け取るメソッドがあり、
ユーザー認証処理はここに設定を記述する
- 26. 認証方式
ユーザー認証処理に、インメモリ認証を使用する
@Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER").and()
.withUser("admin").password("password").roles("USER", "ADMIN");
}
}
インメモリ認証を行う指定
ユーザーは、user と admin の2つを用意する。
パスワードはいずれも password で、
ロールは user が USERで、adminは USER と ADMIN。
(ロールについては後ほど)
- 30. ユーザー認証処理
ユーザー認証処理をJDBC方式にする
@Autowired
private DataSource dataSource;
@Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth
// .inMemoryAuthentication()
// .withUser("user").password("password").roles("USER").and()
// .withUser("admin").password("password").roles("USER", "ADMIN");
.jdbcAuthentication()
.dataSource(dataSource);
}
}
コンテナが管理している
接続先情報を取得
JDBC方式を指定する
接続先を指定する
- 31. ユーザー認証処理
デフォルトのユーザー情報テーブル
create table users(
username varchar_ignorecase(50) not null primary key,
password varchar_ignorecase(50) not null,
enabled boolean not null
);
create table authorities (
username varchar_ignorecase(50) not null,
authority varchar_ignorecase(50) not null,
constraint fk_authorities_users foreign key(username)
references users(username)
);
create unique index ix_auth_username
on authorities (username,authority);
■Spring Security Reference - 37.1 User Schema より
http://docs.spring.io/spring-security/site/docs/4.0.3.CI-SNAPSHOT/reference/htmlsingle/#user-schema
- 34. ユーザー認証処理のカスタマイズ
自前の認証処理を実装する
create table user_info (
username varchar(50) not null primary key,
email varchar(50) not null,
password varchar(50) not null,
enabled boolean not null,
authority varchar(50) not null
);
create unique index ix_user_info_email on user_info (email);
以下のテーブルに登録したユーザー情報で認証する
テーブルを新規作成
メールアドレスを登録し、
この値とパスワードで
認証する
insert into user_info values('test_user', 'test@user', 'test',
TRUE, 'ROLE_USER');
サンプルデータ
- 38. ユーザー認証処理のカスタマイズ
自前の認証処理を実装したServiceクラスを作成する
@Service
public class UserInfoService
implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String s)
throws UsernameNotFoundException {
return null;
}
}
UserDetailsServiceを
実装する
UserDetailsServiceに
定義されているメソッドを実装する
InMemoryUserDetailsManagerなど
既存の認証処理はこのインターフェースを
実装している
- 39. ユーザー認証処理のカスタマイズ
自前の認証処理を実装したServiceクラスを作成する
@Override
public UserDetails loadUserByUsername(String s)
throws UsernameNotFoundException {
if (s==null || "".equals(s)) {
throw new UsernameNotFoundException("Username is empty");
}
UserInfo userInfo = userInfoRepository.findByEmail(s);
if (userInfo == null) {
throw new UsernameNotFoundException(
"User not found for name: " + s);
}
return userInfo;
}
未入力はエラー
入力されたメールアドレスで検索
検索できなかった場合はエラー
検索できたらそれを返す
- 43. パスワードエンコード
ハッシュ値が入るようテーブルを変更
create table user_info (
username varchar(50) not null primary key,
email varchar(50) not null,
password varchar(60) not null,
enabled boolean not null,
authority varchar(50) not null
);
create unique index ix_user_info_email on user_info (email);
以下のテーブルに登録したユーザー情報で認証する
insert into user_info values('test_user', 'test@user',
'$2a$10$fms1eItee/kPxD8FSdnk/uO9bC.CBweUrQE.CLRgZgzIGcFJVcLRu',
TRUE, 'ROLE_USER');
サンプルデータ
パスワードのカラムを拡張
「test」のハッシュ値
- 44. パスワードエンコード
認証プロバイダを変更
@Configuration
@EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserInfoService userInfoService;
@Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth
.authenticationProvider(createAuthProvider());
// .userDetailsService(userInfoService);
// .jdbcAuthentication()
// .dataSource(dataSource);
} 作成したServiceクラスでの認証から、
後述する認証プロバイダに変更する
- 51. BCryptを使った
パスワードのハッシュ化
Spring SecurityでのBCryptのハッシュ値の生成方法
String hashedPassword
= BCrypt.hashpw( “testpassword”, BCrypt.gensalt() );
上記コードを3回実行した結果
$2a$10$UmuUZJ4D6IaJMm5.UQdsAef2fz6QQuRyRDYrSFab4uNv2SzNEHFW2
$2a$10$.XMgSGCnfecWXh25jKDoZOojlyaMWODJxpoCcOh0YorGM53Vcxq22
$2a$10$1Cc9rpBWu5NenpYameu2f.nHohPeo0HfRLn7dAwKnhQMLkNdibOd6
「testpassword」のハッシュ値を取得するコード
ソルト値もハッシュ値も違う
3つの値が生成された
- 53. ロール
サンプルのテーブル構成と実装
create table user_info (
username varchar(50) not null primary key,
email varchar(50) not null,
password varchar(50) not null,
enabled boolean not null,
authority varchar(50) not null
);
以下のテーブルに登録したユーザー情報で認証する
サンプルのテーブルは
1ユーザー1権限
権限を保持するカラム
public class UserInfo implements UserDetails {
~~ 中略 ~~
public Collection<GrantedAuthority> getAuthorities() {
List<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority(authority));
return authorities;
}
標準モデルに合わせて
リストで返却
- 54. ロール
サンプルユーザー
insert into user_info values('test_user', 'test@user',
'xxx', TRUE, 'ROLE_USER');
insert into user_info values('admin_user', 'admin@user',
'xxx', TRUE, 'ROLE_ADMIN');
サンプルのユーザー 一般権限ユーザー
管理者権限ユーザー
- 55. ロール
アクセス出来るURLの範囲を変更する
@Configuration
@EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated();
~~中略~~
「/admin/」内のURLは
管理者権限ユーザーのみが
アセス出来る
- 58. ユニットテスト
MockMVCにSpring Securityの利用を通知
import static org.springframework.security.test.web.servlet.setup.
SecurityMockMvcConfigurers.*;
~~中略~~
public void setup() {
mockMvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity())
.build();
}
MockMVCに通知
- 59. ユニットテスト
ログインしないでアクセスするテスト
@Test
public void ログインせずにルートページへアクセス() throws Exception {
mockMvc.perform(get("/"))
.andExpect(status().isOk());
}
@Test
public void ログインせずにハローページへアクセス() throws Exception {
mockMvc.perform(get("/hello"))
.andExpect(status().is3xxRedirection());
}
ログインせずにルートへアクセスする場合は
OKステータスが返ってくる
ログインせずにハローページへアクセスする場合は
300番台(移動通知)のどれか
(正確には302:Moved Temporarily)が
返ってくる
- 61. ユニットテスト
権限を指定してアクセスするテスト
@Test
@WithMockUser(username="user@user", roles="USER")
public void 一般ユーザーで管理者ページへアクセス() throws Exception {
mockMvc.perform(get("/admin/adminPage"))
.andExpect(status().is4xxClientError());
}
@Test
@WithMockUser(username="admin@user", roles="ADMIN")
public void 管理者ーザーで管理者ページへアクセス() throws Exception {
mockMvc.perform(get("/admin/adminPage"))
.andExpect(status().isOk());
}
「WithMockUser」アノテーションに
権限を記述
一般ユーザーには権限がないので
400番台(処理失敗)のどれか
(正確には403:Forbidden)が返ってくる
管理者ユーザーならアクセス可能
- 62. ユニットテスト
Formログインにアクセスするテスト
@Test
public void ログインページのテスト() throws Exception {
mockMvc.perform(
formLogin()
.user("username", "test@user")
.password("password", "test")
.loginProcessingUrl("/login_page")
).andExpect(status().isFound())
.andExpect(
authenticated()
.withUsername("test_user")
.withRoles("USER")
);
}
Formログインをする
認証に成功し、
ユーザー名とロールが想定通りか
チェックする
認証情報(今回はメアド)、
パスワード、ログインURLを指定
- 65. まとめ
参考
■Spring Security Reference
http://docs.spring.io/spring-security/site/docs/4.0.3.CI-
SNAPSHOT/reference/htmlsingle/
■Spring Bootでユーザ認証 | memorandum
http://ksoichiro.blogspot.jp/2015/03/spring-boot.html
■Spring Boot でログイン画面 + 一覧画面 + 登録画面の Webアプリケーションを作る ( その10 )( ロ
グイン画面作成3 ) - かんがるーさんの日記
http://ksby.hatenablog.com/entry/2015/02/08/030648#LoginControllerTest.java
■BCrypt(Blowfish暗号)について調べたので文書化してみました
http://www.kamiya54.info/post/100503173956/bcryptblowfish%E6%9A%97%E5%8F
%B7%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6%E8%AA%BF%E3%81%B
9%E3%81%9F%E3%81%AE%E3%81%A7%E6%96%87%E6%9B%B8%E5%8C%96%
E3%81%97%E3%81%A6%E3%81%BF%E3%81%BE%E3%81%97%E3%81%9F