이전 게시글에서는 회원정보를 사용하여 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>

로그인 폼은 다음과 같다.

우선은 비밀번호에 대한 유효성 검사를 잠시 막아 놓고 로그인을 수행한다. 잘 수행이 된다.

소스코드는 아래와 같다.

sboot.zip
0.23MB

Posted by 세상을 살아가는 사람
,

웹 개발 시, 입력 파라미터에 대한 유효성 검사를 위해 정규표현식이 사용된다. 이에 대한 교재를 찾았다.



Posted by 세상을 살아가는 사람
,

예전에 웹을 학원에서 배울때 강사가 정규 표현식에 대하여 간단하게 알려 주었고, 회원가입이나 로그인 시 입력 파라미터에 대한 유효성 검증을 할 때 사용했던 것으로 기억한다.

그때는 정확히 모르고 그냥 내가 필요한 것만 적용해서 사용했던 것 같다. 여기서는 이에 대하여 정리를 해보고 여러 가지 사용 예에 대하여 검토를 해 보고자 한다. (참고 : 위키백과 정규 표현식)

1. 정의

  정규 표현식 또는 정규식은 특정한 규칙을 가진 문자열의 집합을 표현하는데 사용하는 언어 형식

  많은 텍스트 편집기와 프로그래밍 언어에서 문자열의 검색과 치환을 위해 지원

  자바스크립트 : 기능 내장

  자바 : 표준 라이브러리로 제공

2. 기본 개념

  주로 패턴으로 부르는 정규 표현식은 특정 목적을 위해 필요한 문자열 집합을 지정하기 위해 쓰이는 식

- 불린 "또는" : 수직선은 여러 항목 중 선택을 하기 위해 구분

  예) gray|grey -> gray 또는 grey와 일치

- 그룹 묶기 : 괄호를 사용하면 연산자의 범위와 우선권을 제어

  예) gray|grey와 gr(a|e)y는 'gray'나 'grey' 집합을 둘 다 기술하는 동일 패턴

- 양의 지정

  ?            : 0또는 1차례까지의 발생을 의미

    예) colou?r는 'color'와 'colour'를 둘다 일치

  *            : 0번 이상의 발생을 의미

    예) ab*c는 'ac', 'abc', 'abbc', 'abbbc' 등을 일치

  +           : 1번 이상의 발생을 의미

    예) ab+c는 'abc', 'abbc', 'abbbc' 등을 일치시키지만, 'ac'는 일치시키지 않음

  {n}         : 정확히 n번만큼 일치시킨다.

  {min,}     : 'min'번 이상만큼 일치

  {min,max} : 적어도 'min'번 만큼 일치시키지만 'max'번을 초과하여 일치시키지는 않는다.

3. 문법

3.1 POSIX 기본 및 확장 표준 문법

- 특수문자(?) : 

  + 외부 : 12개 ('.', '[', ']', '^', '$', '(', ')', '\', '*', ',', '{', '}', '-') 13개(?)

  + 내부 : 4개의 문자 ("\", "^", "-", "]") 자바와 닷넷은 "["를 포함


3.2 POSIX 확장 문법


3.3 문자 클래스


4. 예

- email : /^[-A-Za-z0-9_]+[-A-Za-z0-9_.]*[@]{1}[-A-Za-z0-9_]+[-A-Za-z0-9_.]*[.]{1}[A-Za-z]{2,5}$/

  + 처음 문자로 '-' 또는 대문자, 소문자, 숫자, '_' 로 시작 : 1회 이상(+)

  + 그 다음 문자로 '-', 대문자, 소문자, 숫자, '_' 문자가 0번 이상 (*)

  + '@' 문자가 정확히 1번은 일치

  + 그 다음 문자로 '-', 대문자, 소문자, 숫자, '_'가 1회 이상 (+)

  + 그 다음 문자로 '-', 대문자, 소문자, 숫자, '_', '.'가 0번 이상 (*)

  + '.' 문자는 정확히 1번은 일치

  + 마지막으로 문자로 대문자, 소문자가 최소 2번 이상 5번 초과 안되게

- 비밀번호 : /^(?=.*[a-zA-Z])(?=.*[!@#$%^*+=-])(?=.*[0-9]).{7,16}$/

  + 알파벳, 특수문자, 숫자가 최소 7개 이상 최대 16번 미만 입력

  + 특수문자, 영문, 숫자를 한번은 입력하되 8자 이상 16자 미만...


5. JSP 프로젝트 예

5.1 join.jsp (회원가입)

<script type="text/javascript" charset="utf-8" src="../js/member/join.js"></script>

  <form action="joinPro.jsp" method="post">

    <input type="email" id="id" name="id" placeholder="아이디 입력">

    <input type="password" id="pw" name="pw" placeholder="암호 입력">

    <input type="submit" value="회원가입">

  </form>

5.2 join.js

window.onload = function() {

  var id = document.getElementById("id");

  id.onkeyup = chkId;    // 키보드로 id 값을 입력하면 chkId 함수가 불린다.

  var pw = document.getElementById("pw");

  pw.onkeyup = chkPw;    // 키보드로 password를 입력하면 chkPw 함수가 불린다.

}

var chkId = function() {

var idReg = /^[-A-Za-z0-9_]+[-A-Za-z0-9_.]*[@]{1}[-A-Za-z0-9_]+[-A-Za-z0-9_.]*[.]{1}[A-Za-z]{2,5}$/;

if( !idReg.test(id.value) ) {    // 사용자가 입력한 것이 원하는 값이 아니면 'false'로 표시

id.style.color = "#EE5656";

passId = false;

}

else {                            // 원하는 패턴을 만족하면 'true'로 표시

id.style.color = "blue";

passId = true;

}

}


var chkPd = function() {

var pwReg = /^(?=.*[a-zA-Z])(?=.*[!@#$%^*+=-])(?=.*[0-9]).{7,16}$/;

if( !pwReg.test(pwd.value) ) {    // 사용자가 입력한 것이 원하는 패턴이 아니면 'false'로 표시

pwd.style.color = "#EE5656";

passPw1 = false;

}

else {                                // 원하는 패턴을 만족하면 'true'로 표시

pwd.style.color = "blue";

passPw1 = true;

}

}


정확한 사용은 조금 더 공부가 필요한 것 같네요...

Posted by 세상을 살아가는 사람
,