3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

SpringBootを基づいてのSpringSecurityの導入(管理者、ユーザ分けログイン画面)(第三章)

Posted at

0 事前に修正

0.1 ユーザテーブルの修正

PASSWORD項目の追加
修正後のユーザテーブル
19.png

0.2 その他の修正

※PASSWORD項目により、下記の対象一覧の修正が必要になる
主にPASSWORD項目に対して修正なので、略します

対象一覧
UserModel.java
UserForm.java
UserMapper.java
UserService.java
NewUser.html
UserList.html

1 Spring Securityの導入

セッション管理やURL管理の為に導入した

1.1 pom.xmlにSpring Security用のdependencyの追加

pom.xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

1.2 管理者用ログイン機能追加

1.2.1 管理者用ログイン画面作成
LoginAdmin.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
  <head>
    <meta charset="UTF-8" />
    <title>管理者ログイン画面</title>
  </head>
  <body>
    <h1>管理者ログイン</h1>
    <form action="#" th:action="@{/admin/login}" th:object="${loginForm}" method="post">
      <table>
        <tr>
          <th class="cell_title">ID</th>
          <td><input type="text" th:field="*{Id}"></td>
        </tr>
        <tr>
          <th class="cell_title">パスワード</th>
          <td><input type="password" th:field="*{Password}"></td>
        </tr>
        <tr><td><input type="hidden" name="Admin" value="Admin"></td></tr>
      </table>
      <button type="submit">ログイン</button>
      <input type="button" value="終了" onclick="window.open('about:blank', '_self').close()">
    </form>
  </body>
</html>
1.2.2 共通用ログインフォームの作成
LoginForm.java
package com.example.demo.dto;

import javax.validation.constraints.NotBlank;

import lombok.Data;

@Data
public class LoginForm {
	@NotBlank
	private String Id;

	@NotBlank
	private String Password;

	@NotBlank
	private String Admin;
}
1.2.3 管理者用ログイン用のWebSecurityConfigurerAdapter仕組の導入

アクセス制限、ログイン、ログアウト等が設定可能の場所

AdminLoginSecurityConfig.java
package com.example.demo.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
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.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.SimpleUrlAuthenticationFailureHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import com.example.demo.config.loginSecurity.AdminAuthenticationProvider;
import com.example.demo.config.loginSecurity.LoginAuthenticationFilter;

@Configuration
@EnableWebSecurity
@Order(1)
public class AdminLoginSecurityConfig extends WebSecurityConfigurerAdapter {

	@Autowired
	AdminAuthenticationProvider authenticationProvider;

	@Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/images/**", "/css/**", "/javascript/**");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
		http
		.antMatcher("/admin/**")
		.authorizeRequests()
		.anyRequest()
        .hasRole("ADMIN")
			.and()
		.logout()
			.logoutUrl("/admin/logout").permitAll()
			.logoutSuccessUrl("/admin/login");

		http.addFilterBefore(createAdminFilter(), LoginAuthenticationFilter.class);
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(authenticationProvider);
    }

    private LoginAuthenticationFilter createAdminFilter() throws Exception {
    	LoginAuthenticationFilter filter = new LoginAuthenticationFilter();
        filter.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/admin/login", "POST"));
        filter.setAuthenticationManager(authenticationManagerBean());
        filter.setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler("/admin/loginFail"));
        filter.setAuthenticationSuccessHandler(new SimpleUrlAuthenticationSuccessHandler("/admin/loginSuccess"));
        return filter;
    }

}
1.2.4 共通用のUsernamePasswordAuthenticationFilter仕組の導入

ログイン情報を保存する場所

LoginAuthenticationFilter.java
package com.example.demo.config.loginSecurity;

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

import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import com.example.demo.dto.LoginForm;

public class LoginAuthenticationFilter extends UsernamePasswordAuthenticationFilter  {

	@Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) {
        LoginForm loginInfo = new LoginForm();
        loginInfo.setId(request.getParameter("Id"));
        loginInfo.setPassword(request.getParameter("Password"));
        loginInfo.setAdmin(request.getParameter("Admin"));

        String password = obtainPassword(request);

        UsernamePasswordAuthenticationToken authRequest =
        		new UsernamePasswordAuthenticationToken(loginInfo, password);

        setDetails(request, authRequest);
        return this.getAuthenticationManager().authenticate(authRequest);
    }
}
1.2.5 共通用のAuthenticationProvider仕組の導入

ログイン成功や権限設定の場所

LoginAuthenticationProvider.java
package com.example.demo.config.loginSecurity;

import java.util.ArrayList;
import java.util.Collection;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;

import com.example.demo.dto.LoginForm;
import com.example.demo.service.UserService;

@Configuration
public class LoginAuthenticationProvider implements AuthenticationProvider {

	@Autowired
	private UserService userService;

	@Override
    public Authentication authenticate(Authentication auth) throws AuthenticationException {

		LoginForm loginInfo = (LoginForm)auth.getPrincipal();
        String password = (String)auth.getCredentials();

        Collection<GrantedAuthority> authorityList = getAuthority(loginInfo);

        if(authorityList.size() == 0) {
            throw new BadCredentialsException("Authentication Error");
        }

        return new UsernamePasswordAuthenticationToken(loginInfo, password, authorityList);
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
    }

    private Collection<GrantedAuthority> getAuthority(LoginForm loginInfo) {
    	Collection<GrantedAuthority> authorityList = new ArrayList<>();

    	if(userService.login(loginInfo.getId(), loginInfo.getPassword())) {
    		if (loginInfo.getAdmin().equals("Admin")) {
            	authorityList.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
            } else if (loginInfo.getAdmin().equals("User")) {
            	authorityList.add(new SimpleGrantedAuthority("ROLE_USER"));
            }
    	}

    	return authorityList;
    }
}
1.2.6 ログイン機能のテーブル接続の作成

ユーザサービスクラスに追加

UserService.java
    public boolean login(String Id, String Password) {
        return dao.login(Id, Password).size() == 1;
    }

ユーザマッパークラスに追加

UserMapper.java
    @Select("SELECT * FROM USER WHERE ID=#{Id} AND PASSWORD=#{Password}")
    List<UserModel> login(String Id, String Password);
1.2.7 ログイン用のコントローラーを作成
LoginController.java
package com.example.demo.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import com.example.demo.dto.LoginForm;

@Controller
public class LoginController {

    @RequestMapping("/admin/login")
    public String loginAdmin(LoginForm loginForm) {
        return "LoginAdmin.html";
    }

    @RequestMapping("/admin/loginSuccess")
    public String loginSuccessAdmin(LoginForm loginForm) {
        return "redirect:/admin/userlist";
    }
}
1.2.8 ユーザコントローラーのURLを修正(アクセス制限により)
UserController.java
package com.example.demo.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.UncategorizedSQLException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import com.example.demo.dto.LoginForm;
import com.example.demo.dto.UserForm;
import com.example.demo.model.UserModel;
import com.example.demo.service.UserService;

@Controller
public class UserController {
	private final UserService userService;

    @Autowired
    public UserController(UserService userService) {
        this.userService = userService;

    }

    @RequestMapping("/admin/new")
    public String addUser(UserForm userForm) {
        return "admin/NewUser.html";
    }

    @PostMapping("/admin/new")
    public String create(@Validated @ModelAttribute UserForm userForm, BindingResult errorResult, Model model) {
		if (errorResult.hasErrors()) {
			return "admin/NewUser.html";
		}

    	try {
    		userService.insert(userForm);
    		return "redirect:userlist";

    	} catch(UncategorizedSQLException e) {
    		FieldError fieldError = new FieldError(errorResult.getObjectName(), "Id", "ユーザ新規作成に失敗");
    		errorResult.addError(fieldError);
    		return "admin/NewUser.html";
    	}
    }

    @GetMapping("/admin/userlist")
    public String displayUsers(Model model) {
    	List<UserModel> users = userService.selectAll();
    	model.addAttribute("users", users);
        return "admin/UserList.html";
    }
}
1.2.9 ユーザ一覧画面トユーザ新規画面を管理者専用にすることと伴い、ファイルパスの修正

修正後
20.png

1.3 ユーザ用ログイン機能追加

1.2.1 ユーザ用ログイン画面作成
LoginUser.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
  <head>
    <meta charset="UTF-8" />
    <title>ユーザログイン画面</title>
  </head>
  <body>
    <h1>ユーザログイン</h1>
    <form action="#" th:action="@{/user/login}" th:object="${loginForm}" method="post">
      <table>
        <tr>
          <th class="cell_title">ID</th>
          <td><input type="text" th:field="*{Id}"></td>
        </tr>
        <tr>
          <th class="cell_title">パスワード</th>
          <td><input type="password" th:field="*{Password}"></td>
        </tr>
        <tr><td><input type="hidden" name="Admin" value="User"></td></tr>
      </table>
      <button type="submit">ログイン</button>
      <input type="button" value="終了" onclick="window.open('about:blank', '_self').close()">
    </form>
  </body>
</html>
1.3.2 共通用ログインフォームの作成(1.2.2と同様になるので、割合)
1.3.3 ユーザ用ログイン用のWebSecurityConfigurerAdapter仕組の導入

アクセス制限、ログイン、ログアウト等が設定可能の場所

UserLoginSecurityConfig.java
package com.example.demo.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
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.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.SimpleUrlAuthenticationFailureHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import com.example.demo.config.loginSecurity.LoginAuthenticationFilter;
import com.example.demo.config.loginSecurity.LoginAuthenticationProvider;

@Configuration
@EnableWebSecurity
@Order(2)
public class UserLoginSecurityConfig extends WebSecurityConfigurerAdapter {

	@Autowired
	LoginAuthenticationProvider authenticationProvider;

	@Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/images/**", "/css/**", "/javascript/**");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
		http
		.antMatcher("/user/**")
		.authorizeRequests()
			.anyRequest()
	        .hasRole("USER")
			.and()
		.logout()
			.logoutUrl("/user/logout").permitAll()
			.logoutSuccessUrl("/user/login");

		http.addFilterBefore(createUserFilter(), LoginAuthenticationFilter.class);
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(authenticationProvider);
    }

    private LoginAuthenticationFilter createUserFilter() throws Exception {
    	LoginAuthenticationFilter filter = new LoginAuthenticationFilter();
        filter.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/user/login", "POST"));
        filter.setAuthenticationManager(authenticationManagerBean());
        filter.setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler("/user/loginFail"));
        filter.setAuthenticationSuccessHandler(new SimpleUrlAuthenticationSuccessHandler("/user/loginSuccess"));
        return filter;
    }
}
1.3.4 共通用のUsernamePasswordAuthenticationFilter仕組の導入(1.2.4と同様になるので、割合)
1.3.5 共通用のAuthenticationProvider仕組の導入(1.2.5と同様になるので、割合)
1.3.6 ログイン機能のテーブル接続の作成(1.2.6と同様になるので、割合)
1.3.7 ユーザログイン用のコントローラーをログインコントローラーに追加
LoginController.java
    @RequestMapping("/user/login")
    public String loginUser(LoginForm loginForm) {
        return "LoginUser.html";
    }

    @RequestMapping("/user/loginSuccess")
    public String loginSuccessUser(LoginForm loginForm) {
        return "redirect:/user/userinfo";
    }
1.3.8 ユーザログイン成功後のコントローラーをユーザコントローラーに追加
UserController.java
    @GetMapping("/user/userinfo")
    public String displayUserInfo(Model model) {
    	Authentication auth = SecurityContextHolder.getContext().getAuthentication();
    	LoginForm loginInfo = (LoginForm)auth.getPrincipal();
    	UserModel user = userService.selectUser(loginInfo.getId());
    	model.addAttribute("user", user);
        return "user/UserInfo.html";
    }
1.3.9 ログインユーザの情報を表示する機能の作成

ユーザ情報画面作成
パス: template/user/UserInfo.html

UserInfo.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
  <head>
    <meta charset="UTF-8" />
    <title>ユーザ情報</title>
  </head>
  <body>
    <h1>ユーザ情報</h1>
    <table>
    <thead>
      <tr>
        <th>ID</th>
        <th>名前</th>
        <th>メール</th>
      </tr>
    </thead>
    <tbody>
        <tr th:object="${user}">
            <td th:text="*{Id}"></td>
            <td th:text="*{Name}"></td>
            <td th:text="*{Email}"></td>
        </tr>
      </tbody>
  </table>
   <form action="#" th:action="@{/user/logout}" method="post">
      <button type="submit">ログアウト</button>
    </form>
  </body>
</html>

ユーザサービスクラスにユーザ情報取得機能の追加

UserService.java
    public UserModel selectUser(String Id) {
        return dao.selectUser(Id);
    }

ユーザマッパークラスにユーザ情報取得機能の追加

UserMapper.java
    @Select("SELECT * FROM USER WHERE ID=#{Id}")
    UserModel selectUser(String Id);    

2 デモ

2.1 管理者

2.1.1 ログイン

22.png

2.1.2 ログイン成功(ユーザ一覧表示)

23.png

2.1.3 ログアウト

24.png

2.2 ユーザ

2.2.1 ログイン

25.png

2.2.2 ログイン成功(ユーザ情報表示)

26.png

2.2.3 ログアウト

27.png

3 終わりに

ここまで、SpringSecurityについて、書きました。
前章と比較しながら参照すればよいと思います。

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?