[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에 대한 정보를 찾을 수 없다면서 예외를 발생시킵니다.
  • 따라서 스프링 부트에서 권장하는 사항을 그대로 소스에 반영하는 것이 좋습니다. 


결과화면



접속





참고자료 : https://www.inflearn.com/course/스프링부트


이 글을 공유하기

댓글(1)

  • leo
    2020.05.19 14:19

    디비로 연동하기 해서 하는데 로그인이 안됩니당 ㅠㅠ

Designed by JB FACTORY