들어가며...
앞 글에서 Controller까지 구현하였으므로 설정 부분을 제외하고 Back End 부분은 끝난 상태입니다. 이번 글에서는 Front End 코드만 대략 살펴볼 것입니다. 사용된 기술은 Thymeleaf, Bootstrap 정도입니다.
Bootstrap에 대한 설명은 홈페이지의 도큐먼트가 워낙 잘 나와 있으니 아래 링크를 참조하시면 될 듯 합니다.
https://getbootstrap.com/docs/4.4/getting-started/introduction/
Thymeleaf는 JSP와 같이 Web Template Engine 중 Text Template Engine의 한 종류입니다. 이 또한 이번글에서는 설명을 생략합니다.
본 프로젝트의 크게 7개의 화면으로 구성되어 있습니다. 각 화면의 구성은 1회차 글을 참조하시기 바랍니다.
- Table of Contents
- HTML, CSS and Javascript(jQuery)
HTML, CSS and Javascript(jQuery)
위에서도 언급하였듯이 View 단은 코드에 대한 별도의 설명은 안하고 필요할 경우 화면의 흐름정도만 설명하겠습니다.
● 로그인
● login.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<title>Spring Security Tutorial</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
<link rel="stylesheet" type="text/css" href="/css/login.css" th:href="@{/css/login.css}"/>
</head>
<body>
<div class="container-fluid p-5 mt-5 text-center">
<h1 class="display-4 text-light">Spring Security</h1>
<form action="/registration.html" th:action="@{/registration}" method="get">
<button class="btn btn-sm btn-warning text-uppercase my-4" type="Submit">Go To Sign Up</button>
</form>
<div class="row">
<div class="col-md-3 col-xl-4"></div>
<div class="col-md-6 col-xl-4">
<div class="card login-form border-0">
<i class="fas fa-key mt-4 mx-auto fa-3x text-success"></i>
<div class="card-body">
<form th:action="@{/login}" method="POST" class="form-signin">
<h3 class="text-uppercase mb-5 text-light">Welcome</h3>
<input type="text" id="username" name="username" placeholder="User name" class="form-control my-3 login-text" autofocus />
<input type="password" placeholder="Password" id="password" name="password" class="form-control mb-3 login-text"/>
<div th:if="${param.error}">
<p class="error-message" th:text="${errorMessage}">User Name or Password invalid, please verify</p>
</div>
<button class="btn btn-lg btn-success btn-block text-uppercase" name="Submit" value="Login" type="Submit" th:text="Login">Sign In</button>
</form>
</div>
</div>
</div>
<div class="col-md-3 col-xl-4"></div>
</div>
</div>
<!-- Javascript of Bootstrap -->
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>
<script src="/lib/fontawesome-free-5.12.1-web/js/all.js" crossorigin="anonymous"></script>
</body>
</html>
인증되지 않은 사용자가 "localhost:8080"를 포함 임의의 파일 패스로 접속 시 최초로 로그인 화면으로 이동하게 됩니다. 아래 왼편의 화면이 나오면 정상이며 등록되지 않은 아이디 패스워드로 로그인하려 할 경우 오른편 화면이 나오면 정상입니다.
● login.css
body {
background: #043041;
height: 100vh;
}
.login-form {
background: #043041;
}
.error-message {
color: red;
}
.login-text {
background: #1F4758;
color: #eee;
border: 0;
}
.login-text:focus {
color: #111;
}
● 사용자 등록
로그인 정보가 없을 경우 "GO TO SIGN UP" 버튼을 클릭하여 사용자 등록 화면으로 이동하여 사용자 등록을 수행합니다. 이전 코드에서도 설명하였듯이 사용자명을 "admin" => ADMIN 권한, "user" => MANAGER 권한 그 외의 사용자명일 경우 "GUEST" 권한을 할당하게 해놓았습니다. 이전 코드를 잠시 살펴 보도록 하겠습니다.
/**
* Save the user information
*/
@Override
@Transactional
public Account setUser(Account user) throws Exception {
//< encoding the password
user.setPassword(bcryptPasswordEncoder.encode(user.getPassword()));
//< set the active flag
user.setIsActive(true);
//< set the user role
Role userRole = null;
if(user.getUsername().equals("admin")) {
userRole = roleRepository.findByRole(ERole.ADMIN.getValue());
}
else if(user.getUsername().equals("user")) {
userRole = roleRepository.findByRole(ERole.MANAGER.getValue());
}
else {
userRole = roleRepository.findByRole(ERole.GUEST.getValue());
}
//< set the user roles
user.setRoles(new HashSet<Role>(Arrays.asList(userRole)));
//< save the user information and return result
return accountRepository.save(user);
}
● registration.html
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<title>Registration Form</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
<link rel="stylesheet" type="text/css" href="/css/registration.css" th:href="@{/css/registration.css}"/>
</head>
<body>
<div class="container-fluid p-5 mt-5 text-center">
<h1 class="display-4 text-light">Spring Security</h1>
<form action="/login.html" th:action="@{/}" method="get">
<button class="btn btn-sm btn-success text-uppercase my-4" type="Submit">Go To Sign In</button>
</form>
<div class="row">
<div class="col-md-3 col-xl-4"></div>
<div class="col-md-6 col-xl-4">
<div class="card signup-form border-0">
<i class="fas fa-sign-in-alt mt-4 mx-auto fa-3x text-warning"></i>
<div class="card-body">
<form autocomplete="off" action="#" th:action="@{/registration}" th:object="${account}" method="post" class="form-horizontal" role="form">
<h3 class="text-white text-uppercase mb-5">Registration</h3>
<!-- Email -->
<div class="form-group">
<input type="text" th:field="*{email}" placeholder="Email" class="form-control signup-text" autofocus />
<label th:if="${#fields.hasErrors('email')}" th:errors="*{email}" class="validation-message"></label>
</div>
<!-- user name -->
<div class="form-group">
<input type="text" th:field="*{username}" placeholder="User Name" class="form-control signup-text"/>
<label th:if="${#fields.hasErrors('username')}" th:errors="*{username}" class="validation-message"></label>
</div>
<!-- password -->
<div class="form-group">
<input type="password" th:field="*{password}" placeholder="Password" class="form-control signup-text"/>
<label th:if="${#fields.hasErrors('password')}" th:errors="*{password}" class="validation-message"></label>
</div>
<!-- confirm password -->
<div class="form-group">
<input type="password" th:field="*{confirmPassword}" placeholder="Confirm Password" class="form-control signup-text"/>
<label th:if="${#fields.hasErrors('confirmPassword')}" th:errors="*{confirmPassword}" class="validation-message"></label>
</div>
<!-- submit button -->
<div class="form-group">
<button type="submit" class="btn btn-warning btn-block text-uppercase">sign up</button>
</div>
<h4><span class="text-success" th:utext="${successMessage}">User has been registered successfully</span></h4>
</form>
</div>
</div>
</div>
<div class="col-md-3 col-xl-4"></div>
</div>
</div>
<!-- Javascript of Bootstrap -->
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>
<script src="/lib/fontawesome-free-5.12.1-web/js/all.js" crossorigin="anonymous"></script>
</body>
</html>
아래는 사용자 등록 화면과 사용자 등록 시 validation 체크 시 실패한 화면 입니다.
● registration.css
body {
background: #043041;
height: 100vh;
}
.signup-form {
background: #043041;
}
.signup-text {
background: #1F4758;
color: #eee;
border: 0;
}
.signup-text:focus {
color: #111;
}
.validation-message {
color: #ee4835;
margin-bottom: auto;
}
● 홈화면
사용자 등록이 완료한 후 로그인 페이지로 이동 후 로그인을 수행하면 최초 접속 화면을 볼 수 있습니다.
● index.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<title>Home Page</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
<link rel="stylesheet" type="text/css" href="/css/home.css" th:href="@{/css/home.css}"/>
</head>
<body>
<div class="container-fluid p-5 mt-5">
<div class="text-light text-center my-5">
<h1 class="display-4 mb-4 text-uppercase">Welcome, <span th:text="${username}" class="text-success">Leo</span></h1>
<form th:action="@{/logout}" method="get">
<button class="btn btn-lg btn-info" name="registration" type="Submit">Logout</button>
</form>
</div>
<div class="row mx-5">
<div class="col-md-4">
<div class="card">
<i class="fas fa-users-cog fa-7x mx-auto my-5 text-danger"></i>
<div class="card-body text-center">
<h3 class="card-title text-danger mb-4">Administrator</h3>
<form action="/admin.html" th:action="@{/home/admin}" method="get">
<button class="btn btn-md btn-block btn-danger">Go</button>
</form>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card">
<i class="fas fa-user-friends fa-7x mx-auto my-5 text-success"></i>
<div class="card-body text-center">
<h3 class="card-title text-success mb-4">User</h3>
<form action="/user.html" th:action="@{/home/user}" method="get">
<button class="btn btn-md btn-block btn-success">Go</button>
</form>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card">
<i class="fas fa-user-secret fa-7x mx-auto my-5 text-warning"></i>
<div class="card-body text-center">
<h3 class="card-title text-warning mb-4">Guest</h3>
<form action="/guest.html" th:action="@{/home/guest}" method="get">
<button class="btn btn-md btn-block btn-warning">Go</button>
</form>
</div>
</div>
</div>
</div>
</div>
<!-- Javascript of Bootstrap -->
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>
<script src="/lib/fontawesome-free-5.12.1-web/js/all.js" crossorigin="anonymous"></script>
</body>
</html>
● home.css
body {
background: #073040;
height: 100vh;
}
.card {
background: #214757;
border: 1px solid #535353;
}
● 사용자별 화면 및 에러
현재 정의한 3가지 종류의 권한과 해당 페이지로 이동할 수 있는 메뉴가 존재하며 로그인한 권한의 페이지로 이동하면 정상적인 화면으로 이동되나 그렇지 않을 경우는 권한 없음의 에러 화면이 출력되게 됩니다.
아래는 3가지 권한 및 HTTP error에 대한 HTML과 성공 실패에 대한 화면입니다.
● admin.html
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin Page</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
<link rel="stylesheet" type="text/css" href="/css/common.css" th:href="@{/css/common.css}"/>
</head>
<body>
<div class="container-fluid">
<div class="pt-5 mt-5 text-center">
<i class="fas fa-users-cog fa-7x my-5 text-danger"></i>
<form action="/home.html" th:action="@{/home}">
<button class="btn btn-lg btn-info mb-5">Home</button>
</form>
<h4 class="text-light">Content Available Only <span class="text-danger">Admin</span> Role</h4>
</div>
</div>
<!-- Javascript of Bootstrap -->
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>
<script src="/lib/fontawesome-free-5.12.1-web/js/all.js" crossorigin="anonymous"></script>
</body>
</html>
● user.html
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin Page</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
<link rel="stylesheet" type="text/css" href="/css/common.css" th:href="@{/css/common.css}"/>
</head>
<body>
<div class="container-fluid">
<div class="pt-5 mt-5 text-center">
<i class="fas fa-user-friends fa-7x my-5 text-success"></i>
<form action="/home.html" th:action="@{/home}">
<button class="btn btn-lg btn-info mb-5">Home</button>
</form>
<h4 class="text-light">Content Available Only <span class="text-success">User</span> Role</h4>
</div>
</div>
<!-- Javascript of Bootstrap -->
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>
<script src="/lib/fontawesome-free-5.12.1-web/js/all.js" crossorigin="anonymous"></script>
</body>
</html>
● guest.html
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin Page</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
<link rel="stylesheet" type="text/css" href="/css/common.css" th:href="@{/css/common.css}"/>
</head>
<body>
<div class="container-fluid">
<div class="pt-5 mt-5 text-center">
<i class="fas fa-user-secret fa-7x my-5 text-warning"></i>
<form action="/home.html" th:action="@{/home}">
<button class="btn btn-lg btn-info mb-5">Home</button>
</form>
<h4 class="text-light">Content Available Only <span class="text-warning">Guest</span> Role</h4>
</div>
</div>
<!-- Javascript of Bootstrap -->
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>
<script src="/lib/fontawesome-free-5.12.1-web/js/all.js" crossorigin="anonymous"></script>
</body>
</html>
● error.html
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>404 Page</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
<link rel="stylesheet" type="text/css" href="/css/common.css" th:href="@{/css/common.css}"/>
</head>
<body>
<div class="container-fluid">
<div class="pt-5 mt-5 text-center">
<div th:switch="${errorCode}">
<i th:case="'404'" class="fas fa-times-circle fa-7x my-5 text-danger"></i>
<i th:case="'403'" class="fas fa-hand-paper fa-7x my-5 text-danger"></i>
<i th:case="*" class="fas fa-bomb fa-7x my-5 text-danger"></i>
</div>
<h1 class="display-2 text-danger" th:text="${errorCode}">Error Occurred!!</h1>
<h5 class="text-light mb-5" th:text="${errorMessage}">An error has occurred. Please contact the administrator</h5>
<button id="error-back" class="btn btn-lg btn-info mb-5">Back</button>
</div>
</div>
<!-- Javascript of Bootstrap -->
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>
<script src="/lib/fontawesome-free-5.12.1-web/js/all.js" crossorigin="anonymous"></script>
<script src="/js/common.js"></script>
</body>
</html>
● common.css
body {
background: #043041;
height: 100vh;
}
● common.js
/**
* History back
*/
$("#error-back").on("click", function() {
window.history.back();
});
마무리...
이상으로 Spring Security의 인증 및 권한 부분에 초점을 맞춘 UI라서 html, css의 내용은 크게 어렵지 않으리라 판단됩니다.
U2ful은 ♥입니다. @U2ful Corp.
'Programming > Spring Security' 카테고리의 다른 글
[Spring Security][회원가입 및 로그인 예제 9/9] 마무리 (3) | 2020.03.31 |
---|---|
[Spring Security][회원가입 및 로그인 예제 7/9] Controller 구현 (0) | 2020.03.23 |
[Spring Security][회원가입 및 로그인 예제 6/9] AuthenticationSuccessHandler & AuthenticationFailureHandler 구현 (3) | 2020.03.23 |
[Spring Security][회원가입 및 로그인 예제 5/9] Web Security Configuration 설정 (1) | 2020.03.23 |
[Spring Security][회원가입 및 로그인 예제 4/9] Service Layer 구현 (0) | 2020.03.19 |