Skip to content

Commit

Permalink
✏️ Swagger + Security 수정 (#30)
Browse files Browse the repository at this point in the history
* rename: 로그인 요청 dto @Schema 추가

* chore: server domain 환경 설정 external-api -> infra

* chore: server domain property bean 등록

* chore: application-infra server 블럭 수정

* fix: swagger server url 환경변수 경로 수정

* chore: cors 설정 추가

* refactor: cors 설정 파일 분리

* rename: jwt security config -> security adpater config

* chore: bcryptpasswordencoder -> passwordencoder

* fix: 동일한 클래스명의 DTO @Schema name 속성 설정

* refactor: 운영 환경 별 security filter chain 설정 분리

* chore: external api 모듈 내 jackson nullable module 종속성 추가

* refactor: security auth config 분리

* refactor: security config swagger endpoint 프로필 별 설정 분리

* feat: simple granted authority 역직렬화 이슈로 custom granted authority 클래스 선언

* chore: external-api 내 jackson config 설정

* fix: security user details 역직렬화 문제 해결

* fix: security config 개발 환경 옵션 수정

* fix: custom granted authority equals 수정

* chore: docker hub 경로 수정
  • Loading branch information
psychology50 authored Mar 31, 2024
1 parent 6334e72 commit f925160
Show file tree
Hide file tree
Showing 16 changed files with 254 additions and 74 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ jobs:
- name: docker build and push
run: |
docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
docker build -t jinlee1703/pennyway-was .
docker push jinlee1703/pennyway-was
docker build -t pennyway/pennyway-was .
docker push pennyway/pennyway-was
# 5. AWS SSM을 통한 Run-Command (Docker 이미지 pull 후 docker-compose를 통한 실행)
- name: AWS SSM Send-Command
Expand All @@ -71,5 +71,5 @@ jobs:
command: |
docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
docker system prune -a -f
docker pull jinlee1703/pennyway-was
docker pull pennyway/pennyway-was
docker-compose up -d
3 changes: 3 additions & 0 deletions pennyway-app-external-api/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ dependencies {
/* Swagger */
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.4.0'

/* jackson */
implementation group: 'org.openapitools', name: 'jackson-databind-nullable', version: '0.2.6'

implementation 'org.springframework.boot:spring-boot-starter-web:3.2.3'
implementation 'org.springframework.boot:spring-boot-starter-validation:3.2.3'
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;

@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class SignInReq {
@Schema(name = "signInReqGeneral", title = "로그인 요청")
public record General(
@Schema(description = "아이디", example = "pennyway")
@NotBlank(message = "아이디를 입력해주세요")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public String password() {
}
}

@Schema(title = "일반 회원가입 요청 DTO")
@Schema(name = "signUpReqGeneral", title = "일반 회원가입 요청 DTO")
public record General(
@Schema(description = "아이디", example = "pennyway")
@NotBlank(message = "아이디를 입력해주세요")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package kr.co.pennyway.api.common.security.authentication;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.util.Assert;

import java.io.Serial;
import java.io.Serializable;

public final class CustomGrantedAuthority implements GrantedAuthority, Serializable {
@Serial
private static final long serialVersionUID = 1L;

private final String role;

@JsonCreator
public CustomGrantedAuthority(@JsonProperty("authority") String role) {
Assert.hasText(role, "role must not be empty");
this.role = role;
}

@Override
public String getAuthority() {
return role;
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof CustomGrantedAuthority cga) {
return this.role.equals(cga.getAuthority());
}
return false;
}

@Override
public int hashCode() {
return this.role.hashCode();
}

@Override
public String toString() {
return this.role;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,27 @@

import com.fasterxml.jackson.annotation.JsonIgnore;
import kr.co.pennyway.domain.domains.user.domain.User;
import kr.co.pennyway.domain.domains.user.type.Role;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Arrays;
import java.io.Serial;
import java.util.Collection;
import java.util.List;

@Getter
public class SecurityUserDetails implements UserDetails {
private final Long userId;
private final String username;
private final Collection<? extends GrantedAuthority> authorities;
private final boolean accountNonLocked;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class SecurityUserDetails implements UserDetails {
@Serial
private static final long serialVersionUID = 1L;

private Long userId;
private String username;
private Collection<? extends GrantedAuthority> authorities;
private boolean accountNonLocked;

@JsonIgnore
private boolean enabled;
Expand All @@ -39,10 +45,7 @@ public static UserDetails from(User user) {
return SecurityUserDetails.builder()
.userId(user.getId())
.username(user.getUsername())
.authorities(Arrays.stream(Role.values())
.filter(roleType -> roleType == user.getRole())
.map(roleType -> (GrantedAuthority) roleType::getType)
.toList())
.authorities(List.of(new CustomGrantedAuthority(user.getRole().getType())))
.accountNonLocked(user.getLocked())
.build();
}
Expand Down Expand Up @@ -91,4 +94,5 @@ public String toString() {
", accountNonLocked=" + accountNonLocked +
'}';
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package kr.co.pennyway.api.config;

import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.RequiredArgsConstructor;
import org.openapitools.jackson.nullable.JsonNullableModule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;

@Configuration
@RequiredArgsConstructor
public class JacksonConfig {
@Bean
Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder() {
return new Jackson2ObjectMapperBuilder()
.serializationInclusion(JsonInclude.Include.ALWAYS)
.modulesToInstall(new JsonNullableModule())
.indentOutput(true);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
@Configuration
@OpenAPIDefinition(
servers = {
@Server(url = "${pennyway.domain.local}", description = "Local Server"),
@Server(url = "${pennyway.domain.dev}", description = "Develop Server")
@Server(url = "${pennyway.server.domain.local}", description = "Local Server"),
@Server(url = "${pennyway.server.domain.dev}", description = "Develop Server")
}
)
@RequiredArgsConstructor
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package kr.co.pennyway.api.config.security;

import kr.co.pennyway.infra.common.properties.ServerProperties;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.util.List;

@Configuration
@RequiredArgsConstructor
public class CorsConfig {
private final ServerProperties serverProperties;

@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(List.of(serverProperties.getLocal(), serverProperties.getDev()));
configuration.setAllowedMethods(List.of("GET", "POST", "OPTIONS", "PUT", "PATCH", "DELETE"));
configuration.setAllowedHeaders(List.of("*"));
configuration.setExposedHeaders(List.of(HttpHeaders.AUTHORIZATION, HttpHeaders.SET_COOKIE));
configuration.setMaxAge(3600L);
configuration.setAllowCredentials(true);

UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,23 @@
import kr.co.pennyway.api.common.security.filter.JwtExceptionFilter;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@RequiredArgsConstructor
public class JwtSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
public class SecurityAdapterConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
private final DaoAuthenticationProvider daoAuthenticationProvider;

private final JwtExceptionFilter jwtExceptionFilter;
private final JwtAuthenticationFilter jwtAuthenticationFilter;

@Override
public void configure(HttpSecurity http) throws Exception {
http.authenticationProvider(daoAuthenticationProvider);
http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
http.addFilterBefore(jwtExceptionFilter, JwtAuthenticationFilter.class);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package kr.co.pennyway.api.config.security;

import com.fasterxml.jackson.databind.ObjectMapper;
import kr.co.pennyway.api.common.security.authentication.UserDetailServiceImpl;
import kr.co.pennyway.api.common.security.handler.JwtAccessDeniedHandler;
import kr.co.pennyway.api.common.security.handler.JwtAuthenticationEntryPoint;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;

@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
@Configuration
public class SecurityAuthConfig {
private final UserDetailServiceImpl userDetailsService;
private final ObjectMapper objectMapper;

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

@Bean
public AccessDeniedHandler accessDeniedHandler() {
return new JwtAccessDeniedHandler(objectMapper);
}

@Bean
public AuthenticationEntryPoint authenticationEntryPoint() {
return new JwtAuthenticationEntryPoint(objectMapper);
}

@Bean
public DaoAuthenticationProvider daoAuthenticationProvider() {
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setUserDetailsService(userDetailsService);
daoAuthenticationProvider.setPasswordEncoder(bCryptPasswordEncoder());
return daoAuthenticationProvider;
}

@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
return config.getAuthenticationManager();
}
}
Loading

0 comments on commit f925160

Please sign in to comment.