project/모임웹프로젝트

모임 웹프로젝트 로그인 기능 구현(9)

naspeciallist 2024. 9. 30. 02:11

이제 회원가입 로직을 완료했으니 로그인 기능을 구현하겠습니다.

먼저 시큐리티를 통해서 로그인을 성공하면 어느페이지로 이동 할 지 설정해줍니다.

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    return http
            .authorizeRequests()
            .requestMatchers("/login", "/signup", "/user","image/*","/check-username","/send-verification-code","/verify-code",
                    "/css/**", "/image/**", "/js/**","/","/main").permitAll()
            .anyRequest().authenticated()
            .and()
            .formLogin()
            .loginPage("/login")
            .defaultSuccessUrl("/main", true)
            .and()
            .logout()
            .logoutSuccessUrl("/login")
            .invalidateHttpSession(true)
            .and()
            .csrf().disable()
            .build();
}

저는 일단 로그인 성공 후 처리할 url을 /main으로 설정했습니다.

package org.zerock.wantuproject.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class MainController {

    @GetMapping("main")
    public String main() {
        return "main";
    }
}

그리고 난 뒤 간단하게 컨트롤러를 구성하였습니다.
그리고 데이터베이스에서 로그인을 할때 uid를 통해서 회원을 찾아 줄 서비스 로직을 구현하였습니다.

package org.zerock.wantuproject.service;

import lombok.RequiredArgsConstructor;
import org.hibernate.Hibernate;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.zerock.wantuproject.entity.User;
import org.zerock.wantuproject.repository.UserRepository;

import static org.zerock.wantuproject.entity.QUser.user;

@RequiredArgsConstructor
@Service
public class UserDetailService implements UserDetailsService {

    private final UserRepository userRepository;


    @Override
    public User loadUserByUsername(String uid) throws UsernameNotFoundException {
        return userRepository.findByUid(uid)
                .orElseThrow(() -> new IllegalArgumentException((uid + " not found")));


    }
}


그 뒤에 이제 html파일을 구성을 해줍니다.
login.html파일은 tymleaf를 통해 fragmets로 나누었습니다.

<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>로그인</title>
    <!-- 부트스트랩 cdn -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet"
          integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
    <!-- 부트스트랩 아이콘 cdn -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap-icons/font/bootstrap-icons.css" rel="stylesheet">
    <link th:href="@{css/basic.css}" rel="stylesheet">
    <link th:href="@{css/login.css}" rel="stylesheet">


</head>
<body>
<!--&lt;!&ndash; 헤더 영역 &ndash;&gt;-->
<!--<th:block th:replace="fragments/nav :: header"></th:block>-->

<!--&lt;!&ndash; SNB 영역 &ndash;&gt;-->
<!--<th:block th:replace="fragments/nav :: navMenu"></th:block>-->

<!-- 메인 컨텐츠 영역 -->
<th:block th:replace="fragments/member/loginContent"></th:block>

<!-- 푸터 영역 -->
<th:block th:replace="fragments/nav :: footer"></th:block>

<!--<script th:src="@{js/login.js}"></script>-->

</body>
</html>

 

이렇게 하면 코드를 재사용 할 수가 있고 또한 페이지 이동시 동적으로 처리 할 수 있다는 장점이 있습니다.

나중에 더 자세하게 설명하겠습니다..

css는

/* 기본 설정 */
html, body {
    margin: 0;
    padding: 0;
    width: 100%;
    height: 100%;
}

/* loginContent 전체를 Flexbox로 설정 */
.loginContent {
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    height: 100vh;
    width: 100%;
}

/* 배경 이미지 설정 */
.background-image {
    background-image: url('/image/loginbackground.png'); /* 실제 이미지 경로로 변경 */
    background-size: cover;
    background-position: center;
    width: 100%;
    height: 100vh;
    display: flex;
    justify-content: flex-end; /* 버튼을 아래로 밀어냄 */
    align-items: center;
    padding-bottom: 100px;
    position: relative;
    z-index: 1;
    flex-direction: column;
}

/* 로그인 버튼 스타일 */
.login-button {
    background-color: #f08080;
    color: white;
    border: none;
    padding: 15px 40px;
    border-radius: 30px;
    cursor: pointer;
    font-size: 18px;
    z-index: 2;
}

/* 로그인 폼 스타일 */
.hidden {
    display: none;
}

.login-form {
    width: 550px;
    padding: 30px;
    background-color: white;
    /* border: 1px solid #ddd; */
    border-radius: 10px;
    /* box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1); */
    text-align: center;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    z-index: 100;
    display: none;
    height: 500px;
}

.login-form h2{
    margin-bottom: 50px;
    font-weight: bold;
    margin-top: 40px;
}

.form-group{
    display: flex;
    flex-direction: row;
    align-items: center;
    width: 450px;
    margin: 0 auto;
    margin-bottom: 40px;
    justify-content: space-between;
}

.form-group input{
    width: 365px;
    border: 1px solid black;
    border-radius: 20px;

}

.form-group label{
    font-weight: bold;
    font-size: 20px;
}

.form-group-find{
    display: flex;
    justify-content: flex-end;
    margin-top: -15px;
    margin-bottom: 20px;
}

.find-id-password{
    margin-right: 20px;
}

.form_button{
    display: flex;
    flex-direction: column;
}

.form-login-button{
    width: 300px;
    margin: 0 auto;
    margin-bottom: 20px;
    background-color: white;
    border-radius: 15px;
    padding: 6px;
    font-size: 18px;
    /* font-weight: bold; */
    border: 1px solid black;
}

.form-register-button{
    width: 300px;
    margin: 0 auto;
    margin-bottom: 20px;
    background-color: white;
    border-radius: 15px;
    padding: 6px;
    font-size: 18px;
    /* font-weight: bold; */
    border: 1px solid black;
    color: black;
    text-decoration: none;

}
.form-register-button:hover{
    color: black;
}

.show {
    display: block;
    opacity: 0;
    animation: fadeIn 1s forwards;
}

/* 애니메이션 */
@keyframes fadeIn {
    0% {
        opacity: 0;
        transform: translate(-50%, -60%);
    }
    100% {
        opacity: 1;
        transform: translate(-50%, -50%);
    }
}

 

이렇게 구성을 해놓았습니다. fadeIn애니메이션을 통해서 버튼을 누리면 화면에 로그인 폼이 나타나도록 구성하였습니다. 

<!-- 메인 컨텐츠 영역 -->
<div class="loginContent">
  <div class="background-image">
    <button class="login-button" onclick="showLoginForm()">로그인/회원가입</button>
  </div>
</div>

<!-- 로그인 폼 -->
<div id="loginForm" class="login-form hidden">
  <h2>로그인</h2>
  <form action="/login" method="POST">
    <input type="hidden" th:name="${_csrf?.parameterName}" th:value="${_csrf?.token}" />
    <div class="form-group">
      <label for="username">아이디</label>
      <input type="text" class="form-control" id="username" name="username" placeholder="아이디">
    </div>
    <div class="form-group">
      <label for="password">비밀번호</label>
      <input type="password" class="form-control" id="password" name="password" placeholder="비밀번호">
    </div>
    <div class="form-group-find">
      <div class="find-id-password"><a href="#">아이디 / 비밀번호 찾기</a></div>
    </div>
    <div class="form_button">
      <button type="submit" class="form-login-button">로그인</button>
      <a href="/signup" class="form-register-button">회원가입</a>
    </div>
  </form>
</div>

<script>
  function showLoginForm() {
    const loginForm = document.getElementById('loginForm');
    loginForm.classList.remove('hidden'); // 폼을 보이게 하기
    loginForm.classList.add('show');      // 애니메이션 효과 추가
  }
</script>

로그인 페이지의 html의 컨텐츠 부분입니다.

 

실행화면 입니다