3月30日

67476 ワード

きょう習った

  • セキュリティの使用(2)
  • 作成
  • ユーザ登録ページ
  • Errorページ設定
  • カスタムログインページ
  • を使用
  • navbar修正
  • セキュリティの使用

  • DBでのテーブルの作成と設定
  • ユーザ情報
  • を管理するオブジェクトを作成する.
    package com.myapp.pma.entities;
    
    import javax.persistence.Column;
    import javax.persistence.Entity;
    import javax.persistence.GeneratedValue;
    import javax.persistence.GenerationType;
    import javax.persistence.Id;
    import javax.persistence.Table;
    
    @Entity
    //테이블 자동생성을 사용하지 않고 DB에 테이블을 직접 생성해서 클래스 이름과 다르기 때문에 적어줘야함)
    @Table(name = "user_accounts") 
    public class UserAccount {
    	@Id	// 자동증가
    	@GeneratedValue(strategy = GenerationType.IDENTITY) // DB에서 자동으로 생성
    	@Column(name = "user_id") // 테이블의 Column 이름과 변수명이 다르기 때문에 일치시키기 위해 적어줘야함
    	private long userId;
    	
    	@Column(name = "username") // 테이블의 Column 이름과 변수명이 다르기 때문에 일치시키기 위해 적어줘야함
    	private String userName;
    	
    	private String email;
    	private String password;
    	private String role = "ROLE_USER";
    	private boolean enabled = true;
    	
    	public UserAccount() {
    		// 빈 유저 객체 생성시 role은 유저, enable은 true
    		this.role = "ROLE_USER";
    		this.enabled = true;
    	}
    	public long getUserId() {
    		return userId;
    	}
    	public void setUserId(long userId) {
    		this.userId = userId;
    	}
    	public String getUserName() {
    		return userName;
    	}
    	public void setUserName(String userName) {
    		this.userName = userName;
    	}
    	public String getEmail() {
    		return email;
    	}
    	public void setEmail(String email) {
    		this.email = email;
    	}
    	public String getPassword() {
    		return password;
    	}
    	public void setPassword(String password) {
    		this.password = password;
    	}
    	public String getRole() {
    		return role;
    	}
    	public void setRole(String role) {
    		this.role = role;
    	}
    	public boolean isEnabled() {
    		return enabled;
    	}
    	public void setEnabled(boolean enabled) {
    		this.enabled = enabled;
    	}
    }
    
    登録する
  • ユーザ情報ページ
  • を作成する.
    <!DOCTYPE html>
    <html xmlns:th="http://www.thymeleaf.org">
      <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <link
          href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css"
          rel="stylesheet"
          integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3"
          crossorigin="anonymous"
        />
        <title>시큐리티</title>
      </head>
      <body style="background-color: #ededed">
        <div style="background-color: #337ab7; height: 50px"></div>
        <div class="container-fluid" style="margin-top: 30px">
          <div class="row col-lg-4 mx-auto" style="margin-top: 40px; background-color: #fff; padding: 20px; border: solid 1px #ddd">
            <form autocomplete="off" th:action="@{/register/save}" th:object="${userAccount}" method="post" class="form-signin" role="form">
              <h3 class="form-signin-heading">가입하기 FORM</h3>
              <div class="form-group">
                <div class="mb-2">
                  <input type="text" th:field="*{userName}" placeholder="유저이름" class="form-control" />
                </div>
              </div>
              <div class="form-group">
                <div class="mb-2">
                  <input type="text" th:field="*{email}" placeholder="이메일" class="form-control" />
                </div>
              </div>
              <div class="form-group">
                <div class="mb-2">
                  <input type="password" th:field="*{password}" placeholder="패스워드" class="form-control" />
                </div>
              </div>
              <!-- thymeleaf를 사용하면 아래의 것은 필요 없음-->
              <!-- <input type="hidden" name="_csrf" th:value="${_csrf.token}"> -->
              <div class="form-group">
                <div class="d-grid mb-2">
                  <button type="submit" class="btn btn-primary">가입하기</button>
                </div>
              </div>
              <!-- <span th:utext="${successMessage}"></span> -->
            </form>
          </div>
        </div>
      </body>
    </html>
  • 登録ページからコントローラ
  • に送信.
  • パスワード、データベース
  • に保存
    package com.myapp.pma.controllers;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PostMapping;
    
    import com.myapp.pma.dao.UserAccountRepository;
    import com.myapp.pma.entities.UserAccount;
    
    @Controller
    public class SecurityController {
    
    	@Autowired
    	private UserAccountRepository userRepo;
    	
    	// 암호화는 저장할 때와 인증할때 필요함
    	@Autowired
    	private BCryptPasswordEncoder bEncoder;
    	
    	// 가입하기 화면 표시
    	@GetMapping("/register")
    	public String register(Model model) {
    		UserAccount userAccount= new UserAccount();
    		model.addAttribute("userAccount", userAccount);
    		return "security/register";
    	}
    	
    	@PostMapping("/register/save")
    	public String saveUser(Model model, UserAccount user) {
    		//비밀번호 암호화 후 저장
    		// 넘어온 user정보에서 비밀번호만 얻어서 BCryptPasswordEncoder로 인코딩한 후 저장
    		user.setPassword(bEncoder.encode(user.getPassword()));
    		userRepo.save(user);
    		return "redirect:/";
    	}
    }
  • メモリに一時登録して使用し、データベースのデータを
  • にインポートして検証します.
  • BCryptPasswordEncoderオブジェクトに格納されているパスワードを復号し、入力されたパスワードと一致することを確認します.
    package com.myapp.pma.security;
    
    import javax.sql.DataSource;
    
    import org.springframework.beans.factory.annotation.Autowired;
    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;
    
    // Security 사용순서
    //1. Security 설정을 위해서 WebSecurityConfigurerAdapter 상속 받음
    //2. 어노테이션 @EnableWebSecurity
    @EnableWebSecurity
    @Configuration
    public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    	
    	@Autowired
    	private DataSource dataSource; // MySQL DB와 연결
    	
    	@Autowired
    	private BCryptPasswordEncoder bCryptPasswordEncoder; // 패스워드 인코딩 객체
    	
    	@Override // 3. 인증
    	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    		// 메모리에 아이디, 비밀번호, 역할(권한)설정
    		auth.jdbcAuthentication()
    			.usersByUsernameQuery("select username,password,enabled from user_accounts where username = ? ") // DB에 있는지 확인
    			.authoritiesByUsernameQuery("select username,role from user_accounts where username = ? ")
    			.dataSource(dataSource)
    			.passwordEncoder(bCryptPasswordEncoder); // 암호화된 패스워드를 디코딩해서 입력된 비밀번호와 비교
    	}
    	
    	@Override // 4. 허가
    	protected void configure(HttpSecurity http) throws Exception {
    		http.authorizeHttpRequests()
    			.antMatchers("/projects/new").hasRole("ADMIN") // 새 프로젝트는 관리자만
    			.antMatchers("/projects/save").hasRole("ADMIN")
    			.antMatchers("/employees/new").hasRole("ADMIN") // 새 직원은 관리자만
    			.antMatchers("/employees/save").hasRole("ADMIN")
    			.antMatchers("/employees/").authenticated() // 인증된 유저
    			.antMatchers("/projects/").authenticated()
    			.antMatchers("/","/**").permitAll()				// 누구든 접근 가능
    			.and()
    			.formLogin(); // 로그인창 사용
    	
    		
    		// Security에서는 기본적으로 csrf 방지가 적용중
    //		http.csrf().disable(); // 사용자 의도치 않게 행동하는 것을 방지, save 후 redirect 하는 과정에서 csrf 룰에 위배되어 에러 출력
    	}
    }

    Errorページの設定


  • application.propertiesでデフォルト設定されているエラーページを無効にするserver.error.whitelabel.enabled=false

  • エラー発生時に出力されるコードを受信し、コードに応じて異なるエラーページを移動
  • package com.myapp.pma.controllers;
    
    import javax.servlet.RequestDispatcher;
    import javax.servlet.http.HttpServletRequest;
    
    import org.springframework.boot.web.servlet.error.ErrorController;
    import org.springframework.http.HttpStatus;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.GetMapping;
    
    @Controller
    public class AppErrorController implements ErrorController {
    	
    	// 에러 발생시 주소가 "/error"로 들어온다.
    	@GetMapping("/error")
    	public String handleError(HttpServletRequest request) {
    		// 에러 상태 코드 확인
    		Object status = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
    		
    		if (status != null) { // 에러가 맞을 시
    			Integer statusCode = Integer.valueOf(status.toString()); // 403, 404, 500
    			
    			if (statusCode == HttpStatus.NOT_FOUND.value()) {
    				return "errorpages/404";
    			}
    			else if (statusCode == HttpStatus.INTERNAL_SERVER_ERROR.value()) {
    				return "errorpages/500";
    			}
    			else if (statusCode == HttpStatus.FORBIDDEN.value()) {
    				return "errorpages/403";
    			}
    		}
    		
    		// 위의 에러상태가 아닐 경우 그냥 error 페이지로 이동
    		return "errorpages/error";
    		
    	}
    }
  • カスタムエラーページの準備

  • Loginページの設定

  • セキュリティ構成を変更するライセンス方法
  • 			.formLogin(form -> form.loginPage("/login")
    									.permitAll()) // 커스텀 로그인페이지 사용
    			.logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout")); // 로그아웃 추가, 로그인 페이지를 새로 만들면 로그아웃도 새로 설정
    	

  • templatesフォルダにログインします.htmlファイルの生成


  • 認証が必要なページではなく、カスタムログインページにアクセスします.
  • navbarの変更

  • layouts.htmlのnavbarを変更する
    購読、ログインボタンの追加
    ログイン時に認証するユーザのユーザ名とログアウトボタン出力
  •           <ul class="navbar-nav me-5" th:if="${principal == null}">
                <li class="nav-item">
                  <a class="nav-link active" th:href="@{/register}">가입하기</a>
                </li>
                <li class="nav-item">
                  <a class="nav-link active" th:href="@{/login}">로그인</a>
                </li>
              </ul>
              <form th:if="${principal != null}" method="post" th:action="@{/logout}">
                <span class="text-white" th:text="${'Hi🥇, ' + principal}"></span>
                <button class="btn btn-secondary">로그아웃</button>
              </form>
  • ControllerAddviceは、すべてのコントローラ
  • に使用可能である
  • は、認証するユーザ情報
  • を全ページに送信する.
    package com.myapp.pma;
    
    import java.security.Principal;
    
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.ControllerAdvice;
    import org.springframework.web.bind.annotation.ModelAttribute;
    
    @ControllerAdvice // 모든 Controller에 적용(모든 주소에 적용)
    public class Common {
    
    	// 각 Controller가 화면(뷰)에 보내는 Model객체에 추가
    	@ModelAttribute
    	public void sharedData(Model model, Principal principal) {
    		
    		// Principal은 Security인증시 인증된 유저정보를 담고 있는 객체
    		if (principal != null) {
    			model.addAttribute("principal", principal.getName()); // 인증 유저의 유저네임을 전달
    		}
    	}
    }