본문 바로가기
Spring

Spring Security OAuth2 인증서버 만들기 02

by autumnly 2019. 10. 10.

 

2. Spring Boot 로 OAuth 인증서버 만들기

Spring Boot 와 Spring Security 를 통해 인증 및 리소스 서버를 만들어 보자.

인증서버와 리소스 서버는 분리할 수도 있지만, 먼저 간단한 구현을 위해 하나로 만들어 볼 것이다.

의존성 및 프로젝트 구조는 다음과 같다.

 

 

 

 

프로젝트 구조에서 알 수 있듯이 총 세 가지의 설정파일이 필요한데, 각각

 - 인증 서버에 대한 설정(AuthServerConfig)

 - 리소스 서버에 대한 설정(ResourceServerConfig)

 - OAuth 와 별개인 전반적인 서버에 대한 인증 설정(SecurityConfig)

이다.

 


AuthServerConfig

import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;

...

@EnableAuthorizationServer
@Configuration
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private TokenStore tokenStore;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private UserDetailsService userDetailsService;

    
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("iamclient")
                .secret(passwordEncoder.encode("iamsecret"))
                .authorizedGrantTypes("authorization_code", "password", "refresh_token")
                .scopes("read", "write")
                .accessTokenValiditySeconds(60*60)
                .refreshTokenValiditySeconds(6*60*60)
                .autoApprove(true);

    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.tokenStore(tokenStore)
                 .authenticationManager(authenticationManager)
                 .userDetailsService(userDetailsService);
    }
}

 

인증서버 설정에서는 @EnableAuthorizationServer 어노테이션을 통해 oauth 서버에 필요한 기본설정을 셋팅할 수 있다. (물론 @Configuration 어노테이션도 함께 붙여야 한다.)

클래스에 @EnableAuthorizationServer 어노테이션을 붙이는 것 만으로도 OAuth 관련 endpoints 가 생성된다.

(/oauth/token, /oauth/authorize 등. 자세한 것은 여기 참고)

 

이 클래스는 AuthorizationServerConfigurer 를 구현해야 하며, 주요 메소드를 포함한 구현체인 AuthorizationServerConfigurerAdapter 를 상속받으면 편리하다.

설정 내에서는 configure 메소드를 override 하면 되는데, configure 메소드의 파라미터에 따라 다음 세 가지 종류의 설정을 할 수 있다.

 

1. ClientDetailsServiceConfigurer 

클라이언트에 대한 설정을 할 수 있다. 클라이언트 정보를 DB 로 관리할 것인지, Inmemory 로 관리할 것인지 등에 대한 설정이나, 클라이언트별로 제공할 scope 나 권한 등을 정의할 수 있다. 예제에서는 간단한 테스트를 위해 InMemory 로 설정하였으며 client id 와 secret 을 정의해놓았다. (DB 를 사용할 경우 스키마는 각자 정의하기 나름이지만, Spring 에서는 예시를 제공해주고 있다. 예시)

2. AuthorizationServerSecurityConfigurer
토큰 엔드포인트 (/auth/token) 에 대한 보안관련 설정을 할 수 있다.

3. AuthorizationServerEndpointsConfigurer
인증, 토큰 엔드포인트, 토큰 서비스를 정의할 수 있다.

위에서 tokenStore, userDetailsService 등 객체를 빈으로 가져와 설정에 등록해주고 있는데, 이 빈들은 뒤에 나올 SecurityConfig 에서 등록해주게 될 것이다.

 

 


ResourceServerConfig

OAuth 토큰에 의해 보호되고 있는 자원 서버에 대한 설정이다. Sprint OAuth 에서는 자원에대한 보호를 구현하는 Spring Security Filter 를 제공한다. @EnableResourceServer 어노테이션을 붙임으로서 해당 필터를 활성화 할 수 있다.

이 설정에서는 각 리소스 (여기에서는 user 정보) 에 대한 접근 권한을 설정해주고 있다.

 

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter{


    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.anonymous().disable()
            .authorizeRequests()
            .antMatchers("/users/**").authenticated()
            .and()
            .exceptionHandling()
            .accessDeniedHandler(new OAuth2AccessDeniedHandler());
    }
}

 


SecurityConfig

서버에 대한 전반적인 security 설정을 한다.

대부분 Spring Security 와 관련된 설정들이며, OAuth 관련 설정은 TokenStore 빈을 등록해주는 부분이다.

여기서는 inMemory 토큰을 사용하는 것으로 설정했다. UserDetailsServier 는 Spring Security 에서 제공하는 인터페이스로, 해당 인터페이스를 구현해 회원정보를 관리할 수 있다.

 

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter{

    @Resource(name="userService")
    private UserDetailsService userDetailsService;


    @Bean
    @Override
    protected AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }

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

    @Bean
    public TokenStore tokenStore() {
        return new InMemoryTokenStore();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService)
                .passwordEncoder(encoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .cors()
                .and()
            .csrf()
                .disable()
            .anonymous()
                .disable()
            .authorizeRequests()
            .antMatchers("/api-docs/**")
                .permitAll();
    }

}

 


User

리소스 서버의 리소스인 User 정보에 대한 구현이다.

다음과 같이 User 정보를 담을 엔티티를 하나 만들어준다.

 

@Entity
@Data
public class User {

    @Id
    @GeneratedValue
    private Long id;

    private String username;

    private String password;

}

 


UserController

그리고 리소스를 만들어준다.

 

@RestController
@RequestMapping("/users")
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/user")
    public List<User> listUser(){
        return userService.findAll();
    }

    @PostMapping("/user")
    public User create(@RequestBody User user){
        return userService.save(user);
    }

}

 


UserRepository

public interface UserRepository extends JpaRepository<User, Long> {

    User findByUsername(String username);
}

 


UserService

위에서 언급한 UserDetailsService 를 구현하는 UserService 를 만들어준다. 또한 처음 빈 등록 시 User 객체 하나를 생성해준다. (테스트를 위해)

 

@Service
public class UserService implements UserDetailsService{

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private PasswordEncoder passwordEncoder;

    public List<User> findAll() {
        return userRepository.findAll();
    }

    public User save(User user) {
        user.setPassword(passwordEncoder.encode(user.getPassword()));
        return userRepository.save(user);
    }

    @PostConstruct
    public void init(){
        User autumn = userRepository.findByUsername("autumn");
        if(autumn == null){
            User user = new User();
            user.setUsername("autumn");
            user.setPassword("pass");
            System.out.println(this.save(user));
        }
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userRepository.findByUsername(username);
        return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), getAuthorities());
    }

    private Collection<? extends GrantedAuthority> getAuthorities() {
        return Arrays.asList(new SimpleGrantedAuthority("ROLE_USER"));
    }
}

 


 

이제 준비가 끝났다. 어플리케이션을 실행시켜 보자.

실행 시 autumn 이라는 유저가 생성되는 것을 볼 수 있다.

 

테스트에는 PostMan 을 활용했다.

 

 

먼저 token 을 얻기 위해 요청을 보내보자. token 을 얻기 위해서는 client id, secret 정보를 함께 요청해야 한다. 위에서 inMemory 로 설정해두었던 정보를 입력하자.

다음은 요청 Body 에 User 의 인증정보를 넣어준다. (자원 소유자를 인증하기 위해 구글, 페이스북 등에 로그인하는 과정이 될 것이다.)

 

 

curl -X POST \
  http://localhost:8081/oauth/token \
  -H 'Authorization: Basic aWFtY2xpZW50OmlhbXNlY3JldA==' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'username=autumn&password=pass&grant_type=password'

 

한번의 요청에 클라이언트, 자원 소유자의 정보를 모두 입력했지만, 이는 password 방식의 grant type 을 사용한 것이다. authorization code 방식도 지원하도록 설정해놓았으므로, 따로 요청하는 것도 가능하다.

요청을 보내면 다음과 같이 토큰 정보를 얻을 수 있다.

 

 

 

이번에는 얻어낸 토큰을 이용해 자원에 접근해보자.

아래처럼 요청에 따라 자원을 조회할 수 있다.

curl -X GET \
  http://localhost:8081/users/user \
  -H 'Authorization: Bearer df1c4a87-863d-4056-b5a8-fb272e1c08b1'

 

간단한 예제를 통해 OAuth 인증 서버와 자원 서버를 구현해 보았다.

 

 

참고URL 공식문서 백기선님 유튜브