[Spring Boot #30] 스프링 부트 시큐리티 커스터마이징

2021. 3. 25. 01:41 Spring Framework/Spring boot2

| 스프링 부트 시큐리티 커스터마이징

 

스프링 부트에서는 사용자의 요청에 따라 어플리케이션 개발자가 인증 절차를 상황에 맞게 설정할 수 있습니다.

 

프로젝트 구조

+---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/스프링부트



출처: https://engkimbs.tistory.com/807?category=767865 [새로비]