이전 게시글에서는 회원정보를 사용하여 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 세상을 살아가는 사람
,
헤더의 navbar에 custom icon을 적용하는 방법을 기술한다. 이것을 일반적인 버튼에 적용하여도 된다.

<body onload="init();" id="stage" class="theme">
  <p><span id="message">Custom Icon.</span></p>
  <div data-role="navbar" class="custom_icon">
    <ul>
      <li><a href="#" id="icon1" data-icon="custom">Alarm</a></li>
      <li><a href="#" id="icon2" data-icon="custom">Call</a></li>
      <li><a href="#" id="icon3" data-icon="custom">Home</a></li>
      <li><a href="#" id="icon4" data-icon="custom">Message</a></li>
    </ul>
  </div>
</body>

CSS를 이용하여 버튼에 아이콘 이미지를 적용한다. 위치와 크기를 조절해 준다.
<style type="text/css">
#icon1 .ui-icon {
  background: url(images/icon_alarm.png) 0% -15% no-repeat;
  background-size: 34px 34px;
}
#icon2 .ui-icon {
  background: url(images/icon_call.png) 0% -15% no-repeat;
  background-size: 34px 34px;
}
#icon3 .ui-icon {
  background: url(images/icon_home.png) 0% -15% no-repeat;
  background-size: 34px 34px;
}
#icon4 .ui-icon {
  background: url(images/icon_message.png) 0% -15% no-repeat;
  background-size: 34px 34px;
}
.custom_icon .ui-btn .ui-icon {
  width: 100%; height: 100%;
  margin-left: -17px;
}
</style>


'앱개발' 카테고리의 다른 글

Android Stuido Layout Editor와 AVD 한글처리  (0) 2017.01.31
안드로이드 교육 목차  (0) 2017.01.25
Android Studio 2.2.3 설치  (0) 2017.01.25
PhoneGap에 광고 추가하기  (0) 2015.07.18
스마트폰의 resolution 정리  (0) 2015.07.18
Posted by 세상을 살아가는 사람
,