이 시리즈에 나오는 모든 내용은 인프런 인터넷 강의 - 스프링 시큐리티 - Spring Boot 기반으로 개발하는 Spring Security - 에서 기반된 것입니다. 그리고 여기서 인용되는 PPT 이미지 또한 모두 해당 강의에서 가져왔음을 알립니다. 추가적으로 여기에 작성된 코드들 또한 해당 강의의 github 에 올라와 있는 코드를 참고해서 만든 겁니다.
이제 이론은 배웠으니 구현을 해보자.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.11</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>me.dailycode</groupId>
<artifactId>core-spring-security</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>core-spring-security</name>
<description>core-spring-security</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.modelmapper</groupId>
<artifactId>modelmapper</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
User, Manager, Admin 전용 Controller 를 따로따로 만든다.
package me.dailycode.controller.user;
@Controller
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
private final PasswordEncoder passwordEncoder;
@GetMapping("/mypage")
public String myPage() {
return "user/mypage";
}
@GetMapping("/users")
public String createUser() {
return "user/login/register";
}
@PostMapping("/users")
public String createUser(AccountDto accountDto) {
ModelMapper modelMapper = new ModelMapper();
Account account = modelMapper.map(accountDto, Account.class);
account.setPassword(passwordEncoder.encode(account.getPassword()));
userService.createUser(account);
return "redirect:/";
}
}
// -----------------------------------------------------//
package me.dailycode.controller.user;
@Controller
public class MessageController {
@GetMapping(value="/messages")
public String mypage() throws Exception {
return "user/messages";
}
}
// -----------------------------------------------------//
package me.dailycode.controller.admin;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class ConfigController {
@GetMapping("/config")
public String config() {
return "admin/config";
}
}
여기서 보이는 Service 코드나, Repository 코드는 약간 주제를 벗어남으로 더 자세히 적지는 않겠다.
궁금하면 강의에서 제공하는 깃헙 주소에 접속하여 코드 확인 가능
package me.dailycode.security.configs;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.security.servlet.PathRequest;
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.builders.WebSecurity;
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.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
@EnableWebSecurity
@Slf4j
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
String password = passwordEncoder().encode("1111");
auth.inMemoryAuthentication().withUser("user").password(password).roles("USER");
auth.inMemoryAuthentication().withUser("manager").password(password).roles("MANAGER");
auth.inMemoryAuthentication().withUser("admin").password(password).roles("ADMIN");
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.requestMatchers(PathRequest.toStaticResources().atCommonLocations())
.mvcMatchers("/favicon.ico", "/resources/**", "/error");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/users").permitAll()
.antMatchers("/mypage").hasRole("USER")
.antMatchers("/messages").hasRole("MANAGER")
.antMatchers("/config").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.formLogin();
}
}
위에서 Spring 설정코드를 보면 아래와 같은 코드를 확인할 수 있다.
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.requestMatchers(PathRequest.toStaticResources().atCommonLocations())
.mvcMatchers("/favicon.ico", "/resources/**", "/error");
}
이 코드는 애플리케이션에서 전역적으로 사용되는 js
, css
, image
등의 파일들에
대해서 보안 필터를 적용하지 않기 위한 설정이다.
http.authorizeRequests().antMatchers("/", "/user").permitAll();
그런데 permitAll()
과 유사하다. 어떤 차이점이 있을까?
permitAll
: 인증사용자든, 익명사용자든, 권한이 있고 없든 다 상관없이
무조건 Spring Security Filter 처리 프로세스를 탄다.
webIngore
: Spring Security Filter 처리 프로세스를 그냥 무시한다.
==>
보안 필터의 처리 자체가 필요 없으면 webIgnore 로 설정해주자.