Skip to content

[Spring MVC(인증)] 남해윤 미션 제출합니다. #112

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
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