스프링 시큐리티/실전프로젝트 - 인증 프로세스 Ajax 인증 구현

ch07. Ajax 로그인 구현 & CSRF 설정

webmaster 2022. 1. 21. 22:32
728x90
  • 헤더 설정
    • 전송 방식이 Ajax 인지의 여부를 위한 헤더 설정
    • xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
  • CSRF 같은 경우
    • AJAX 같은 경우 직접 생성해 주어야 한다.
      • <meta id="_csrf" name="_csrf" th:content="${_csrf.token}"/>
      • <meta id="_csrf_header" name="_csrf_header" th:content="${_csrf.headerName}"/>
      • var csrfHeader = $('meta [name="_csrf_header"]'). attr('content')
      • var csrfToken = $('meta [name="_csrf"]'). attr('content’)
      • xhr.setRequestHeader(csrfHeader, csrfToken);
    • Login.html
      • <!DOCTYPE html>
        <html xmlns:th="http://www.thymeleaf.org">
        
        <meta id="_csrf" name="_csrf" th:content="${_csrf.token}"/>
        <meta id="_csrf_header" name="_csrf_header" th:content="${_csrf.headerName}"/>
        <head th:replace="layout/header::userHead"></head>
        <script>
            function formLogin(e) {
        
                var username = $("input[name='username']").val().trim();
                var password = $("input[name='password']").val().trim();
                var data = {"username" : username, "password" : password};
        
                // var csrfHeader = $('meta[name="_csrf_header"]').attr('content')
                // var csrfToken = $('meta[name="_csrf"]').attr('content')
        
                $.ajax({
                    type: "post",
                    url: "/api/login",
                    data: JSON.stringify(data),
                    dataType: "json",
                    beforeSend : function(xhr){
                        xhr.setRequestHeader(csrfHeader, csrfToken);
                        xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
                        xhr.setRequestHeader("Content-type","application/json");
                    },
                    success: function (data) {
                        console.log(data);
                        window.location = '/';
        
                    },
                    error : function(xhr, status, error) {
                        console.log(error);
                        window.location = '/login?error=true&exception=' + xhr.responseText;
                    }
                });
            }
        </script>
        <body>
        <div th:replace="layout/top::header"></div>
        <div class="container text-center">
            <div class="login-form d-flex justify-content-center">
                <div class="col-sm-5" style="margin-top: 30px;">
                    <div class="panel">
                        <p>아이디와 비밀번호를 입력해주세요</p>
                    </div>
                    <div th:if="${param.error}" class="form-group">
                        <span th:text="${exception}" class="alert alert-danger">잘못된 아이디나 암호입니다</span>
                    </div>
                    <form th:action="@{/login_proc}" class="form-signin" method="post">
                        <input type="hidden" th:value="secret" name="secret_key" />
                        <div class="form-group">
                            <input type="text" class="form-control" name="username" placeholder="아이디" required="required" autofocus="autofocus">
                        </div>
                        <div class="form-group">
                            <input type="password" class="form-control" name="password" placeholder="비밀번호" required="required">
                        </div>
                        <button type="submit" onclick="formLogin()" id="formbtn" class="btn btn-lg btn-primary btn-block">로그인</button>
        
                    </form>
                </div>
            </div>
        </div>
        </body>
        </html>
      • csrf 값을 Ajax로 전달한다.
    • home.html
      • <!DOCTYPE html>
        <html lang="ko" xmlns:th="http://www.thymeleaf.org">
        <meta id="_csrf" name="_csrf" th:content="${_csrf.token}"/>
        <meta id="_csrf_header" name="_csrf_header" th:content="${_csrf.headerName}"/>
        <head th:replace="layout/header::userHead"></head>
        <script>
            function messages() {
                var csrfHeader = $('meta[name="_csrf_header"]').attr('content')
                var csrfToken = $('meta[name="_csrf"]').attr('content')
        
                $.ajax({
                    type: "post",
                    url: "/api/messages",
                    dataType: "json",
                    beforeSend : function(xhr){
                        xhr.setRequestHeader(csrfHeader, csrfToken);
                        xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
                        xhr.setRequestHeader("Content-type","application/json");
                    },
                    error : function(xhr, status, error) {
                        console.log(error);
                        if(xhr.responseJSON.status == '401'){
                            window.location = '/api/login?error=true&exception=' + xhr.responseJSON.message;
                        }else if(xhr.responseJSON.status == '403'){
                            window.location = '/api/denied?error=true&exception=' + xhr.responseJSON.message;
                        }
                    }
                });
            }
        </script>
        <body>
        <div th:replace="layout/top::header"></div>
        <div class="container">
            <div class="row align-items-start">
                <nav class="col-md-2 d-none d-md-block bg-light sidebar">
                    <div class="sidebar-sticky">
                        <ul class="nav flex-column">
                            <li class="nav-item">
                                <div style="padding-top:10px;" class="nav flex-column nav-pills" aria-orientation="vertical">
                                    <a th:href="@{/}" style="margin:5px;" class="nav-link active">대시보드</a>
                                    <a th:href="@{/mypage}" style="margin:5px;" class="nav-link text-primary">마이페이지</a>
                                    <a href="a" onclick="messages()" style="margin:5px;" class="nav-link text-primary">메시지</a>
                                    <a th:href="@{/config}" style="margin:5px;" class="nav-link text-primary">환경설정</a>
                                </div>
                            </li>
                        </ul>
                    </div>
                </nav>
                <div style="padding-top:50px;"  class="col">
                    <div class="container text-center">
                        <h1 class="text-primary">DASHBOARD</h1>
                        <div class="security"></div>
                        <h1>Core Spring Security 에 오신 것을 환영합니다.</h1>
                    </div>
                </div>
            </div>
        </div>
        <div th:replace="layout/footer::footer"></div>
        </body>
        </html>
    • @GetMapping(value = {"/login", "/api/login"})
      public String login(
              @RequestParam(value = "error", required = false) boolean error,
              @RequestParam(value = "exception", required = false) String exception,
              Model model) {
          model.addAttribute("error", error);
          model.addAttribute("exception", exception);
          return "/user/login/login";
      }
      
      
      @GetMapping(value = {"/denied", "/api/denied"})
      public String accessDenied(@RequestParam(value = "exception", required = false) String exception, Model model){
          Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
          Account account = (Account) authentication.getPrincipal();
          model.addAttribute("username", account.getUsername());
          model.addAttribute("exception", exception);
          return "user/login/denied";
      }
    • CSRF TOken을 화면을 Meta로 전달해 줄 수 있다.

 

728x90