이전 게시글에서는 회원정보를 사용하여 security를 통한 로그인 기능을 처리를 하였다. 세션 정보도 저장을 하였다.
여기서는 security 모듈에서 지정한 로그인 폼이 아니라 별도의 url을 사용하고 로그인 폼 및 처리를 하는 방법을 알아본다. url은 /member/login을 사용한다. 따라서 /member url을 처리하는 MemberController를 별도로 생성한다.
com.example.sboot.member.controller 패키지 밑에 MemberController를 다음과 같이 추가한다.
package com.example.sboot.member.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
@Controller
@RequestMapping("/member")
@RequiredArgsConstructor
@Log4j2
public class MemberController {
@GetMapping("/login")
public void login() {
log.info("login...");
}
}
로그인 폼을 위해 templates/member 팀에 login.html을 다음과 같이 추가한다.
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<th:block th:replace="~{/layout/basic :: setContent(~{this::content} )}">
<th:block th:fragment="content">
<script src="/js/login.js"></script>
<h1 class="mt-4">Login Page</h1>
<form class="form-signin" method="post" action="/member/login">
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
<h2 class="form-signin-heading">Please sign in</h2>
<p>
<label for="username" class="sr-only">Username</label>
<input type="text" id="username" name="username" class="form-control" placeholder="Username" th:value="${result}" required autofocus>
</p>
<p>
<label for="password" class="sr-only">Password</label>
<input type="password" id="password" name="password" class="form-control" placeholder="Password" required>
</p>
<p><input type='checkbox' name='remember-me'/> Remember me on this computer.</p>
<button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
</form>
<h2 class="form-signin-heading">Login with OAuth 2.0</h2>
<table class="table table-striped">
<tr>
<td><a href="/oauth2/authorization/google">Google</a></td>
</tr>
</table>
</th:block>
</th:block>
로그인 폼 및 로그인 POST 처리를 위한 url을 /member/login으로 변경을 하였으므로 SecurityConfig.java 클래스의 시큐리티 설정 정보도 다음과 같이 변경한다.
package com.example.sboot.config;
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.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import com.example.sboot.security.service.CustomUserDetailsService;
import lombok.extern.log4j.Log4j2;
@Configuration
@Log4j2
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private CustomUserDetailsService userDetailsService;
@Bean
PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin().loginPage("/member/login");
http.userDetailsService(userDetailsService);
http.logout();
}
}
login.html에서 파라미터의 유효성 검사를 처리하는 login.js는 다음과 같다. 아이디와 비밀번호에 대하여 유효성 검사를 한다. 유효성 검사가 통과되면 서버로 로그인 요청을 보낸다.
$(document).ready(function() {
var useridValid = false; // 아이디 유효성 검사 결과
var passwordValid = false; // 비밀번호 유효성 검사 결과
var token = $("meta[name='_csrf']").attr("content");
var header = $("meta[name='_csrf_header']").attr("content");
var formObj = $(".form-signin");
// 아이디 유효성 검사
$("input[name='username']").on("keyup", function(e) {
var regExp = /^[-A-Za-z0-9_]+[-A-Za-z0-9_.]*[@]{1}[-A-Za-z0-9_]+[-A-Za-z0-9_.]*[.]{1}[A-Za-z]{2,5}$/;
if( !regExp.test($("input[name='username']").val()) ) {
$("input[name='username']").css("color", "#EE5656");
useridValid = false;
}
else {
$("input[name='username']").css("color", "blue");
useridValid = true;
}
});
// 비밀번호 유효성 검사
$("input[name='password']").on("keyup", function(e) {
var regExp = /^(?=.*[a-zA-Z])(?=.*[!@#$%^*+=-])(?=.*[0-9]).{5,15}$/;
if( !regExp.test($("input[name='password']").val()) ) {
$("input[name='password']").css("color", "#EE5656");
passwordValid = false;
}
else {
$("input[name='password']").css("color", "blue");
passwordValid = true;
}
});
$(".btn-primary").on("click", function(e) {
e.preventDefault();
var userid = $("input[name='username']").val();
if(userid == null || userid.length == 0) {
alert("아이디를 입력하세요");
userid.focus();
return;
}
if(useridValid == false) {
alert("이메일 형식의 고유한 아이디를 사용하세요.");
return;
}
var password = $("input[name='password']").val();
if(password == null || password.length == 0) {
alert("암호를 입력하세요");
password.focus();
return;
}
if(passwordValid == false) {
alert("특수문자, 영문, 숫자의 조합으로 8자 이상 15자 이하를 사용하세요.");
return;
}
console.log("submit clicked");
formObj.submit();
});
});
basic.html에서 절대경로를 고려하기 위하여 다음과 같이 수정하여 주어야 한다. thymeleaf에서 url은 th:를 붙여주고 @{}를 사용하여야 한다.
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<th:block th:fragment="setContent(content)">
<head>
<meta charset="utf-8" />
<meta name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<meta name="description" content="" />
<meta name="author" content="" />
<title>Simple Sidebar - Start Bootstrap Template</title>
<!-- Favicon-->
<link rel="icon" type="image/x-icon" th:href="@{/assets/favicon.ico}" />
<!-- Core theme CSS (includes Bootstrap)-->
<link th:href="@{/css/styles.css}" rel="stylesheet" />
<!-- Bootstrap core JS-->
<script
src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
<!-- Core theme JS-->
<script th:src="@{/js/jquery.min.js}"></script>
<script th:src="@{/js/scripts.js}"></script>
</head>
<body>
<div class="d-flex" id="wrapper">
<!-- Sidebar-->
<div class="border-end bg-white" id="sidebar-wrapper">
<div class="sidebar-heading border-bottom bg-light">Start
Bootstrap</div>
<div class="list-group list-group-flush">
<a class="list-group-item list-group-item-action list-group-item-light p-3"
href="#!">Dashboard</a>
<a class="list-group-item list-group-item-action list-group-item-light p-3"
href="#!">Shortcuts</a>
<a class="list-group-item list-group-item-action list-group-item-light p-3"
href="#!">Overview</a>
<a class="list-group-item list-group-item-action list-group-item-light p-3"
href="#!">Events</a>
<a class="list-group-item list-group-item-action list-group-item-light p-3"
href="#!">Profile</a>
<a class="list-group-item list-group-item-action list-group-item-light p-3"
href="#!">Status</a>
</div>
</div>
<!-- Page content wrapper-->
<div id="page-content-wrapper">
<!-- Top navigation-->
<nav
class="navbar navbar-expand-lg navbar-light bg-light border-bottom">
<div class="container-fluid">
<button class="btn btn-primary" id="sidebarToggle">Toggle
Menu</button>
<button class="navbar-toggler" type="button"
data-bs-toggle="collapse"
data-bs-target="#navbarSupportedContent"
aria-controls="navbarSupportedContent" aria-expanded="false"
aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav ms-auto mt-2 mt-lg-0">
<li class="nav-item active"><a class="nav-link" href="#!">Home</a></li>
<li class="nav-item"><a class="nav-link" href="#!">Link</a></li>
<li class="nav-item dropdown"><a
class="nav-link dropdown-toggle" id="navbarDropdown" href="#"
role="button" data-bs-toggle="dropdown" aria-haspopup="true"
aria-expanded="false">Dropdown</a>
<div class="dropdown-menu dropdown-menu-end"
aria-labelledby="navbarDropdown">
<a class="dropdown-item" href="#!">Action</a> <a
class="dropdown-item" href="#!">Another action</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="#!">Something else here</a>
</div>
</li>
</ul>
</div>
</div>
</nav>
<!-- Page content-->
<div class="container-fluid">
<th:block th:replace = "${content}"></th:block>
</div>
</div>
</div>
<script>
$("#sidebarToggle").click(function(e) {
e.preventDefault();
$("#wrapper").toggleClass("toggled");
});
</script>
</body>
</th:block>
</html>
로그인 폼은 다음과 같다.
우선은 비밀번호에 대한 유효성 검사를 잠시 막아 놓고 로그인을 수행한다. 잘 수행이 된다.
소스코드는 아래와 같다.
'웹개발' 카테고리의 다른 글
스프링 부트를 이용한 포트폴리오 만들기(8/) - 회원가입 (0) | 2022.12.14 |
---|---|
스프링 부트를 이용한 포트폴리오 만들기(7/) - 로그아웃 처리 (0) | 2022.12.14 |
스프링 부트를 이용한 포트폴리오 만들기(5/) - 시큐리티 적용 (0) | 2022.12.13 |
스프링 부트를 이용한 포트폴리오 만들기(4/) - 회원정보 구성 (0) | 2022.12.13 |
스프링 부트를 이용한 포트폴리오 만들기(3/) - 뷰 템플리트 설정 (0) | 2022.12.13 |