📌 JWT library
https://mvnrepository.com/artifact/com.auth0/java-jwt/3.18.2
📌 프로젝트 생성
plugins {
id 'org.springframework.boot' version '2.6.3'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java'
}
group = 'com'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
// https://mvnrepository.com/artifact/com.auth0/java-jwt
implementation group: 'com.auth0', name: 'java-jwt', version: '3.18.2'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
runtimeOnly 'mysql:mysql-connector-java'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
}
test {
useJUnitPlatform()
}
📌 security 관련 내용
▸ csrf
- csrf란 사용자가 자신의 의지와 무관하게 공격자가 의도한 행위를 하게 하는 것
- get의 공격 경우, img 태그 안에 url과 얻고자 하는 parameter값을 넣어서 웹 페이지 로딩 시 특정 url로 요청을 보내도록 할 수 있다.
- post의 경우 form tag를 hidden으로 사이트를 load하여서 사이트가 load되자 마자 특정 url로 요청을 보내도록 할 수 있다.
- spring은 기본적으로 csrf protection을 제공한다.
- api 서버로 사용할 경우 어차피 token인증 방식을 기본으로 하기 때문에 csrf protection을 disable()로 사용하는 것을 권장한다.
▸ cookie 와 jwt의 차이점
- cookie - session
- cookie는 사용자 정보를 client에 저장하는 방식이고, session은 사용자 인증 정보를 서버에 보관하는 방식이다.
- cookie의 경우 key와 value값으로 이루어져 있는데, cookie는 무결성이 보장되지 않는 반면 jwt는 무결성이 보장된다.
- cookie는 사용자 정보를 client에 저장하는 방식이고, session은 사용자 인증 정보를 서버에 보관하는 방식이다.
- cookie와 bearer token
- 가장 큰 차이점은 cookie는 요청(request)을 보낼 때 자동적으로 보내진다는 것이다.
- 반면에 bearer token방식은 http request에 명시적으로 추가해서 보내야 한다.
- 자동적으로 보내기 때문에 csrf 와 같은 공격을 당해 사용자 정보가 탈취될 수 있다.
- 또한 cookie는 browser기반이 아닌 안드로이드, 타블렛 앱 등이 서버의 api를 사용하는데 어렵다.
▸ bearer 란
JWT 혹은 OAuth에 대한 토큰을 사용한다는 의미이다.
📌 SecurityConfig 기본 설정
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.formLogin().disable()
.httpBasic().disable()
.authorizeRequests()
.antMatchers("/api/v1/user/**")
.access("hasRole('ROLE_USER') or hasRole('ROLE_MANAGER') or hasRole('ROLE_ADMIN')")
.antMatchers("/api/v1/manager/**")
.access("hasRole('ROLE_MANAGER') or hasRole('ROLE_ADMIN')")
.antMatchers("/api/v1/admin/**")
.access("hasRole('ROLE_ADMIN')")
.anyRequest().permitAll();
}
}
- http.sessionManageMent().sessionCreationPolicy(SessionCreationPolicy.SATELESS)
- 세션을 사용하지 않겠다.
- httpBasic().disable()
- 원래는 기본으로 http basic auth 기반으로 로그인 창이 뜬다. 해당 기능을 사용하지 않겠다.
- authorizeRequest()
- 요청에 대한 권한을 지정할 수 있다.
- 시큐리티 처리에 HttpServletRequest를 사용한다는 것을 의미한다.
- antMatchers() , access()
- 해당 경로를 access() 안에 있는 hasRole('') argument값만 허락한다.
- anyRequest().permitAll()
- 나머지 요청에 대해서는 모두 허락한다.
📌 CorsFilter 추가
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final CorsFilter corsFilter;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilter(corsFilter)
.formLogin().disable()
.httpBasic().disable()
.authorizeRequests()
.antMatchers("/api/v1/user/**")
.access("hasRole('ROLE_USER') or hasRole('ROLE_MANAGER') or hasRole('ROLE_ADMIN')")
.antMatchers("/api/v1/manager/**")
.access("hasRole('ROLE_MANAGER') or hasRole('ROLE_ADMIN')")
.antMatchers("/api/v1/admin/**")
.access("hasRole('ROLE_ADMIN')")
.anyRequest().permitAll();
}
}
▸ CorsConfig.java
package com.jwtpractice.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
@Configuration
public class CorConfig {
@Bean
public CorsFilter corsFilter(){
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true); // server 가 client 에서 json 처리를 javascript 로 할 수 있게 허락
config.addAllowedOrigin("*"); // 모든 ip 에 응답 허용
config.addAllowedHeader("*"); // 모든 header 에 응답 허용
config.addAllowedMethod("*"); // 모든 method 에 응답 허용
source.registerCorsConfiguration("/api/**",config);
return new CorsFilter(source);
}
}
▸ http basic 방식
매 요청마다 id, password를 보내는 방식
📌 필터 걸기
▸ MyFilter1.java
package com.jwtpractice.filter;
import javax.servlet.*;
import java.io.IOException;
public class MyFilter1 implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("필터1");
chain.doFilter(request,response);
}
}
servlet 밑에 있는 servlet을 상속 받아야 한다.
▸ SecurityConfig.java
package com.jwtpractice.config;
import com.jwtpractice.filter.MyFilter1;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
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.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.web.filter.CorsFilter;
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final CorsFilter corsFilter;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(new MyFilter1(), BasicAuthenticationFilter.class); // 이 부분 추가
http.csrf().disable();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilter(corsFilter)
.formLogin().disable()
.httpBasic().disable()
.authorizeRequests()
.antMatchers("/api/v1/user/**")
.access("hasRole('ROLE_USER') or hasRole('ROLE_MANAGER') or hasRole('ROLE_ADMIN')")
.antMatchers("/api/v1/manager/**")
.access("hasRole('ROLE_MANAGER') or hasRole('ROLE_ADMIN')")
.antMatchers("/api/v1/admin/**")
.access("hasRole('ROLE_ADMIN')")
.anyRequest().permitAll();
}
}
위처럼 하면 필터가 걸린다.
그런데 필터가 여러 개면 위처럼 여러 개를 config에 작성하기에 좋지 않다.
그래서 FilterConfig class를 따로 뺀다.
▸ FilterConfig.java
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean<MyFilter1> filter1(){
FilterRegistrationBean<MyFilter1> bean = new FilterRegistrationBean<>(new MyFilter1());
bean.addUrlPatterns("/*");
bean.setOrder(0);
return bean;
}
}
만약 필터를 여러개를 걸고 싶으면 아래와 같이 필터를 새로 생성하고, FilterConfig에 필터를 다시 등록해주면 된다.
- bean.setOrder()
- 낮은 번호의 Order가 먼저 실행된다.
▸ FilterConfig.java
package com.jwtpractice.config;
import com.jwtpractice.filter.MyFilter1;
import com.jwtpractice.filter.MyFilter2;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean<MyFilter1> filter1(){
FilterRegistrationBean<MyFilter1> bean = new FilterRegistrationBean<>(new MyFilter1());
bean.addUrlPatterns("/*");
bean.setOrder(0);
return bean;
}
@Bean
public FilterRegistrationBean<MyFilter2> filter2(){
FilterRegistrationBean<MyFilter2> bean = new FilterRegistrationBean<>(new MyFilter2());
bean.addUrlPatterns("/*");
bean.setOrder(1);
return bean;
}
}
- MyFilter2는 MyFilter1을 그대로 복붙했다.
▸ 필터 실행 순서
이 때, security filter chain이 내가 만든 기본 filter보다 먼저 동작하게 돼 있다.
@Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(new MyFilter3(),BasicAuthenticationFilter.class); // <-- 이 부분 추가
http.csrf().disable();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
...
}
addFilterBefore 에 MyFilter3를 추가해보면 MyFilter3 -> MyFilter1 -> MyFilter2 순으로 호출되는 것을 볼 수 있다.
addFilterAfter는 security filter chian안에서 가장 나중에 동작하는 필터이다.
▸ SecurityFilterChian 그림
https://atin.tistory.com/590
bearer 란?
https://velog.io/@cada/%ED%86%A0%EA%B7%BC-%EA%B8%B0%EB%B0%98-%EC%9D%B8%EC%A6%9D%EC%97%90%EC%84%9C-bearer%EB%8A%94-%EB%AC%B4%EC%97%87%EC%9D%BC%EA%B9%8C
csrf
https://zzang9ha.tistory.com/341
https://velog.io/@woohobi/Spring-security-csrf%EB%9E%80
https://wave1994.tistory.com/150
cookie vs jwt
https://stackoverflow.com/questions/37582444/jwt-vs-cookies-for-token-based-authentication
쿠키 세션 설명
https://www.youtube.com/watch?v=OpoVuwxGRDI
authorizeRequest()
https://velog.io/@jayjay28/2019-09-04-1109-%EC%9E%91%EC%84%B1%EB%90%A8
'스프링 & Jpa > 📌Spring Security 강좌' 카테고리의 다른 글
인프런 시큐리티 강좌 #3 - jwt (0) | 2022.01.22 |
---|---|
인프런 시큐리티 강좌 #1 - 도입 (0) | 2022.01.19 |
댓글