Skip to content
Open
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ dependencies {
testImplementation 'io.rest-assured:rest-assured:5.3.1'

runtimeOnly 'com.h2database:h2'

implementation 'org.projectlombok:lombok'
}

test {
Expand Down
56 changes: 56 additions & 0 deletions src/main/java/roomescape/auth/AdminInterceptor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package roomescape.auth;

import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import roomescape.jwt.JwtProvider;
import roomescape.member.Member;
import roomescape.member.MemberDao;

public class AdminInterceptor implements HandlerInterceptor {

private final JwtProvider jwtProvider;
private final MemberDao memberDao;

public AdminInterceptor(JwtProvider jwtProvider, MemberDao memberDao) {
this.jwtProvider = jwtProvider;
this.memberDao = memberDao;
}

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

Cookie[] cookies = request.getCookies();
if (cookies == null) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}

String token = extractTokenFromCookies(cookies);
if (token == null || token.isEmpty() || !jwtProvider.isValidToken(token)) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}

String email = jwtProvider.extractEmail(token);
Member member = memberDao.findByEmailAndPassword(email, null); // 비밀번호 검증은 생략


if (member == null || !"ADMIN".equals(member.getRole())) { //관리자 권한
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}

return true;
}

private String extractTokenFromCookies(Cookie[] cookies) {
for (Cookie cookie : cookies) {
if ("token".equals(cookie.getName())) {
return cookie.getValue();
}
}
return null;
}
}
19 changes: 19 additions & 0 deletions src/main/java/roomescape/auth/LoginMember.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package roomescape.auth;

import lombok.Getter;

@Getter
public class LoginMember {

private Long id;
private String name;
private String email;
private String role;

public LoginMember(Long id, String name, String email, String role) {
this.id = id;
this.name = name;
this.email = email;
this.role = role;
}
}
61 changes: 61 additions & 0 deletions src/main/java/roomescape/auth/LoginMemberArgumentResolver.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package roomescape.auth;

import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import roomescape.jwt.JwtProvider;
import roomescape.member.Member;
import roomescape.member.MemberDao;

public class LoginMemberArgumentResolver implements HandlerMethodArgumentResolver {

private final MemberDao memberDao;
private final JwtProvider jwtProvider;

public LoginMemberArgumentResolver(MemberDao memberDao, JwtProvider jwtProvider) {
this.memberDao = memberDao;
this.jwtProvider = jwtProvider;
}

@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterType().equals(LoginMember.class);
}

@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {

HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
Cookie[] cookies = request.getCookies();
String token = extractTokenFromCookies(cookies);

if (token == null || token.isEmpty()) {
throw new IllegalArgumentException("Token not found in cookies");
}

if (jwtProvider.isValidToken(token)) {
String email = jwtProvider.extractEmail(token); // 이메일 추출
Member member = memberDao.findByEmailAndPassword(email, null); // 비밀번호는 검증 단계에서 사용하지 않음

if (member == null) {
throw new IllegalArgumentException("Member not found for email: " + email);
}

return new LoginMember(member.getId(), member.getName(), member.getEmail(), member.getRole());
}
throw new IllegalArgumentException("Invalid token");
}

private String extractTokenFromCookies(Cookie[] cookies) {
for (Cookie cookie : cookies) {
if ("token".equals(cookie.getName())) {
return cookie.getValue();
}
}
return "";
}
}
35 changes: 35 additions & 0 deletions src/main/java/roomescape/config/WebConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package roomescape.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import roomescape.auth.AdminInterceptor;
import roomescape.auth.LoginMemberArgumentResolver;
import roomescape.jwt.JwtProvider;
import roomescape.member.MemberDao;

import java.util.List;

@Configuration
public class WebConfig implements WebMvcConfigurer {

private final JwtProvider jwtProvider;
private final MemberDao memberDao;

public WebConfig(JwtProvider jwtProvider, MemberDao memberDao) {
this.jwtProvider = jwtProvider;
this.memberDao = memberDao;
}

@Override //3단계
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new LoginMemberArgumentResolver(memberDao, jwtProvider));
}

@Override //3단계
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new AdminInterceptor(jwtProvider, memberDao))
.addPathPatterns("/admin/**");
}
}
12 changes: 12 additions & 0 deletions src/main/java/roomescape/jwt/JwtProvider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package roomescape.jwt;

import roomescape.member.Member;

public interface JwtProvider {

String generateToken(Member member);
boolean isValidToken(String token);
// Long extractSubject(String token);
String extractEmail(String token);

}
60 changes: 60 additions & 0 deletions src/main/java/roomescape/jwt/JwtProviderImpl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package roomescape.jwt;

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import org.springframework.stereotype.Component;
import roomescape.member.Member;

import java.nio.charset.StandardCharsets;

@Component
public class JwtProviderImpl implements JwtProvider{

private static final String SECRET_KEY="Yn2kjibddFAWtnPJ2AFlL8WXmohJMCvigQggaEypa5E=";
private static final long EXPRIATION_TIME = 86400000; // 1일

@Override
public String generateToken(Member member) {
return Jwts.builder()
.setSubject(member.getId().toString())
.claim("name", member.getName())
.claim("email", member.getEmail())
.claim("role", member.getRole())
.signWith(Keys.hmacShaKeyFor(SECRET_KEY.getBytes(StandardCharsets.UTF_8)))
.compact();
}

@Override
public boolean isValidToken(String token) {
try {
Jwts.parserBuilder()
.setSigningKey(Keys.hmacShaKeyFor(SECRET_KEY.getBytes(StandardCharsets.UTF_8)))
.build()
.parseClaimsJws(token);
return true;
} catch (Exception e){
return false;
}
}

// @Override
// public Long extractSubject(String token) {
// Long memberId = Long.valueOf(Jwts.parserBuilder()
// .setSigningKey(Keys.hmacShaKeyFor(SECRET_KEY.getBytes()))
// .build()
// .parseClaimsJws(token)
// .getBody().getSubject());
// return memberId;
// }

@Override
public String extractEmail(String token) {
return Jwts.parserBuilder()
.setSigningKey(Keys.hmacShaKeyFor(SECRET_KEY.getBytes(StandardCharsets.UTF_8)))
.build()
.parseClaimsJws(token)
.getBody()
.get("email", String.class);
}

}
53 changes: 53 additions & 0 deletions src/main/java/roomescape/login/LoginController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package roomescape.login;

import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import roomescape.member.MemberResponse;

@RestController
public class LoginController {

private final LoginService loginService;

public LoginController(LoginService loginService) {
this.loginService = loginService;
}

@PostMapping("/login")
public ResponseEntity<Void> login(@RequestBody LoginRequest loginRequest, HttpServletResponse response){

String token = loginService.login(loginRequest.getEmail(), loginRequest.getPassword());
//쿠키 생성
Cookie cookie = new Cookie("token", token);
cookie.setHttpOnly(true);
cookie.setPath("/");
response.addCookie(cookie);

return ResponseEntity.ok().build();
}

@GetMapping("/login/check") //쿠키 조회
public ResponseEntity<MemberResponse> checkLogin(HttpServletRequest request) {

Cookie[] cookies = request.getCookies();
String token = extractTokenFromCookies(cookies);
MemberResponse memberResponse = loginService.validateToken(token);

return ResponseEntity.ok(memberResponse);
}

private String extractTokenFromCookies(Cookie[] cookies) {
for (Cookie cookie: cookies){
if ("token".equals(cookie.getName())){
return cookie.getValue();
}
}
return "";
}
}
14 changes: 14 additions & 0 deletions src/main/java/roomescape/login/LoginRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package roomescape.login;

public class LoginRequest {
private String email;
private String password;

public String getEmail() {
return email;
}

public String getPassword() {
return password;
}
}
9 changes: 9 additions & 0 deletions src/main/java/roomescape/login/LoginService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package roomescape.login;

import roomescape.member.MemberResponse;

public interface LoginService {

String login(String email, String password);
MemberResponse validateToken(String token);
}
42 changes: 42 additions & 0 deletions src/main/java/roomescape/login/LoginServiceImpl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package roomescape.login;

import org.springframework.stereotype.Service;
import roomescape.jwt.JwtProvider;
import roomescape.member.Member;
import roomescape.member.MemberDao;
import roomescape.member.MemberResponse;

@Service
public class LoginServiceImpl implements LoginService {

private final MemberDao memberDao;
private final JwtProvider jwtProvider;

public LoginServiceImpl(MemberDao memberDao, JwtProvider jwtProvider) {
this.memberDao = memberDao;
this.jwtProvider = jwtProvider;
}

@Override
public String login(String email, String password) {
Member member = memberDao.findByEmailAndPassword(email, password);
return jwtProvider.generateToken(member);
}

@Override
public MemberResponse validateToken(String token) {

if (!jwtProvider.isValidToken(token)) {
throw new IllegalArgumentException("Invalid token");
}

String email = jwtProvider.extractEmail(token);
Member member = memberDao.findByEmailAndPassword(email, null); // 비밀번호는 로그인에서만 검증

if (member == null) {
throw new IllegalArgumentException("Member not found for email: " + email);
}

return new MemberResponse(member.getId(), member.getName(), member.getEmail());
}
}
2 changes: 1 addition & 1 deletion src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ spring.datasource.url=jdbc:h2:mem:database
#spring.jpa.ddl-auto=create-drop
#spring.jpa.defer-datasource-initialization=true

#roomescape.auth.jwt.secret= Yn2kjibddFAWtnPJ2AFlL8WXmohJMCvigQggaEypa5E=
roomescape.auth.jwt.secret= Yn2kjibddFAWtnPJ2AFlL8WXmohJMCvigQggaEypa5E=
Loading