Visual Studio CodeによるSpring5 MVC Webアプリ開発　Spring Security使用編 1/3【準備編】

#はじめに
[SQLServer接続編](https://qiita.com/t_skri/items/4e3c11050f93e4761dda)で作成したプロジェクトを拡張していきます。
順番にコピペしていけば、とりあえず動く物は出来上がります。
コピペ中にエラーが発生しない順番にこだわりました。
ですので、このタイミングでこのコードが存在しているの？必要か？と言う部分もあります。
エラーチェック処理は不完全です。あらかじめご了承下さい。

下記の仕様で作成しました。

- [SQLServer接続編](https://qiita.com/t_skri/items/4e3c11050f93e4761dda)で作成したプロジェクトを拡張する。
- 独自のユーザーテーブルを作成して認証を行う。
- ロールによる動作の違いを見られるように、管理ユーザーと一般ユーザー2つを作成する。
- 一般ユーザーを登録出来るのは管理ユーザーのみとする。
- 管理ユーザーは一つしか作成出来ない。
- CSS、JavaScriptは使用しない。

#環境
OS:Windows 10 Pro 64bit
DB:SQL Server 2019(Cent OS 8 on Hyper-V)
Editor:Visual Studio Code 1.44.2
JDK:AdoptOpenJDK 11.0.6+10 x64
Apache Maven:v3.6.3
Apache Tomcat:v9.0.31

#作成手順
1.ユーザー情報を保存するテーブルの作成
2.DBアクセスに必要なentity、repositoryの作成
3.Spring Securityを使用する為の設定
4.管理ユーザー登録ページの作成
5.一般ユーザー登録ページの作成

#ユーザー情報を保存するテーブルの作成
[こちら](https://qiita.com/t_skri/items/8d69c423042e310434d0)で作成したDBにユーザー情報を保存するUserInfoテーブルと、ロールを保存するUserRolesテーブルを作成します。

```SQL:UserInfo
CREATE TABLE [dbo].[UserInfo](
	[UserId] [varchar](255) NOT NULL,
	[Password] [varchar](255) NOT NULL,
	[UserNameJP] [nvarchar](255) NOT NULL,
	[SectionNameJP] [nvarchar](255) NOT NULL,
	[Enabled] [bit] NOT NULL,
 CONSTRAINT [PK_UserInfo] PRIMARY KEY CLUSTERED 
(
	[UserId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY];
```

```SQL:UserRoles
CREATE TABLE [dbo].[UserRoles](
	[UserId] [varchar](255) NOT NULL,
	[Authority] [varchar](255) NOT NULL,
 CONSTRAINT [PK_UserRoles] PRIMARY KEY CLUSTERED 
(
	[UserId] ASC,
	[Authority] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY];
```

#プロジェクト作成
[SQLServer接続編](https://qiita.com/t_skri/items/4e3c11050f93e4761dda)で作成したプロジェクトを「D:\JAVA\Project\securitySample」にコピーして作成しました。

###pom.xml
Spring Securityに必要なRepositoryと、パスワードの文字数チェックに使用するRepositoryを追加します。

```
<properties>
	<spring-security.version>5.3.0.RELEASE</spring-security.version>
</properties>

<dependency>
	<groupId>org.springframework.security</groupId>
	<artifactId>spring-security-web</artifactId>
	<version>${spring-security.version}</version>
</dependency>

<dependency>
	<groupId>org.springframework.security</groupId>
	<artifactId>spring-security-config</artifactId>
	<version>${spring-security.version}</version>
</dependency>

<dependency>
	<groupId>org.springframework.security</groupId>
	<artifactId>spring-security-taglibs</artifactId>
	<version>${spring-security.version}</version>
</dependency>

<dependency>
   	<groupId>org.hibernate.validator</groupId>
    	<artifactId>hibernate-validator</artifactId>
    	<version>6.1.2.Final</version>
    	<scope>compile</scope>
</dependency>
```

pom.xml全体です。

```XML:pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.example</groupId>
	<artifactId>securitySample1</artifactId>
	<version>1.0-SNAPSHOT</version>
	<packaging>war</packaging>
 
	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<java.version>11</java.version>
		<spring.version>5.2.4.RELEASE</spring.version>
		<spring-security.version>5.3.0.RELEASE</spring-security.version>
        <!-- web.xmlが無い場合でもビルドを実行する -->
        <failOnMissingWebXml>false</failOnMissingWebXml>
	</properties>

	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>3.1</version>
				<configuration>
					<source>${java.version}</source>
					<target>${java.version}</target>
				</configuration>
			</plugin>
		</plugins>
	</build>
	<dependencies>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.11</version>
			<scope>test</scope>
		</dependency>

		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-webmvc</artifactId>
			<version>${spring.version}</version>
			<scope>compile</scope>
		</dependency>

		<dependency>
    		<groupId>org.springframework</groupId>
    		<artifactId>spring-context-support</artifactId>
    		<version>${spring.version}</version>
    		<scope>compile</scope>
		</dependency>

		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-jdbc</artifactId>
			<version>${spring.version}</version>
			<scope>compile</scope>
		</dependency>

		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-web</artifactId>
			<version>${spring-security.version}</version>
		</dependency>

		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-config</artifactId>
			<version>${spring-security.version}</version>
		</dependency>

		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-taglibs</artifactId>
			<version>${spring-security.version}</version>
		</dependency>

		<dependency>
    		<groupId>com.microsoft.sqlserver</groupId>
    		<artifactId>mssql-jdbc</artifactId>
    		<version>8.2.1.jre11</version>
    		<scope>runtime</scope>
		</dependency>

		<dependency>
		    <groupId>com.zaxxer</groupId>
		    <artifactId>HikariCP</artifactId>
		    <version>3.4.2</version>
		    <scope>compile</scope>
		</dependency>

		<dependency>
			<groupId>org.thymeleaf</groupId>
			<artifactId>thymeleaf-spring5</artifactId>
			<version>3.0.11.RELEASE</version>
			<scope>compile</scope>
		</dependency>

		<dependency>
			<groupId>org.thymeleaf.extras</groupId>
			<artifactId>thymeleaf-extras-java8time</artifactId>
			<version>3.0.4.RELEASE</version>
			<scope>compile</scope>
		</dependency>

		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>javax.servlet-api</artifactId>
			<version>4.0.1</version>
			<scope>provided</scope>
		</dependency>

		<dependency>
	    	<groupId>org.hibernate.validator</groupId>
	    	<artifactId>hibernate-validator</artifactId>
	    	<version>6.1.2.Final</version>
	    	<scope>compile</scope>
		</dependency>

		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<version>1.18.10</version>
			<scope>provided</scope>
		</dependency>
	</dependencies>
</project>
```
##entity作成
ユーザー情報を格納するentityを作成します。

- UserInfo.java
- UserRoles.java


```
D:\JAVA\Project\securitySample\src\main\java\com\example\persistence
└─entity
  ├─UserInfo.java
  └─UserRoles.java
```
###UserRoles.java
ロール（権限）を格納します。

```java:UserRoles.java
package com.example.persistence.entity;

import lombok.Data;

@Data
public class UserRoles {
	private String userId;
	private String authority;

	public UserRoles() {}

	public UserRoles(String userId, String authority) {
		this.userId = userId;
		this.authority = authority;
	}

}
```

###UserInfo.java
ユーザー情報を格納します。

```java:UserInfo.java
package com.example.persistence.entity;

import java.util.List;

import lombok.Data;

@Data
public class UserInfo {
    private String userId;
	private String password;
	private String userNameJP;
	private String sectionNameJP;
	private Boolean enabled;
	private List<UserRoles> userRolesList;

	public UserInfo() {}

	public UserInfo(String userId, String userNameJP, String sectionNameJP, Boolean enabled) {
		this.userId = userId;
		this.userNameJP = userNameJP;
		this.sectionNameJP = sectionNameJP;
		this.enabled = enabled;
	}
}
```

##repositoryの作成
DBへアクセスするrepositoryを作成します。

- UserInfoRepository.java
- UserInfoRepositoryImpl.java
- UserRolesRepository.java
- UserRolesRepositoryImpl.java

```
D:\JAVA\Project\securitySample\src\main\java\com\example\persistence
└─repository
  ├─UserInfoRepository.java
  ├─UserInfoRepositoryImpl.java
  ├─UserRolesRepository.java
  └─UserRolesRepositoryImpl.java
```
###UserInfoRepository.java

```java:UserInfoRepository.java
package com.example.persistence.repository;

import java.util.List;
import java.util.Optional;

import com.example.persistence.entity.UserInfo;

public interface UserInfoRepository {
    Optional<UserInfo> selectDetailByUserId(String id);

	Integer insert(UserInfo userInfo);

	Integer update(UserInfo userInfo);

	Integer delete(String userId);

	List<UserInfo> findUserAll();
}
```

###UserInfoRepositoryImpl.java

```java:UserInfoRepositoryImpl.java
package com.example.persistence.repository;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.stereotype.Repository;

import com.example.persistence.entity.UserInfo;
import com.example.persistence.entity.UserRoles;

@Repository
public class UserInfoRepositoryImpl implements UserInfoRepository{
    @Autowired
    NamedParameterJdbcTemplate jdbcTemplate;

    private static final RowMapper<UserInfo> INFO_MAPPER = (rs, rowNum) ->
    new UserInfo(rs.getString("UserId"),
            rs.getString("UserNameJP"),
            rs.getString("SectionNameJP"),
            rs.getBoolean("Enabled")
            );

    private static final RowMapper<UserRoles> ROLES_MAPPER = (rs, rowNum) ->
    new UserRoles(rs.getString("UserId"),
            rs.getString("Authority")
            );

    @Override
    public Optional<UserInfo> selectDetailByUserId(String id) {
        try {

            UserInfo userInfo = new UserInfo();
            List<UserRoles> userRolesList = new ArrayList<UserRoles>();

            String sql = "SELECT UserInfo.UserId, UserInfo.Password, UserInfo.UserNameJP, UserInfo.SectionNameJP, UserInfo.Enabled"
                    + " , UserRoles.Authority"
                    + " FROM UserInfo INNER JOIN UserRoles ON UserInfo.UserId = UserRoles.UserId "
                    + " WHERE (UserInfo.UserId = :id)";

            MapSqlParameterSource parameterSource = new MapSqlParameterSource();
            parameterSource.addValue("id", id);
            List<Map<String, Object>> resultList = jdbcTemplate.queryForList(sql, parameterSource);

            for(Map<String, Object> result : resultList) {
                UserRoles userRoles = new UserRoles();
                userRoles.setUserId((String) result.get("UserId"));
                userRoles.setAuthority((String) result.get("Authority"));
                userRolesList.add(userRoles);

                userInfo.setUserId((String) result.get("UserId"));
                userInfo.setPassword((String) result.get("Password"));
                userInfo.setUserNameJP((String) result.get("UserNameJP"));
                userInfo.setSectionNameJP((String) result.get("SectionNameJP"));
                userInfo.setEnabled((Boolean) result.get("Enabled"));
            }

            userInfo.setUserRolesList(userRolesList);

            return Optional.of(userInfo);

        }catch (EmptyResultDataAccessException e) {

            return Optional.empty();
        }
    }

    @Override
    public Integer insert(UserInfo userInfo) {
        String sql = "INSERT INTO UserInfo"
                + " VALUES(:UserId, :Password, :UserNameJP, :SectionNameJP, :Enabled);";
        MapSqlParameterSource parameterSource = new MapSqlParameterSource();
        parameterSource.addValue("UserId",          userInfo.getUserId());
        parameterSource.addValue("Password",        userInfo.getPassword());
        parameterSource.addValue("UserNameJP",      userInfo.getUserNameJP());
        parameterSource.addValue("SectionNameJP",   userInfo.getSectionNameJP());
        parameterSource.addValue("Enabled",         userInfo.getEnabled());

        Integer rows = jdbcTemplate.update(sql, parameterSource);
        return rows;
    }

    @Override
    public Integer update(UserInfo userInfo) {
        String sql = "UPDATE UserInfo SET "
                + " Password = :Password "
                + ",UserNameJP = :UserNameJP "
                + ",SectionNameJP = :SectionNameJP "
                + ",Enabled = :Enabled "
                + " WHERE UserId = :UserId;";

        MapSqlParameterSource parameterSource = new MapSqlParameterSource();
        parameterSource.addValue("UserId",          userInfo.getUserId());
        parameterSource.addValue("Password",        userInfo.getPassword());
        parameterSource.addValue("UserNameJP",      userInfo.getUserNameJP());
        parameterSource.addValue("SectionNameJP",   userInfo.getSectionNameJP());
        parameterSource.addValue("Enabled",         userInfo.getEnabled());

        Integer rows = jdbcTemplate.update(sql, parameterSource);
        return rows;
    }

    @Override
    public Integer delete(String userId) {
        String sql = "DELETE FROM UserInfo WHERE UserId = :UserId;";
        MapSqlParameterSource parameterSource = new MapSqlParameterSource();
        parameterSource.addValue("UserId", userId);

        Integer rows = jdbcTemplate.update(sql, parameterSource);
        return rows;
    }

    @Override
    public List<UserInfo> findUserAll() {
        String sql1 = "SELECT DISTINCT UserInfo.UserId, UserInfo.UserNameJP, UserInfo.SectionNameJP, UserInfo.Enabled"
                + " FROM UserInfo INNER JOIN UserRoles ON UserInfo.UserId = UserRoles.UserId "
                + " WHERE UserRoles.Authority = 'ROLE_USER'"
                + " ORDER BY UserInfo.UserId;" ;

        List<UserInfo> userInfoList = jdbcTemplate.query(sql1, INFO_MAPPER);

        userInfoList.forEach(userInfo -> {
            String sql2 = "SELECT * FROM UserRoles WHERE UserId = :UserId;";
            MapSqlParameterSource parameterSource = new MapSqlParameterSource();
            parameterSource.addValue("UserId", userInfo.getUserId());
            List<UserRoles> userRolesList = jdbcTemplate.query(sql2, parameterSource, ROLES_MAPPER);

            userInfo.setUserRolesList(userRolesList);
        });

        return userInfoList;
    }
}

```

###UserRolesRepository.java

```java:UserRolesRepository.java
package com.example.persistence.repository;

import com.example.persistence.entity.UserRoles;

public interface UserRolesRepository {
	Integer insert(UserRoles userRoles);

	Integer update(UserRoles userRoles);

	Integer delete(String userId);

	Integer adminCheck();
}
```

###UserRolesRepositoryImpl.java

```java:UserRolesRepositoryImpl.java
package com.example.persistence.repository;

import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.stereotype.Repository;

import com.example.persistence.entity.UserRoles;

@Repository
public class UserRolesRepositoryImpl implements UserRolesRepository {
    @Autowired
    NamedParameterJdbcTemplate jdbcTemplate;

    @Override
    public Integer insert(UserRoles userRoles) {
        String sql = "INSERT INTO UserRoles"
                + " VALUES(:UserId, :Authority);";
        MapSqlParameterSource parameterSource = new MapSqlParameterSource();
        parameterSource.addValue("UserId", userRoles.getUserId());
        parameterSource.addValue("Authority", userRoles.getAuthority());

        Integer rows = jdbcTemplate.update(sql, parameterSource);
        return rows;
    }

    @Override
    public Integer update(UserRoles userRoles) {
        String sql = "UPDATE UserRoles SET Authority = :Authority"
                + " WHERE UserId = :UserId;";
        MapSqlParameterSource parameterSource = new MapSqlParameterSource();
        parameterSource.addValue("UserId", userRoles.getUserId());
        parameterSource.addValue("Authority", userRoles.getAuthority());

        Integer rows = jdbcTemplate.update(sql, parameterSource);
        return rows;
    }

    @Override
    public Integer delete(String userId) {
        String sql = "DELETE FROM UserRoles WHERE UserId = :UserId;";
        MapSqlParameterSource parameterSource = new MapSqlParameterSource();
        parameterSource.addValue("UserId", userId);

        Integer rows = jdbcTemplate.update(sql, parameterSource);
        return rows;
    }

    @Override
    public Integer adminCheck() {
        Integer rows = 0;

        String sql = "SELECT COUNT(*) AS CNT"
                + " FROM UserRoles"
                + " WHERE Authority = 'ROLE_ADMIN';" ;
        MapSqlParameterSource parameterSource = new MapSqlParameterSource();

        Map<String, Object> result = jdbcTemplate.queryForMap(sql,parameterSource);
        rows = Integer.parseInt(result.get("CNT").toString());

        return rows;
    }
}
```

##security部作成
「D:\JAVA\Project\sqlSample\src\main\java\com\example」に「security」フォルダを作成します。
その直下にconfigフォルダを作成します。
configフォルダに以下のファイルを作成します。

- SecurityConfig.java
- SpringSecurityInitializer.java

securityフォルダ直下には以下のファイルを作成します。

- MyAccessDeniedHandler.java
- MyAuthenticationSuccessHandler.java
- MyUserDetails.java
- MyUserDetailsService.java

```
D:\JAVA\Project\securitySample\src\main\java\com\example\security
├─config
|  ├─SecurityConfig.java
|  └─SpringSecurityInitializer.java
├─MyAccessDeniedHandler.java
├─MyAuthenticationSuccessHandler.java
├─MyUserDetails.java
└─MyUserDetailsService.java
```

###MyAuthenticationSuccessHandler.java
認証成功時に表示するページを指定します。
ロールごとに分ける事も可能です。

```java:MyAuthenticationSuccessHandler.java
package com.example.security;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.core.Authentication;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;


public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler  {
	@Override
    public void onAuthenticationSuccess(
        HttpServletRequest request, HttpServletResponse response, Authentication authentication)
        		throws IOException, ServletException {

        RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

        //認証後のリダイレクト先
        if (authentication.getAuthorities().toString().contains("ADMIN")) {
            // ADMINロールはユーザー登録ページを表示する。
        	redirectStrategy.sendRedirect(request, response, "/userReg/index");
        } else {
            // それ以外のロールの時に表示するページ。
        	redirectStrategy.sendRedirect(request, response, "/hello/index");
        }
    }
}
```

###MyAccessDeniedHandler.java
認証処理でエラーが発生した際、表示するページを指定します。


```java:MyAccessDeniedHandler.java
package com.example.security;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.access.AccessDeniedHandler;

public class MyAccessDeniedHandler implements AccessDeniedHandler{
	@Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        response.setStatus(HttpServletResponse.SC_FORBIDDEN);

        RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
        redirectStrategy.sendRedirect(request, response, "/auth/403");
    }
}
```

###MyUserDetails.java
標準の認証情報に追加したい項目を追加します。
ユーザー名の日本語項目と部署名を追加しています。

```java:MyUserDetails.java
package com.example.security;

import java.util.List;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;

import com.example.persistence.entity.UserInfo;

public class MyUserDetails extends User {
    private static final long serialVersionUID = 1L;
    private final String userNameJP;
	private final String sectionNameJP;

	public MyUserDetails(UserInfo userInfo, List<? extends GrantedAuthority> authorityList) {
		super(userInfo.getUserId(), userInfo.getPassword(), userInfo.getEnabled(), true, true, true, authorityList);
		this.userNameJP = userInfo.getUserNameJP();
		this.sectionNameJP = userInfo.getSectionNameJP();
	}

	public String getUserNameJP() {
		return userNameJP;
	}

	public String getSectionNameJP() {
		return sectionNameJP;
	}
}
```

###MyUserDetailsService.java

```java:MyUserDetailsService.java
package com.example.security;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

import com.example.persistence.entity.UserInfo;
import com.example.persistence.entity.UserRoles;
import com.example.persistence.repository.UserInfoRepository;

public class MyUserDetailsService implements UserDetailsService {
    @Autowired
    UserInfoRepository userInfoRepository;

    @Override
    public UserDetails loadUserByUsername(String userId) throws UsernameNotFoundException {
        Optional<UserInfo> userInfoOpt = userInfoRepository.selectDetailByUserId(userId);
        if (userInfoOpt == null) {
            throw new UsernameNotFoundException("");
        }

        UserInfo userInfo = userInfoOpt.get();

        List<SimpleGrantedAuthority> authorityList = new ArrayList<SimpleGrantedAuthority>();

        List<UserRoles> roles = userInfo.getUserRolesList();
        for (UserRoles role: roles) {
            authorityList.add(new SimpleGrantedAuthority(role.getAuthority()));
        }

        return new MyUserDetails(userInfo, authorityList);
    }
}
```
###SecurityConfig.java
今回のアプリではBasic認証は使っておりませんが、Form認証と併用する例として、記述してあります。
Basic認証を優先する記述になっています。

```java:SecurityConfig.java
package com.example.security.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
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.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.csrf.CsrfFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.web.filter.CharacterEncodingFilter;

import com.example.security.MyAccessDeniedHandler;
import com.example.security.MyAuthenticationSuccessHandler;
import com.example.security.MyUserDetailsService;

@Configuration
@EnableWebSecurity
public class SecurityConfig {
	@Autowired
	public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
		auth.userDetailsService(myUserDetailsService());
	}

	@Bean
	public UserDetailsService myUserDetailsService() {
		return new MyUserDetailsService();
	}

	@Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
	/**
	 * Basic認証
	 */
	@Configuration
	@Order(Ordered.HIGHEST_PRECEDENCE)
	public class WebSecurityBasicConfig extends WebSecurityConfigurerAdapter {

		@Override
		protected void configure(HttpSecurity http) throws Exception {
			http
				.csrf().disable()
				.antMatcher("/api/**")
				.authorizeRequests()
					.antMatchers("/api/admin/**").hasAnyRole("ADMIN")
				.anyRequest().authenticated()
					.and()
					.httpBasic()
            ;
		}
	}

	/**
	 * Form認証
	 */
	@Configuration
	public class WebSecurityFormConfig extends WebSecurityConfigurerAdapter {

		@Override
		protected void configure(HttpSecurity http) throws Exception {
			CharacterEncodingFilter filter = new CharacterEncodingFilter();
	        filter.setEncoding("UTF-8");
	        filter.setForceEncoding(true);

			  http
			  	.addFilterBefore(filter, CsrfFilter.class)
			  	.authorizeRequests()

			  	//どのロールでもアクセス可
		  		.antMatchers("/", "/resources/**", "/webjars/**", "/auth/**", "/adminReg/**").permitAll()

		  		// ロールにるアクセス設定
		  		// '/userReg/'で始まるURLには、'ADMIN'ロールのみアクセス可
			    .antMatchers("/userReg/**").hasRole("ADMIN")

			  	.anyRequest().authenticated()
			  	.and()
			  		.formLogin()
			  		.loginProcessingUrl("/login")
			  		.loginPage("/auth/login")
			  		.failureUrl("/auth/login-error")
			  		.successHandler(new MyAuthenticationSuccessHandler())
			  		.permitAll()
		        .and()
		        // ログアウト処理の設定
		        	.logout()
		            // ログアウト処理のURL
		            .logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
		            // ログアウト成功時の遷移先URL
		            .logoutSuccessUrl("/auth/login")
		            // ログアウト時に削除するクッキー名
		            .deleteCookies("JSESSIONID")
		            // ログアウト時のセッション破棄を有効化
		            .invalidateHttpSession(true)
		            .permitAll()
		        .and()
		        //403エラー時の遷移先設定
		        	.exceptionHandling()
		        	.accessDeniedHandler(new MyAccessDeniedHandler())

		        ;
		}

	}
}

```

###SpringSecurityInitializer.java

```java:SpringSecurityInitializer.java
package com.example.security.config;

import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;

public class SpringSecurityInitializer extends AbstractSecurityWebApplicationInitializer {

}
```

###WebAppInitializer.java追記
WebAppInitializerのgetRootConfigClassesにSecurityConfig.classを追加します。

```
protected Class<?>[] getRootConfigClasses() {
    return new Class<?>[] {SecurityConfig.class, PersistenceConfig.class, ServiceConfig.class};
}
```
WebAppInitializer.java全体です。

```java:WebAppInitializer.java
package com.example.web.config;

import java.nio.charset.StandardCharsets;

import javax.servlet.Filter;

import org.springframework.web.filter.CharacterEncodingFilter;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

import com.example.persistence.config.PersistenceConfig;
import com.example.security.config.SecurityConfig;
import com.example.service.config.ServiceConfig;

public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    /**
     * ビジネスロジックなど、Spring MVC以外に関するJava Configクラスを指定します。
     */
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class<?>[] {SecurityConfig.class, PersistenceConfig.class, ServiceConfig.class};
    }

    /**
     * Spring MVCに関するJava Configクラスを指定します。
     */
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[] {MvcConfig.class};
    }

    /**
     * DispatcherServletに対するURLパターンを指定します。
     * "/"を指定することで、全リクエストをDispatcherServletが受け取ります。
     */
    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }

    /**
     * サーブレットフィルターを指定します。
     * 複数のフィルターがあった場合、配列に指定した順番に実行されます。
     */
    @Override
    protected Filter[] getServletFilters() {
        return new Filter[] {
                new CharacterEncodingFilter(StandardCharsets.UTF_8.name(), true)
            };
    }
}
```

#まとめ
以上でSpring Securityを使う上で必要な準備が終わりました。
次はログインページ、管理ユーザー作成ページを作っていきます。