目的
-
Spring Securityの概要を理解する
- Spring Securityを利用したform認証を実践する
- 権限は2種類用意する:一般、管理
実践環境
-
Spring Boot 2
- Spring Security
- Spring Web Starter
- Thymeleaf
- PostgreSQL + JPA
-
pom.xml(依存関係の箇所のみ抜粋)
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- ▼ test関連 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
参考情報
- Spring徹底入門 Chapter9
- https://terasolunaorg.github.io/guideline/5.1.0.RELEASE/ja/Security/SpringSecurity.html
- https://docs.spring.io/spring-security/site/docs/3.2.4.RELEASE/reference/htmlsingle/
基本機能
- 認証機能
- user認証を行う
- 認可機能
- access制御を行う
強化機能
- session管理機能
- session hijacking対策、session固定攻撃対策、lifecycle制御
- CSRF対策機能
- security header出力機能
- その他
関連module(一部)
- spring-security-core
- 認証/認可
- spring-security-web
- web applicationのsecurity対策
- spring-security-config
- componentのsetupのsupport
- spring-security-acl
- ACLを使用した認可制御
- その他
- LDAP,OpenID,CAS,OAuth
実践1:Login画面の実装
目標
http://localhost:8080/
http://localhost:8080/xxxx
にaccessすると自動でlogin画面に遷移する
※xxxxは任意のURL
※認証処理は次の実践で行う
1) Configurationの実装
package com.example.demo;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
/**
* security設定
*
* <pre>
* @EnableWebSecurity:
* Spring Securityを利用するために必要となるcomponentのBean定義を自動で行う
*
* WebSecurityConfigurerAdapter
* 継承することでdefaultで定義されるBean定義をcustomizeする
* </pre>
*/
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
protected void configure(HttpSecurity http) throws Exception{
// form loginの設定
http.formLogin() // filterを返す
.loginPage("/login") // login form認証のpathを指定
.usernameParameter("userName") // user名のparameter名(inputのname属性)
.passwordParameter("password") // passwordのparameter名(inputのname属性)
.permitAll(); // 全userにlogin formへのaccess権を付与する
// 「/」配下に対して認証を要する
http.authorizeRequests()
.antMatchers("/**")
.authenticated();
}
}
2) Controllerの実装
package com.example.demo;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
public class LoginController {
@RequestMapping("/login")
String loginPage() {
return "login";
}
}
3) Login画面の実装(login.html)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Login Form</title>
</head>
<body>
<p th:if="${param.error}" style="color:red;">login error!</p>
<form th:action="@{/login}" method="POST">
<table>
<tr>
<th>User名</th>
<td><input type="text" name="userName" /></td>
</tr>
<tr>
<th>Password</th>
<td><input type="password" name="password" /></td>
</tr>
<tr>
<th></th>
<td><input type="submit" value="Login" /></td>
<tr>
</table>
</form>
</body>
</html>
4) test
- http://localhost:8080 にaccessすると1.3で作成したlogin画面が表示される
- http://localhost:8080/xxxx にaccessすると1.3で作成したlogin画面が表示される
実践2:仮認証の実装
目標
- 実践1を改造して認証機能を実装する
但し、本実践では認証に使用する資格情報は固定とする
※以降の実践でデータベースから資格情報を取得する箇所を実装する - パスワードは暗号化する
- 資格情報はuser名、password、権限(admin,user)の3つとする
1)資格情報の実装
package com.example.demo;
/**
* 資格情報
*
* @author kimura
*
*/
public class Account {
/** 権限 */
public enum AccountRole{ROLE_ADMIN,ROLE_USER}
private String userName;
private String password;
private AccountRole role;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public AccountRole getRole() {
return role;
}
public void setRole(AccountRole role) {
this.role = role;
}
}
2)UserDetailsの実装
package com.example.demo;
import java.util.Collection;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
/**
* 認証処理で必要となる資格情報を提供する
*
* @author kimura
*
*/
public class AccountUserDetails implements UserDetails{
package com.example.demo;
import java.util.Collection;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
/**
* 認証処理で必要となる資格情報を提供する
*
* @author kimura
*
*/
public class AccountUserDetails implements UserDetails{
private static final long serialVersionUID = 1L;
private final Account account;
private final Collection<GrantedAuthority> authorities;
public AccountUserDetails(Account account)
{
this.account = account;
this.authorities = createAuthoritiesFrom(account);
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
// 権限情報一覧を返す
return authorities;
}
@Override
public String getPassword() {
return account.getPassword();
}
@Override
public String getUsername() {
return account.getUserName();
}
@Override
public boolean isAccountNonExpired() {
// Accountが期限切れでないかどうかを返す
// 今回は期限判定を行わないので固定でtrue(期限有効)を返す
return true;
}
@Override
public boolean isAccountNonLocked() {
// Accountがlockされていないかどうかを返す
// 今回はlock判定を行わないので固定でtrue(未lock)を返す
return true;
}
@Override
public boolean isCredentialsNonExpired() {
// 資格情報が期限切れでないかどうかを返す
// 今回は期限判定を行わないので固定でtrue(期限有効)を返す
return true;
}
@Override
public boolean isEnabled() {
// 認証情報が有効or無効であるかを返す
// 今回は有効/無効判定は行わないので固定でtrue(有効)を返す
return true;
}
/**
* 資格情報からUserDetailsに沿った権限情報を生成する
*
* @param account
* @return
*/
private Collection<GrantedAuthority> createAuthoritiesFrom(Account account) {
Collection<GrantedAuthority> result = null;
switch (account.getRole()) {
case ROLE_ADMIN:
result = AuthorityUtils.createAuthorityList("ROLE_ADMIN","ROLE_USER");
break;
case ROLE_USER:
result = AuthorityUtils.createAuthorityList("ROLE_USER");
break;
default:
throw new IllegalArgumentException("未知のaccess権限を検出しました。");
}
return result;
}
}
3)UserDetailsServiceの実装
package com.example.demo;
import java.util.Collection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
/**
* 資格情報をDataStoreから取得する
*/
@Service
public class AccountService implements UserDetailsService {
package com.example.demo;
import java.util.Collection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
/**
* 資格情報をDataStoreから取得する
*/
@Service
public class AccountService implements UserDetailsService {
@Autowired
PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Account account = null;
// 本来はData Store(DB等)から資格情報を取得するが、
// 今回は資格情報を固定で返す
if (username.equals("user")) {
account = new Account();
account.setUserName(username);
account.setPassword(passwordEncoder.encode("pass"));
account.setRole(Account.AccountRole.ROLE_USER);
}
else if (username.equals("admin")) {
account = new Account();
account.setUserName(username);
account.setPassword(passwordEncoder.encode("pass"));
account.setRole(Account.AccountRole.ROLE_ADMIN);
}
else {
throw new UsernameNotFoundException("指定したuserは存在しません。");
}
return new AccountUserDetails(account);
}
}
4)認証成功時の画面を実装(index.html)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>Hello World</h1>
</body>
</html>
5)Controllerの改造
@Controller
public class LoginController {
@RequestMapping("/")
String index() {
return "index";
}
...
}
6)WebSecurityConfigの改造
// 資格情報を取得するためのService
@Autowired
private AccountService accountService;
/**
* passwordをhash化するためのclassをDI Containerに登録する
* @return
*/
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(accountService) // DaoAuthenticationProviderを有効化
.passwordEncoder(passwordEncoder()); // DaoAuthenticationProviderにpasswordEncoderを設定する
}
...
7) test
- http://localhost:8080/login で「user/pass」と入力してlogin buttonをclickする
- index.html画面が表示されること
- http://localhost:8080/login で「use/pass」と入力してlogin buttonをclickする
- login画面が表示され、error messageが表示されること
実践3:資格情報をDatabaseから取得する
目標
- 実践2を改造して資格情報をdatabase(postgresql)から取得する
- PostgreSQL自体の設定方法については省略する
1)各種設定を行う
- pom.xmlにjpa及びpostgressqlを追加する
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
- application.propertiesに接続設定を追加する
- Databaseの設定は以下の通りとする
- Host:localhost
- Post:5432(default)
- DB名:account
- user名:account
- password:password
- Databaseの設定は以下の通りとする
# data source
## DB接続設定
spring.datasource.url=jdbc:postgresql://localhost:5432/account
spring.datasource.username=account
spring.datasource.password=account11
spring.datasource.sql-script-encoding=UTF-8
## jpa
spring.jpa.database=POSTGRESQL
### jpaが生成したSQLを表示するようにする
spring.jpa.show-sql=true
### EntityからDatabaseのTable自動生成する
spring.jpa.hibernate.ddl-auto=create
- postgresqlにはblob型がないのでblog型を無効にする(hibernate.properties)
- 設定しなくても問題はないが、起動時に例外が出力される
hibernate.jdbc.lob.non_contextual_creation = true
2)初期データの用意(import.sql)
INSERT INTO account(user_name,password,role) VALUES('admin','$2a$10$F79QICld5Ep2RU.qPDaGRetp2EPQki/GnTuwrNVZ6onkO1UJPUBDm',0);
INSERT INTO account(user_name,password,role) VALUES('user','$2a$10$F79QICld5Ep2RU.qPDaGRetp2EPQki/GnTuwrNVZ6onkO1UJPUBDm',1);
- 「$2a...」はpassをBCryptPasswordEncoderでencodeした値
3)資格情報をEntity化
package com.example.demo;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
/**
* 資格情報
*
* @author kimura
*
*/
@Entity
@Table(name="account")
public class Account {
public enum AccountRole{ROLE_ADMIN,ROLE_USER}
@Id
@Column(nullable=false,unique=true)
private String userName;
@Column(nullable=false)
private String password;
@Column(nullable=false)
private AccountRole role;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public AccountRole getRole() {
return role;
}
public void setRole(AccountRole role) {
this.role = role;
}
}
4)Repositoryの作成(AccountRepository)
package com.example.demo;
import org.springframework.data.jpa.repository.JpaRepository;
public interface AccountRepository extends JpaRepository<Account,String>{
public Account findByUserName(String username);
}
5)UserDetailsServiceの改造
- databaseから認証情報を取得する
package com.example.demo;
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.Service;
/**
* 資格情報をDataStoreから取得する
*/
@Service
public class AccountService implements UserDetailsService {
@Autowired
private AccountRepository accountRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Account account = null;
// 検索条件check
if (username == null || username.isEmpty()) {
throw new UsernameNotFoundException("指定したuserは存在しません。");
}
// accountを検索する
account = accountRepository.findByUserName(username);
if (account == null) {
throw new UsernameNotFoundException("指定したuserは存在しません。");
}
return new AccountUserDetails(account);
}
}
6)test
- http://localhost:8080/login で「user/pass」と入力してlogin buttonをclickする
- index.html画面が表示されること
- http://localhost:8080/login で「use/pass」と入力してlogin buttonをclickする
- login画面が表示され、error messageが表示されること
実践4:認可(Pathへのaccess制限)
目標
- 実践3を改造してAccess制限機能を設ける
- 「/**」は認証不要で誰でもアクセス可能
- 「/user/**」はUSER権限を持つ認証userのみアクセス可能
- 「/admin/**」はADMIN権限を持つ認証userのみアクセス可能
- access制限を設定する際の注意点
- 先頭から順に判断される
1)html fileの用意
- ADMIN権限用(/templates/admin/index.html)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>Hello Admin Page</h1>
</body>
</html>
- USER権限用(/templates/user/index.html)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>Hello User Page</h1>
</body>
</html>
2)Controllerの改造
package com.example.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class LoginController {
...
@RequestMapping("/user")
String userPage() {
return "user/index";
}
@RequestMapping("admin")
String adminPage() {
return "admin/index";
}
}
3)Configurationの改造
package com.example.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
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.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* security設定
*
* <pre>
* @EnableWebSecurity:
* Spring Securityを利用するために必要となるcomponentのBean定義を自動で行う
*
* WebSecurityConfigurerAdapter
* 継承することでdefaultで定義されるBean定義をcustomizeする
* </pre>
*/
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
...
protected void configure(HttpSecurity http) throws Exception{
...
http.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN") // admin権限を持つuserのみaccess可能(※ROLE_を先頭につけてはいけない)
.antMatchers("/user/**").hasAnyRole("ADMIN","USER") // admin,user権限を持つuserのみaccess可能(※ROLE_を先頭につけてはいけない)
.antMatchers("/**").permitAll(); // 誰でもaccess可能
}
...
}
* hasRole以外にも権限を設定するmethodが幾つか存在する
* 複数の条件を設定することも可能
4)test
- http://localhost:8080 にaccessする
- index.html画面が表示されること
- loginを求められないこと
- index.html画面が表示されること
- http://localhost:8080/login で「user/pass」と入力してlogin buttonをclickする
- http://localhost:8080/user にaccessする
- /user/index.html画面が表示されること
- http://localhost:8080/admin にaccessする
- error画面が表示されること
- http://localhost:8080/user にaccessする
- http://localhost:8080/login で「admin/pass」と入力してlogin buttonをclickする
- http://localhost:8080/user にaccessする
- /user/index.html画面が表示されること
- http://localhost:8080/admin にaccessする
- /admin/index.html画面が表示されること
- http://localhost:8080/user にaccessする
実践5:認可(Methodへのaccess制限)
- 実践4を改造して使用する
- 今回は書籍で使っている「@Pre〜」を利用する
- 参考:@PreAuthorize,@PostAuthorize,@PreFilter,@PostFilter,@Secured,@RolesAllowd等がある
1) AOP認可有効用のConfigurationを作成
package com.example.demo;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
/**
* <pre>
* @EnableGlobalMethodSecurity
* method呼び出しに対する認可処理を行うAOPを有効にする
* 今回はprePostEnabledのみ有効化
* </pre>
*
* @author kimura
*/
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class MethodSecurityConfig {
}
2) AccountServiceの改造
package com.example.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.parameters.P;
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.Service;
/**
* 資格情報をDataStoreから取得する
*/
@Service
public class AccountService implements UserDetailsService {
...
/**
* 自分又は管理者権限を持つuserの情報のみaccess可能(method実行前にcheckする)
* <pre>
* annotationは複数設定可能
* principal.userName : 引数にaccessできる
* </pre>
*
* @param userName
* @return
*/
@PreAuthorize("hasRole('ADMIN') or (#userName == principal.username)")
public Account findOne(@P("userName") String userName) {
return accountRepository.findByUserName(userName);
}
/**
* 自分又は管理者権限を持つuserの情報のみaccess可能(method実行後にcheckする)
*
* <pre>
* annotationは複数設定可能
* principal.account.userName : 戻り値にaccessできる
*
* ※今回はこのmethodは利用しないが参考までに記述する
* </pre>
* @param userName
* @return
*/
@PostAuthorize("hasAnyRole('ADMIN') or (#userName == principal.account.userName)")
public Account findOneHoge(String userName) {
return accountRepository.findByUserName(userName);
}
...
}
3) Controllerの改造
package com.example.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class LoginController {
...
@RequestMapping("/info/{userName}")
String info(@PathVariable("userName") String userName,Model model) {
Account account = null;
try {
account = accountService.findOne(userName); // access policyが設定されたmethod
model.addAttribute("isAccess","true");
model.addAttribute("userName",account.getUserName());
model.addAttribute("role",account.getRole());
}
catch(Exception e) {
model.addAttribute("isAccess","false");
}
return "info";
}
...
}
4)確認用のhtmlを作成(/info.html)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>UserInfo</h1>
<div th:if="${isAccess}">
<p th:text="${userName}"></p>
<p th:text="${role}"></p>
</div>
<div th:if="${!isAccess}">
<p>指定したuser情報にaccessする権限がありません。</p>
</div>
</body>
</html>
5) test
- loginせずに以下のurlに遷移する
- http://localhost:8080/info/user
- 「指定したuser情報にaccessする権限がありません。」と画面に表示されること
- http://localhost:8080/info/admin
- 「指定したuser情報にaccessする権限がありません。」と画面に表示されること
- http://localhost:8080/info/user
- 「user」でloginした後に以下のurlに遷移する
- http://localhost:8080/info/user
- 「user」のuser名を権限が画面に表示されること
- http://localhost:8080/info/admin
- 「指定したuser情報にaccessする権限がありません。」と画面に表示されること
- http://localhost:8080/info/user
- 「admin」でloginした後に以下のurlに遷移する
- http://localhost:8080/info/user
- 「user」のuser名を権限が画面に表示されること
- http://localhost:8080/info/admin
- 「admin」のuser名を権限が画面に表示されること
- http://localhost:8080/info/user
実践6:認証済みの資格情報をthymeleafから利用する
参考site
目標
- 実践5を改造して使用する
- thymeleafから資格情報を利用する
1)各種設定を行う
- pom.xmlにthymeleaf-extras-springsecurity5を追加する
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
2)htmlを改造する(/index.html)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>Hello World</h1>
<div sec:authorize="isAuthenticated()">
<p>こんにちは「<span sec:authentication="principal.username"></span>」さん</p>
<p sec:authorize="hasRole('ROLE_ADMIN')">あなたは、管理者権限を持っています</p>
<p sec:authorize="hasRole('ROLE_USER')">あなたは、一般ユーザ権限を持っています</p>
</div>
<div sec:authorize="!isAuthenticated()">
<p>ログインしていません</p>
</div>
</body>
</html>
3)test
- loginせずに以下のurlに遷移する
- http://localhost:8080/
- 「ログインしていません」と表示されること
- http://localhost:8080/
- 「user」でloginした後に以下のurlに遷移する
- http://localhost:8080/
- user名「user」と保持している権限が表示されること
- http://localhost:8080/
- 「admin」でloginした後に以下のurlに遷移する
- http://localhost:8080/
- user名「admin」と保持している権限が表示されること
- http://localhost:8080/
実践7:logoutの実装
目標
- 実践6を改造して使用する
- logout機能を実装する
1)index.htmlを改造(/index.html)
...
<div sec:authorize="!isAuthenticated()">
<a th:href="@{/login}">ログイン</a>
</div>
<div sec:authorize="isAuthenticated()">
<a th:href="@{/logout}">ログアウト</a>
</div>
...
2)login.htmlを改造
...
<p th:if="${param.logout}" style="color:red;">ログアウトしました。</p>
...
3)Configurationの改造
package com.example.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
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.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* security設定
*
* <pre>
* @EnableWebSecurity:
* Spring Securityを利用するために必要となるcomponentのBean定義を自動で行う
*
* WebSecurityConfigurerAdapter
* 継承することでdefaultで定義されるBean定義をcustomizeする
* </pre>
*/
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
...
protected void configure(HttpSecurity http) throws Exception{
// logout
http.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout")) // logoutのURLを指定
.logoutSuccessUrl("/login?logout") // logout成功時の遷移先URL。
.permitAll();
// logout補足
CSRFを追加するとHTTP POSTのみ反応する。
->http.logout().logoutUrl("/logout")
とした場合、LogoutFilterは「/logout」のPOSTに対してのみ反応する
これによって悪意のあるuserがuserを強制的にlogoutできなくなる。
logoutをGET methodで行いたい場合は、サンプルのように「logoutRequestMatcher」
を利用する。
...
}
...
}
4) test
- loginせずに以下のurlに遷移する
- http://localhost:8080/
- 「ログインしていません」と表示されること
- 「ログイン」linkが表示されること
- http://localhost:8080/
- login後に以下のurlに遷移する
- http://localhost:8080/
- 「ログアウト」linkが表示されること
- 「ログアウト」linkをclickするとlogoutされ、login画面に遷移すること
- http://localhost:8080/
おまけ1:認証eventのhandling
目標
- 実践7を改造して使用する
- 認証eventのhandlingについて理解する
1) Listenerを作成する
package com.example.demo;
import org.springframework.context.event.EventListener;
import org.springframework.security.authentication.event.AuthenticationSuccessEvent;
import org.springframework.stereotype.Component;
/**
* 認証event listener
* <pre>
* @Componentをつけること
* </pre>
*
* @author kimura
*/
@Component
public class AccountResponseListener {
/**
* 認証成功時のイベント(method名は任意)
* <pre>
* @EventListenerをつけること(つけるだけで認識してくれる)
* </pre>
*
* @param event
*/
@EventListener
public void handleFor(AuthenticationSuccessEvent event) {
}
}
2) test
- handlerFor methodにbreak pointを設定し、http://localhost:8080/login にaccessしてloginする
- break pointに来ることを確認する
補足)認証関係のその他のEvent
- 成功
- AuthenticationSuccessEvent
- AuthenticationProvide"による"認証処理が成功したこ事を通知する
- SessionFixationProtectionEvent
- session固定攻撃対策処理が成功した事を通知する
- InteractiveAuthenticationSuccessEvent
- 認証処理が"全て"成功した事を通知する
- AuthenticationSuccessEvent
- 失敗(発行timingはclass名を見ればわかるので省略する)
- AuthenticationFailuerBadCredentialsEvent
- AuthenticationFailuerDisableEvent
- AuthenticationFailuerLockedEvent
- AuthenticationFailuerExpiredEvent
- AuthenticationFailuerCredentialsExpiredEvent
- AuthenticationFailuerServiceExceptionEvent
おまけ2:認可eventのhandling
目標
- 実践7を改造して使用する
- 認可eventのhandlingについて理解する
- 権限のないresourceにaccess使用とした場合は強制logout+login画面へ遷移させる
1)Configurationの改造
package com.example.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
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.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
/**
* security設定
*
* <pre>
* @EnableWebSecurity:
* Spring Securityを利用するために必要となるcomponentのBean定義を自動で行う
*
* WebSecurityConfigurerAdapter
* 継承することでdefaultで定義されるBean定義をcustomizeする
* </pre>
*/
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
...
protected void configure(HttpSecurity http) throws Exception{
...
// 認可error handling
// access権限のないresourceにaccessした場合は強制logout
http.exceptionHandling()
.accessDeniedPage("/logout");
}
...
}
補足)default動作のcustomize
default実装をcustomizeしたい場合
protected void configure(HttpSecurity http) throws Exception{
...
http.exceptionHandling()
.authenticationEntoryPoint(customizeAuthenticationEntoryPoint()) // 自前のAuthenticationEntoryPointを使う
.accessDeniedHandler(customizeAccessDeniedHandler()) // 自前のAccessDeniedHandlerを使う
}
おまけ3:Javaから認証情報へaccessする
目標
- Javaから認証情報へaccessする方法を理解する
1) サンプル
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth.isAuthenticated()) {
// 認証済み
String userName = auth.getName();
}