dependencies {
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'
implementation group: 'com.auth0', name: 'java-jwt', version: '4.2.1'
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
runtimeOnly 'com.mysql:mysql-connector-j'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
}
package com.choigoyo.entity;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@NoArgsConstructor // 기본 생성자
@Data
@Entity
@Table(name ="UserEntityJWT")// db에 생성될 테이블 이름
public class UserEntityJWT {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY) // id값 자동 증감
private Long id; // 회원번호
private String userName; // 회원 이름
private String password; // 회원 비밀번호
private String role; // 회원 역할 User, Manager ,Admin
public List<String> getRoleList(){
if (this.role.length() > 0) {
return Arrays.asList(this.role.split(",")); // 콤마 기준으로 잘라서 반환
}
return new ArrayList<>(); // null이 뜨는 것을 방지하기 위해 임시로 리스트 반환
}
}
package com.choigoyo.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class RestApiController {
@GetMapping("/")
public String home(){
return "<h1>HOME</h1>";
}
}
package com.choigoyo.config;
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.web.filter.CorsFilter;
@EnableWebSecurity
@Configuration
@RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final CorsFilter corsFilter;
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf().disable();
httpSecurity.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // session을 사용하지 않는다.
.and()
.addFilter(corsFilter) // @CrossOrigin(인증 없을 때) , 시큐리티 필터에 등록(인증 있을 때)
.formLogin().disable() // id pw 로그인을 form 로그인을 하지 X
.httpBasic().disable()
.authorizeRequests()
.antMatchers("/api/v1/user/**")
.access("hasRole('user') or hasRole('manager') or hasRole('admin')")
.antMatchers("/api/v1/manager/**")
.access("hasRole('manager') or hasRole('admin')")
.antMatchers("/api/v1/admin/**")
.access("hasRole('admin')")
.anyRequest().permitAll(); // 설정 외 모든 경로는 인증없이 접근가능
}
}
이전 Spring Security와 다른점은
STATELESS로 세션을 사용하지 않고, CrossOrigin 정책을 사용하지 않는다는 설정을하였고
Formlogin을 사용하지 않았습니다.
httpSecurity.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // session을 사용하지 않는다.
JWT를 사용하는 경우, 토큰 자체가 인증에 필요한 모든 정보를 가지고 있기 때문에 서버 측에서는 세션을 유지할 필요가 없습니다. 따라서 Spring Security 설정에서 STATELESS를 사용하여 서버가 상태를 유지하지 않도록 구성합니다.
.addFilter(corsFilter)
CrossOrigin 정책은 브라우저의 보안상의 이유로 동일한 출처에서 오는 리소스에 대한 제한을 두는 것입니다. 즉, 서로 다른 출처에서 오는 리소스 간에 접근을 제어합니다. JWT를 사용하는 경우, 인증 정보를 포함하는 토큰이 서버에 의해 생성되어 다른 도메인의 클라이언트에서도 인증이 가능해야 합니다. 따라서 CrossOrigin 정책을 따르지 않는 것입니다.
.formLogin().disable()
Form login은 Spring Security에서 기본적으로 제공하는 로그인 방식 중 하나입니다. 그러나 JWT를 사용하는 경우, 로그인 페이지를 통해 인증하는 방식이 아닌, 클라이언트에서 JWT를 생성하여 서버에 전달하고, 서버에서는 JWT의 유효성을 검증하여 인증하는 방식을 사용합니다. 따라서 Form login 설정은 필요하지 않습니다.
.httpBasic().disable()
.httpBasic().disable()는 Spring Security의 기본 인증 방식 중 하나인 HTTP Basic Authentication을 비활성화하는 설정입니다. 이 설정을 통해 HTTP Basic 인증을 사용하지 않고 JWT 토큰을 사용한 인증을 적용할 수 있습니다.
HTTP Basic Authentication은 클라이언트에서 사용자 이름과 비밀번호를 직접 전송하는 방식으로, 인증 정보가 암호화되지 않고 평문으로 전송되므로 보안에 취약합니다. 따라서 JWT 토큰과 같이 보안 수준이 높은 인증 방식을 사용하기 위해 HTTP Basic Authentication을 비활성화하고 다른 방식의 인증을 적용하는 것이 좋습니다.
package com.choigoyo.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 CorsConfig {
@Bean
public CorsFilter corsFilter(){
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true); // 서버가 응답할 때 json 을 js 에서 처리할 수 있게 할지 설정
config.addAllowedOrigin("*"); // 모든 ip에 응답 허용
config.addExposedHeader("*"); // 모든 header 에 응답 허용
config.addAllowedMethod("*"); // 모든 post ,get, delete , patch 요청을 허용
source.registerCorsConfiguration("/api/**",config);
return new CorsFilter(source);
}
}
인증없이 index 페이지를 반환하는 것을 확인하기