인프런 강의를 보고 실습하고 정리하기! (무료입니다~)
회원가입, 로그인, 권한 로직을 실제로 구현해보고 Spring Security의 인증과 인가 로직을 경험해본다.
- 인증 : Request를 보낸 User가 누구인지? 올바른 User인지 확인하는 과정
- 인가 : Request가 수행하고자 하는 행동이 허가된 행동인지를 확인하는 과정
JWT
: json 객체를 사용해서 토큰 저장하는 웹토큰
- 가볍게 적용할 수 있어서 사이드 프로젝트 시 유용하다.
- 장점 : 데이터 스토어에 대한 의존성이 없어 수평 확장 유리
- 단점 : palyoad의 정보가 많아지면 네트워크 사용량 증가, 토큰이 클라이언트에 저장됨.
구조
- Header : 알고리즘 정보
- Payload : 실제로 사용할 정보
- Signature : 토큰 유효성 검증하는 문자열
개발환경
- Java11
- jdk11
- Spring Boot 2.7.13
- Gradle
- 종속성
- Lombok - 편의
- Spring Web
- Spring Data JPA
- Spring Security
- H2 - 메모리DB
- Validation
프로젝트 생성
** lombok을 사용하기 때문에 설정 > 어노테이션 처리 활성화 켜두는게 편함
controller > HelloController.java
@RestController
@RequestMapping("/api")
public class HelloController {
@GetMapping("/hello")
public ResponseEntity<String> hello() {
return ResponseEntity.ok("hello");
}
}
👉 http://localhost:8080/api/hello 를 호출하면 401 unauthorized 에러가 발생한다.
401 unauthorized 해결을 위한 security 설정
config > SecurityConfig.java
[TroubleShooting] 강의에 나오는 WebSecurityConfigurerAdapter 는 지원 중단됐다.
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/api/hello").permitAll() // 해당 Request는 허용한다.
.anyRequest().authenticated();
return http.build();
}
}
👉 200 성공 응답을 받을 수 있다.
Datasource, JPA 설정
resources > application.properties -> application.yml 로 변경한다 (읽기 쉬움)
- ddl-auto: create-drop : SessionFactory가 시작될 때 Drop, Create, Alter / 종료될 때 Drop
spring:
h2:
console:
enabled: true
datasource:
url: jdbc:h2:mem:testdb
driver-class-name: org.h2.Driver
username: sa
password:
jpa:
defer-datasource-initialization: true
database-platform: org.hibernate.dialect.H2Dialect
hibernate:
ddl-auto: create-drop
properties:
hibernate:
format_sql: true
show_sql: true
logging:
level:
me.silvernine: DEBUG
Entity 생성
entity > Autority.java, Users.java 클래스 생성
resources > data.sql
ddl-auto: create-drop 이므로 서버가 실행할 때마다 data.sql 쿼리들이 실행된다
H2 Console 결과 확인
SecurityConfig.java
h2-console 하위 모든 요청들, 파비콘 관련 요청은 Spring Security 로직을 수행하지 않도록 추가
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
// h2-console 하위 모든 요청들, 파비콘 관련 요청은 Spring Security 로직을 수행하지 않는다
return (web) -> web.ignoring().antMatchers("/h2-console/**", "/favicon.ico");
}
[TroubleShooting]
data.sql 에서 insert문 실행 시 user 테이블을 찾을 수 없다고 에러발생
-> H2 Database가 2.1.212 버전에서 user 키워드가 예약어로 지정되어 해당 버전 이후부턴 user라는 테이블 생성이 이제 안된다. -> user 테이블명을 다른거로 바꿔줌.
-> 그래도 에러발생
-> Spring Boot 2.5버전부터 data.sql은 Hibernate 초기화 되기 전에 실행되는 내용이 추가되었다.
jpa:
defer-datasource-initialization: true
👉 h2 DB에 연결할 수 있는 화면이 나옴
[TroubleShooting]
h2 1.4.198 이후 버전부터는 보안 문제로 데이터베이스가 자동으로 생성되지 않기 때문에 연결 시 에러 발생
c:/Users/사용자 경로로가서 test.mv.db 파일을 생성해준다.
연결 성공! 그런데 users 테이블이 보이지 않는다.
음 강의를 다시보니 JDBC URL이 나와 다르다! ;;;;
👉 data.sql 의 쿼리내용이 잘 들어와있는지 확인
잘 들어가 있네요 하하..
JWT 설정 추가
application.yml 에 jwt 설정 추가
- HS512 알고리즘을 사용하기 때문에 Secret Key는 64 Byte 이상이 돼야한다.
- 토큰 만료시간: 86400초 설정
jwt:
header: Authorization
#HS512 알고리즘을 사용할 것이기 때문에 512bit, 즉 64byte 이상의 secret key를 사용해야 한다.
#echo 'silvernine-tech-spring-boot-jwt-tutorial-secret-silvernine-tech-spring-boot-jwt-tutorial-secret'|base64
secret: c2lsdmVybmluZS10ZWNoLXNwcmluZy1ib290LWp3dC10dXRvcmlhbC1zZWNyZXQtc2lsdmVybmluZS10ZWNoLXNwcmluZy1ib290LWp3dC10dXRvcmlhbC1zZWNyZXQK
token-validity-in-seconds: 86400
build.gradle 에 JWT 의존성 추가
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'
JWT 관련 코드 개발
jwt > TokenProvider.java
- 토큰의 생성, 토큰의 유효성 검증
- InitializingBean 을 implements하고 afterPropertiesSet을 오버라이드 - 빈이 생성되고 주입을 받은 후에 secret값을 Base64 Decode해서 key 변수에 할당
- createToken() : Authentication객체의 권한정보를 이용해서 토큰 생성
- getAuthentication() : Token에 담겨있는 정보를 이용해 Authentication 객체를 리턴
- validateToken() : 토큰의 유효성 검증 수행
jwt > JwtFilter.java
- JWT를 위한 커스텀 필터 만들기
- GenericFilterBean을 extends해서 doFilter 오버라이드, 실제 필터링 로직은 doFilter 내부에 작성
- doFilter() : 토큰의 인증정보를 SecurityContext에 저장
- resolveToken() : Request Header에서 토큰 정보를 꺼내오기
jwt > JwtSecurityConfig.java
- TokenProvider, JwtFilter를 SecurityConfig에 적용
- SecurityConfigurerAdapter를 extends하고 TokenProvider를 주입받아서 JwtFilter를 통해 Seurity로직에 필터 등록
jwt > JwtAuthenticationEntryPoint.java
- 유효한 자격증명을 제공하지 않고 접근하려 할 때 401 unauthorized 에러 리턴
jwt > JwtAccessDeniedHandler
- 필요한 권한이 존재하지 안흔ㄴ 경우 403 forbidden 에러 리턴
SecurityConfig 추가
SecurityConfig.java
- TokenProvider, JwtAuthenticationEntryPoint, JwtAccessDeniedHandler 주입
- passwordEncoder() : BCryptPasswordEncoder 사용
- configure()
- csrf 설정 disable
- Exception 을 핸들링할때 JwtAuthenticationEntryPoint, JwtAccessDeniedHandler 추가
- h2-console을 위한 설정 추가
- 세션 설정을 stateless로 설정
- /api/authenticate, /api/signup - permitAll() 설정
- JwtSecurityConfig 클래스 적용
@EnableWebSecurity
@EnableMethodSecurity
@Configuration
public class SecurityConfig {
private final TokenProvider tokenProvider;
private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
private final JwtAccessDeniedHandler jwtAccessDeniedHandler;
public SecurityConfig(
TokenProvider tokenProvider,
JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint,
JwtAccessDeniedHandler jwtAccessDeniedHandler
) {
this.tokenProvider = tokenProvider;
this.jwtAuthenticationEntryPoint = jwtAuthenticationEntryPoint;
this.jwtAccessDeniedHandler = jwtAccessDeniedHandler;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
// h2-console 하위 모든 요청들, 파비콘 관련 요청은 Spring Security 로직을 수행하지 않는다
return (web) -> web.ignoring().antMatchers("/h2-console/**", "/favicon.ico");
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// token을 사용하는 방식이기 때문에 csrf를 disable합니다.
http
.csrf().disable();
http
.authorizeRequests()
.antMatchers("/api/hello", "/api/authenticate", "/api/signup").permitAll() // 해당 Request는 허용한다.
.requestMatchers(PathRequest.toH2Console()).permitAll()
.anyRequest().authenticated();
// exception handling for jwt
http
.exceptionHandling()
.authenticationEntryPoint(jwtAuthenticationEntryPoint)
.accessDeniedHandler(jwtAccessDeniedHandler);
// 세션을 사용하지 않기 때문에 STATELESS로 설정
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// enable h2-console
http
.headers().frameOptions().sameOrigin();
// Apply JWT
http
.apply(new JwtSecurityConfig(tokenProvider));
return http.build();
}
}
참고
강의 Git
websecurityconfigureradapter 지원 중단에 대한 대응
트러블슈팅2
'개발공부 > JAVA Spring' 카테고리의 다른 글
Spring MVC vs WebFlux 패턴 비교하기 (0) | 2023.08.13 |
---|---|
[SpringBoot] Spring Batch 5 (1) | 2023.07.20 |
[SpringBoot 통신] RestTemplate, WebClient (0) | 2023.05.09 |
객체지향 패러다임 (0) | 2023.04.12 |
[Spring Boot] 예외처리 (0) | 2023.03.28 |
댓글