스프링 시큐리티
Spring Security 소개
Spring Security는 Java 기반의 웹 애플리케이션에 대한 보안 솔루션을 제공하는 오픈 소스 프레임워크입니다. Spring Security는 인증(Authentication)과 권한 부여(Authorization)를 통해 웹 애플리케이션의 보안을 강화합니다. Spring Security는 다음과 같은 주요 기능을 제공합니다:
인증 (Authentication): 사용자의 신원을 확인하는 과정입니다. Spring Security는 여러 인증 메커니즘을 지원하며, 사용자가 직접 커스텀 인증 로직을 구현할 수 있습니다.
권한 부여 (Authorization): 인증된 사용자에게 특정 리소스에 대한 접근 권한을 부여하는 과정입니다. Spring Security는 메서드 레벨과 URL 레벨의 권한 부여를 지원합니다. 또한, 접근 제어 목록 (Access Control List, ACL)을 이용한 세밀한 권한 관리도 가능합니다.
CSRF (Cross-Site Request Forgery) 방지: 웹 애플리케이션에서 발생할 수 있는 CSRF 공격을 방지하기 위한 기능을 제공합니다. Spring Security는 CSRF 토큰을 자동으로 생성하고 관리하여 안전한 웹 애플리케이션을 구축할 수 있습니다.
세션 관리: Spring Security는 세션 고정 공격(Session Fixation) 방지, 세션 타임아웃, 동시 세션 제어 등의 기능을 제공하여 웹 애플리케이션의 세션 관리를 강화합니다.
비밀번호 저장: Spring Security는 비밀번호 저장 시 안전한 방법을 사용하도록 지원합니다. 일반적으로 해시 알고리즘과 솔트(Salt)를 사용하여 비밀번호를 저장하며, 이를 통해 보안을 강화합니다.
확장성: Spring Security는 다양한 인증 및 권한 부여 메커니즘을 지원하며, 사용자 정의 컴포넌트를 쉽게 통합할 수 있습니다. 이를 통해 개발자는 웹 애플리케이션의 보안 요구사항에 맞게 손쉽게 Spring Security를 확장할 수 있습니다.
Spring Security는 이러한 기능들을 제공하며, 웹 애플리케이션 개발자가 보안 관련 작업을 쉽게 처리할 수 있도록 도와줍니다. 이를 통해 보안 강화된 웹 애플리케이션을 구축하고 유지할 수 있습니다.
스프링 부트 프로젝트에서 스프링 시큐리티 사용하기 절차
스프링 부트 프로젝트에서 스프링 시큐리티를 사용하기 위한 기본 절차는 다음과 같습니다:
의존성 추가:
pom.xml
또는build.gradle
에 스프링 시큐리티 의존성을 추가합니다. Maven을 사용하는 경우pom.xml
에 아래 코드를 추가하세요.<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
Gradle을 사용하는 경우
build.gradle
에 아래 코드를 추가하세요.implementation 'org.springframework.boot:spring-boot-starter-security'
보안 설정 클래스 생성: 프로젝트에 보안 설정을 위한 클래스를 생성하고,
WebSecurityConfigurerAdapter
를 상속받아 웹 보안 설정을 구성합니다. 예를 들어, 아래와 같은 클래스를 생성할 수 있습니다.import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { // 원하는 보안 설정을 여기에 구성 } }
보안 설정 구성:
configure
메서드에서 원하는 보안 설정을 구성합니다. 예를 들어, 인증 없이 접근 가능한 경로를 설정하거나, 로그인 폼의 경로를 지정할 수 있습니다.@Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/", "/home", "/public/**").permitAll() // 인증 없이 접근 가능한 경로 설정 .anyRequest().authenticated() // 나머지 경로는 인증이 필요함 .and() .formLogin() .loginPage("/login") // 로그인 페이지 경로 설정 .permitAll() // 로그인 페이지는 인증 없이 접근 가능하게 설정 .and() .logout() .permitAll(); // 로그아웃 페이지는 인증 없이 접근 가능하게 설정 }
사용자 인증 및 권한 관리: 사용자 인증 및 권한 관리를 위해
UserDetailsService
를 구현하거나,AuthenticationManager
및AuthenticationProvider
를 사용하여 사용자 인증 정보를 처리할 수 있습니다.테스트 및 실행: 프로젝트를 실행하고 구성한 보안 설정이 정상적으로 동작하는지 확인합니다.
이러한 기본 절차를 따라 스프링 부트 프로젝트에서 스프링 시큐리티를 사용할 수 있습니다.
스프링 시큐리티 초간단 로그인, 회원 정보 표시, 로그아웃 구현하기
스프링 시큐리티를 사용하여 Spring Boot MVC와 타임리프를 이용한 초간단 로그인, 로그인 정보 표시, 로그아웃 기능을 구현하기 위해 다음 절차를 따르세요.
프로젝트 설정:
- Spring Boot 프로젝트를 생성하고, 필요한 의존성들을 추가하세요.
- pom.xml 파일에 다음 의존성들을 추가합니다.
<dependencies>
<!-- Spring Boot Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Thymeleaf -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>
Spring Security 설정:
src/main/java
폴더에 새로운 Java 클래스를 생성하고,WebSecurityConfigurerAdapter
를 상속받아 설정을 구성합니다.
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.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// 인증(Authentication) 설정
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication() // 인메모리 인증 사용
.withUser("Administrator") // "Administrator" 사용자
.password(passwordEncoder().encode("Pa$$w0rd")) // "Pa$$w0rd" 비밀번호
.roles("Administrators"); // "Administrators" 역할
}
// 인가(Authorization) 설정
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests() // 요청에 대한 권한 설정
.antMatchers("/login").permitAll() // "/login" 경로는 모든 사용자에게 허용
.antMatchers("/**").permitAll() // 모든 요청에 대해 접근 허용, 인증 기능 사용할 땐 이 라인 주석 처리
.anyRequest().authenticated() // 그 외의 요청은 인증된 사용자만 허용
.and()
.formLogin() // 로그인 페이지 설정
.loginPage("/login") // "/login" 경로로 접속시 로그인 페이지로 이동
.defaultSuccessURL("/welcome") // 로그인 성공시 이동할 페이지 설정
.and()
.logout() // 로그아웃 설정
.logoutUrl("/logout") // "/logout" 경로로 접속시 로그아웃 실행
.logoutSuccessUrl("/login?logout") // 로그아웃 성공시 이동할 페이지 설정
.permitAll(); // 로그아웃은 모든 사용자에게 허용
}
// 비밀번호 인코더 Bean 등록
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(); // BCryptPasswordEncoder 인스턴스 반환
}
}
컨트롤러 생성:
src/main/java
폴더에 새로운 Java 클래스를 생성하고,@Controller
애너테이션을 사용하여 컨트롤러를 구성합니다.
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class MyController {
@GetMapping("/login")
public String login() {
return "login";
}
@GetMapping("/welcome")
public String welcome(Authentication authentication, Model model) {
model.addAttribute("username", authentication.getName());
return "welcome";
}
}
타임리프 템플릿 생성:
src/main/resources/templates
폴더에login.html
와welcome.html
파일을 생성하여 각각 로그인 페이지와 로그인 정보 표시 페이지를 구성합니다.
login.html:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Login</title>
</head>
<body>
<div th:if="${param.logout}">
<p>You have been logged out.</p>
</div>
<form th:action="@{/login}" method="post">
<div>
<label for="username">Username:</label>
<input type="text" id="username" name="username" required autofocus/>
</div>
<div>
<label for="password">Password:</label>
<input type="password" id="password" name="password" required/>
</div>
<button type="submit">Log in</button>
</form>
</body>
</html>
welcome.html:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Welcome</title>
</head>
<body>
<h1>Welcome, <span th:text="${username}"></span>!</h1>
<form th:action="@{/logout}" method="post">
<button type="submit">Log out</button>
</form>
</body>
</html>
이제 애플리케이션을 실행하면, Administrator
사용자 아이디와 Pa$$w0rd
비밀번호를 사용하여 로그인하고 로그아웃할 수 있습니다. 로그인 후에는 로그인 정보가 표시되는 화면이 나타납니다.
UserDetailsService
Spring Security에서 UserDetailsService는 인증을 위해 사용자 정보를 제공하는 인터페이스입니다. UserDetailsService를 구현하면 사용자 정보를 로드하여 인증 매커니즘에 전달할 수 있습니다.
Spring Boot에서는 UserDetailsService를 구현하는 대신 스프링 시큐리티가 제공하는 JdbcUserDetailsManager를 사용하여 데이터베이스에서 사용자 정보를 가져올 수 있습니다. 이 방법은 Spring Boot에서 가장 일반적으로 사용되는 방법 중 하나입니다.
JdbcUserDetailsManager를 사용하려면 DataSource가 필요합니다. 다음은 Spring Boot에서 DataSource를 구성하는 방법입니다.
@Configuration
public class DataSourceConfig {
@Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/mydatabase");
dataSource.setUsername("root");
dataSource.setPassword("password");
return dataSource;
}
}
이제 UserDetailsService를 사용하여 인증 매커니즘을 구성할 수 있습니다. 이를 위해 다음과 같이 UserDetailsService를 구현하는 클래스를 작성합니다.
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private JdbcUserDetailsManager userDetailsManager;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserDetails userDetails = userDetailsManager.loadUserByUsername(username);
if (userDetails == null) {
throw new UsernameNotFoundException("User not found");
}
return userDetails;
}
}
이 예제에서는 JdbcUserDetailsManager를 사용하여 사용자 정보를 가져옵니다. UserDetailsService를 구현한 클래스에서는 로드된 UserDetails를 반환해야 합니다. 반환된 UserDetails는 Spring Security에서 인증을 수행하는 데 사용됩니다.
이제 UserDetailsService를 구현한 클래스를 사용하여 Spring Security를 구성할 수 있습니다. 예를 들어 다음과 같이 HttpSecurity를 구성할 수 있습니다.
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/user/**").hasAnyRole("ADMIN", "USER")
.anyRequest().authenticated()
.and()
.formLogin()
.and()
.logout().logoutSuccessUrl("/login").permitAll();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
}
이 예제에서는 UserDetailsService를 구현한 UserDetailsServiceImpl 클래스를 사용하여 사용자 정보를 가져옵니다. HttpSecurity를 사용하여 권한이 필요한 경로를 보호합니다. 또한 AuthenticationManagerBuilder를 사용하여 UserDetailsService를 사용하여 사용자 인증을 처리합니다.
Spring Security에서는 UserDetailsService를 사용하여 인증 매커니즘을 구성하는 것이 일반적입니다. 이 인터페이스는 loadUserByUsername() 메서드를 포함하고 있습니다. 이 메서드를 사용하여 사용자 이름을 기반으로 사용자 정보를 로드할 수 있습니다. 이 정보에는 사용자 이름, 암호, 권한 등이 포함될 수 있습니다.
일반적으로 Spring Security에서는 UserDetailsService 인터페이스를 구현하는 대신 데이터베이스에서 사용자 정보를 가져오는 JdbcUserDetailsManager를 사용합니다. JdbcUserDetailsManager를 사용하면 데이터베이스에서 사용자 정보를 검색하여 UserDetails 객체를 반환할 수 있습니다. 이를 통해 사용자가 인증되는 동안 사용자 정보가 로드되고 인증 매커니즘이 실행됩니다.
UserDetailsService를 사용하여 인증 매커니즘을 구성할 때, HttpSecurity를 사용하여 권한이 필요한 경로를 보호할 수 있습니다. 또한 AuthenticationManagerBuilder를 사용하여 UserDetailsService를 사용하여 사용자 인증을 처리합니다. UserDetailsService를 사용하는 방법은 Spring Security를 사용하여 인증 및 권한 부여를 처리하는 데 필수적입니다.
역할에 따른 사용자 관리
Spring Boot에서 Spring Security를 사용하여 역할에 따른 사용자 관리를 구현하려면 다음과 같은 절차를 따를 수 있습니다.
- 의존성 추가
먼저, Spring Boot 프로젝트에 Spring Security 의존성을 추가합니다. 이를 위해 pom.xml 파일에 다음 의존성을 추가합니다.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
- 역할 및 권한 설정
Spring Security에서는 권한을 사용하여 역할을 정의합니다. 역할에 대한 정보를 데이터베이스에 저장할 수 있습니다. 역할 정보를 저장하기 위한 테이블을 만듭니다.
CREATE TABLE roles (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL
);
다음으로, 역할에 대한 권한을 정의하기 위해 authorities 테이블을 만듭니다.
CREATE TABLE authorities (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
role_id INT NOT NULL,
FOREIGN KEY (role_id) REFERENCES roles(id)
);
위의 스키마에서 roles 테이블은 역할을 정의하고, authorities 테이블은 권한을 정의합니다. authorities 테이블은 roles 테이블과 관계가 있습니다.
- 사용자 테이블 생성
사용자 테이블을 만듭니다. 사용자에 대한 정보를 저장하기 위해 users 테이블을 만듭니다.
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
password VARCHAR(100) NOT NULL,
enabled TINYINT(1) NOT NULL
);
- 사용자-권한 연결 테이블 생성
사용자와 권한을 연결하기 위한 테이블을 만듭니다.
CREATE TABLE user_authorities (
user_id INT NOT NULL,
authority_id INT NOT NULL,
PRIMARY KEY (user_id, authority_id),
FOREIGN KEY (user_id) REFERENCES users(id),
FOREIGN KEY (authority_id) REFERENCES authorities(id)
);
위의 스키마에서 users 테이블은 사용자에 대한 정보를 저장하고, authorities 테이블은 권한을 정의합니다. user_authorities 테이블은 users와 authorities 테이블을 연결하는 역할을 합니다.
- 데이터베이스 설정
데이터베이스 연결 정보를 설정합니다. Spring Boot에서는 application.properties 파일에 데이터베이스 연결 정보를 저장합니다.
spring.datasource.url=jdbc:mysql://localhost:3306/mydatabase
spring.datasource.username=root
spring.datasource.password=password
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
- UserDetails 클래스
Spring Security에서는 인증을 위해 UserDetails 인터페이스를 제공합니다. UserDetails 인터페이스는 사용자 정보를 제공합니다. 다음과 같이 UserDetails 인터페이스를 구현하는 사용자 클래스를 작성할 수 있습니다.
@Entity
@Table(name = "users")
public class User implements UserDetails {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true)
private String username;
private String password;
@Column(nullable = false)
private Boolean enabled;
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(
name = "user_authorities",
joinColumns = {@JoinColumn(name = "user_id", referencedColumnName = "id")},
inverseJoinColumns = {@JoinColumn(name = "authority_id", referencedColumnName = "id")}
)
private Set<Authority> authorities = new HashSet<>();
// getters and setters
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return enabled;
}
}
이 예제에서는 UserDetails 인터페이스를 구현하며, UserDetails의 각 메서드를 구현합니다. getAuthorities() 메서드는 사용자의 권한을 반환하며, isAccountNonExpired(), isAccountNonLocked(), isCredentialsNonExpired(), isEnabled() 메서드는 사용자의 계정 상태를 반환합니다.
위의 예제에서는 사용자 이름, 암호, 사용자가 활성화되었는지 여부 및 사용자가 가진 권한을 저장하는 사용자 엔티티를 정의합니다. 사용자 엔티티에는 @ManyToMany 어노테이션을 사용하여 권한과 사용자 간의 다대다 관계를 설정합니다.
- 사용자, 권한 및 역할 생성
사용자, 권한 및 역할을 생성합니다. 이를 위해 다음과 같은 코드를 사용하여 사용자, 권한 및 역할을 생성할 수 있습니다.
@Autowired
private JdbcUserDetailsManager userDetailsManager;
@Transactional
public void addRole(String roleName, String... authorities) {
Role role = new Role(roleName);
for (String authority : authorities) {
role.addAuthority(new Authority(authority));
}
userDetailsManager.createRole(role);
}
@Transactional
public void addUser(String username, String password, boolean enabled, String... authorities) {
List<GrantedAuthority> authorityList = AuthorityUtils.createAuthorityList(authorities);
User user = new User(username, passwordEncoder.encode(password), enabled, authorityList);
userDetailsManager.createUser(user);
}
addRole() 메서드는 JdbcUserDetailsManager를 사용하여 역할을 생성합니다. 역할에는 역할 이름과 권한 목록이 포함됩니다. addAuthority() 메서드는 권한을 역할에 추가합니다. 이 메서드를 사용하여 역할의 권한을 지정할 수 있습니다.
addUser() 메서드는 JdbcUserDetailsManager를 사용하여 사용자를 생성합니다. 사용자에는 사용자 이름, 암호, 사용자가 활성화되었는지 여부, 사용자의 권한 목록이 포함됩니다.
PasswordEncoder 빈이 설정되어 있어야 비밀번호를 암호화할 수 있습니다. 이를 위해 WebSecurityConfigurerAdapter를 사용하여 PasswordEncoder 빈을 구성합니다.
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private DataSource dataSource;
@Autowired
private PasswordEncoder passwordEncoder;
@Bean
public JdbcUserDetailsManager userDetailsManager() {
JdbcUserDetailsManager userDetailsManager = new JdbcUserDetailsManager();
userDetailsManager.setDataSource(dataSource);
userDetailsManager.setUsersByUsernameQuery("SELECT username, password, enabled FROM users WHERE username=?");
userDetailsManager.setAuthoritiesByUsernameQuery("SELECT u.username, a.name FROM authorities a, users u, user_authorities ua WHERE u.username=? AND u.id=ua.user_id AND a.id=ua.authority_id");
return userDetailsManager;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/user/**").hasAnyRole("USER", "ADMIN")
.anyRequest().authenticated()
.and()
.formLogin()
.and()
.logout().logoutSuccessUrl("/login").permitAll();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
위의 예제에서는 JdbcUserDetailsManager 빈을 구성합니다. JdbcUserDetailsManager는 사용자 및 권한 데이터를 가져오는 데 사용됩니다. WebSecurityConfigurerAdapter를 사용하여 PasswordEncoder 빈을 구성합니다. PasswordEncoder 빈을 사용하여 사용자 비밀번호를 암호화할 수 있습니다.
configure(AuthenticationManagerBuilder auth) 메서드는 사용자 인증을 구성합니다. auth.userDetailsService() 메서드를 사용하여 UserDetailsService 빈을 설정합니다. UserDetailsService 빈은 사용자 이름으로 사용자를 가져옵니다. passwordEncoder를 사용하여 암호화된 비밀번호와 사용자가 제공한 비밀번호를 비교하여 사용자를 인증합니다.
configure(HttpSecurity http) 메서드는 HTTP 보안을 구성합니다. .antMatchers() 메서드를 사용하여 URL 패턴 및 권한을 지정할 수 있습니다. .authenticated() 메서드는 인증된 사용자에게만 액세스를 허용합니다. .formLogin() 메서드는 로그인 페이지를 사용하도록 설정합니다. .logout() 메서드는 로그아웃 처리를 구성합니다.
- 컨트롤러 생성
컨트롤러를 생성합니다. 사용자 및 권한 데이터를 표시합니다. 사용자 및 권한을 추가, 편집, 삭제할 수 있습니다.
@Controller
public class UserController {
@Autowired
private JdbcUserDetailsManager userDetailsManager;
@GetMapping("/admin/users")
public String getUsers(Model model) {
List<User> userList = userDetailsManager.getUsers();
model.addAttribute("userList", userList);
return "admin/users";
}
@GetMapping("/admin/roles")
public String getRoles(Model model) {
List<Role> roleList = userDetailsManager.getRoles();
model.addAttribute("roleList", roleList);
return "admin/roles";
}
@GetMapping("/admin/addUser")
public String getAddUser(Model model) {
List<Role> roleList = userDetailsManager.getRoles();
model.addAttribute("roleList", roleList);
model.addAttribute("user", new User());
return "admin/addUser";
}
@PostMapping("/admin/addUser")
public String postAddUser(@ModelAttribute User user) {
userDetailsManager.createUser(user);
return "redirect:/admin/users";
}
@GetMapping("/admin/addRole")
public String getAddRole(Model model) {
model.addAttribute("role", new Role());
return "admin/addRole";
}
@PostMapping("/admin/addRole")
public String postAddRole(@ModelAttribute Role role) {
userDetailsManager.createRole(role);
return "redirect:/admin/roles";
}
@GetMapping("/admin/deleteUser")
public String getDeleteUser(@RequestParam Long id) {
userDetailsManager.deleteUser(id);
return "redirect:/admin/users";
}
@GetMapping("/admin/deleteRole")
public String getDeleteRole(@RequestParam Long id) {
userDetailsManager.deleteRole(id);
return "redirect:/admin/roles";
}
}
위의 예제에서는 JdbcUserDetailsManager 빈을 사용하여 사용자 및 권한 데이터를 표시합니다. @GetMapping 어노테이션을 사용하여 사용자 및 권한 목록을 표시합니다. @PostMapping 어노테이션을 사용하여 사용자 및 권한을 추가합니다. @GetMapping 어노테이션을 사용하여 사용자 및 권한을 삭제합니다.
getUsers() 메서드는 JdbcUserDetailsManager를 사용하여 사용자 목록을 가져옵니다. getRoles() 메서드는 JdbcUserDetailsManager를 사용하여 역할 목록을 가져옵니다.
getAddUser() 메서드는 사용자를 추가하는 데 필요한 데이터를 제공합니다. getAddRole() 메서드는 역할을 추가하는 데 필요한 데이터를 제공합니다.
postAddUser() 메서드는 사용자를 추가합니다. postAddRole() 메서드는 역할을 추가합니다.
getDeleteUser() 메서드는 사용자를 삭제합니다. getDeleteRole() 메서드는 역할을 삭제합니다.
- 사용자 인증
웹 응용 프로그램에서 사용자 인증을 수행하는 방법을 보여주는 예제를 살펴보겠습니다.
@Controller
public class LoginController {
@GetMapping("/login")
public String getLoginPage() {
return "login";
}
@GetMapping("/login-error")
public String getLoginErrorPage(Model model) {
model.addAttribute("loginError", true);
return "login";
}
@GetMapping("/")
public String getHomePage() {
return "home";
}
}
위의 예제에서는 로그인 페이지와 로그인 오류 페이지를 표시하는 데 사용되는 컨트롤러를 구현합니다. 로그인 페이지에는 사용자 이름과 비밀번호를 입력하는 폼이 있습니다. 로그인 오류 페이지에는 로그인 오류 메시지가 포함됩니다.
getLoginPage() 메서드는 로그인 페이지를 표시합니다. getLoginErrorPage() 메서드는 로그인 오류 페이지를 표시합니다. getHomePage() 메서드는 애플리케이션의 홈 페이지를 표시합니다.
로그인 페이지에서 사용자 이름과 비밀번호를 제출하면 AuthenticationManager를 사용하여 사용자를 인증합니다. AuthenticationManager는 UserDetailsService 빈을 사용하여 사용자를 가져옵니다. PasswordEncoder 빈은 비밀번호를 암호화합니다.
로그인 성공시 AuthenticationSuccessHandler를 사용하여 홈 페이지로 리디렉션합니다. 로그인 실패시 AuthenticationFailureHandler를 사용하여 로그인 오류 페이지로 리디렉션합니다.
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private DataSource dataSource;
@Autowired
private PasswordEncoder passwordEncoder;
@Bean
public JdbcUserDetailsManager userDetailsManager() {
JdbcUserDetailsManager userDetailsManager = new JdbcUserDetailsManager();
userDetailsManager.setDataSource(dataSource);
userDetailsManager.setUsersByUsernameQuery("SELECT username, password, enabled FROM users WHERE username=?");
userDetailsManager.setAuthoritiesByUsernameQuery("SELECT u.username, a.name FROM authorities a, users u, user_authorities ua WHERE u.username=? AND u.id=ua.user_id AND a.id=ua.authority_id");
return userDetailsManager;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/user/**").hasAnyRole("USER", "ADMIN")
.anyRequest().authenticated()
.and()
.formLogin().loginPage("/login")
.successHandler(loginSuccessHandler())
.failureHandler(loginFailureHandler())
.and()
.logout().logoutSuccessUrl("/login").permitAll();
}
@Bean
public AuthenticationSuccessHandler loginSuccessHandler() {
return (request, response, authentication) -> response.sendRedirect("/");
}
@Bean
public AuthenticationFailureHandler loginFailureHandler() {
return (request, response, exception) -> response.sendRedirect("/login-error");
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
위의 예제에서는 AuthenticationSuccessHandler와 AuthenticationFailureHandler 빈을 구성합니다. formLogin().loginPage("/login") 메서드를 사용하여 로그인 페이지를 사용하도록 설정합니다. successHandler() 메서드를 사용하여 로그인 성공시 리디렉션을 처리합니다. failureHandler() 메서드를 사용하여 로그인 실패시 리디렉션을 처리합니다.
로그인 성공시 loginSuccessHandler() 빈이 홈 페이지로 리디렉션합니다. 로그인 실패시 loginFailureHandler() 빈이 로그인 오류 페이지로 리디렉션합니다.
마무리
이제 Spring Boot의 Spring Security를 사용하여 역할 기반 인증 및 권한 부여를 구현하는 방법을 배웠습니다. 이를 통해 사용자 인증, 사용자 데이터 관리, 보안 구성 및 로그인 처리를 수행할 수 있습니다. Spring Security는 매우 강력하고 유연한 보안 프레임워크입니다. 이를 사용하여 웹 응용 프로그램의 보안을 강화할 수 있습니다.