Skip to content
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

8주차 미션 / 서버 3조 김상균 #4

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.kuit.kuit4serverauth.argumentResolver;

import com.kuit.kuit4serverauth.dto.UserInfo;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Component;
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;

@Component
public class UserInfoArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterType().equals(UserInfo.class);
}

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

HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
String username = (String)request.getAttribute("username");
String role = (String)request.getAttribute("role");
return new UserInfo(username, role);
}
}
18 changes: 16 additions & 2 deletions src/main/java/com/kuit/kuit4serverauth/config/WebConfig.java
Original file line number Diff line number Diff line change
@@ -1,20 +1,34 @@
package com.kuit.kuit4serverauth.config;

import com.kuit.kuit4serverauth.argumentResolver.UserInfoArgumentResolver;
import com.kuit.kuit4serverauth.interceptor.AuthInterceptor;
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 java.util.List;

@Configuration
public class WebConfig implements WebMvcConfigurer {
private final AuthInterceptor authInterceptor;
private final UserInfoArgumentResolver userInfoArgumentResolver;

public WebConfig(AuthInterceptor authInterceptor) {
public WebConfig(AuthInterceptor authInterceptor,
UserInfoArgumentResolver userInfoArgumentResolver) {
this.authInterceptor = authInterceptor;
this.userInfoArgumentResolver = userInfoArgumentResolver;
}

@Override
public void addInterceptors(InterceptorRegistry registry) {
// TODO /profile, /admin 앞에 붙이기
registry.addInterceptor(authInterceptor)
.addPathPatterns("/profile")
.addPathPatterns("/admin");
}

@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(userInfoArgumentResolver);
}
}
Original file line number Diff line number Diff line change
@@ -1,43 +1,32 @@
package com.kuit.kuit4serverauth.controller;

import com.kuit.kuit4serverauth.exception.CustomException;
import com.kuit.kuit4serverauth.exception.ErrorCode;
import com.kuit.kuit4serverauth.model.User;
import com.kuit.kuit4serverauth.dto.LoginRequest;
import com.kuit.kuit4serverauth.dto.TokenDTO;
import com.kuit.kuit4serverauth.repository.UserRepository;
import com.kuit.kuit4serverauth.service.AuthService;
import com.kuit.kuit4serverauth.service.JwtUtil;
import org.springframework.http.HttpStatus;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

@RestController
@RequiredArgsConstructor
public class AuthController {
private final UserRepository userRepository;
private final JwtUtil jwtUtil;

public AuthController(UserRepository userRepository, JwtUtil jwtUtil) {
this.userRepository = userRepository;
this.jwtUtil = jwtUtil;
}
private final AuthService authService;

@PostMapping("/login")
public ResponseEntity<Map<String, String>> login(@RequestBody Map<String, String> credentials) {
String username = credentials.get("username");
String password = credentials.get("password");

User user = userRepository.findByUsername(username);
if (user == null || !user.getPassword().equals(password)) {
throw new CustomException(ErrorCode.INVALID_USERNAME_OR_PASSWORD);
}
public ResponseEntity<TokenDTO> login(@RequestBody LoginRequest loginRequest) {
return ResponseEntity.ok(authService.login(loginRequest));
}

String token = jwtUtil.generateToken(user.getUsername(), user.getRole());
Map<String, String> response = new HashMap<>();
response.put("token", token);
return ResponseEntity.ok(response);
// 해당 url로 요청을 보내면 RefreshToken을 통해 AccessToken을 새로 발급해줌.
@PostMapping("/refresh-token")
public ResponseEntity<TokenDTO> refreshToken(@RequestBody TokenDTO tokenRequest) {
return ResponseEntity.ok(authService.refresh(tokenRequest));
}
}

Original file line number Diff line number Diff line change
@@ -1,23 +1,33 @@
package com.kuit.kuit4serverauth.controller;

import com.kuit.kuit4serverauth.dto.UserInfo;
import com.kuit.kuit4serverauth.repository.UserRepository;
import jakarta.servlet.http.HttpServletRequest;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestAttribute;
import org.springframework.web.bind.annotation.RestController;

@RestController
@Slf4j
public class UserController {

@GetMapping("/profile")
public ResponseEntity<String> getProfile(HttpServletRequest request) {
// TODO : 로그인 한 사용자면 username 이용해 "Hello, {username}" 반환하기
public ResponseEntity<String> getProfile(UserInfo userInfo) {
if (userInfo.getUsername() != null) {
return ResponseEntity.ok("Hello, " + userInfo.getUsername());
}
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Unauthorized");
}

@GetMapping("/admin")
public ResponseEntity<String> getAdmin(HttpServletRequest request) {
// TODO: role이 admin이면 "Hello, admin" 반환하기
public ResponseEntity<String> getAdmin(UserInfo userInfo) {
if (userInfo.getRole() != null && userInfo.getRole().equals("ROLE_ADMIN")) {
return ResponseEntity.ok("Hello, admin");
}
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("Forbidden");
}
}
12 changes: 12 additions & 0 deletions src/main/java/com/kuit/kuit4serverauth/dto/LoginRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.kuit.kuit4serverauth.dto;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class LoginRequest {

private String username;
private String password;
}
12 changes: 12 additions & 0 deletions src/main/java/com/kuit/kuit4serverauth/dto/TokenDTO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.kuit.kuit4serverauth.dto;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public class TokenDTO {

private final String refreshToken;
private final String accessToken;
}
13 changes: 13 additions & 0 deletions src/main/java/com/kuit/kuit4serverauth/dto/UserInfo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.kuit.kuit4serverauth.dto;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;

@Getter
@RequiredArgsConstructor
public class UserInfo {

private final String username;
private final String role;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
public enum ErrorCode {
INVALID_USERNAME_OR_PASSWORD(401, "Invalid username or password"),
INVALID_TOKEN(401, "Invalid or expired token"),
INVALID_REFRESH_TOKEN(401, "Invalid or expired refresh token"),
MISSING_AUTH_HEADER(401, "Missing or invalid Authorization header"),
FORBIDDEN_ACCESS(403, "Access denied"),
INTERNAL_SERVER_ERROR(500, "Internal server error");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.kuit.kuit4serverauth.repository;

import com.kuit.kuit4serverauth.model.User;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

@Repository
public class RefreshTokenRepository {

private final JdbcTemplate jdbcTemplate;

public RefreshTokenRepository(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}

public String findByUsername(String username) {
String sql = "SELECT refreshToken FROM refreshTokens WHERE username = ?";
return jdbcTemplate.queryForObject(sql, (rs, rowNum) -> rs.getString("refreshToken"), username);
}

public void save(String username, String refreshToken) {
String sql = "INSERT INTO refreshTokens (username, refreshToken) VALUES (?, ?)";
jdbcTemplate.update(sql, username, refreshToken);
}

public void updateRefreshToken(String username, String refreshToken) {
String sql = "UPDATE refreshTokens SET refreshToken = ? WHERE username = ?";
jdbcTemplate.update(sql, refreshToken, username);
}
}
66 changes: 66 additions & 0 deletions src/main/java/com/kuit/kuit4serverauth/service/AuthService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package com.kuit.kuit4serverauth.service;

import com.kuit.kuit4serverauth.dto.LoginRequest;
import com.kuit.kuit4serverauth.dto.TokenDTO;
import com.kuit.kuit4serverauth.exception.CustomException;
import com.kuit.kuit4serverauth.exception.ErrorCode;
import com.kuit.kuit4serverauth.model.User;
import com.kuit.kuit4serverauth.repository.RefreshTokenRepository;
import com.kuit.kuit4serverauth.repository.UserRepository;
import io.jsonwebtoken.Claims;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestBody;

@Slf4j
@Service
@RequiredArgsConstructor
public class AuthService {

private final JwtUtil jwtUtil;
private final UserRepository userRepository;
private final RefreshTokenRepository refreshTokenRepository;


public TokenDTO login(LoginRequest loginRequest) {
String username = loginRequest.getUsername();
String password = loginRequest.getPassword();

User user = userRepository.findByUsername(username);
if (user == null || !user.getPassword().equals(password)) {
throw new CustomException(ErrorCode.INVALID_USERNAME_OR_PASSWORD);
}

String accessToken = jwtUtil.generateToken(user.getUsername(), user.getRole());
String refreshToken = jwtUtil.generateRefreshToken(user.getUsername());

refreshTokenRepository.save(username, refreshToken);

return new TokenDTO(accessToken, refreshToken);
}

public TokenDTO refresh(@RequestBody TokenDTO tokenRequest) {
String refreshToken = tokenRequest.getRefreshToken();

Claims claims = jwtUtil.validateToken(refreshToken);
String username = claims.getSubject();

String oldRefreshToken = refreshTokenRepository.findByUsername(username);

if(!oldRefreshToken.equals(refreshToken)) {
throw new CustomException(ErrorCode.INVALID_REFRESH_TOKEN);
}

String newAccessToken = jwtUtil.generateToken(username, (String)claims.get("role"));

//Refresh-Token-Rotation
String newRefreshToken = jwtUtil.generateRefreshToken(username);
refreshTokenRepository.updateRefreshToken(username, newRefreshToken);


return new TokenDTO(newAccessToken, newRefreshToken);
}


}
24 changes: 20 additions & 4 deletions src/main/java/com/kuit/kuit4serverauth/service/JwtUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,21 @@
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.Date;

@Component
public class JwtUtil {
private final String secret = "mysecretkey";
private final long expirationMs = 3600000; // 1 hour
@Value("${jwt.secret}")
private String secret;

@Value("${jwt.expiration}")
private long expirationMs; // 1 hour

@Value("${jwt.refresh-expiration}")
private long refreshExpirationMs;

public String generateToken(String username, String role) {
return Jwts.builder()
Expand All @@ -24,10 +31,19 @@ public String generateToken(String username, String role) {
.compact();
}

public String generateRefreshToken(String username) {
return Jwts.builder()
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + refreshExpirationMs))
.signWith(SignatureAlgorithm.HS256, secret)
.compact();
}

public Claims validateToken(String token) {
try {
return Jwts.parser()
.setSigningKey(secret)
return Jwts.parser() // jwt 타입인지
.setSigningKey(secret) // 내가 발급한 게 맞는지
.parseClaimsJws(token)
.getBody();
} catch (Exception e) {
Expand Down
6 changes: 4 additions & 2 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ spring:
path: /h2-console

jwt:
secret: mysecretkey
expiration: 3600000
secret: mysecretkey01234567890123456789012345678901234
expiration: 100000
refresh-expiration: 60480000


6 changes: 6 additions & 0 deletions src/main/resources/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,10 @@ CREATE TABLE users (
username VARCHAR(50) NOT NULL,
password VARCHAR(100) NOT NULL,
role VARCHAR(20) NOT NULL
);

CREATE TABLE refreshTokens (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL,
refreshToken VARCHAR(255) NOT NULL
);