目的

  • 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>

参考情報

 

基本機能

  • 認証機能
    • 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

実践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
# 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

実践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

実践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に遷移する
  • 「user」でloginした後に以下のurlに遷移する
  • 「admin」でloginした後に以下のurlに遷移する

実践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が表示されること
  • login後に以下のurlに遷移する
    • http://localhost:8080/
      • 「ログアウト」linkが表示されること
      • 「ログアウト」linkをclickするとlogoutされ、login画面に遷移すること

おまけ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
      • 認証処理が"全て"成功した事を通知する
  • 失敗(発行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();
}