Java – Dreaming for the Future 영원한 개발자를 향해서. 월, 13 1월 2025 13:44:09 +0000 ko-KR hourly 1 https://wordpress.org/?v=4.7 108384747 Spring-security를 활용한 JWToken 인증하기 /index.php/2020/08/31/web-bearer-authorization-with-jwt-and-spring-security/ Mon, 31 Aug 2020 01:44:40 +0000 /?p=782

Continue reading ‘Spring-security를 활용한 JWToken 인증하기’ »]]> 퍼블릭 환경에서 제공되는 API 서비스를 만들 때 가장 고민되는 부분은 보안이다. API가 제공하는 기능이 민감한 정보가 아니라면 개발자 입장에서 행복하다. 하지만 값어치가 나가는 유료 정보나 개인 정보 혹은 개인화 기반 정보가 제공되는 경우에는 고민이 깊어진다.

General Web Security

보안 정책을 웹 환경에서 구현하는 방법은 여러가지가 있을 수 있다. 보호 대상의 성격에 따라 다를 수 있고, 서비스 혹은 시스템의 연계성에 의해서 방법이 변경될 수 있다. 그럼에도 큰 맥락에서 2가지 기능으로 이 보안 정책은 구현된다.

Authentication

인증은 말 그대로 접근할려는 사용자 혹은 시스템이 맞는지 확인하는 절차다. 일반적으로 ID/Password를 이용한다. 요구되는 보안 사항이 높지 않은 경우, 이것만으로도 충분하다. 물론 이것보다 간단하게 생각되는 방식이 API Key 방식이다. 하지만 ID/Password나 API Key나 따지고 보면 같다. 개발자 관점에서 값을 하나를 사용할지 두개를 쓸지 차이뿐.

인증은 서비스에 접근한 존재가 누군지 알 수 있려준다. 여기에서 좀 더 복잡한 그 다음 프로세스를 원한다면 인증이 성공했다를 알려준다. 이 알려준 값은 권한(Authorization) 체크 용도로 활용될 수 있다. System vs. System 사이의 연동이라면 인증 처리만으로도 충분히 권한까지 함께 처리할 수 있다. 만약 사용자에 관련된 이슈라면?

Authorization

권한은 간단히 이야기하면, 인증을 요구하는 요청이 적합한지, 실행 권한이 적절히 부여되어 있는지 확인하는 절차다. Authorization이란 단어를 생각하면 “권한”을 먼저 생각하겠지… 하지만 현실에서는 올바르게 인증된 경우를 체크하는게 일반사다. 실제로 권한을 체크할려면, 권한 관리 시스템(모듈)을 통해 권한 클라이언트가 필요로하는 권한을 가지고 있는지 요청해야 한다. 권한 여부에 따라 True/False 결과만 확인하면 된다. 얼핏 이야기했지만 상당히 복잡하다. 복잡하다는 건 확인을 위한 Operation Cost가 많이 든다는 걸 의미한다. 그래서 이와 같은 권한 체크는 일반 사용자보다는 어드민 혹은 관리 시스템들에 한해 적용한다. 관리 시스템은 통상 내부망에서 동작하기 때문에 이런 복잡성을 굳이 가질 필요가 없다. 쉬운 방법을 충분히 찾을 수 있다.

결론적으로 일반 사용자, 그리고 대용량 사용자 대상 서비스의 경우에는 요청이 올바른 인증 정보를 포함하고 있는지만 확인하는 것만으로도 충분하다.

OAuth and JWT – JSON Web Token

이와 같은 인증 및 권한 관리 체계의 대표적인 모델이 OAuth 모델이다. 이를 구현하기 위해 적용된 기본 기술이 JWT이다. 예전에 JWT가 뭔지 어떻게 활용하면 되는지 정리한 글이 있으니, 더 궁금한 분이 있다면 참고한다. OAuth에 대해 안다고 주접떨기 보다는 아래 2개 링크를 참고하자.

  • https://ko.wikipedia.org/wiki/OAuth – OAuth wikipedia, 기본 개념정도는 이해 가능.
  • https://oauth.net/2/ – OAuth 2.0 standard

간단히 동작되는 Architecture를 머리 속에 그려보면 아래와 같다.

OAuth Authorization Architecture

간단히 그려보면 아래와 같은 일반 구조와 같다.

JWT를 만드는 역할을 Service에서 할 일은 아니지만, Authentication Service에서 만들어준 JWT 값을 가져오고, 올바른지 확인하는 역할은 Client Service의 역할이다. 헤더에서 이걸 가져오고 Public Key를 관리해서 이걸 검증하는 동작은 비슷한 패턴이다. 각 서비스에서 이를 구현하는 건 반복적인 코딩이 될 수 밖에 없다. 그래서 이걸 Spring-Security에서 좀 더 쉽게 할 수 있도록 지원한다.

 

Validating with Spring Security

자, 그래서 인증이 올바른지 체크하는 Spring Security 모듈을 처리해보자.

Maven setting

pom.xml

 

Configuration in Action

Configuration coding in the spring-boot project
이렇게 설정이 준비되면, spring 코딩을 마무리할 시점인갑다.

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    protected void configure(HttpSecurity http) throws Exception {
        http.cors().and().csrf().disable()
                .authorizeRequests().antMatchers(whitelistedUriPatterns()).permitAll().and()
                .authorizeRequests().anyRequest().authenticated().and()
                .oauth2ResourceServer().jwt();
    }

    private String[] whitelistedUriPatterns() {
        return new String[] {
                "/health",
                "/swagger*/**", "/webjars*/**", "/v2/api-docs"
        };
    }
}

이렇게 처리하면 된다.

주목할 점은 특정 URI들의 경우에는 Load balancing과 내부 테스트등을 위해서는 별도의 인증을 타지 않도록 설정해야 한다는 점이다. 여기에서는 /health endpoint가 대표적이다. 만약 이걸 예외처리하지 않으면 L7 Switch의 경우, 설정된 endpoint가 비정상(401을 반환할 것이기 때문에)이라고 판단해서 Target group에서 이를 빼버리기 때문이다. 예제에서는 Swagger 관련 설정도 예외 처리를 했다.

application.yml

Configuration과 관련된 코딩을 반영했다면, 내부적으로 JWT Token의 Validation을 위한 Public Key endpoint를 추가로 잡아준다. spring.security.oauth2.resourceserver.jwt.issuer-url 속성에 Public Key가 존재하는 URL을 설정해주면 된다.

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: https://auth.games.com/public

 

물론 이렇게 하지 않고, 별도로 따로 Filter를 하나 만들어서 손수 체크할 수도 있다.

Authorization JWT handling

이렇게 설정을 하고 나면, JWT 객체안에 있는 특정 필드 혹은 객체 정보를 @AuthenticationPricipal annotation을 활용해 끄집어낼 수 있다. JWT내의 특징 필드를 끄집어낼때는 해당 field의 이름을 지정해서 추출한다. 아래 예제에서 subject라는 필드는 에 포함된 sub 필드를 의미한다.

@PostMapping("/access/product/{productId}")
public SecurityAction verifyClientExecution(@AuthenticationPrincipal(expression = "subject") String id,
                                            @PathVariable("product") String product,
                                            @RequestBody SecurityInfo info) {
...
}

만약 JWT 전체 값을 참조하고 싶은 경우에는 아래와 같이 Jwt 클래스(org.springframework.security.oauth2.jwt.Jwt)를 파라미터로 지정해서 사용 가능하다.

 

Appendix

  •  – MVC 모델에서 Argument Resolver 및 특정 타입으로의 타입/모델 변경에 대한 샘플을 제공한다.

 

]]> 782
Amazing Lamda /index.php/2016/05/04/amazing-lamda/ Wed, 04 May 2016 06:10:12 +0000 /?p=120

Continue reading ‘Amazing Lamda’ »]]> Java 8에서 제대로 지원하는 stream과 람다(lamda)를 섞어서 쓰면 좋은데 람다가 코드를 어느 수준까지 줄여주는지를 한번 살펴보니 무시무시하다라는 생각이 든다.

public class OneLoginSimpleUserService {
    public UserAuthority processLogin(Authentication auth) {
        return new UserAuthority() {
            @Override
            public Collection<? extends Authority> getOwnedRoles() {
                return Arrays.asList(new Authority[]{ new Authority() {
                    @Override
                    public String toStringCode() {
                        return"ROLE_ADMIN";
                    }
                }});
            }
        };
    }
}

이걸 람다를 적용해서 고치면…

public class OneLoginSimpleUserService {
    public UserAuthority processLogin(Authentication auth) {
        return () -> Arrays.asList(new Authority[] { () -> "ROLE_ADMIN" });
    }
}

기존 자바의 인터페이스를 사용해서 불필요한 라인들이 딱 한줄의 return문으로 정리된다.

이걸 해석하는데 좀 시간이 필요하긴 한데, 얻을 수 있는게 참 많을 것 같다. 특히나 Interface를 활용해서 DI 기법으로 코드를 작성하는 방식이 더욱 더 각광받을 것 같다.

]]> 120
SonarQube 이용해서 만드는 CI /index.php/2016/03/21/sonarqube-for-ci/ /index.php/2016/03/21/sonarqube-for-ci/#comments Mon, 21 Mar 2016 02:18:48 +0000 /?p=66

Continue reading ‘SonarQube 이용해서 만드는 CI’ »]]> CI 들어봤나?

Continuous Integration의 약자이다. 해석하자면 “지속적으로 통합하라” 라는 이야기다.  뭘? 코드를.  누가 작성한 코드를?  여러분과 여러분들의 동료들이 작성한 코드를 말한다.

왜 통합을 해야할까?

이유는 간단하다. 통합을 위해서.

뭔 소리냐구?

통합을 지속적으로 시도하는 이유는 바로 필요한 시점에 사고 터지는 걸 막기 위해서다.  누구나 익히 알고 있듯이 모든 개발은 팀 작업이다.  혼자 잘해서 되는 개발은 더 이상은 없다.

사람들이 하나의 목표로 달린다지만 속된말로 꿍꿍이는 다 다르다.  이런 불일치가 지속된다고 가정해보자.  계속 내 일은 내가 알아서 하는 상태로, 너와 나의 일은 깔끔한 문서로 정리되어 있다면?

여러분들은 이들의 작업이 성공적인 결합으로 결론지어질 수 있을거라고 생각하나? 절대 불가능하다.

어떻게해야 할까?

가장 현실적이고 최선인 대답은 가능한 빠른 시간에 합쳐보라는 것이다.  “가능한 빨른 시간”라는 말은 여기서 두 가지 의미를 내포한다.  첫 번째는 현재를 기점으로 가장 이른 시간에 합쳐보는 시도를 해야한다.  두 번째 의미는 이전에 해 본 이후그 다음 번 합칠 때까지 시간을 늦추지 말라는 것이다.  나뉘어져 개발되는 결과를 합쳐보는 시도는 당연히 빨리 해야한다. 아마도 모두가 동의하는 사실일 것이다.

두번째 그럼 얼마나 빨리?  은 종종(?)을 이야기했다. 하지만 최근 CI를 열창(!)하는 에자일이나 XP쪽에서는 가능한 많이 하루를 넘기면 죄악(?)이라고 이야기한다.  개인적으로 동감하는 바이다. 하지만 죄악은 일상의 연속이며 실행하기는 참 어렵다. 그래서 회개는 반드시 해야하는 모양이다.

Best Practices

그럼 CI를 실행하는 최선의 방법은 뭘까?  위키에서 언급된 부분을 번역 수준에서 읊어보자면…

  • 코드 저장소(Code Repostiory)
  • 자동화된 빌드
  • 자동화된 테스팅
  • 일관된 커밋 규칙을 갖기 – 이건 전체 참여자들이 다들 지키는 규칙을 만드는게 어려운 것 같다.
  • 동작 가능한 커밋하기
  • 빨랑 빌드되기
  • Stage 환경 갖추기
  • Nightly build 환경 갖추기
  • 개방된 CI 결과 – 쪽팔린다고 테스트 결과를 숨기거나 빌드 오류를 숨기지 않는다.
  • 자동화된 배포

이걸 종합해보면 아래와 같은 순환적 코드 관리가 이뤄지면 된다.

개발자들은 개발된 결과를 코드 저장소에 저장하고, 저장된 결과는 자동화된 빌드 및 테스트를 통해 결과가 올바른지 확인하고 그 피드백을 통해 신규 코드를 추가하거나 기존 코드의 개선을 진행한다.

적어도 통합은 여러분이 일하는 그 날의 마지막 작업이어야 한다.  가능하면 그 결론이 성공적인 통합인 것으로!

And Then…

그런데 CI의 개념은 알겠지만 이걸 어떻게 만들어야 하는거지?  지금까지 이야기를 정리하면 CI를 구현적 관점에서 만들려면 다음 두 가지 기술이 필요하다.

  • 주기적으로 뭔가를 돌려주는 배치 시스템 – 이거에 딱인 시스템을 우리는 잘 알고 있다. 바로 젠킨스(Jenkins)!
  • 소스 코드의 “분석/빌드/테스트” 결과를 수집해서 리포팅 해주는 물건 – 이 물건이 바로 소나큐브다.

이제 본격적으로 알아보도록 하자.

 

SonarQube

네이버에서는 클로버(Clover)라는 툴을 사용했다. Atlanasian에서 만든 툴인데 기억에 상당히 비쌌던 것 같다. 찾아봤더니. 1년에 이정도면 회사에서 사용하기에 그리 비싼 도구는 아닌 것 같긴한데? 하지만 역시나 빈한한 개발자에게는 무리가 되는 금액이긴 하다.

소나큐브는 오픈소스다. 다른 말로 하면 공짜다.  친절한 유지보수는 없지만 덕을 볼려는 개발자들에게 이런 정도의 수고는 의무가 아닐까?

What is it?

근데 정확하게 가 뭐하는 거죠?

앞에서 이야기한 것처럼 소나큐브의 기본 역할은 정적 분석이고 여러분이 작성한 테스트와 꿍짝해서 코드가 테스트에 의해 얼마나 검증됐는지 커버리지(Coverage)를 측정한다.

  • Complexity
  • Duplications
  • Coding Rules
  • Potential Bugs
  • Test Cases
  • Coverage

 

Installation

젠킨스(Jenkins)를 써야겠지? 맥이나 아마 리눅스에선 다음 명령을 이용하면 젠킨스는 바로 설치할 수 있을 것이다.

sudo wget -O /etc/yum.repos.d/jenkins.repo http://pkg.jenkins-ci.org/redhat/jenkins.repo
sudo rpm --import https://jenkins-ci.org/redhat/jenkins-ci.org.key
sudo yum install jenkins

만약 젠킨스가 깔리 서버에 JDK8이상이 설치되지 않았다면 이것도 설치해야 한다.  신상을 원한다면 그건 알아서 확인하자.

curl -v -j -k -L -H "Cookie: oraclelicense=accept-securebackup-cookie" \
http://download.oracle.com/otn-pub/java/jdk/8u73-b02/jdk-8u73-linux-x64.rpm > jdk-8u73-linux-x64.rpm

sudo rpm -ivh jdk-8u73-linux-x64.rpm

인제 준비 완료. 정말 소나큐브를 깔아보자.  먼저 해야할 일은 최신 버전을 다운로드 받는거다.  http://www.sonarqube.org/downloads/ 에서 얻을 수 있다.  최신 버전을 확인한 다음 다음의 과정을 죽 진행하면 설치가 마무리된다.

wget https://sonarsource.bintray.com/Distribution/sonarqube/sonarqube-5.4.zip
unzip sonarqube-*.zip

# setup for auto execution when reboot
sudo ln -s $SONAR_HOME/bin/linux-x86-64/sonar.sh /usr/bin/sonar
sudo chmod 755 /etc/init.d/sonar
sudo chkconfig --add sonar

# start sonar
sonar start

Connecting to Jenkins

소나큐브 서버까지 설치를 했으면, 다시 젠킨스와의 연결 고리를 만든다. 젠킨스에서 지원하는 훌륭한 플러그인들이 많은데 우리가 할 일은 그것들을 콘솔상에서 깔아주기만 하면 된다.  대부분은 이미 설치되어 있고 윤택한 개발을 위해 다음 플러그인들만 추가로 설치한다. “젠킨스 관리 > 플러그인 관리” 메뉴를 통해 추가로 플러그인을 설치할 수 있다.

  • Git client plugin
  • Git plugin
  • Maven integration plugin
  • SonarQube plugin

자, 여기까지 했으면 다음으로 이것들에 대한 설정을 잡는다. 설정은 젠킨스 전역 설정이 있고, 개별 프로젝트 단위로 해야할 설정이 따로 있다.

전역 설정

설정은 “젠킨스 관리 > 시스템 설정” 메뉴에서 잡는다.  다른 플러그인은 알아서 깔면 될 것 같고,  소나큐브에 대한 설정은 아래 화면을 참고한다.

sonarqube-jenkins-config

잡아줘야 할 항목들 가운데 대부분은 기본 값으로 설정한다.  만약 본인이 이전 소나큐브 설치 과정에서 변경을 했다면 해당 값을 여기에 입력한다.  예를 들어 기본으로 제공해주는 embeded DB가 아닌 MySQL DB를 사용하는 경우에는 해당 값을 반영해야겠다.

주의해야할 필드는 “Additional analysis properties” 항목 값이다. 다음 2가지 속성에 대한 값이 반영되야한다.  각 속성에 여러 값을 입력하는 경우는 콤마(,)로 구분하면 되고, 이 두개 항목의 구분은 세미콜론(;)으로 구분한다.

  • sonar.sources
  • sonar.java.binaries

만약 다른 프로그래밍 언어 혹은 개발 플랫폼을 이용한다면 그 값을 프로젝트별 설정에서 추가로 설정해줘야 한다.

프로젝트별 설정

우리는 자바 개발을 좋아하는 개발자이기 때문에 자바, 메이븐(Maven) 기준이다.

메이븐에서 커버리지를 측정할려면 각 코드가 어떻게 수행되는지를 측정해야 한다.  소나큐브와 가장 많이 어울리는 툴이 라는 툴이다.  물론 이 측정이 일반 메이븐 필드 과정에 껴들면 곤란하기 때문에 일반 빌드와는 별도로 설정하도록 되어 있으며 이를 위한 설정은 아래 plugin 설정으로 묶어서 build  과정에 포함시켜둔다.

      <plugin>
        <groupId>org.jacoco</groupId>
        <artifactId>jacoco-maven-plugin</artifactId>
        <version>0.7.6.201602180812</version>
        <executions>
          <execution>
            <id>default-prepare-agent</id>
            <goals>
              <goal>prepare-agent</goal>
            </goals>
          </execution>
        </executions>
      </plugin>

pom.xml 파일에 이렇게 추가하고, 이제 젠킨스 프로젝트 설정을 보자.  플러그인이 돌아야지  커버리지를 볼 수 있기 때문에 빌드 Goal을 아래와 같이 설정한다.

sonarqube-jenkins-proj-config

  • Goals and options: clean org.jacoco:jacoco-maven-plugin:prepare-agent install
  • Analysis properties
    • sonar.projectKey
    • sonar.projectName
    • sonar.projectVersion

여기에서 설정하는 project 필드는 소나큐브의 프로젝트를 정의한다.  여기에서 이름이나 버전등을 변경하면 새로운 프로젝트 소나큐브에 만들어진다. 만약 전역 관점에서 sources, binaries 들을 설정하지 않았다면 여기에서 추가로 설정할 수 있다.

설정을 마치고 메이븐 프로젝트를 빌드해보자.  그럼 보게 될 것이다!!

sonarqube-installed

 

]]> /index.php/2016/03/21/sonarqube-for-ci/feed/ 2 66