목표
: SpringSecurity기반의 앱에 JWT를 이용한 보안 강화
1. JWT 생성 및 갱신, 클라쪽에서 보낸 JWT의 유효성 검증 등
2. SpringSecurity의 Filter Chain과의 연계
Step
Step 1. 사전작업 : 현재 글
Step 2. 로그인 인증 구현 ( 인증 / Authentication ) : https://radpro.tistory.com/310
Step 3. 자격증명 및 검증 구현 ( 인가 / Authorization ) : https://radpro.tistory.com/311
build.gradle
dependencies {
...
// SpringSecurity를 위한 라이브러리
implementation 'org.springframework.boot:spring-boot-starter-security'
// JWT 기능을 위한 jjwt 라이브러리
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'
}
신규 클래스 추가
SecurityConfiguration.class
1. SecurityFilterChain인터페이스 타입 메서드 선언 : http요청을 받아 처리하는 security필터
2. PasswordEncoder Bean 객체 생성 : 패스워드 인코딩용
3. corsConfigurationSource Bean 객체 생성 : 구체적인 CORS 정책설정용
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.Arrays;
// Security Configuration(V1)
@Configuration
public class SecurityConfigurationV1 {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.headers().frameOptions().sameOrigin() // H2같은 웹 콘솔은 내부적으로 태그를 사용하므로, 추가한 부분. .frameOptions().sameOrigin()을 호출하면, 동일 출처로 부터 들어오는 request만 페이지 렌더링 허용
.and()
.csrf().disable() // 로컬 환경에서 사용할 경우. disable안하면 403 에러발생
.cors(Customizer.withDefaults()) // .cors(withDefaults()) 일 경우, corsConfigurationSource라는 이름으로 등록된 Bean을 이용. CORS를 처리하는 가장 쉬운 방법은 CorsFilter를 사용하는 것인데 CorsConfigurationSource Bean을 제공함으로써 CorsFilter를 적용
// CORS는 출처가 다른 스크립트 기반 HTTP 통신을 하더라도 선택적으로 리소스에 접근할 수 있는 권한을 부여하도록 브라우저에 알려주는 정책 (애플리케이션 간에 출처(Origin)가 다를 경우 스크립트 기반의 HTTP 통신(XMLHttpRequest, Fetch API)을 통한 리소스 접근이 제한 되)
.formLogin().disable() // formLogin은 SSR 방식에서 사용. CSR(Client Side Rendering) 방식에서는 주로 JSON 포맷으로 Username과 Password를 전송하는 방식을 사용할 것이므로 폼 로그인 방식을 비활성화
.httpBasic().disable() // HTTP Basic 인증은 request를 전송할 때 마다 Username/Password 정보를 HTTP Header에 실어서 인증을 하는 방식
//폼 로그인과 HTTP Basic 인증을 disable하면 해당 인증과 관련된 Security Filter(UsernamePasswordAuthenticationFilter, BasicAuthenticationFilter 등)가 비활성화
.authorizeHttpRequests(authorize -> authorize.anyRequest().permitAll()); // 아직 JWT를 적용하지 않아, 우선 모든 HTTP request에 대해 접근 허용해둠
return http.build();
}
// passwordEncoder Bean 객체 생성
@Bean
public PasswordEncoder passwordEncoder () {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
// 구체적인 CORS 정책 설정을 위한 CorsConfigurationSource Bean 객체 생성
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("*")); // 모든 출처(Origin)에 대해 스크립트 기반의 HTTP 통신 허용. 이 설정은 운영 서버 환경에서 요구사항에 맞게 변경가능
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE")); // 파라미터로 지정한 HTTP Method에 대한 HTTP통신 허용
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); // CorsConfigurationSource 인터페이스의 구현 클래스인 UrlBasedCorsConfigurationSource 클래스의 객체
source.registerCorsConfiguration("/**", configuration); // 모든 URL에 앞에서 구성한 CORS 정책(CorsConfiguration)을 적용
return source;
}
}
기존 클래스 수정
1. MemberDTO.class :
Post 클래스에 password 필드 추가
* 여기선 JWT 테스트용이라 password만 추가.
* 원래는 패스워드 확인 필드 추가, 두 패스워드의 일치여부 검증 로직도 필요
@NotBlank
private String password;
2. Member엔티티.class :
패스워드 필드 추가
@Column(length = 100, nullable = false)
private String password;
사용자 권한 등록용 권한 테이블 생성 필드 추가
// --- 사용자 권한 등록을 위한 권한 테이블 생성 부분 ---
@ElementCollection(fetch = FetchType.EAGER)
private List<String> roles = new ArrayList<>();
3. MemberService.class :
1) 필드 추가
private final PasswordEncoder passwordEncoder;
private final CustomAuthorityUtils authorityUtils;
2) DI용 파라미터 추가 (또는 그냥 @RequiredArgsConstructor 로 퉁치자)
public MemberService(MemberRepository memberRepository,
ApplicationEventPublisher publisher,
PasswordEncoder passwordEncoder,
CustomAuthorityUtils authorityUtils) {
this.memberRepository = memberRepository;
this.publisher = publisher;
this.passwordEncoder = passwordEncoder;
this.authorityUtils = authorityUtils;
}
3) createMember 핸들러 메서드에, 패스워드 암호화 코드 추가
4) createMember 핸들러 메서드에, DB에 User Role저장 코드 추가
public Member createMember(Member member) {
verifyExistsEmail(member.getEmail()); // 이메일(고유값)이 기존에 존재하는지 검증 => 있으면 create(회원가입) 실패
// 패스워드 받아서 인코딩하는 부분 (패스워드 암호화)
String encryptedPassword = passwordEncoder.encode(member.getPassword()); // encrypt된 패스워드를 만들기 위해 비밀번호 불러와서 인코더로 인코딩함
member.setPassword(encryptedPassword); // 인코딩된 패스워드로 멤버의 패스워드 업데이트
// DB에 User Role 저장
List<String> roles = authorityUtils.createRoles(member.getEmail());
member.setRoles(roles);
Member savedMember = memberRepository.save(member); // 멤버타입으로 멤버레파지토리에 있는 save 메서드 실행해서 저장된 멤버객체 생성
// 추가된 부분
publisher.publishEvent(new MemberRegistrationApplicationEvent(this, savedMember));
return savedMember;
}
'Codestates [Back-end] > 데일리 로그 [TIL]' 카테고리의 다른 글
22.09.27 JWT - SpringSecurity 인증 (Step 3. 자격증명 및 검증 구현) [진행중] (0) | 2022.09.27 |
---|---|
22.09.27 JWT - SpringSecurity 인증 (step 2. 로그인 인증 구현) [진행중] (0) | 2022.09.27 |
22.09.26 JWT - 생성 및 검증 테스트 (1) | 2022.09.26 |
22.09.26 JWT 이론 (0) | 2022.09.26 |
22.09.23 SpringSecutiry - DelegatingPasswordEncoder (0) | 2022.09.23 |