login 기능 구현, 공개범위 설정

기여·2024년 7월 30일
0

소소한 개발팁

목록 보기
62/103

chat gpt와 대화하다 보니 mapper.xml 파일까지 생략 가능하다는 걸 알게 되었다.
대신에 sql문은 interface에서 작성하면 되고, 매핑 오류가 철저히 없어지는 효과가 보였다.

A. 기획의도:

  • 최초 진입 시 로그인 여부와 상관없이 홈, 전체상품, 로그인, 회원가입 버튼이 보이는 것

  • 이미 로그인 중이면 로그인, 회원가입 버튼은 비출력
    . 로그인 중 해당 id 상단 출력

  • 공통적으로 로그인한 관리자 & 사용자 모두 홈, 전체상품, 로그아웃 버튼이 보임

  • 관리자만 상품관리, 회원관리 페이지 보이고 진입 가능
    . 추후 사용자만 볼수있는 페이지도 있어야겠지~

로그아웃했을 때의 화면:

로그인 실패 화면:

+) 로그아웃 즉시 세션이 무효화돼서 로그인 전과 동일하게 '회원가입' 버튼이 보이는 게 어쩔수없나봐..


B. 소스코드:
1, LoginVo

@Data
public class LoginVo {
	private String username;
    private String password;
    private String loginType; // admin 또는 user
}

2, AdminMapper.java #interface
ㄴ db 호출
ㄴ 인증 메서드 (authenticate) 정의

@Mapper
public interface AdminMapper {    
	@Select("SELECT COUNT(*) FROM admin "
			+ "WHERE aid = #{username} "
			+ "AND AES_DECRYPT(encryption, 'encryption_key') = #{password}")
	
    int authenticate(@Param("username") String username, 
    		@Param("password") String password);
}

/*
AES_DECRYPT: 해독 - 암호화 해제
AES_ENCRYPT: 암호화
MyBatis를 사용하여 암호화된 비밀번호를 검증하려면 
데이터베이스 쿼리를 통해 암호를 해독하고 비교해야 함
*/
  • username, password: 입력 받고 db와 비교할 값

  • aid, encryption: db에서 지정한 로그인 정보 필드명.
    해당 필드명을 애초에 각각 username, password로 통일했으면 더 편하지 않았을까 싶다가도
    한편으론 다르게 지정한 것도 뭐가 뭔지 구분하기에 도움이 되지 않나 싶기도?
    결론적으로는 나 통일파다. 다음 플젝엔 통일하도록 하자. (편한 건 최고)

3, AdminService.java

ㄴ 인증결과 반환
입력된 (username, password) 짝이 db에 존재하면 해당 레코드 건수 반환

@Service
public class AdminService {	
	@Autowired
    private AdminMapper adminMapper;

    public boolean authenticate(String username, String password) {
        int count = adminMapper.authenticate(username, password);
        return count > 0;
    }
}

User도 마찬가지로 UserMapper & UserService 2종세트 별도 준비

4, LoginCtrl

@RequestMapping("/login/")
@Controller
public class LoginCtrl {
	
	@Autowired
	private AdminService adminService;
	
	@Autowired
	private UserService userService;
	
	@Autowired
	HttpSession session;
	
	@GetMapping("loginForm") //로그인 페이지 진입
	public String loginForm() {
		System.out.println("loginForm");
		return "login/loginForm";
	}
	
	@PostMapping("login") //로그인 정보 제출
	public String login(LoginVo vo) {
		System.out.println("login");
		
		String username = vo.getUsername();
	    String password = vo.getPassword();
		String loginType = vo.getLoginType();		
		boolean authenticated;
		
		if ("admin".equals(loginType)) { //관리자 로그인 시
		    authenticated = adminService.authenticate(username, password); //관리자 로그인 정보 비교
		    
		    if (authenticated) { // 관리자 로그인 성공, count > 0
            
            	//프론트에 username, loginType 전달
		    	session.setAttribute("username", username);
		    	session.setAttribute("loginType", loginType);
                
				System.out.println("관리자 로그인 성공: " + username+" / loginType: "+loginType);
				
				return "redirect:/prd/prdList"; //관리자 로그인 후 상품관리 페이지로 이동
		    }
		    
		} else if ("user".equals(loginType)) { //사용자 로그인 시
		    authenticated = userService.authenticate(username, password); //사용자 로그인 정보 비교
		    
		    if (authenticated) {	 //사용자 로그인 성공, count > 0
            
            	//프론트에 username, loginType 전달
				session.setAttribute("username", username);
				session.setAttribute("loginType", loginType);
                
				System.out.println("사용자 로그인 성공: " + username+" / loginType: "+loginType);
				
				return "redirect:/prd/prdList4user"; //사용자 로그인 후 전체상품 페이지로 이동		    
			}		    
		}
		
		// 로그인 실패 (else 필요x)
			System.out.println("로그인 실패");
			return "login/loginAgain"; //로그인 실패하면 다시 로그인 페이지로 이동
	}
	
	@GetMapping("logout")
	public String logout() {
		System.out.println("logout");
		session.invalidate();
		
		return "login/logout"; //이름만 logout이지, 실은 다시 login 유도하는 거임 ㅎ
	}	
}

C. Templates:
1, top.html

<html xmlns:th="http://www.thymeleaf.org">

<div align="center">
	<div th:if="${session.username != null}">
	    <span th:utext="'<strong>' + ${session.username} + '</strong>님 환영합니다.'"></span>
	</div>
</div>
...
<nav>
    <!-- 1. 전체 공개 -->    
	    <a href="/index">🏠 홈</a>&emsp;
		<a href="/prd/prdList4user">🛍️ 전체 상품</a>&emsp;	
	
	    <!-- 1.1. 로그인 전 (이미 로그인 중이면 출력 필요x) -->
		    <div th:if="${session.username == null}" style="display:inline;">
		        <a href="/login/loginForm">➡️ 로그인</a>&emsp;
		        <a href="/user/joinForm">😎 회원가입</a>
		    </div>
	    <!-- 1.1. 로그인 전 끝 -->
    
    <!-- 1. 전체 공개 끝-->
    
    
	<!-- 2. 로그인 중에만 보이는 영역 -->    
    
	    <!-- 2.1. 로그인한 계정 중, admin만 볼 수 있는 영역 (user에게는 비출력) -->
	       <div th:if="${session.username != null and session.loginType == 'admin'}" style="display:inline;">           
	                       
			<div class="dropdown">
				<button class="dropbtn"> 
					<span class="dropbtn_icon">🖥️</span> 상품 관리
				</button>
				
				<div class="dropdown-content">
					<a href="/prd/prdList">상품 목록</a>
					<a href="/prd/addPrdForm">상품 추가</a>
				</div>
			</div>
	           	           
			<div class="dropdown">
				<button class="dropbtn"> 
					<span class="dropbtn_icon">💟</span> 회원 관리
				</button>
				
				<div class="dropdown-content">
					<a href="/user/userList">회원 목록</a>
				</div>
			</div>
	                       
	       </div>               
	    <!-- 2.1. admin만 볼 수 있는 영역 끝 -->        
	    
	    <!-- 2.2. 로그인한 계정 중, user만 볼 수 있는 영역-->
			<!--     cart, mypage 등 -->
	    <!-- 2.2. user만 볼 수 있는 영역 끝-->
	        
		<!-- 2.3. 로그인 중에만 보이는 영역 (admin & user 공통) -->   
		    <div th:if="${session.username != null}" style="display:inline;">
		        <a href="/login/logout">⬅️ 로그아웃</a>    
		    </div>
	    <!-- 2.3. 로그인 중에만 보이는 영역 (admin & user 공통) 끝 -->
    
    <!-- 2. 로그인 중에만 보이는 영역 -->
</nav>

2, loginForm.html

<form action="/login/login" method="post">

	<div class="form-container">		
			<div class="radio-container">
		        <input type="radio" id="admin" name="loginType" value="admin" checked>
		        <label for="admin" class="radio-label">관리자 로그인</label>
		        &emsp;
		        <input type="radio" id="user" name="loginType" value="user">
		        <label for="user" class="radio-label">사용자 로그인</label>
		    </div>
				
			<div class="form-group">
		        <label for="username">ID:</label>
		        <input type="text" id="username" name="username" required>
		    </div>
		    
		    <div class="form-group">
		        <label for="password">비밀번호:</label>
		        <input type="password" id="password" name="password" required>
		    </div>
	</div>
		
		<br>
		
		<button type="submit" class=button>로그인</button>
		&emsp;
		<button type="button" class=subbtn>회원가입</button>
	
</form>

3, loginAgain.html
ㄴ 위와 똑같고 '로그인 정보가 올바르지 않습니다. 다시 확인해주세요.' 문구만 추가

4, logout.html
ㄴ 위와 거의 똑같고 '로그아웃하셨습니다.' 문구만 대체로 씀
'회원가입' 버튼은 없애고!

D. Static:
ㄴ 드롭다운 위한 css

.dropdown{
  position : relative;
  display : inline-block;
}

.dropbtn{
  border : none;
  background-color: #85B5E1;
  color : #ffffff;
  padding : 5px;
  width : 120px;
  text-align: left;
  cursor : pointer;
  font-size : 16px;
  line-height: 25px;
}
.dropdown-content{
  display : none;
  position : absolute;
  z-index : 1; /*다른 요소들보다 앞에 배치*/
  background-color: #85B5E1;
  min-width : 120px;
}

.dropdown-content a{
  display : block;
  text-decoration : none;
  color : #ffffff;
  font-size: 16px;
  padding : 5px;
  line-height: 30px;
}

.dropdown-content a:hover{
  background-color : rgb(174, 190, 232)
}

.dropdown:hover .dropdown-content {
  display: block;
}

css 참고자료

profile
기기 좋아하는 여자

0개의 댓글