[Spring Boot #30] 스프링 부트 시큐리티 커스터마이징
| 스프링 부트 시큐리티 커스터마이징
스프링 부트에서는 사용자의 요청에 따라 어플리케이션 개발자가 인증 절차를 상황에 맞게 설정할 수 있습니다.
프로젝트 구조
+---src
| +---main
| | +---java
| | | \---com
| | | \---example
| | | \---springsecurity
| | | Security.java
| | | SimpleController.java
| | | SpringSecurityApplication.java
| | |
| | \---resources
| | | application.properties
| | |
| | +---static
| | \---templates
| | hello.html
| | index.html
| | my.html
의존성 관리
<dependencies>
<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.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>
HTML 파일
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>This is Security Test</h1>
<a href="/my">my</a>
<a href="/hello">hello</a>
</body>
</html>
my.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>my</h1>
</body>
</html>
hello.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>Hello</h1>
</body>
</html>
소스 코드
@SpringBootApplication
public class SpringSecurityApplication {
public static void main(String[] args) {
SpringApplication.run(SpringSecurityApplication.class, args);
}
}
@Controller
public class SimpleController {
@GetMapping("/hello")
public String hello(){
return "hello";
}
@GetMapping("/my")
public String my(){
return "my";
}
}
@Component
public class Security extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/", "/hello").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.and()
.httpBasic();
}
}
- WebSecurityConfigurerAdapter를 상속받아 configure 메서드를 오버라이딩함으로써 시큐리티 설정을 할 수 있습니다.
- index.html, hello.html 파일에 접근할 때( / , /hello )를 제외하고 모든 사용자 요청은 인증을 받도록 설정하였습니다.
- httpBasic 그리고 formLogin 둘 동시에 인증을 받도록 설정하였습니다.
결과화면
- http://localhost:8080/my 로 접근할 시 다음과 같이 formLogin 화면이 출력됩니다. ( / 와 /hello 경로로 접근 시 바로 접근할 수 있습니다)
- 로그인에 필요한 정보는 기본적으로 username: user, password는 아래처럼 스프링 부트가 실행될 때 콘솔창에 출력되는 비밀번호를 입력해야합니다.
Using generated security password: 05d55648-2059-410c-a137-ba981383fa9a
| DB를 통한 유저 정보 생성 및 인증하기
거의 모든 웹에서는 DB를 통해 유저정보를 관리하고 유저가 로그인같은 인증 절차를 받으려고 시도할 때 DB에 있는 정보를 토대로 인증 절차를 진행하게 됩니다. 아래는 스프링 부트를 통해 어떻게 인증 절차를 구현하는 지 알아보는 프로젝트입니다.
프로젝트 구조
+---src
| +---main
| | +---java
| | | \---com
| | | \---example
| | | \---springsecurity
| | | Account.java
| | | AccountAddRunner.java
| | | AccountRepository.java
| | | AccountService.java
| | | SecurityConfig.java
| | | SimpleController.java
| | | SpringSecurityApplication.java
| | |
| | \---resources
| | | application.properties
| | |
| | +---static
| | \---templates
| | hello.html
| | index.html
| | my.html
- 이 프로젝트에서는 H2 인메모리 DB를 기준으로 유저 정보를 생성하고 그 정보를 통한 인증 절차를 진행하는 기능을 구현할 것입니다.
의존성 관리
<dependencies>
<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.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>
<!-- 아래는 기존 것에서 추가 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
</dependencies>
소스 코드
@Entity
public class Account {
@Id
@GeneratedValue
private Long id;
private String userName;
private String password;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return userName;
}
public void setUsername(String username) {
this.userName = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", username='" + userName + '\'' +
", password='" + password + '\'' +
'}';
}
}
- DB와 어플리케이션 간의 데이터 이동이 있을 때 그 데이터에 대한 정보를 담고 있는 객체의 클래스입니다. ( Data Transfer Object(DTO) 라 부름 )
public interface AccountRepository extends JpaRepository<Account, Long> {
Optional<Account> findByUserName(String username);
}
- JpaRepository 를 상속하여 DB에 의해 관리되는 정형화된 데이터를 추상화된 형태로 접근할 수 있습니다.
- findByUserName 메서드를 통해 username을 기준으로 데이터를 가져올 수 있습니다.
@Component
public class AccountAddRunner implements ApplicationRunner {
@Autowired
AccountService accountService;
@Override
public void run(ApplicationArguments args) throws Exception {
Account saelobi = accountService.createAccount("saelobi", "1234");
System.out.println(saelobi.getUsername() + " " + saelobi.getPassword());
}
}
- H2 데이터베이스에 사용자 정보를 임의로 넣는 코드입니다.
@Service
public class AccountService implements UserDetailsService {
@Autowired
private AccountRepository accountRepository;
@Autowired
private PasswordEncoder passwordEncoder;
public Account createAccount(String username, String password) {
Account account = new Account();
account.setUsername(username);
account.setPassword(passwordEncoder.encode(password));
return accountRepository.save(account);
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Optional<Account> byUserName = accountRepository.findByUserName(username);
Account account = byUserName.orElseThrow(() -> new UsernameNotFoundException(username));
return new User(account.getUsername(), account.getPassword(), authorities());
}
private Collection<? extends GrantedAuthority> authorities() {
return Arrays.asList(new SimpleGrantedAuthority("ROLE_USER"));
}
}
- Service 계층에 계정을 만드는 메서드(createAccount)와 UserDetailsService의 인터페이스를 구현한 loadUserByUserName이 있습니다.
- UserDetailsService의 loadUserByUsername은 사용자 인증 처리를 할 시,사용자가 보내온 인증 정보와 DB에 적재된 사용자 로그인 데이터의 일치 여부를 확인하는 중요한 역할을 하는 메서드입니다.
@Component
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/", "/hello").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.and()
.httpBasic();
}
@Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
}
- 스프링 부트 시큐리티 설정 클래스에 PasswordEncoder 에 대한 반환값을 생성하는 메서드를 작성하였습니다.
- PasswordEncoder를 반환하는 메서드를 구현하지 않으면 스프링 부트에서는 PassEncoder에 대한 정보를 찾을 수 없다면서 예외를 발생시킵니다.
- 따라서 스프링 부트에서 권장하는 사항을 그대로 소스에 반영하는 것이 좋습니다.
결과화면
접속
'Spring Framework > Spring boot #2' 카테고리의 다른 글
[스프링 부트/ Spring Boot] 스프링 게시판 만들기 - 부트로 쉽게 구현한 Spring 게시판 (0) | 2021.03.25 |
---|---|
스프링 부트로 OAuth2 구현(페이스북, 구글, 카카오, 네이버) (0) | 2021.03.25 |
[Spring Boot #32] 스프링 부트 Actuator, JConsole, VisualVM, 스프링 Admin (0) | 2021.03.25 |
[Spring Boot #31] 스프링 부트 RestTemplate, WebClient (0) | 2021.03.25 |
[Spring Boot #29] 스프링 부트 시큐리티 (0) | 2021.03.25 |
[Spring Boot #28] 스프링 부트 몽고DB(Mongo DB) 연동하기 (0) | 2021.03.25 |
[Spring Boot #27] 스프링 부트 레디스(Redis) 연동하기 (0) | 2021.03.25 |
[Spring Boot #26] Flyway를 이용한 데이터 마이그레이션 (0) | 2021.03.25 |