반응형

공부하면서 만든 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
반응형
반응형

 

구글에서 대용량 아키텍쳐 설계 대한 2일짜리 워크샵을 진행한다고 해서 신청을 했다. 신청한 사람 중에 일부만 참석할수 있는 워크샵 이어서 선정이 안될까 조마조마 했다. 워크샵 내용은 아래 표처럼 이틀동안 강의도 듣고 실습도 해볼수 있는 일정이었다. 교육은 "대용량 아키텍처와 성능 튜닝" 저자인 조대협님께서 직접 해주셨다.

 

우선 기술적인 자세한 내용들은 워낙 방대하고 적을수 없어서 여기에다 일일이 적지는 않겠다.  기술적인 내용보다는 첫날 들었던 "아키텍트"는 어떻게 해야 하는지에 대해서 몇가지 인상깊었던 것에 대해 적어보려고 한다. 


아키텍트는 전달을 잘해야 한다. 

아키텍트는 설계를 하는 사람이다. 아니, 설계만 하는 사람이라고 생각을 했었다. 그런데 그것도 중요하지만 다른 중요한것이 있었다. 바로 커뮤니케이션이다. 아키텍트는 설계를 한후 개발팀이 설계한 것을 보고 개발을 할수 있도록 전달을 해야 한다. 그렇기 때문에 개발팀과의 커뮤니케이션이 정말 중요하다. 보통 설계 문서를 주지만 이건 한계가 있다. 왜냐? 개발자들은 설계문서를 잘 안본다는 안타깝지만 사실에 가까운 팩트이다. 결국 찾아다니면서 설명을 해줘야 한다. 그리고 말보다는 그림이 더 설명을 하기도, 이해를 하기도 쉽다.



위 사진을 한번 살펴보자. 인스타그램 같은 앱에서 사진을 올렸을 경우 사진을 저장하는 절차를 설계한 그림이다. 모바일컨트롤러(MC) 에서 API Gateway를 거쳐서 인증을 받은후 API 서버를 거쳐서 사진을 저장하게 된다. 각각의 흐름에는 번호가 붙어 있다. 각각의 트랜잭션이 일어날때 마다 필요한 DATA 와 받는 Data도 표현을 해준다. 각각의 번호는 하나의 RestAPI 로 설계가 될수 있다. 이런 형태로 하나 하나 다 정의를 해줘야 한다고 한다.(아키텍트는 정말 힘든 직업인것 같다 ㅡㅡ;;)  


그리고 커뮤니케이션과 더불어서 개발팀의 능력치 파악도 중요한 포인트이다. 개발팀의 능력에 따라서 일의 양이나 일정을 결정 할 수 있다. 그리고 누구한테 어떤 일을 맡겨야하는것 까지 파악이 된다면 정말 도움이 많이된다. 이런 것들을 잘 파악하기 위해서는 같이 술을 먹어야 한다고...


아키텍처는 상황에 따라서 변한다.

설계 변경이야 늘 있는 일이라서 새삼 놀랍지도 않은 일이다. 아침에 받았던 설계를 점심 이후에 다시 가져와서 수정해야 된다고 들은적이 정말 많다. 그런데 하나의 product를 유지하기 위해 환경이 변화해서 전체 아키텍처가 변경이 된다는 말에서 놀랐다. 

처음 product  를 만들 때에는 정해진 feature를 만들어서 제공하기 위한 제품 위주의 아키텍처 였다고 가정해 보자. 제품을 출시하고 나면 다른 경쟁사들에게 뒤쳐지지 않기 위해서 빠르게 개발해서 빠르게 대응할 수 있는 아키텍처가 필요하다고 한다. 이런 형태로 처음 구상했던 아키텍처를 끝까지 가지고 가는게 아니라 변화하는 상황에 빨리 대응하기 위한 형태로 아키텍처를 그때 그때 알맞게 변경을 해야 한다고 한다. 생각해 보면 하루에도 수십개, 수천개씩 쏟아져 나오는 product들 사이에서 살아 남으려면 변화는 필수인것 같다. 그리고 아키텍처도 예외는 아니라는 것이다. 


이틀간의 짧은 시간동안 많은 내용을 배웠다. 모든 내용을 다 이해해서 내 머릿속에 담을수 있었으면 더 좋았겠지만 아직은 좀 무리였다. 하지만 앞으로 공부를 더 해나가면 발전할 수 있을거라는 생각은 한다. 오랜만에 다녀온 워크샵에서 좋은 지식과 자극을 같이 얻을수 있어서 너무 좋았다. 


728x90
반응형

'Development > Tech&Seminar' 카테고리의 다른 글

TCC가 뭐지???  (0) 2018.09.07
Openssl로 SSL 을 위한 인증서 발급하기 (HTTPS)  (2) 2018.08.22
#2 OpenID Connect Flow  (0) 2018.08.14
#1 Open ID Connect 가 뭐야???  (0) 2018.08.07
Google Cloud Study Jams 후기  (0) 2018.05.15
반응형

아침 저녁으로 출퇴근 하면서 생활코딩의 "지옥에서 온 Git" 을 듣고 있는데 재미있는 내용이 있었다.


https://www.youtube.com/playlist?list=PLuHgQVnccGMA8iwZwrGyNXCGy2LAAsTXk


Git 을 사용하면 프로젝트 폴더 안에 .git 이라는 폴더가 생긴다. 생긴다라고만 알고 있었고 여기에 뭔가 history 가 저장되겠거니 라는 추측만 했었다. 실제로 열어본적도 없어서 뭐가 들어있는지도 몰랐다. 그런데 강의 내용중에 Git 명령어를 실행될 때에 어떻게 그 History를 저장하고 추적하는지에 대해서 설명을 해주었다. 


Git 을 사용할 경우 Add 명령을 이용해서 새로 생성된 파일을 stage area 영역으로 추가할수 있다. 이때에 .git 디렉토리 안에 objects 라는 폴더에 새로운 폴더가 생성된다. 


  


왼쪽 화면과 오른쪽 화면을 보자. 왼쪽화면은 잘은 안보이겠지만 현재 내가 작업하고 있는 WORKSPACE 에서 파일을 수정하고 "git status" 명령어를 실행했을 때의 모습이다. 빨갛게 파일 이름이 보이고 modified 라고 써있다. 그리고 오른쪽은 gistory 라는 git log 분석하는 툴이다. 오픈소스 툴이며 https://github.com/egoing/gistory 에 가면 설치 방법을 확인 할 수 있다. 내가 지금가지 commit 한 내용이 오른쪽 처럼 표현되고 있다. 


 


이제 좀전의 build.gradle 파일을 ADD 명령어를 이용해서 ADD를 했다. 어떤 변화가 일어났는지 확인을 해봤다. 오른족에 보면 맨 위에 2개 파일에 just now라고 써있다. 그 2개가 변경이 됐다는 의미이다.



우선 objects를 선택해서 보니 내가 변경한 파일의 내용이 보여진다. [blob]a97..... 라는 key로 되어있다. 그리고 보면 앞에 a9는 objects 폴더의 하위 디렉토리 명으로 되어있다. 





이번에는 좀전에 그 파일을 commit 을 해보았다. 그랬더니 위에 화면처럼 많은 파일들이 변경이 되었다. 그중 몇개 파일을 살펴 보았다.



이 파일에는 [commit] 이라는 타입의 object 이다. 안에 내용을 보면 tree 라는 것이 있고 parent 라는 것이 있다. 그리고 commit 을 한 사람 정보가 나오고 마지막에는 commit 할때 작성했던 message 가 있다. tree 는 현재 commit 한 시점의 모든 file 들이 tree 형태로 존재를 하고 있다. 그리고 parent 는 이전 commit 에 대한 정보들을 담고 있는 object 이다. parent 도 tree 처럼 구성이 되어있을 것이다. 이처럼 commit 을 하게 되면 현재의 파일들을 현재 모습 그대로 snapshot을 찍게 된다. 



그리고 tree 를 클릭해보면 이렇게 파일들을 볼수 있다. 파일의 형태는 단일 파일일 경우 blob 으로 표시 되어있고 또 폴더 일 경우 tree 로 표시가 되어있다. 


결론적으로 Git 이라는 툴은 파일이 변경되고 추가되고 한 작업의 모든 부분을 object 화 해서 관리를 하고 있다. 그 object 가 단일파일일 경우도 있고 tree 형태를 할 경우도 있다. 그리고 commit 자체도 하나의 object 로 관리가 되고 있다. 이런 형태로 파일들이 관리가 되고 있었다니 정말 놀라웠다. 


Git을 쓰고 있긴 하지만 부족한것 같아서 듣기 시작한 강의 였는데 정말 듣기를 잘했다는 생각이 들었다. 아직 다 듣지 않아서 앞으로 들을 내용들이 더 기대가 된다.


728x90
반응형
반응형

Oauth 2.0 에 대해서 공부를 하다가 용어에 대한 명확한 이해가 필요해서 정리를 했다.


Access Token : 보호된 리소스에 일정 기간동안 접근할 수 있는 권한을 가진 문자열

- Scope : Token 소유자가 접근할수 있는 보호된 리소스 셋


Client : Resource를 이용하려고 하는 Web 이나 App. 

Resource Sever : 실제 정보를 가지고 있는 대상. 

Resource Owner : Resource 에 대한 소유자.


Access Token 을 얻는 절차는 아래처럼 설명할 수 있다.


Resource Owner 

 Client

 Resource Server

 1. Client 에게 정보 요청


 

 

 2. Resource Server 에 있는 Resource Owner의 정보를 접근 할수 있는 권한 요청

 

3. Resource Server 에 로그인

(Client 에서 Resource 서버에 로그인할수 있는 페이지를 바로 연결해줌)

 


 4. Resource Server에 있는 Resource Owner의 특정 정보에 Client가 접근하는것을 허용하는지 여부 확인 (Yes/No)

 

 

 

 

 5. Resource Owner 가 Client의 접근을 허용할 경우 Client 에게 Code를 보냄

 

 6. Code를 받아서 Client_id, secret를 code와 함께 Resource Server로 보냄

 
  

 7. Client가 보낸 client_id, secret, code를 확인한 후 access tockent을 발급

 

 8. 발급받은 access token으로 Resource Server 에 있는 owner 의 정보에 접근.

 


위 표에서 설명한 것은 Access Token을 얻는 방법중 한가지에 대해서 설명한 것이다. 꼭 이것과 동일하지 않을 수 있다. 그렇지만 저런 한가지 흐름을 알아 두면 다른것을 이해하기에 도움이 될거라 생각이 된다. 


728x90
반응형
반응형

Spring Security 를 적용하는 내용을 처음부터 차근차근 정리를 해보려고 한다. 

목표는 Spring Security 를 공부하면서 각각의 기능들을 적용해보는것이다. 진행하다보면 Spring Security 뿐만 아니라 다른 내용들도 점점 추가될것 같다. 다 만들고 나서는 git에 소스를 공유할 생각이다. ^^;; 언제가 될지는 잘 모르겠다.


환경 : java 1.8, Spring Boot 1.5.3 Release, Maria DB, JPA, gradle


build.gradle

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
buildscript {
    ext {
        springBootVersion = '1.5.3.RELEASE'
    }
    repositories {
        jcenter()
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}
 
apply plugin: 'java'
apply plugin: 'org.springframework.boot'
 
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8
 
repositories {
    jcenter()
    mavenCentral()
}
 
 
dependencies {
    compile('org.springframework.boot:spring-boot-starter-web')
    compile('org.springframework.boot:spring-boot-starter-data-jpa')
    compile('org.springframework.boot:spring-boot-starter-security')
 
    testCompile('org.springframework.boot:spring-boot-starter-test')
 
    runtime ('org.mariadb.jdbc:mariadb-java-client')
}
cs



아직 초반이어서 라이브러리가 몇개 없다. spring-boot 에서 사용하는 jpa, security, test, web 라이브러리가 끝이다. 그리고 DB를 사용하기 위한 mariadb client까지만 추가되어있다.


도메인.

Account.java

1
2
3
4
5
6
7
8
9
10
11
12
13
@Entity
public class Account {
    @Id
    private String loingId;
 
    private String username;
 
    private String password;
 
    private String email;
 
    private boolean enabled;
}
cs


Account 도메인이 있다. 만들기는 더 여러개 만들어놓았는데 우선은 이것만 필요해서 적었다. 위 Account.java 소스에는 getter/setter 메소드는 삭제하고 올렸다. (너무 길어서)


다음은 Account 관련 Service와 Repository 이다. 


AccountRepository.java

1
2
public interface AccountRepository extends JpaRepository<Account, String> {
}
cs


AccountService.java

1
2
3
4
public interface AccountService {
    Account get(String loginId);
}
 
cs


AccountServiceImpl.java

1
2
3
4
5
6
7
8
9
10
11
@Service
public class AccountServiceImpl implements AccountService {
 
    @Autowired
    private AccountRepository accountRepository;
 
    @Override
    public Account get(String loginId) {
        return accountRepository.findOne(loginId);
    }
}
cs


Repository를 다이렉트로 호출해도 되긴 하지만 아무래도 구조상 service 레이어가 있는게 맞다고 판단해서 repository는 Service 에서 호출하도록 한번 감쌌다. 지금은 간단히 호출만 하지만 나중에는 로직도 들어갈것이 예상된다.


CustomUserDetailService.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Service
public class CustomUserDetailService implements UserDetailsService{
 
    @Autowired
    private AccountService accountService;
 
    @Override
    public UserDetails loadUserByUsername(String loginId) throws UsernameNotFoundException {
 
        Account account = accountService.get(loginId);
 
        List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
 
        User user = new User(account.getLoingId(), account.getPassword(), grantedAuthorities);
 
        return user;
    }
}
cs


UserDetailsService에 있는 loadUserByUsername 메소드를 제정의 해서 UserDetails 객체를 정의해준다. UserDetails는 사용자의 인증정보를 담고있다. 위에서 Account Service에서 loginId로 정보를 조회해서 가져온 id, password, 권한(아직 미구현으로 객체만 생성했다) 정보를 user로 생성해주고 있다.




ResourceSecurityConfiguration.java

1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
@EnableGlobalAuthentication
public class ResourceSecurityConfiguration extends WebSecurityConfigurerAdapter {
 
    @Autowired
    private CustomUserDetailService customUserDetailService;
 
    public void init(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(this.customUserDetailService);
    }
}
 
cs


WebSecurityConfigurerAdapter에 있는 init 메소드를 사용해서 AuthenticationManangerBuilder에 userDetailService를 내가 새로 만든 CustomUserDetailService를 사용하겠다고 설정해준다.


StudySpringApplication.java

1
2
3
4
5
6
7
8
9
10
@SpringBootApplication
public class StudySpringApplication {
 
    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(StudySpringApplication.class);
 
        app.run(args);
    }
}
 
cs


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
server:
  port: 9000
 
spring:
  datasource:
    driver-class-name: org.mariadb.jdbc.Driver
    url: jdbc:mariadb://localhost:3306/holmes
    username: root
    validation-query: select 1
    test-while-idle: true
    time-between-eviction-runs-millis: 300000
    test-on-borrow: false
 
  jpa:
    database: mysql
    show-sql: true
    hibernate:
      ddl-auto: update
    properties:
      hibernate:
        format_sql: true
        dialect: org.hibernate.dialect.MySQLDialect
        default_schema: holmes
cs


이제 어플리케이션을 실행해보면 로그인 페이지가 나온다. 





위에 소스와 약간 차이가 있을수 있지만 아래 Git repository 에 가면 소스를 확인할 수 있다. 


https://github.com/blusky10/study_spring


728x90
반응형
반응형
AWS 에 가상서버를 하나 만들면 이 서버를 시작하고 정지 할 때마다 공인 IP 주소가 변경된다. 그래서 AWS 는 Elastic IP 라는 고정 공인 IP 주소를 할당해 주는 서비스를 제공해주고 있다. 


EC2 서비스에 보면 왼쪽에 Elastic IPs 라는 메뉴가 보인다. 



메뉴에 들어가보면 위와 같은 화면이 나온다. Allocate new address를 클릭하면 아래와 같이 Elastic IP가 할당된다.



IP가 할당되었으니 이제 가상서버하고 연결을 해주면 된다.





Actions 메뉴에 있는 Associate address를 선택한다. 




Instance 항목에 가상 서버의 인스턴스 ID를 입력하면 된다. 콤보박스로 선택하게 되어있다. 



그러면 이렇게 연결이 완료된다. 


가상 서버 만드는 것만큼이나 아주 간단하고 쉽다. 



728x90
반응형

'Development > AWS' 카테고리의 다른 글

네트워크 관련 내용들  (0) 2023.01.13
[AWS] EC2 Ubuntu 서버에 FTP 서버 설정  (0) 2018.04.01
[AWS]AWS 에 가상서버 만들기  (0) 2017.06.17
반응형

기존에 사용하고 있던 넷북을 서버로 켜놓을까 하다가 항상 켜놓을 수 없고 매일 사용할때마다 코드 꼽고 하는 작업이 번거로웠다. 그리고 SSH로 접속을 해보았는데 좀 느려서 써먹기는 좀 힘들어 보였다. 그래서 AWS 에 가상 서버를 한번 만들어서 써보자는 생각이 들었다. 어차피 프리티어 기간에는 어느정도 수준 까지는 공짜로 사용할수 있으니 딱 좋았다. 그리고 예전에 AWS 세미나 갔을때 받았던 크래딧도 있어서 이 기회에서 써봐야 겠다고 마음먹었다.




먼저 아마존 Console Home 에서 EC2 를 클릭하고 나오는 화면에서 Create Instance 밑에 있는 Launch Instance 를 클릭한다. 



여기에서는 AWS 에서 만들 가상 서버 종류를 선택할 수 있다. 서버 아래에 보면 "Free Tier Eligible" 이라는 단어가 있는 서버가 프리티어에서 제공되는 서버인것 같다. 참고로 Virtual Appliance 는 하이퍼 바이저에서 실행할수 있는 OS와 기타 소트프웨어를 포함하는 이미지 이다. 그리고 위에 보이는 AMI(Amazon Machine Image)는 EC2 서비스를 사용하기 위핸 Virtual Appliance 다.



Instance Type은 가상 서버의 크기를 결정하는 것이다. 여기도 역시 "Free Tier Eligible" 이라고 써있는것을 선택한다.



따로 설정을 변경할 일이 없기 때문에 모든 설정은 Default 로 되어있는것으로 하고 바로 Next를 누른다.



가상 서버에 저장장치를 추가하는 화면이다. 역시 Default로 하고 Next를 누른다.


   


여기는 태그를 추가하는 화면이다. 키-값의 쌍으로 이루어져 있다. 적당한 값을 넣고 Next를 누른다.





방화벽은 SSH로 접근을 허용할 수 있도록 기본 구성을 하고 "Review and Launch"를 클릭한다.



지금까지 설정사항을 보여준다. Launch를 클릭한다.



가상 서버의 키를 선택하는 화면이다. 나는 미리 키를 만들어둬서 기존에 만들어놨던 키를 선택했다. 만약 만들지 않았다면 EC2 서비스 화면에서 "Key Paris" 메뉴를 선택해서 만들면된다. 가상 서버를 로그인 하려면 키가 필요하며 이 키는 비밀번호를 대신해서 사용한다. "Launch Instances"를 클릭하면 모든게 마무리 된다. 



잘 동작 하는지 확인해보려면 맨 처음 보았던 화면(EC2서비스 화면) 에서 Instances를 클릭하면 현재 서버가 어떤 상태인지 확인 할 수 있다.

지금 글로 적기는 했지만 실제로 해보면 정말 간단하다. 윈도우 설치보다도 더 간단하다. 클릭클릭 몇번만 하면 내가 사용할 수 있는 서버가 짠~ 하고 만들어진다. 아직 모르는게 많지만 앞으로 써보면서 많이 배워야겠다.


728x90
반응형
반응형

테스트케이스를 만들어서 작업을 하면 소스코드가 수정될 경우 코드를 테스트 해보기가 참 수월하다. 그런데 이 테스트 케이스 작성하는게 생각보다 만만치는 않다. 

실제 DB 를 읽어서 테스트를 해야 하는지. 아니면 Mock 객체를 정의를 해서 사용을 해야 하는지. 실제 DB 를 사용할 경우 저장된 data 가 변경이 되어서 구현했을 당시 테스트 케이스는 Pass였지만 나중에 빌드 시점에 테스트 케이스가 실행될 경우에 Fail 이 나면 어떻게 할것인지. 

생각해보면 그냥 서비스 구현해서 화면 띄우고 버튼 눌러서 테스트 하는것이 더 편할지도 모른다는 생각이 들기도 한다. 

작성할 때마나 서비스 테스트,  repository테스트, 컨트롤러 테스트에 대해서 구글링 하면서 작성을 하다보니 뭔가 남는게 없는것 같아서 샘플을 한번 만들어보기로 했다. 

 

최근에 필요하기도 했고 나중에 또 써먹을 일도 있을것 같아서 Controller 테스트 케이스를 작성한 것을 공유해 본다.

 

각각의 구성은 아래와 같이 되어있다. 

(java : 1.8, SpringBoot : 1.5.3)

 

Book.java

@Entity
public class Book {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long bookId;

    private String title;

    // Getter & Setter 생략
}

 

BookRepository.java

public interface BookRepository extends JpaRepository<Book, Long>{}

BookService.java

public interface BookService {
    Book getBook(Long id);
}
 

BookServiceImpl.java

@Service
public class BookServiceImpl implements BookService {

    @Autowired
    private BookRepository bookRepository;

    @Override
    public Book getBook(Long id) {
        return bookRepository.findOne(id);
    }
}
 

BookController.java

@RestController
public class BookController {

    @Autowired
    private BookService bookService;

    @RequestMapping(value = "/book/{bookId}", method = RequestMethod.GET, produces = "application/json")
    public Book getBook(@PathVariable Long bookId){
        return bookService.getBook(bookId);
    }
}

/book/{bookId} 라는 url 로 request 를 보내면 bookId 에 맞는 Book 객체를 리턴해주면 되는 형태이다. 테스트 케이스 없이 테스트 하려면 톰캣으로 띄워놓고 실제로 화면에서 위에 정의한 서비스를 호출하는 컴포넌트를 클릭해서 정상 동작을 하는지 확인해봐야한다. 그러다가 소스에 글자라도 하나 틀리면 수정한다음에 다시 톰캣 재기동을 하는 번거로운 작업을 진행해야 한다. 

 

 

 

 

 

 

 

 

 

 

이런 번거로움을 피하기 위해 테스트 케이스를 작성해 보았다.

@RunWith(SpringRunner.class)
@SpringBootTest
public class BookControllerTest {

    private MockMvc mockMvc;

    @MockBean
    BookController bookController;

    @Before
    public void setup(){
        mockMvc = MockMvcBuilders.standaloneSetup(bookController).build();
    }


    @Test
    public void getBookTest() throws Exception {
        given(this.bookController.getBook(new Long(1)))
                .willReturn(new Book("Homes"));

        mockMvc.perform(get("/book/{bookId}", 1))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$['title']", containsString("Homes")))
                .andDo(print());
    }
}

BookController를  MockBean으로 정의를 해주었다. 그리고 BookController 의 getBook메소드에 파라메터가 1이 들어왔을 때 리턴 받는 결과를 미리 정의한다. (18~19 라인) 그리고 화면에서 요청하는 것처럼 Request를 수행해준다.  perform에 있는 파라메터를 보면 get 메소드를 호출하게 되며 파라메터로 1값을 넣어서 실행을 한다. OK 응답을 받게 되고 리턴 받는객체의 title이 "Homes"  인지 비교를 한다. 19라인에서 책 이름을 미리 Homes  로 정의 했기때문에 테스트는  Pass가 된다. 마지막에 andDo(print()) 는 실제 수행된 로그가 콘솔창을 통해 볼수 있도록 처리해 준것이다.

 

처음에 만들때는 좀 삽질을 하긴 했지만 만들고 보니 앞으로 자주 써먹을것 같다. 앞으로도 바쁘지만 테스트케이스를 만들면서 코드 작성을 하도록 해야겠다.

 

참고로 위 소스를 작성한 gradle.build 파일은 아래와 같다.

dependencies {
    compile('org.springframework.boot:spring-boot-starter-web')
    compile('org.springframework.boot:spring-boot-starter-data-jpa')

    runtime('group:com.h2database:h2');

    testCompile('group:com.jayway.jsonpath:json-path')

    testCompile('org.springframework.boot:spring-boot-starter-test')
}
 

 

728x90
반응형

'Development > Java' 카테고리의 다른 글

[OAuth] Oauth의 간략한 흐름.  (0) 2017.07.04
[Spring Security]간단 Spring Security  (0) 2017.06.27
[Spring]Jasypt 를 이용한 properties 암호화  (6) 2017.04.25
[SpringCloud]Spring Config..  (0) 2016.01.26
spring Cache  (0) 2015.12.05

+ Recent posts