본문 바로가기

BackEnd/Spring, SpringBoot

[SpringBoot] Oauth2.0 jwt token 구현

resoucre 서버와 authorization 서버를 함께 구성하였음

주요 Dependencies

- lombok
- spring-boot-starter-security
- spring-security-oauth2
- spring-boot-starter-data-jpa
- spring-boot-starter-web
- mysql

프로젝트 구조

OauthApplication - 기본 springboot application
AuthorizationServiceConfigurerAdapterImpl - Authorizaion Server 구현
ResourceServerConfigurerAdapterImpl - Resourcet Server 구현
Member - 서비스할 객체
UserRepository - api service

Application 설정들

application.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#MYSQL
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/study_db?autoReconnect=true&useSSL=false
    driverClassName: com.mysql.jdbc.Driver
    username: root
    password: 1234
  jpa:
    show-sql: true
#security 설정
security:
  user:
    name: user
    password: test
  oauth2:
    resource:
      jwt:
        key-uri: http://localhost:9093/oauth/token_key
    client:
      client-id: foo
      client-secret: bar
    authorization:
      token-key-access: isAuthenticated()
#로깅레벨
logging:
  level:
    ROOT: error
#포트
server:
  port: 9093
 
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4ftext-decoration:none">Colored by Color Scripter
 


mysql 지정, spring security에서 client와 resoucre-owner정보 설정,
oauth의 토큰을 요청하는 url을 지정해주고(oauth/token_key는 기본값이다)
jwt 생성에 사용하는 키는 설정하지 않아 현재 자동으로 생성된다

API 구현

Member.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Entity
@Getter
@Setter
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class Member {
 
    @Id
    @GeneratedValue
    private Long id;
    private String name;
    private String username;
    private String remark;
    
    @Builder
    public Member(String name, String username, String remark) {
        this.name = name;
        this.username = username;
        this.remark = remark;
    }
}
 
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4ftext-decoration:none">Colored by Color Scripter
 
 
 

jpa 형식으로 id, name, username, remark 필드를 갖는 Member 도메인 생성
@Id는 해당 프로퍼티가 테이블의 primary key 역할을 한다는것을 지정
@GeneratedVlaue는 id값을 DB의 identity 컬럼, 시퀀스, 테이블등을 이용하여 자동으로 생성해주도록 지정한다.
인자가없는 생성자를 private로 선언


MemberRepository.java

1
2
3
4
@RepositoryRestResource
public interface MemberRepository extends PagingAndSortingRepository<Member, Long> {}
 
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4ftext-decoration:none">Colored by Color Scripter
 

dependency에서 선언한 spring-data-rest에서 제공하는 기능으로
@RepositoryRestResource 어노테이션을 위와같은 방법으로 사용하는것 만으로
내부적으로 Rest API를 만들어 준다(여러종류의 요청을 생성)

여기까지의 구현만으로
localhost:{port}/members      (전체 member조회)  
localhost:{port}/members/2   (id가 2인 member조회)
등의 요청을 받아 응답할 수 있는 api서버가 만들어진다

ResourceServer 설정

ResourceServerConfigurerAdapterImpl.java

1
2
3
4
5
6
7
8
9
10
11
12
@EnableResourceServer // 자원서버 설정
@Configuration
public class ResourceServerConfigurerAdapterImpl extends ResourceServerConfigurerAdapter {
 
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
        .antMatchers("/members""/members/**").access("#oauth2.hasScope('read')")
        .anyRequest().authenticated();
    }
}
 
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4ftext-decoration:none">Colored by Color Scripter
 
 
 
ResourceServerConfigurerAdapter를 상속받아 configure를 override하여 자원서버에대한 설정을 수행
localhost:{port}/members와 이후 url 요청시 권한을 가져야 가능하도록 하고(token이 있고, 해당 스코프를 가져야함)

 
 

AuthorizationServer 설정

AuthorizationServiceConfigurerAdapterImpl.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@EnableAuthorizationServer
@Configuration
public class AuthorizationServiceConfigurerAdapterImpl extends OAuth2AuthorizationServerConfiguration{
    
    private final JwtAccessTokenConverter jwtAccessTokenConverter;
    
    public AuthorizationServiceConfigurerAdapterImpl(BaseClientDetails details,
            AuthenticationManager authenticationManager, ObjectProvider<TokenStore> tokenStore,
            ObjectProvider<AccessTokenConverter> tokenConverter, AuthorizationServerProperties properties) {
        super(details, authenticationManager, tokenStore, tokenConverter, properties);
        this.jwtAccessTokenConverter = new JwtAccessTokenConverter();
    }
    
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints)
            throws Exception {
        super.configure(endpoints);
        endpoints.accessTokenConverter(jwtAccessTokenConverter);
    }
 
}
 
@Configuration
class OAuth2Configuration {
 
    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }
 
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        return new JwtAccessTokenConverter();
    }
 
}
 
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4ftext-decoration:none">Colored by Color Scripter
http://colorscripter.com/info#e" target="_blank" style="text-decoration:none;color:white">cs
 


OAuth2AuthorizationServerConfiguration를 상속받아 AuthorizionServer를 구현한다
상속시 AuthorizationServiceConfigurerAdapterImpl 메소드를 구현해야 하는데 기본설정에
jwtAccessTokenConverter만 추가로 지정하였다
Oauth2Configuration에서는 토큰을 jwt로 사용하기위해
configuration을 추가하여 tokenstore와 Converter bean을 만들어줬다

테스트

1. api를 통해 조회할 자료 생성

지정해준 mysql에 member테이블을 만들고 조회할 값들을 미리 넣어준다(@Entity로 선언된 Member Class와연동)

2. access token 없이 조회

token이 없을시 MemberRepository를 이용해 구현한 api에 조회할 수 없다

3. access token 발급 후 조회

  application.yml에 설정해둔 client_id, security의 기본 user, password등의 값을 이용해 토큰을 발급받는다

권한설정부분과 body에 필요한 파라미터를 설정후 /oauth/token url에 post로 요청하여 토큰을 발급받는다

발급받은 토큰을 확인해보자

jwt.io 사이트를 이용해 토큰을 Decode하여 확인 결과 정상적으로 발급되었음을 알수있다

발급된토큰을 포함하여 /members를 다시 요청해보면

정상적으로 조회하려던 api의 리턴을 받을 수 있다

해당프로젝트 소스 : https://github.com/m0oogi/oauth2_msa

 

m0oogi/oauth2_msa

Contribute to m0oogi/oauth2_msa development by creating an account on GitHub.

github.com