Skip to content

Commit 51c5ff3

Browse files
authored
✨ [feat] 관리자 로그인/로그아웃, 인증 정보 조회 기능 (#55)
* ✨ feat: 관리자 form 로그인, 로그아웃 기능 * 🚚 rename: 로그아웃 성공 핸들러 이름 변경 * ✨ feat: 사용자/관리자 인증 정보 조회 기능 * 🐛 fix: 관리자 로그인 성공 시, last_login 업데이트 * chore: Java 스타일 수정 * 🚚 rename: dto 클래스 이름 변경 * chore: Java 스타일 수정 * 🐛 fix: CORS 설정 추가 * chore: Java 스타일 수정 * 🐛 fix: OAuth 인증 성공 시 redirect 경로 지정 * chore: Java 스타일 수정 * ♻️ refactor: CORS 요청 경로 모두 허용 * chore: Java 스타일 수정 * 🐛 fix: OAuth 인증 후 리다이렉트 경로 수정 --------- Co-authored-by: github-actions <>
1 parent c4834de commit 51c5ff3

24 files changed

+353
-38
lines changed
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package io.f1.backend.domain.admin.app;
2+
3+
import io.f1.backend.domain.admin.dao.AdminRepository;
4+
import io.f1.backend.domain.admin.dto.AdminPrincipal;
5+
import io.f1.backend.domain.admin.entity.Admin;
6+
7+
import lombok.RequiredArgsConstructor;
8+
9+
import org.springframework.security.core.userdetails.UserDetails;
10+
import org.springframework.security.core.userdetails.UserDetailsService;
11+
import org.springframework.security.core.userdetails.UsernameNotFoundException;
12+
import org.springframework.stereotype.Service;
13+
14+
@Service
15+
@RequiredArgsConstructor
16+
public class AdminDetailService implements UserDetailsService {
17+
18+
private final AdminRepository adminRepository;
19+
20+
@Override
21+
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
22+
Admin admin =
23+
adminRepository
24+
.findByUsername(username)
25+
.orElseThrow(
26+
() -> new UsernameNotFoundException("E404007: 존재하지 않는 관리자입니다."));
27+
// 프론트엔드로 내려가지 않는 예외
28+
return new AdminPrincipal(admin);
29+
}
30+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package io.f1.backend.domain.admin.app.handler;
2+
3+
import com.fasterxml.jackson.databind.ObjectMapper;
4+
5+
import io.f1.backend.domain.admin.dto.AdminLoginFailResponse;
6+
7+
import jakarta.servlet.http.HttpServletRequest;
8+
import jakarta.servlet.http.HttpServletResponse;
9+
10+
import lombok.RequiredArgsConstructor;
11+
12+
import org.springframework.security.core.AuthenticationException;
13+
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
14+
import org.springframework.stereotype.Component;
15+
16+
import java.io.IOException;
17+
18+
@Component
19+
@RequiredArgsConstructor
20+
public class AdminLoginFailureHandler implements AuthenticationFailureHandler {
21+
22+
private final ObjectMapper objectMapper;
23+
24+
@Override
25+
public void onAuthenticationFailure(
26+
HttpServletRequest request,
27+
HttpServletResponse response,
28+
AuthenticationException exception)
29+
throws IOException {
30+
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); // 401
31+
response.setContentType("application/json;charset=UTF-8");
32+
33+
AdminLoginFailResponse errorResponse =
34+
new AdminLoginFailResponse("E401005", "아이디 또는 비밀번호가 일치하지 않습니다.");
35+
36+
response.getWriter().write(objectMapper.writeValueAsString(errorResponse));
37+
}
38+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package io.f1.backend.domain.admin.app.handler;
2+
3+
import static io.f1.backend.domain.user.constants.SessionKeys.ADMIN;
4+
import static io.f1.backend.global.util.SecurityUtils.getCurrentAdminPrincipal;
5+
6+
import io.f1.backend.domain.admin.dao.AdminRepository;
7+
import io.f1.backend.domain.admin.dto.AdminPrincipal;
8+
import io.f1.backend.domain.admin.entity.Admin;
9+
10+
import jakarta.servlet.http.HttpServletRequest;
11+
import jakarta.servlet.http.HttpServletResponse;
12+
import jakarta.servlet.http.HttpSession;
13+
14+
import lombok.RequiredArgsConstructor;
15+
16+
import org.springframework.security.core.Authentication;
17+
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
18+
import org.springframework.stereotype.Component;
19+
20+
import java.time.LocalDateTime;
21+
22+
@Component
23+
@RequiredArgsConstructor
24+
public class AdminLoginSuccessHandler implements AuthenticationSuccessHandler {
25+
26+
private final AdminRepository adminRepository;
27+
private final HttpSession httpSession;
28+
29+
@Override
30+
public void onAuthenticationSuccess(
31+
HttpServletRequest request,
32+
HttpServletResponse response,
33+
Authentication authentication) {
34+
35+
AdminPrincipal principal = getCurrentAdminPrincipal();
36+
Admin admin =
37+
adminRepository
38+
.findByUsername(principal.getUsername())
39+
.orElseThrow(() -> new RuntimeException("E404007: 존재하지 않는 관리자입니다."));
40+
41+
admin.updateLastLogin(LocalDateTime.now());
42+
adminRepository.save(admin);
43+
httpSession.setAttribute(ADMIN, principal.getAuthenticationAdmin());
44+
45+
response.setStatus(HttpServletResponse.SC_OK); // 200
46+
}
47+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package io.f1.backend.domain.admin.dao;
2+
3+
import io.f1.backend.domain.admin.entity.Admin;
4+
5+
import org.springframework.data.jpa.repository.JpaRepository;
6+
import org.springframework.stereotype.Repository;
7+
8+
import java.util.Optional;
9+
10+
@Repository
11+
public interface AdminRepository extends JpaRepository<Admin, Long> {
12+
13+
Optional<Admin> findByUsername(String username);
14+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package io.f1.backend.domain.admin.dto;
2+
3+
import lombok.AllArgsConstructor;
4+
import lombok.Getter;
5+
6+
@Getter
7+
@AllArgsConstructor
8+
public class AdminLoginFailResponse {
9+
private String code;
10+
private String message;
11+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package io.f1.backend.domain.admin.dto;
2+
3+
import io.f1.backend.domain.admin.entity.Admin;
4+
5+
import lombok.Getter;
6+
7+
import org.springframework.security.core.GrantedAuthority;
8+
import org.springframework.security.core.userdetails.UserDetails;
9+
10+
import java.util.Collection;
11+
import java.util.Collections;
12+
13+
@Getter
14+
public class AdminPrincipal implements UserDetails {
15+
16+
public static final String ROLE_ADMIN = "ROLE_ADMIN";
17+
private final AuthenticationAdmin authenticationAdmin;
18+
private final String password;
19+
20+
public AdminPrincipal(Admin admin) {
21+
this.authenticationAdmin = AuthenticationAdmin.from(admin);
22+
this.password = admin.getPassword();
23+
}
24+
25+
@Override
26+
public Collection<? extends GrantedAuthority> getAuthorities() {
27+
return Collections.singleton(() -> ROLE_ADMIN);
28+
}
29+
30+
@Override
31+
public String getPassword() {
32+
return this.password;
33+
}
34+
35+
@Override
36+
public String getUsername() {
37+
return authenticationAdmin.username();
38+
}
39+
40+
@Override
41+
public boolean isAccountNonExpired() {
42+
return true;
43+
}
44+
45+
@Override
46+
public boolean isAccountNonLocked() {
47+
return true;
48+
}
49+
50+
@Override
51+
public boolean isCredentialsNonExpired() {
52+
return true;
53+
}
54+
55+
@Override
56+
public boolean isEnabled() {
57+
return true;
58+
}
59+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package io.f1.backend.domain.admin.dto;
2+
3+
import io.f1.backend.domain.admin.entity.Admin;
4+
5+
public record AuthenticationAdmin(Long adminId, String username) {
6+
7+
public static AuthenticationAdmin from(Admin admin) {
8+
return new AuthenticationAdmin(admin.getId(), admin.getUsername());
9+
}
10+
}

backend/src/main/java/io/f1/backend/domain/admin/entity/Admin.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,12 @@
88
import jakarta.persistence.GenerationType;
99
import jakarta.persistence.Id;
1010

11+
import lombok.Getter;
12+
1113
import java.time.LocalDateTime;
1214

1315
@Entity
16+
@Getter
1417
public class Admin extends BaseEntity {
1518

1619
@Id
@@ -25,4 +28,8 @@ public class Admin extends BaseEntity {
2528

2629
@Column(nullable = false)
2730
private LocalDateTime lastLogin;
31+
32+
public void updateLastLogin(LocalDateTime lastLogin) {
33+
this.lastLogin = lastLogin;
34+
}
2835
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package io.f1.backend.domain.auth.api;
2+
3+
import static io.f1.backend.global.util.SecurityUtils.getAuthentication;
4+
5+
import io.f1.backend.domain.admin.dto.AdminPrincipal;
6+
import io.f1.backend.domain.auth.dto.CurrentUserAndAdminResponse;
7+
import io.f1.backend.domain.user.dto.UserPrincipal;
8+
9+
import lombok.RequiredArgsConstructor;
10+
11+
import org.springframework.http.ResponseEntity;
12+
import org.springframework.security.core.Authentication;
13+
import org.springframework.web.bind.annotation.GetMapping;
14+
import org.springframework.web.bind.annotation.RequestMapping;
15+
import org.springframework.web.bind.annotation.RestController;
16+
17+
@RestController
18+
@RequestMapping("/auth")
19+
@RequiredArgsConstructor
20+
public class AuthController {
21+
22+
@GetMapping("/me")
23+
public ResponseEntity<?> getCurrentUserOrAdmin() {
24+
Authentication authentication = getAuthentication();
25+
Object principal = authentication.getPrincipal();
26+
27+
if (principal instanceof UserPrincipal userPrincipal) {
28+
return ResponseEntity.ok(CurrentUserAndAdminResponse.from(userPrincipal));
29+
}
30+
return ResponseEntity.ok(CurrentUserAndAdminResponse.from((AdminPrincipal) principal));
31+
}
32+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package io.f1.backend.domain.auth.dto;
2+
3+
import io.f1.backend.domain.admin.dto.AdminPrincipal;
4+
import io.f1.backend.domain.user.dto.UserPrincipal;
5+
6+
public record CurrentUserAndAdminResponse(Long id, String name, String role) {
7+
8+
public static CurrentUserAndAdminResponse from(UserPrincipal userPrincipal) {
9+
return new CurrentUserAndAdminResponse(
10+
userPrincipal.getUserId(),
11+
userPrincipal.getUserNickname(),
12+
UserPrincipal.ROLE_USER);
13+
}
14+
15+
public static CurrentUserAndAdminResponse from(AdminPrincipal adminPrincipal) {
16+
return new CurrentUserAndAdminResponse(
17+
adminPrincipal.getAuthenticationAdmin().adminId(),
18+
adminPrincipal.getUsername(),
19+
AdminPrincipal.ROLE_ADMIN);
20+
}
21+
}

0 commit comments

Comments
 (0)