Skip to content
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
8 changes: 8 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ repositories {

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'com.auth0:java-jwt:4.2.0'

runtimeOnly 'com.h2database:h2'

testImplementation 'org.springframework.security:spring-security-test'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

//
@SpringBootApplication
public class SpringOrderManagementApplication {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package prgms.lecture.order_management.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import prgms.lecture.order_management.security.Jwt;
import prgms.lecture.order_management.security.JwtTokenConfigure;

@Configuration
public class ServiceConfigure {

@Bean
public Jwt jwt(JwtTokenConfigure jwtTokenConfigure) {
return new Jwt(jwtTokenConfigure.getIssuer(), jwtTokenConfigure.getClientSecret(), jwtTokenConfigure.getExpirySeconds());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package prgms.lecture.order_management.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import prgms.lecture.order_management.security.*;
import prgms.lecture.order_management.user.domain.Role;
import prgms.lecture.order_management.user.service.UserService;

@Configuration
@EnableWebSecurity
public class WebSecurityConfigure extends WebSecurityConfigurerAdapter {

private final Jwt jwt;

private final JwtTokenConfigure jwtTokenConfigure;

private final JwtAccessDeniedHandler accessDeniedHandler;

private final EntryPointUnauthorizedHandler unauthorizedHandler;

public WebSecurityConfigure(Jwt jwt, JwtTokenConfigure jwtTokenConfigure, JwtAccessDeniedHandler accessDeniedHandler, EntryPointUnauthorizedHandler unauthorizedHandler) {
this.jwt = jwt;
this.jwtTokenConfigure = jwtTokenConfigure;
this.accessDeniedHandler = accessDeniedHandler;
this.unauthorizedHandler = unauthorizedHandler;
}

@Bean
public JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter() {
return new JwtAuthenticationTokenFilter(jwtTokenConfigure.getHeader(), jwt);
}

@Override
public void configure(WebSecurity web) {
web.ignoring().antMatchers("/webjars/**", "/static/**", "/templates/**", "/h2/**");
}

@Autowired
public void configureAuthentication(AuthenticationManagerBuilder builder, JwtAuthenticationProvider authenticationProvider) {
builder.authenticationProvider(authenticationProvider);
}

@Bean
public JwtAuthenticationProvider jwtAuthenticationProvider(UserService userService) {
return new JwtAuthenticationProvider(userService);
}

@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}

@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.headers()
.disable()
.exceptionHandling()
.accessDeniedHandler(accessDeniedHandler)
.authenticationEntryPoint(unauthorizedHandler)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/users/login").permitAll()
.antMatchers("/api/products/**").permitAll()
.antMatchers("/api/**").hasRole(Role.USER.name())
.anyRequest().permitAll()
.and()
.formLogin()
.disable();
http
.addFilterBefore(jwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package prgms.lecture.order_management.exception;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.HttpMediaTypeException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.NoHandlerFoundException;
import prgms.lecture.order_management.utils.ApiUtils;

import javax.validation.ConstraintViolationException;

import static prgms.lecture.order_management.utils.ApiUtils.error;


@ControllerAdvice
public class GeneralExceptionHandler {

private final Logger log = LoggerFactory.getLogger(getClass());

private ResponseEntity<ApiUtils.ApiResult<?>> newResponse(Throwable throwable, HttpStatus status) {
return newResponse(throwable.getMessage(), status);
}

private ResponseEntity<ApiUtils.ApiResult<?>> newResponse(String message, HttpStatus status) {
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Type", "application/json");
return new ResponseEntity<>(error(message, status), headers, status);
}

// 필요한 경우 적절한 예외타입을 선언하고 newResponse 메소드를 통해 응답을 생성하도록 합니다.

@ExceptionHandler({
NoHandlerFoundException.class,
NotFoundException.class
})
public ResponseEntity<?> handleNotFoundException(Exception e) {
return newResponse(e, HttpStatus.NOT_FOUND);
}

@ExceptionHandler(UnauthorizedException.class)
public ResponseEntity<?> handleUnauthorizedException(Exception e) {
return newResponse(e, HttpStatus.UNAUTHORIZED);
}

@ExceptionHandler({
IllegalArgumentException.class,
IllegalStateException.class,
ConstraintViolationException.class,
MethodArgumentNotValidException.class
})
public ResponseEntity<?> handleBadRequestException(Exception e) {
log.debug("Bad request exception occurred: {}", e.getMessage(), e);
if (e instanceof MethodArgumentNotValidException) {
return newResponse(
((MethodArgumentNotValidException) e).getBindingResult().getAllErrors().get(0).getDefaultMessage(),
HttpStatus.BAD_REQUEST
);
}
return newResponse(e, HttpStatus.BAD_REQUEST);
}

@ExceptionHandler(HttpMediaTypeException.class)
public ResponseEntity<?> handleHttpMediaTypeException(Exception e) {
return newResponse(e, HttpStatus.UNSUPPORTED_MEDIA_TYPE);
}

@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public ResponseEntity<?> handleMethodNotAllowedException(Exception e) {
return newResponse(e, HttpStatus.METHOD_NOT_ALLOWED);
}

@ExceptionHandler({Exception.class, RuntimeException.class})
public ResponseEntity<?> handleException(Exception e) {
log.error("Unexpected exception occurred: {}", e.getMessage(), e);
return newResponse(e, HttpStatus.INTERNAL_SERVER_ERROR);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package prgms.lecture.order_management.exception;

public class NotFoundException extends RuntimeException {

public NotFoundException(String message) {
super(message);
}

public NotFoundException(String message, Throwable cause) {
super(message, cause);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package prgms.lecture.order_management.exception;

public class UnauthorizedException extends RuntimeException {

public UnauthorizedException(String message) {
super(message);
}

public UnauthorizedException(String message, Throwable cause) {
super(message, cause);
}

}
54 changes: 54 additions & 0 deletions src/main/java/prgms/lecture/order_management/security/Claims.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package prgms.lecture.order_management.security;

import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;

import java.util.Arrays;
import java.util.Date;

public class Claims {
Long userKey;
String name;
String[] roles;
Date iat;
Date exp;

private Claims() {}

Claims(DecodedJWT decodedJWT) {
Claim userKey = decodedJWT.getClaim("userKey");
Claim name = decodedJWT.getClaim("name");
Claim roles = decodedJWT.getClaim("roles");
if (!userKey.isNull()) {
this.userKey = userKey.asLong();
}
if (!name.isNull()) {
this.name = name.asString();
}
if (!roles.isNull()) {
this.roles = roles.asArray(String.class);
}
this.iat = decodedJWT.getIssuedAt();
this.exp = decodedJWT.getExpiresAt();
}

public static Claims of(long userKey, String name, String[] roles) {
Claims claims = new Claims();
claims.userKey = userKey;
claims.name = name;
claims.roles = roles;
return claims;
}

@Override
public String toString() {
return "Claims{" +
"userKey=" + userKey +
", name='" + name + '\'' +
", roles=" + Arrays.toString(roles) +
", iat=" + iat +
", exp=" + exp +
'}';
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package prgms.lecture.order_management.security;

import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class EntryPointUnauthorizedHandler implements AuthenticationEntryPoint {

private static final String _401 = "{\"success\":false,\"response\":null,\"error\":{\"message\":\"Unauthorized\",\"status\":401}}";

@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
response.getWriter().write(_401);
response.getWriter().flush();
response.getWriter().close();
}
}
Loading