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
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'org.springframework.boot:spring-boot-gradle-plugin:2.5.4'
classpath 'org.springframework.boot:spring-boot-gradle-plugin:2.7.5'
classpath 'io.spring.gradle:dependency-management-plugin:1.0.11.RELEASE'
}
}
Expand Down
5 changes: 2 additions & 3 deletions instagram-api/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@ dependencies {

implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation group: 'io.springfox', name: 'springfox-swagger2', version: '2.9.2'
implementation group: 'io.springfox', name: 'springfox-swagger-ui', version: '2.9.2'
// implementation 'org.springframework.boot:spring-boot-starter-security'
implementation "io.springfox:springfox-boot-starter:3.0.0"
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.2'
implementation group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.2'
implementation group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.2'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package clone.instagram.global.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.CorsUtils;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

@Configuration
public class SecurityConfig {

private static final String[] AUTH_WHITELIST_SWAGGER = {"/v2/api-docs", "/configuration/ui", "/swagger-resources",
"/swagger-resources/**", "/configuration/security", "/swagger-ui.html", "/webjars/**", "/swagger-ui/**"};
private static final String[] AUTH_WHITELIST_STATIC = {"/static/css/**", "/static/js/**", "*.ico"};
private static final String[] AUTH_WHITELIST = {"/accounts"};

@Bean
public CorsConfigurationSource configurationSource() {
CorsConfiguration configuration = new CorsConfiguration();

configuration.addAllowedOriginPattern("*");
configuration.addAllowedHeader("*");
configuration.addAllowedMethod("*");
configuration.setAllowCredentials(true);
configuration.setMaxAge(3600L);

UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);

return source;
}

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);

http.logout().disable()
.formLogin().disable()
.httpBasic().disable();

http.cors()
.configurationSource(configurationSource())
.and()
.csrf()
.disable()
.authorizeRequests()
.requestMatchers(CorsUtils::isPreFlightRequest)
.permitAll()
.antMatchers(AUTH_WHITELIST)
.permitAll()
.antMatchers(AUTH_WHITELIST_STATIC)
.permitAll()
.antMatchers(AUTH_WHITELIST_SWAGGER)
.permitAll()
.anyRequest().hasAuthority("ROLE_USER");

return http.build();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package clone.instagram.global.config;

import java.util.ArrayList;
import java.util.List;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.web.bind.annotation.RestController;

import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.builders.ResponseBuilder;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.ApiKey;
import springfox.documentation.service.AuthorizationScope;
import springfox.documentation.service.Response;
import springfox.documentation.service.SecurityReference;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;

@Configuration
public class SwaggerConfig {

@Bean
public Docket api() {
final List<Response> responseMessages = new ArrayList<>();
responseMessages.add(new ResponseBuilder().code("405")
.description("G002 - 허용되지 않은 HTTP method 입니다.").build());
responseMessages.add(new ResponseBuilder().code("500")
.description("G001 - 내부 서버 오류입니다.").build());

return new Docket(DocumentationType.SWAGGER_2)
.useDefaultResponseMessages(false)
.globalResponses(HttpMethod.POST, responseMessages)
.globalResponses(HttpMethod.GET, responseMessages)
.globalResponses(HttpMethod.DELETE, responseMessages)
.globalResponses(HttpMethod.PUT, responseMessages)
.apiInfo(apiInfo())
.securityContexts(List.of(securityContext()))
.securitySchemes(List.of(apiKey()))
.select()
.apis(RequestHandlerSelectors.withClassAnnotation(RestController.class))
.paths(PathSelectors.any())
.build();
}

private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("Instagram's API Docs")
.version("1.0")
.description("API 명세서")
.build();
}

private ApiKey apiKey() {
return new ApiKey("JWT", "Authorization", "header");
}

private SecurityContext securityContext() {
return SecurityContext.builder()
.securityReferences(defaultAuth())
.build();
}

private List<SecurityReference> defaultAuth() {
AuthorizationScope authorizationScope =
new AuthorizationScope("global", "accessEverything");
AuthorizationScope[] authorizationScopes =
new AuthorizationScope[1];

authorizationScopes[0] = authorizationScope;
return List.of(new SecurityReference("JWT", authorizationScopes));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import static clone.instagram.result.ResultCode.*;

import javax.validation.Valid;

import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
Expand All @@ -20,7 +22,6 @@
import lombok.RequiredArgsConstructor;

@Api(tags = "멤버 인증 API")
@Validated
@RestController
@RequiredArgsConstructor
public class RegisterController {
Expand All @@ -38,7 +39,7 @@ public class RegisterController {
+ "M007 - 인증 이메일 전송을 먼저 해야합니다.")
})
@PostMapping(value = "/accounts")
public ResponseEntity<ResultResponse> register(@RequestBody RegisterRequest registerRequest) {
public ResponseEntity<ResultResponse> register(@RequestBody @Valid RegisterRequest registerRequest) {
final RegisterUseCase.Command command = mapRequestToCommand(registerRequest);
return toResponseEntity(registerUseCase.invoke(command));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,38 @@
package clone.instagram.member.request;

import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;

import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
public class RegisterRequest {

@ApiModelProperty(value = "유저네임", example = "dlwlrma", required = true)
@NotBlank(message = "username을 입력해주세요")
@Size(min = 4, max = 12, message = "사용자 이름은 4문자 이상 12문자 이하여야 합니다")
@Pattern(regexp = "^[0-9a-zA-Z]+$", message = "username엔 대소문자, 숫자만 사용할 수 있습니다.")
private String username;

@ApiModelProperty(value = "비밀번호", example = "a12341234", required = true)
@NotBlank(message = "비밀번호를 입력해주세요")
@Pattern(regexp = "^(?=.*[A-Za-z])(?=.*\\d)[A-Za-z\\d]{8,}$", message = "비밀번호는 8자 이상, 최소 하나의 문자와 숫자가 필요합니다")
@Size(max = 20, message = "비밀번호는 20문자 이하여야 합니다")
private String password;

@ApiModelProperty(value = "이름", example = "이지금", required = true)
@NotBlank(message = "이름을 입력해주세요")
@Size(min = 2, max = 12, message = "이름은 2문자 이상 12문자 이하여야 합니다")
private String name;

@ApiModelProperty(value = "이메일", example = "[email protected]", required = true)
@NotBlank(message = "이메일을 입력해주세요")
@Email(message = "이메일의 형식이 맞지 않습니다")
private String email;

}
4 changes: 3 additions & 1 deletion instagram-api/src/main/resources/application.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
spring:
profiles:
include: adapter, data, application, local

mvc:
pathmatch:
matching-strategy: ant_path_matcher
auth:
whitelist:
api: /login, /login/recovery, /accounts, /**/without,\
Expand Down