ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • spring security oauth2 jwt
    카테고리 없음 2023. 4. 21. 15:26
    spring security oauth2 jwt 설정하는 법에 대해 알아보자. jwt란 JSON Web Token의 약자로 일반 oauth2 토큰을 기반으로 하는 것과 비슷하다. 인터넷에 잘 나와 있으니 참고하길 바란다. 일단 spring boot기반으로 작성할 예정이다.
    <dependency>
        <groupId>org.springframework.security.oauth</groupId>
        <artifactId>spring-security-oauth2</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-jwt</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
    
    아주 기본적인것만 추가 하였다. security jwt와 oauth2를 추가하면 된다. 간단한 샘플코드이므로 Resource 서버와 Authorization 서버는 한곳에 넣었다. 분리하고 싶다면 간단하게 EnableResourceServer와 EnableAuthorizationServer 어노테션으로 분리 해주면 될 것이다.
    @Configuration
    public class OAuth2ServerConfig {
    
      @Configuration
      @EnableResourceServer
      protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
    
        @Value("${resource.id:spring-boot-application}")
        private String resourceId;
    
        @Override
        public void configure(ResourceServerSecurityConfigurer resources) {
          resources.resourceId(resourceId);
        }
    
        @Override
        public void configure(HttpSecurity http) throws Exception {
    
          http.authorizeRequests()
            .antMatchers("/simple/**").hasRole("USER");
        }
      }
    
    
      @Configuration
      @RequiredArgsConstructor
      @EnableAuthorizationServer
      public static class JwtOAuth2AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
    
        private final AuthenticationManager authenticationManager;
    
        @Value("${resource.id:spring-boot-application}")
        private String resourceId;
    
        @Value("${access_token.validity_period:3600}")
        private int accessTokenValiditySeconds = 3600;
    
        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints)
          throws Exception {
          endpoints.accessTokenConverter(jwtAccessTokenConverter())
            .authenticationManager(this.authenticationManager);
        }
    
        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
          clients.inMemory()
            .withClient("bar")
            .authorizedGrantTypes("password")
            .authorities("ROLE_USER")
            .scopes("read", "write")
            .resourceIds(resourceId)
            .accessTokenValiditySeconds(accessTokenValiditySeconds)
            .secret("foo");
        }
    
        @Bean
        public JwtAccessTokenConverter jwtAccessTokenConverter() {
          JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
          KeyPair keyPair = new KeyStoreKeyFactory(
            new ClassPathResource("server.jks"), "qweqwe".toCharArray())
            .getKeyPair("hello", "zaqwsx".toCharArray());
          converter.setKeyPair(keyPair);
          return converter;
        }
      }
    }
    
    oauth2를 알면 나머지는 다 알겠지만 jwtAccessTokenConverter만 새롭게 느껴질 것이다. jwt토큰 방식의 좋은점은 db가 필요 없다는것이다. 서명을 통한 인증만 하면 된다. 기본적으로 JwtAccessTokenConverter 만 등록해도 상관은 없다.
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
      return new JwtAccessTokenConverter();
    }
    
    하지만 이 예제에서는 KeyPair도 등록하는 방법을 해보자. keytool을 통해 server.jks파일을 생성하자.
    keytool -genkeypair -alias hello -keyalg RSA -dname "CN=Web Server,OU=Unit,O=Organization,L=City,S=State,C=US" -keypass zaqwsx -keystore server.jks -storepass qweqwe
    
    여기서 중요한것은 alias의 hello와 keypass의 zaqwsx, storepass의 qweqwe 이것들이다. 필자도 인터넷에서 찾아서 해서 뭘뜻하는지는 자세히 모른다.ㅎㅎㅎ 자신의 맞게 패스워드들을 만들어서 생성하면 된다. 엔터를 치면 server.jks가 생성된다. 그 생성된 파일을 classpath에 두자. 그리고 테스트를 위해 두개의 controller를 만들자.
    @RestController
    public class AnonymousController {
    
      @GetMapping("/anonymous")
      public String simple(){
        return "Anonymous";
      }
    }
    
    @RestController
    public class SimpleController {
    
      @GetMapping("/simple")
      public String simple(){
        return "hi spring boot";
      }
    }
    
    anonymous는 인증을 하지 않아도 되는 경우이고 simple인증을 해야 되는 경우이다.
    curl http://localhost:8080/anonymous
    Anonymous
    
    curl http://localhost:8080/simple
    {"error":"unauthorized","error_description":"Full authentication is required to access this resource"}
    
    anonymous인 경우에는 정상적으로 값이 출력 되지만 simple인 경우에는 인증이 되지 않았다고 출력 되었다. 그럼 이제 인증을 해보자. 참고로 테스트를 위해 yml파일에 다음과 같이 넣었다.
    security:
      user:
        name: admin
        password: test
    
    curl -u bar:foo http://localhost:8080/oauth/token -d "grant_type=password&username=admin&password=test"
    
    이와 같이 password타입으로 아이디와 패스워드를 넘긴후 인증 처리를 하였다. 물론 지금 예제는 password 타입밖에 설정이 되어 있지 않다. 결과를 보면 다음과 같다.
    {"access_token":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsic3ByaW5nLWJvb3QtYXBwbGljYXRpb24iXSwidXNlcl9uYW1lIjoiYWRtaW4iLCJzY29wZSI6WyJyZWFkIiwid3JpdGUiXSwiZXhwIjoxNDY3NjE1ODA2LCJhdXRob3JpdGllcyI6WyJST0xFX1VTRVIiXSwianRpIjoiMzU0NDkyYzctMjg5OC00NjE1LWE0YTEtMWQ1N2I0ODUzYWE4IiwiY2xpZW50X2lkIjoiYmFyIn0.dySxGmpgHZsTegkqi39AGaogEK0Gzvg9-gNqxZDG3MaHWpjbU5-5gbYfPm4olANOeu-y5hyOeetuoWtAkMsMKcTqYEqJweKaPfbVo_5m6W6MaMv2VqUUNsOXZFlSR6-RYsixE8dC92pu5YXQ--edog8pJ43skPvk7XD63967dVNJLkp0nDGWwO-Pmv_6t6QzG0eQA_la4o1xl88Cv8gkweH7SNYjnm3UKMiTCS9fxO_wHFnEuQ4PthgMrAOk0myWVmnYOIxR-_FzxkhN_MCGdhvy_PqDhbVRTrEM6MXWd-SEWX3coqZrrgllr1MYQWupKjz8-M8DFKFHmyb-hYb4vA","token_type":"bearer","expires_in":3599,"scope":"read write","jti":"354492c7-2898-4615-a4a1-1d57b4853aa8"}
    
    access_token을 발급 받았다. https://jwt.io/ 사이트에 가면 json으로 확인 할 수 있다. 이제 다시 access_token과 함께 simple에 접속해보자.
    curl -H "authorization: bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsic3ByaW5nLWJvb3QtYXBwbGljYXRpb24iXSwidXNlcl9uYW1lIjoiYWRtaW4iLCJzY29wZSI6WyJyZWFkIiwid3JpdGUiXSwiZXhwIjoxNDY3NjE1ODA2LCJhdXRob3JpdGllcyI6WyJST0xFX1VTRVIiXSwianRpIjoiMzU0NDkyYzctMjg5OC00NjE1LWE0YTEtMWQ1N2I0ODUzYWE4IiwiY2xpZW50X2lkIjoiYmFyIn0.dySxGmpgHZsTegkqi39AGaogEK0Gzvg9-gNqxZDG3MaHWpjbU5-5gbYfPm4olANOeu-y5hyOeetuoWtAkMsMKcTqYEqJweKaPfbVo_5m6W6MaMv2VqUUNsOXZFlSR6-RYsixE8dC92pu5YXQ--edog8pJ43skPvk7XD63967dVNJLkp0nDGWwO-Pmv_6t6QzG0eQA_la4o1xl88Cv8gkweH7SNYjnm3UKMiTCS9fxO_wHFnEuQ4PthgMrAOk0myWVmnYOIxR-_FzxkhN_MCGdhvy_PqDhbVRTrEM6MXWd-SEWX3coqZrrgllr1MYQWupKjz8-M8DFKFHmyb-hYb4vA" http://localhost:8080/simple
    
    그럼 성공적으로 hi spring boot가 출력 된 것을 볼수 있다. 엉뚱한 값을 다시 넣어서 테스트 해보자.
    curl -H "authorization: bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsic3ByaW5nLWJvb3QtYXBwbGljYXRpb24iXSwidXNlcl9uYW1lIjoiYWRtaW4iLCJzY29wZSI6WyJyZWFkIiwid3JpdGUiXSwiZXhwIjoxNDY3NjE1ODA2LCJhdXRob3JpdGllcyI6WyJST0xFX1VTRVIiXSwianRpIjoiMzU0NDkyYzctMjg5OC00NjE1LWE0YTEtMWQ1N2I0ODUzYWE4IiwiY2xpZW50X2lkIjoiYmFyIn0.dySxGmpgHZsTegkqi39AGaogEK0Gzvg9-gNqxZDG3MaHWpjbU5-5gbYfPm4olANOeu-y5hyOeetuoWtAkMsMKcTqYEqJweKaPfbVo_5m6W6MaMv2VqUUNsOXZFlSR6-RYsixE8dC92pu5YXQ--edog8pJ43skPvk7XD63967dVNJLkp0nDGWwO-Pmv_6t6QzG0eQA_la4o1xl88Cv8gkweH7SNYjnm3UKMiTCS9fxO_wHFnEuQ4PthgMrAOk0myWVmnYOIxR-_FzxkhN_MCGdhvy_PqDhbVRTrEM6MXWd-SEWX3coqZrrgllr1MYQWupKjz8-M8DFKFHmyb-hYb4vA11" http://localhost:8080/simple
    
    그럼 잘못된 토근이라고 리턴해준다.
    {"error":"invalid_token","error_description":"Cannot convert access token to JSON"}
    
    우리는 이렇게 spring security jwt에 대해 알아봤다. 참고: keytool -export -keystore server.jks -alias hello -file example.cer 키 저장소 비밀번호 입력: qweqwe 인증서가 <example.cer> 파일에 저장되었습니다. openssl x509 -inform der -in example.cer -pubkey -noout -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgdy5rIIFN3gKeBb+pDkR QabaDo+Rk/bz8NsiAn+OR1OavsEe5SqgbJw+KhpaFubMcfWbC5b4AkLqQh9xzIRE cRAKlDhOPoaoTaFeq2ocpCjOipq7s6UuVAqmx7WOj5PbcasyG6rMeTEr0rZrwFSw S6NQCoTM0n5rpxXL9S2qTTIUUYY1fjJ/y1Hocmpg9opIvU8xc0YXnoHoucmogpOE 4dcwIfXMSwXPkiFAcZnApcXjH4VBYsrhho+acqZDUSzCr8OttzHifHCj2+tFnKYI ggddxP7tmwlkU7gPEmO9okXfg5mzlnif6FUepmRTsT1CrcBqGhic6TZCOEVcZGS+ QQIDAQAB -----END PUBLIC KEY----- public key 추출하는 방법이다.
    security:
      oauth2:
        resource:
          jwt:
            key-value: |
                -----BEGIN PUBLIC KEY-----
                MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgdy5rIIFN3gKeBb+pDkR
                QabaDo+Rk/bz8NsiAn+OR1OavsEe5SqgbJw+KhpaFubMcfWbC5b4AkLqQh9xzIRE
                cRAKlDhOPoaoTaFeq2ocpCjOipq7s6UuVAqmx7WOj5PbcasyG6rMeTEr0rZrwFSw
                S6NQCoTM0n5rpxXL9S2qTTIUUYY1fjJ/y1Hocmpg9opIvU8xc0YXnoHoucmogpOE
                4dcwIfXMSwXPkiFAcZnApcXjH4VBYsrhho+acqZDUSzCr8OttzHifHCj2+tFnKYI
                ggddxP7tmwlkU7gPEmO9okXfg5mzlnif6FUepmRTsT1CrcBqGhic6TZCOEVcZGS+
                QQIDAQAB
                -----END PUBLIC KEY-----
    

    댓글

Designed by Tistory.