본문 바로가기
IT/Spring

Spring Security (Springboot + thymeleaf)

by sgoho01 2020. 5. 22.

Spring Security

SpringSecurity는 스프링 기반의 어플리케이션에서의 보안(인증, 권한)을 담당하는 프레임워크이다.

인증 : 허가된 사용자인지 체크
권한 : 허가된 사용자가 사용할 수 있는 권한을 가졌는지 체크

Security를 사용하지 않는 경우에는 자체적으로 세션을 만들고 관리하고 체크해야 하지만,
Security를 사용하면 Security에서 구현해둔 보안 관리를 사용 할 수 있다.

Security는 스프링 mvc와 별개로 필터(filter)를 사용하여 보안을 처리 하고있다.

클라이언트 요청을 받아 DispatcherServlet이 처리하기 전에 Filter에서 먼저 요청을 intercept해서 Filter를 거친후 로직을 실행하는데, 이때 Securiry의 Filter에서 보안(인증,권한) 체크를 하게된다.

 

Securiry 인증순서

- UserDetailsService에서 사용자의 정보를 조회, UserDetails로 반환하여 AuthenticationsProvider에게 전달

UserDetailsService의 loadUserByUserName(string) 메소드로 db에서 사용자 정보를 조회 후 UserDetails객체로 변환

- ProviderManager에서 AuthenticationProvider에게 인증을 위임

AuthenticationProvider에서는 UserDetailsService, UserDetails, PasswordEncoding등을 이용하여 조회한 사용자와 접속사용자 비교 후 인증처리

- 인증에 성공하면 Authentication 객체를 리턴. 리턴한 Authentication은 SecurityContext에 저장, 클라이언트에 session id(JSESSIONID)를 내려준다.

- 이 후 클라이언트에서는 JSESSIONID로 사용자 인증요청

 

테스트 코드 작성

Security를 테스트 해보기 위해 간단한 코드작성.

전체적인 프로젝트 구조

 

의존성 추가

   <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</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-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </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-thymeleaf</artifactId>
        </dependency>
    </dependencies>

Springboot를 이용하였고 Template Engine으로 Thymeleaf를 사용. 그외 JPA, Lombok, H2를 이용하였다.

 

도메인 생성

@Getter
@EqualsAndHashCode(of = "id")
@NoArgsConstructor
@ToString
@Entity
public class Account {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(unique = true)
    private String email;

    private String password;

    @Builder
    public Account(String email, String password) {
        this.email = email;
        this.password = password;
    }
}

사용자 정보를 저장할 간단한 Account 도메인 생성

 

Security 설정

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()    // 다음 리퀘스트에 대한 권한 체크
                // 해당 리퀘스트는 누구나 접근가능
                .antMatchers("/", "/sign-up", "/auth").permitAll()
                // admin으로 시작하는 리퀘스트는 ADMIN 권한을 가진 사용자만 접근가능
                .antMatchers("/admin/**").hasRole("ADMIN")
                // 그외 리퀘스트는 권한을 가진 사용자만 접근가능
                .anyRequest().authenticated()
            .and()
                .formLogin()
                .loginPage("/login").permitAll()
                .usernameParameter("email")
                .passwordParameter("password")
                .defaultSuccessUrl("/main")
            .and()
                .logout()
                .logoutUrl("/logout")
                .logoutSuccessUrl("/")
        ;
    }
}

Security의 사용설정할 Config 파일을 만든 후 @EnableWebSecurity 어노테이션으로 Security를 사용한다고 선언한다.

WebSecurityConfigurerAdapter를 상속받아 Security에서 옵션을 커스텀 할 수 있다.

사용자의 비밀번호를 암호화 하는 PasswordEncoding을 빈으로 등록하여 스프링에서 사용할 수 있도록하고 
HttpSecurity를 인자로 받는 configure 메소드를 오버라이드 하여 커스텀 설정을 한다.

authorizeRequests()
- antMatchers(url).perrmitAll() : 해당 URL으로 오는 요청은 인증을 거치지 않고 모든 사용자를 통과시킨다.
- antMatchers(url).hasRole("ROLE") : 해당 URL은 정의한 ROLE을 가지고 있는 사용자만 통과시킨다.
- anyRequest().authenticated() : 위에 정의한 요청 외에는 모두 인증을 거쳐야 한다.

formLogin()
- loginPage().permitAll() : 로그인 페이지 이동 URL은 모든 사용자가 통과.
- usernameParameter, passwordParameter : 로그인에서 아이디와 비밀번호를 넘기는 값의 이름을 정의할수 있다.
   (default값은 username은 username, password는 password)
- defaultSuccessUrl() : 로그인 성공 후 이동 URL

logout()
- logoutUrl() : 로그아웃 요청 URL (post)
- logoutSuccessUrl() : 로그아웃 성공 후 이동 URL

 

UserDetailsService 작성

@Service
@RequiredArgsConstructor
@Transactional
public class AccountService implements UserDetailsService {

    private final AccountRepository accountRepository;
    private final PasswordEncoder passwordEncoder;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        Account account = accountRepository.findByEmail(username);
        if(account == null) {
            throw new UsernameNotFoundException(username);
        }
        return new AccountUser(account);
    }


    public Account signUp(AccountSignUpDto accountSignUpDto) {
        accountSignUpDto.setPassword(passwordEncoder.encode(accountSignUpDto.getPassword()));
        return accountRepository.save(accountSignUpDto.toEntity());
    }

}
@Getter
public class AccountUser extends org.springframework.security.core.userdetails.User {

    private Account account;

    public AccountUser(Account account) {
        super(account.getEmail(), account.getPassword(), List.of(new SimpleGrantedAuthority("ROLE_USER")));
        this.account = account;
    }
}

DB에서 사용자를 조회 한 후 해당 객체를 UserDetails의 커스텀 객체(AccountUser)로 변환하여 리턴해준다.

위의 설정을 하고나면 간단한 Security 설정은 끝이다.

 

앱을 실행시키고 테스트를 해보자.

위 설정파일에 permitAll에 정의 하지 않은 URL 을 접속하면 로그인페이지로 튕기게 된다.
ex) localhost:8080/main

 

우선 로그인할 계정을 생성하기 위해 localhost:8080/sign-up 으로 접속하여 계정을 생성한다.

 

계정을 생성 후 로그인을 하면 아까는 로그인창으로 튕겼던 페이지에 접속이 가능하다.

 

 

 

git source : https://github.com/sgoho01/Srping-Security

'IT > Spring' 카테고리의 다른 글

ModelMapper  (0) 2021.06.21
Spring AOP - 어노테이션 만들기  (0) 2019.11.28
설정파일 YAML  (0) 2019.04.30
메이븐 (Maven)  (0) 2019.04.30
에러 처리 (ControllerAdvice)  (0) 2019.04.29

댓글