반응형

Open ID Connect 에 대해서 공부하면서 이것저것 찾아본것을 정리해 보았다. 


1. OpenID Connect 란? 


- OpenID Connect 는 Oauth 2.0을 확장해서 개발 되었다. 

OpenID Connect 는 openid라는 scope 값을 포함해서 Authorization Request를 보내며 인증(Authentication) 에 대한 정보는 ID Token 이라고 불리는 JSON Web Token(JWT) 을 리턴해준다.  (scope에 openid 를 무조건 포함해야 하는지는 좀 헷갈린다.. )

OpenID Provider (OP) : End-User 를 인증하고 인증이벤트 및 End-User에 대한 당사지에게 클레임을 제공할수 있는 Oauth 2.0 인증서버 (원문 : OAuth 2.0 Authorization Server that is capable of Authenticating the End-User and providing Claims to a Relying Party about the Authentication event and the End-User. )



2. ID Token

- ID Token 은 End-User의 인증에 대한 Claims 를 담고 있는 암호화 된 토큰이다. ID Token 은 JSON Web Token(JWT) 로 표현된다.


타입 

 필수여부

 설명

 iss

 Required

 ID Token  발급에 대한 고유 식별값. host, port 등이 포함되기도 한다.

 sub

 Required

 End-User 에 대한 고유한 값

 aud

 Required

 ID Token 과 관련된 청중, client_id 와 관련이 있다.

 exp

 Required

 토큰 만료 시간

 iat

 Required

 JWT 를 발행한 시간

 auth_time

 Required/Optional

 End-User에 대한 인증이 일어난 시간.

 nonce

 

 

 acr

 Optional

 Authentication Context Class Reference. 

 amr

 Optional

 Authentication Method Reference. 

 azp

 Optional

 Authorized Party.

 

Json Object 예시

  {

   "iss": "https://server.example.com",

   "sub": "24400320",

   "aud": "s6BhdRkqt3",

   "nonce": "n-0S6_WzA2Mj",

   "exp": 1311281970,

   "iat": 1311280970,

   "auth_time": 1311280969,

   "acr": "urn:mace:incommon:iap:silver"

  }



3. OpenID Connect Authentication Flow

 

OepnID Connect Authentication 흐름에는 3가지 종류가 있다. 


  1. Authorization Code Flow
  2. Implicit Flow
  3. Hybrid Flow

우선 이 흐름을 설명하기 전에 아래 표를 먼저 살펴보자. 

 Property 

 Authorization Code Flow

 Implicit Flow 

 Hybrid Flow 

 All tokens returned from Authorization Endpoint

 NO

 YES

 NO

 All tokens returned from Token Endpoint

 YES

 NO

 NO

 Tokens not revealed to User Agent

 YES

 NO

 NO

 Client can be authenticated

 YES

 NO

 YES

 Refresh Token possible

 YES

 NO

 YES

 Communication in one round trip

 NO

 YES

 NO

 Most communication server-to-server

 YES

 NO

 VARIES


무슨말인지 이해가 안될지도 모르지만 우선 내 생각이 이거 2가지만 기억하면 좋을것 같다. 


#1 All tokens returned from Authorization Endpoint : token 발급 위치에 대한 내용이다. Oauth 2.0 이나 OpenID Connect 나 정의 되어있는 URL 을 보면 끝에 authorize 로 끝나는 URL 이 있다. (아닐수도 있지만 보통 비슷하다) 이 URL 이 바로 Authorization Endpoint 이다. 먼저 인증이 되었는지 안되었는지 확일을 하는 URL 이고 이 URL 의 응답에 따라서 client 에서는 로그인 창으로 리다이렉트 되기도 한다. 이 값이 NO 라는 말은 Token 발급 Endpoint 가 아니라는 의미이다. 뒤에 또 설명을 하겠지만 간단히 설명하면 Authorization Code 방식은 Authorization Endpoint 에서 인증이 확인되면 Code를 발급하고 그 Code 를 가지고 Token Endpoint로 요청을 해서 Token을 발급받는다.

#2 All tokens returned from Token Endpoint : URL 이 accessToken 또는 token 이렇게 끝나는 URL 이다. Implicit Flow 같은 경우는 이미 Authorization Endpoint 에서 Token을 발급하기 때문에 Token Endpoint 가 필요 없다. 

4. Response_type


위에서 잠깐 언급한 3가지의 Flow Authorization Request response_type 값에 따라서 결정된다. response_type 필수 이다.

 

response_type value

Flow

code

Authorization Code

id_token

Implicit

id_token token

Implicit

code id_token

Hybrid

code token

Hybrid

code id_token token

Hybrid

 

각각의 흐름에 대한 특징들은 다음 글에서 다시 설명하겠다. 


참고자료

https://connect2id.com/learn/openid-connect

http://openid.net/specs/openid-connect-core-1_0.html#IDToken

https://medium.com/@darutk/diagrams-of-all-the-openid-connect-flows-6968e3990660


728x90
반응형
반응형

공부하면서 만든 Oauth Server에 대한 테스트를 Postman으로는 했는데 실제로 Client Code가 필요하게 되었다. 


2017/09/04 - [Development/Java] - [Spring Boot]Oauth server 적용해보기


테스트만 할 경우에는 Postman만 써도 상관이 없지만 실제 Client 가 호출을 하려면 code가 필요하다. 그래서 여기저기 구글링을 해가면서 찾아봤다. 


우선 Oauth Token을 발급 받기위한 코드가 필요하다. 


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
public String getOAuth2Token(String username, String password) {
        final String CLIENT_ID = "myclient";
        final String CLIENT_SECRET = "secret";
        final String GRANT_TYPE = "password";
        final String SERVER_URL = "http://localhost:" + port;
        final String API_OAUTH_TOKEN = "/oauth/token";
 
        String clientCredentials = CLIENT_ID + ":" + CLIENT_SECRET;
        String base64ClientCredentials = new String(Base64.encodeBase64(clientCredentials.getBytes()));
 
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        headers.add("Authorization""Basic " + base64ClientCredentials);
 
        MultiValueMap<StringString> parameters = new LinkedMultiValueMap<>();
        parameters.add("grant_type", GRANT_TYPE);
        parameters.add("username", username);
        parameters.add("password", password);
 
        HttpEntity<MultiValueMap<StringString>> request = new HttpEntity<>(parameters, headers);
 
        @SuppressWarnings("rawtypes")
        ResponseEntity<Map> response;
 
        URI uri = URI.create(SERVER_URL + API_OAUTH_TOKEN);
        response = restTemplate.postForEntity(uri, request, Map.class);
        return  (String) response.getBody().get("access_token");
    }
cs


위에 작성된 메소드를 보면 파라메터로 id, password를 받게 되어있다. 이건 실제 사용자의 id, password를 넣으면 된다. 그리고 그 이외에 필요한 정보들은 final 상수로 정의를 해놓았다. 다만 Testcase로 작성 하다 보니 port 는 Random하게 들어간다. 호출 형태는 Postman에서 작성했던 것을 그대로 Code로 옮긴걸로 생가하면 된다.  저렇게 호출을 하게되면 결과값으로 access_token값을 받게된다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Test
public void getOAuth2TokenTest(){
    Assert.assertNotNull(accessToken);
}
 
@Test
public void getTestWithAccessToken(){
    final String SERVER_URL = "http://localhost:" + port;
    final String API_URL = "/private?access_token={access_token}";
 
    ResponseEntity<String> responseEntity = restTemplate.getForEntity(
            SERVER_URL + API_URL,
            String.class,
            accessToken);
 
    Assert.assertEquals("private", responseEntity.getBody());
}
cs


이렇게 받은 access_token을 실제 API 에 넣어서 보내주면 전에 글에서 postman  으로 실행했던 것과 동일한 결과를 얻을 수 있다.

위에 작성된 소스 코드는 https://github.com/blusky10/study_spring 에 가서 OauthServiceTest.java파일을 보면 확인 할 수 있다.


728x90
반응형
반응형

Spring Boot Project에 OauthServer를 설정해보았다.

 

소스는 https://github.com/blusky10/study_spring 의 simple-spring-oauth 브랜치를 다운로드 받으면 된다.

 

 Client 는 private이라는 api에 접근하기 위해서 oauthserver 에 token 발급 요청을 한다.

발급된 token을 가지고 private이라는 api에 접근한다.

(Client 는 미리 등록되어있다고 가정한다. 따라서 Client를 Oauth서버에 등록하는 과정은 생략된다.)

 

1. 먼저 ResourceServer를 설정한다. ResourceServer는 Resource Owner의 정보를 가지고 있는 서버를 의미한다. 

 

1
2
3
4
5
6
7
8
9
10
11
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter{
 
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/").permitAll()
                .antMatchers("/private").authenticated();
    }
}
cs

 

"/" 주소로 오는 url은 모두 허용하게 되지만 "/private"으로 접근 되는 url은 인증이 필요하다. 

 

2. 두번째로 Authserver를 설정한다. 

 

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
@Configuration
@EnableAuthorizationServer
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {
 
    @Autowired
    private AuthenticationManager authenticationManager;
 
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.checkTokenAccess("isAuthenticated()");
    }
 
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory().withClient("myclient")
                .authorizedGrantTypes("client_credentials""password")
                .authorities("ROLE_CLIENT""ROLE_TRUSTED_CLIENT")
                .scopes("read""write""trust")
                .resourceIds("oauth2-resource")
                .accessTokenValiditySeconds(500)
                .secret("secret");
 
    }
 
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(authenticationManager);
    }
}
cs

 

중간에 보이는 client를 설정하는 부분을 보면 "myclient"는 client의 이름을 나타낸다. 그리고 secret은 client에게 발급되는 비밀번호이다. Facebook 과 같은 곳에서 인증을 하게 되면 client를 따로 등록하게 되는데 이때 등록을 하게 되면 Client 고유의 비밀번호를 발급받게 된다. 이 번호는 절대로 노출되어서는 안된다. 지금 내가 만드는 서버는 myclient라는 client가 등록되어있고 그 client 에게 발급된 비밀번호는 secret이라고 생각하면 된다.

접근할수 있는 client의 role은 "client" 이다.

 

 

 

3. AuthenticationManager를 이용해서 userDetailService를 재정의 해준다. 

 

1
2
3
4
5
6
7
8
9
10
    @Autowired
    public void authenticationManager(AuthenticationManagerBuilder builder, AccountService accountService) throws Exception{
 
        builder.userDetailsService(new UserDetailsService() {
            @Override
            public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
                return new CustomUserDetails(accountService.get(username));
            }
        });
    }
cs

 

 

 

 

 

 

 

여기에 Parameter로 받는 AccoutService는 사용자를 조회하는 서비스이다. 그런데 여기에서 UserDetailServiced의 loadUserByUsername 메소드는 UserDetails 를 리턴해야 하기 때문에 UserDetails를 재정의 해줘야 한다. 그게 바로 CustomUserDetails 이다. accountService.get(username) 하면 Account 객체를 리턴해주고 그 Account 객체가 가지고 있는 정보를 UserDatails 에 셋팅해준다.

 

4. CustomUserDetails

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class CustomUserDetails implements UserDetails {
 
    private String username;
    private String password;
    Collection<extends GrantedAuthority> authorities;
 
    public CustomUserDetails(Account account) {
        this.username = account.getLoingId();
        this.password = account.getPassword();
 
        List<GrantedAuthority> authorityList = new ArrayList<>();
        for (Role role : account.getRoles()){
            authorityList.add(new SimpleGrantedAuthority(role.getName().toUpperCase()));
        }
 
        this.authorities = authorityList;
    }
    // Getter, Setter 생략
}
cs

 

Custom UserDetails 생성자에서 받은 Account 객체로 username, password, Role 정보를 설정해준다. 

 

이제 Test를 해보자.

 

인증이 필요없는 URL

 

인증이 필요한 URL

 

인증이 필요없는 URL 은 상관 없지만 /private으로 호출을 하면 error 가 나온다. 인증되지 않은 User가 접근을 했기 때문이다.

 

 

/oauth/token URL 로 인증토큰 발급 요청을 한다. 이때 필요한 것이 clientid 와 secret 이다. 내가 위에서 설정한 client id 와 secret을 써준다.

 

 

그리고 Body 에는 grant_type, username, password 를 넣어준다. 

 

 

request를 보내면 이렇게 access_token을 발급 받을 수 있다. 

 

 

이제 발급 받은 Access_token을 private url 뒤에 넣어서 보내면 위에 그림처럼 private이라는 메세지를 볼수 있다. 

 

 

간단한 예제를 만들어 봤다. 좀더 자세한 내용을 보려면 좀더 공부를 해야하고 이론적인 부분도 상당히 많이 알아야 할것이다. 

 

이예제는 아래 Youtube 동영상을 보면서 따라서 만들어본 예제이다. 

 

 

728x90
반응형

+ Recent posts