기존의 스프링 레거시 프로젝트에서 스프링 시큐리티를 사용할때, 직접 configuration xml 파일 설정을 통해 시큐리티 설정을 xml방식으로 설정해주었다.
스프링부트 3.x.x이후 버전에서 스프링 시큐리티를 통해 로그인 및 접근 권한을 더 간편하게 할 수있다.
스프링 시큐리티 디펜던시 추가
Java 클래스 WebSecurityConfig를 통한 시큐리티 설정
package com.boot.board_240214.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import javax.sql.DataSource;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig {
@Autowired
private DataSource dataSource;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((requests) -> requests
.requestMatchers("/", "/css/**","/image/**","/account/register").permitAll() // 다음 url은 인증없이 모든 사용자에게 허용
.anyRequest().authenticated() // 그 이외의 모든 요청은 인증 필요함
)
// 허가되지않은 경우 -> /login으로 이동
.formLogin((form) -> form
.loginPage("/account/login")
.permitAll()
)
.logout((logout) -> logout.permitAll()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/login"));
return http.build();
}
// JDBC Authentication 처리 - datasource 정보에 관해 시큐리티 보안처리를 한다.
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth)
throws Exception {
auth.jdbcAuthentication()
.dataSource(dataSource)
.passwordEncoder(passwordEncoder())
// 쿼리문을 조정하여 DB에 만든 user 테이블을 통해 사용자 정보를 인증하기위한 쿼리문 작성
.usersByUsernameQuery("select username,password,enabled "
+ "from user "
+ "where username = ?")
// user 테이블과 role 테이블은 다대다 관계(여러 유저가 여러 역할을 가질수 있음)이기때문에 중간에 user_role 테이블을 만들어 양쪽에서 1대다 관계로 만들어 join 구문 활용
.authoritiesByUsernameQuery("select username,name "
+ "from user_role ur "
+ "inner join user u "
+ "on ur.user_id = u.id "
+ "inner join role r "
+ "on ur.role_id = r.id "
+ "where username = ?");
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
@Configuration 어노테이션을 통해 해당 스프링 시큐리티 설정을 변경할수있다.
SecurityFilterChain 클래스를 통한 설정 변경은 스프링 부트 2.7.x버전 이후 새롭게 변경된 방식이다.
- authorizeHttpRequests() : Http에서 스프링 시큐리티의 인가설정을 준다.
- requestMatchers() : 특정 url에 대해 접근 여부를 설정할수 있다. permitAll()이면 권한이 없어도 접근이 가능하다.
- authenticated() : 인증이 완료되어야만 접근가능하다.
- formLogin() : 스프링 시큐리티에서 제공하는 form방식의 로그인으로 기본 제공페이지 또는 개인적으로 사용하는 로그인 페이지를 통해 스프링 시큐리티가 자동으로 로그인 인증을 처리해준다.
PasswordEncoder를 통해 비밀번호 인코딩하기
위의 코드에 맨 밑에 보이는 BCryptPasswordEncoder()는 스프링 시큐리티에서 제공하는 비밀번호 암호화 메소드이다.
해당 방식은 단방향으로 비밀번호를 암호화처리해준다.
회원가입할때 입력한 비밀번호를 위 메소드를 사용하여 암호화처리해서 저장하기위해 Bean에 등록해준다.
JDBC Authentication 방식 VS UserDetailsService 직접구현 방식
JDBC Authentication 는
로그인 및 권한 정보를 관리하기위해 시큐리티에서 기본적으로 제공하는 jdbc를 통한 인증방식이다.
JDBC Authentication 방식에서는 입력된 DataSource를 바탕으로 스프링 시큐리티에서 DB에 접근해서 사용자 정보를 가져와 인증을 처리한다.
반면에 UserDetailsService를 상속받아 사용자의 개별적인 요구에 맞게
loadUserByUsername()메소드를 통해 UserDetails를 변경할수 있다.
두가지 방식 모두 AuthenticationManager를 통해 사용자 인증을 처리하지만, UserDetailsService를 직접 구현해서 여러가지 비즈니스 요구에 맞추어 인증을 처리하는 경우가 많다고 한다.
위의 코드에서는 JDBC를 사용하여 인증을 처리하기위해 configureGlobal()를 구현해주었다.
회원가입 처리하기
View에서 받아온 User를 DB에 저장하기전에, 비밀번호를 encode해주고, 권한을 넣어준다.
(다대다)Many To Many 테이블 관계
예를들어 사용자 정보를 가진 User 테이블과 권한정보를 가진 Role 테이블이 있다고하자.
< User >
<Role>
한명의 User는 ROLE_USER와 ROLE_ADMIN을 가질수있고,
하나의 ROLE_USER는 여러 User를 가질수있다.
즉, 다대다 관계이다.
이러한 다대다 관계에서 유저가 가진 권한정보를 나타내려면 정규화 원칙에 위배되고,
JPA의 ORM에서는 다대다 테이블을 표현할수없다.
따라서 해당 User 테이블과 Role테이블을 중간에서 일대다 관계로 받아주는 중간 테이블이 필요하다.
<User_Role>
user_role 테이블은 user테이블의 id와 role테이블의 id를 FK로 가지고 있고, 해당 두 key를 묶어 PK로 가진다.
이렇게하면 하나의 유저가 여러 권한을 가지고, 또 하나의 권한을 여러 유저가 가지는것을 표현할 수 있다.
데이터베이스에서 해당 테이블 스키마를 표현해주었다면,
JPA에서 이러한 테이블관계를 표현해주기위해 Entity를 따로 수정해주어야 한다.
다대다 관계를 표현하기위한 JPA Entity
위의 데이터베이스 Table구조를 JPA의 ORM으로 표현하면 다음과 같다.
@ManyToMany 관계를 표현하기위해 Join을 사용하여 user_role 테이블의 FK들을 입력해준다.
그리고 User테이블은 컬렉션을 통해 가진 권한들을 나타낼수있다.
타임리프에서 스프링시큐리티 사용하기
해당 의존성을 통해 Thymeleaf에서 시큐리티의 권한정보를 가져와 사용할수있다.
<sec:authentication> 문법을 통해 스프링 시큐리티 Authentication 객체를 가져와 해당 사용자에 대한 정보 또는 권한정보를 꺼내 쓸수있다.
이러한 정보를 바탕으로 조건문을 통해 로그인되지않은경우 로그인 버튼을, 로그인한 경우 로그아웃 버튼을 보이게 해주었다.
스프링 시큐리티를 통해 사용자 정보를 가져오는 방법
스프링 시큐리티에서 인증이 완료된 사용자에 대한 정보는 SecurityContextHolder 에 담아 보관한다.
그리고 시큐리티 contextHolder은 세션에 담아서 보관되고, 어디에서든 사용할수있게 전역에서 꺼내쓸수있도록 설계되었으므로 사용자 정보를 가져오고싶다면 어디에서든
SecurityContextHolder 에서 Authentication 객체를 꺼내 특정 사용자의 권한정보, 유저정보를 꺼내 사용할수있다.
그러므로 기존의 Session을 통해 사용자 아이디를 가져와서 findById등의 메소드로 DB에서 가져올 필요가 없다!
'Category > Spring' 카테고리의 다른 글
Spring의 멀티스레드와 Blocking I/O (0) | 2025.03.12 |
---|---|
스프링 스케줄러를 사용하여 커넥션풀의 현재 커넥션 개수 측정하기 (0) | 2024.10.16 |
[SpringBoot] JPA를 통해 게시판 페이징 처리하기 (0) | 2024.02.15 |
[SpringBoot] @Valid, Validator를 통한 유효성 검사 (1) | 2024.02.15 |
[스프링부트] Thymeleaf - classappend와 fragment (0) | 2024.02.14 |