@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
http.csrf(csrf->csrf.disable())
.authorizeHttpRequests(
authorize->authorize
.requestMatchers("/admin/**") // admin 하위 페이지에 갈 경우 다 거름
.hasAnyRole("ADMIN")
.requestMatchers("/member/**") // member 하위 페이지에 갈 경우 다 거름
.hasAnyRole("MEMBER")
.anyRequest()
.permitAll()
)
.formLogin(
form->form
.loginPage("/user/login")
.defaultSuccessUrl("/member/index")
)
.logout(
logout->logout
.logoutUrl("/user/logout")
.logoutSuccessUrl("/index")
);
return http.build();
}
#기본문서 안띄우기: WebMvcConfigurer 인터페이스
루트페이지로 이동하게 되면
index.html이 자동으로 반환되는 것이
불편하기 시작했다..
기본문서가 반환되지 않게
처리할 수 있는 방법은 없을까?
WebMvcConfigurer 인터페이스를 활용해보자.
💡 WebMvcConfigurer 인터페이스란?
출처: 챗지피티(https://chat.openai.com)
인터페이스를 상속받아서
기본문서 반환을 저지해보자.
출처: 뉴렉처(https://www.youtube.com/@newlec1)
어노테이션을 달아서 객체화 하고
RedirectView를 해보자.
출처: 뉴렉처(https://www.youtube.com/@newlec1)
💡 filter와 WebMvcConfigurer의 차이점
WebMvcConfigurer를 사용하는 이유는 Spring MVC를 세부적으로 구성하고 사용자 정의 설정을 쉽게 적용하기 위함이다. 이 인터페이스를 통해 다양한 MVC 관련 설정을 추가하고 변경할 수 있다.
Filter는 Spring MVC와는 다른 목적으로 사용되며 다른 기능을 제공한다.
WebMvcConfigurer
Filter
Spring MVC에 특화된 설정을 제공한다.
주로 웹 애플리케이션의 컨트롤러와 뷰의 동작, 요청 및 응답 처리, 뷰 리졸버 설정 등을 구성하는 데 사용된다.
Spring MVC의 생명주기와 함께 작동하며 Spring MVC의 일부로 인식된다.
Servlet 스펙의 일부로, 모든 HTTP 요청과 응답을 처리한다.
Spring MVC와는 독립적으로 동작하며 모든 서블릿 기반 애플리케이션에서 사용할 수 있다.
주로 요청 및 응답을 가로채고 수정, 로깅, 보안 및 인증 등과 같은 일반적인 웹 애플리케이션 기능을 구현하는 데 사용된다.
Spring MVC의 생명주기와 관련이 없으며 Spring MVC 애플리케이션 외부에서 동작한다.
Spring MVC 특화된 설정 및 동작에 관련된 컨트롤러, 뷰, 리소스 관리와 같은 MVC 기능을 구성하는 데 사용된다.
모든 HTTP 요청에 대한 전역적인 처리를 제공한다.
두 가지 방식을 조합하여 Spring MVC 애플리케이션을 원하는 대로 구성하고 보안 및 요청 처리를 조절할 수 있다.
#JDBC 방법으로 UserDetailsService 만들기
in memory방식이 아니라
DB에서 직접 데이터를 끌어와서
사용자 정보를 저장해보자.
우리가 .yml파일에 저장한
DB서버와 관련된 정보도
객체화 되어 있기 때문에
DataSource 자료형의 객체를 만들어서
꽂아주기만 하면 된다.
//설정을 가지고 있는 녀석이라서
//@Component 말고 @Configuraiton이라고 특화된 이름 사용
@Configuration
public class RlandSecurityConfig {
/* --------- 1. DB에 대한 정보 객체화하기 --------- */
@Autowired
private DataSource dataSource;
/* --------- 2. JDBC 방법으로 UserDetails 만들기 --------- */
public UserDetailsService jdbcUserDetailsService() {
/*
* db서버는 어디고 아이디/비밀번호는 무엇이고,
* 회원데이터 쿼리, 역할데이터 쿼리를 제공해주면
* 알아서 결과를 만들어서 서비스로 제공해준다.
* InMemory는 직접 사용자를 담아주었다면,
* jdbc는 사용자를 담기위한 쿼리를 제공해주면 된다.
* db서버 관련된 내용은 .yml파일에 적어두었는데,
* 그것조차 객체화 되어서 저장되어 있기 때문에
* 그 객체를 꺼내오기만 하면 된다.
* 그 객체의 자료형은 DataSource
*/
JdbcUserDetailsManager manager = new JdbcUserDetailsManager(dataSource);
return manager;
}
}
그렇다면 JDBC방법으로
userDetailsService를 만들기 위해서는
쿼리를 해야 하는데,
여기서 명심할 것은!
스프링에게 줘야 하는 것은 테이블명이 아니라
최종적으로 만들어 놓은 결과집합이라는 점이다.
결과집합 모양으로 만들어서 전달해주어야 한다. (select 쿼리)
그리고 이 결과집합 모양(속성)도
스프링에서 지정해두었다.
username, password, enabled로 만들어야함!
출처: 뉴렉처(https://www.youtube.com/@newlec1)
먼저 sql로 결과집합을 만들어보자.
select user_name username, password, 1 enabled from member
where user_name = 'newlec';
이번에는 role을 가져와보자.
이때의 결과집합 속성도 지정되어 있다.
(username과 authority)
출처: 뉴렉처(https://www.youtube.com/@newlec1)
이때 DB의 데이터들은
앞에 "ROLE_"이라는 예약어가 붙어있어야 한다.
select user_name username, 'ROLE_MEMBER' authority from member
where user_name = 'newlec';
출처: 뉴렉처(https://www.youtube.com/@newlec1)
sql문을 메소드 안에 값으로 넣는다.
이때 사용자 정보가 해당되는 부분은
물음표(? )로 대체한다.
출처: 뉴렉처(https://www.youtube.com/@newlec1)
코드로 다시 한 번 확인해보자.
//얘도 객체로 만들어서
//스프링에게 알려주어야 한다.
@Bean
public UserDetailsService jdbcUserDetailsService() {
/*
* db서버는 어디고 아이디/비밀번호는 무엇이고,
* 회원데이터 쿼리, 역할데이터 쿼리를 제공해주면
* 알아서 결과를 만들어서 서비스로 제공해준다.
* InMemory는 직접 사용자를 담아주었다면,
* jdbc는 사용자를 담기위한 쿼리를 제공해주면 된다.
* db서버 관련된 내용은 .yml파일에 적어두었는데,
* 그것조차 객체화 되어서 저장되어 있기 때문에
* 그 객체를 꺼내오기만 하면 된다.
* 그 객체의 자료형은 DataSource
*/
String nameQuery =
"select user_name username, password, 1 enabled from member where user_name = ?";
String authQuery =
"select user_name username, 'ROLE_MEMBER' authority from member where user_name = ?";
JdbcUserDetailsManager manager = new JdbcUserDetailsManager(dataSource);
manager.setUsersByUsernameQuery(nameQuery);
manager.setAuthoritiesByUsernameQuery(authQuery);
return manager;
}
하지만 이렇게 실행시 패스워드 인코딩 오류 발생!
#패스워드 인코더
비밀번호가 인코딩되지 않은 상태로 입력되어서
다시 패스워드 인코딩 오류가 발생했다..!
💡 in memory 방식으로 실습 했을 때는 {noop}으로 임시처리 했었음
스프링이 지원하는 인코딩 방식은 다음과 같다.
💡 스프링이 지원하는 인코딩 방식들
1. bcrypt: 비크립트, 가장 많이 쓰이는 방식 2. noop: 암호화하지 않음 3. pbkdf2 4. scrypt 5. sha256
출처: 뉴렉처(https://www.youtube.com/@newlec1)
#(번외) 암호화의 방식: 단방향, 양방향
양방향
단방향
원문 컨텐츠가 있을 경우에 그 컨텐츠를 암호화한 경우 다시 복호화(디코딩)이 가능하다.
암호화가 되면 원문을 훼손시켜서 다시 복호화 할 수 없다. 컨텐츠가 다르면 절대 같은 값이 나오지 않는 알고리즘이다.
잠시 LOCK을 거는 것과 같은 원리 (꺼내볼 수 있다.)
하나의 키에 대한 식별자를 뽑고 원본을 훼손 (꺼내볼 수 없다.)
#인코딩 방법: PasswordEncoder
우리는 비크립트 방법을 사용해서 인코딩 할 것이기 때문에
비크립트 방식을 이용할 것이라는 것을
스프링에게 알려주면 된다.
만든 뒤에는 반드시 객체화해야 한다. (@Bean)
출처: 뉴렉처(https://www.youtube.com/@newlec1)
하지만 현재 비밀번호는 암호화된 것이 아니라서
에러는 나지 않지만 로그인이 안될 것이다.
로그인할 때, 스프링이, 입력하는 패스워드를 인코딩 한 뒤에
DB에 있는 날것 그대로의 비밀번호와 비교하기 때문에
값이 일치하지 않아서 오류가 나는 것이다.
그래서 DB에 password도
인코딩한 상태의 값으로 저장해놓아야 한다.
#해시 알고리즘
💡 해시: 식별자
💡 해시 알고리즘이란? 해시 알고리즘은 데이터를 특정 길이를 가진 고정된 크기의 값(해시 값 또는 해시 코드라고 함)으로 변환하는 과정을 말한다.
이 변환 과정에서 사용되는 함수를 해시 함수라고 하며, 해시 함수는 다양한 용도로 사용되는 암호학, 데이터 저장, 데이터 검색 등에서 중요한 역할을 한다.
해시 알고리즘은 그 특성상 입력 데이터가 조금만 달라져도 전혀 다른 해시 값을 생성한다. 이러한 특성 때문에 데이터의 무결성 검증이나 보안적 측면에서 널리 활용된다.