복붙노트

[SPRING] 스프링 부트 2 및 OAuth2 / JWT 구성

SPRING

스프링 부트 2 및 OAuth2 / JWT 구성

내 Spring boot 1.5 애플리케이션을 Spring Boot 2로 포팅하려고한다.

지금은 OAuth2 액세스 토큰을 얻을 수 없습니다.

이것은 스프링 부트 1.5에서 성공적으로 사용한 코드입니다.

public static String loginAndGetAccessToken(String username, String password, int port) {

    ResourceOwnerPasswordResourceDetails resourceDetails = new ResourceOwnerPasswordResourceDetails();
    resourceDetails.setUsername(username);
    resourceDetails.setPassword(password);
    resourceDetails.setAccessTokenUri(String.format("http://localhost:%d/api/oauth/token", port));
    resourceDetails.setClientId("clientapp");
    resourceDetails.setClientSecret("123456");
    resourceDetails.setGrantType("password");
    resourceDetails.setScope(Arrays.asList("read", "write"));

    DefaultOAuth2ClientContext clientContext = new DefaultOAuth2ClientContext();

    OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(resourceDetails, clientContext);
    restTemplate.setMessageConverters(Arrays.asList(new MappingJackson2HttpMessageConverter()));

    return restTemplate.getAccessToken().toString();
}

다음 예외가있는 경우 실패합니다.

java.lang.IllegalStateException: An OAuth 2 access token must be obtained or an exception thrown.
    at org.springframework.security.oauth2.client.token.AccessTokenProviderChain.obtainAccessToken(AccessTokenProviderChain.java:124)
    at org.springframework.security.oauth2.client.OAuth2RestTemplate.acquireAccessToken(OAuth2RestTemplate.java:221)
    at org.springframework.security.oauth2.client.OAuth2RestTemplate.getAccessToken(OAuth2RestTemplate.java:173)

http : // localhost : % d / api / oauth / token 엔드 포인트가 현재 보안 상태이며 액세스 할 수 없습니다.

이것은 내 구성입니다.

OAuth2 서버 구성

@Configuration
public class OAuth2ServerConfig {

    public static final String RESOURCE_ID = "restservice";
    public static final String EXAMPLE_CLIENT_ID = "example_client_id";

    @Value("${jwt.access.token.converter.signing.key}")
    private String jwtAccessTokenConverterSigningKey;

    @Value("${jwt.access.token.validity.seconds}")
    private int accessTokenValiditySeconds;

    @Autowired
    private UserDetailsService userDetailsService;

    @Bean
    @Primary
    public DefaultTokenServices tokenServices() {
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setTokenStore(tokenStore());
        tokenServices.setSupportRefreshToken(true);
        tokenServices.setAccessTokenValiditySeconds(accessTokenValiditySeconds);
        return tokenServices;
    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter converter = new UserAwareAccessTokenConverter();

        converter.setSigningKey(jwtAccessTokenConverterSigningKey);

        DefaultAccessTokenConverter accessTokenConverter = new DefaultAccessTokenConverter();
        DefaultUserAuthenticationConverter userTokenConverter = new DefaultUserAuthenticationConverter();
        userTokenConverter.setUserDetailsService(userDetailsService);
        accessTokenConverter.setUserTokenConverter(userTokenConverter);

        converter.setAccessTokenConverter(accessTokenConverter);

        return converter;
    }

    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(accessTokenConverter());
    }

    @Configuration
    @EnableAuthorizationServer
    protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

        @Autowired
        @Qualifier("authenticationManagerBean")
        private AuthenticationManager authenticationManager;

        @Value("${jwt.access.token.validity.seconds}")
        private int accessTokenValiditySeconds;

        @Autowired
        private TokenStore tokenStore;

        @Autowired
        private TokenEnhancer tokenEnhancer;

        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
            // @formatter:off
            endpoints
                .tokenStore(tokenStore)
                .tokenEnhancer(tokenEnhancer)
                .authenticationManager(this.authenticationManager);
            // @formatter:on
        }

        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            // @formatter:off
            clients
                .inMemory()
                    .withClient("clientapp")
                        .authorizedGrantTypes("password","refresh_token")
                        .authorities("ROLE_CLIENT")
                        .scopes("read", "write")
                        .resourceIds(RESOURCE_ID)
                        .secret("123456")
                    .and()
                    .withClient(EXAMPLE_CLIENT_ID)
                        .authorizedGrantTypes("implicit")
                        .scopes("read", "write")
                        .autoApprove(true)
                    .and()
                        .withClient("my-trusted-client")
                        .authorizedGrantTypes("password", "authorization_code", "refresh_token", "implicit")
                        .authorities("ROLE_CLIENT", "ROLE_TRUSTED_CLIENT")
                        .scopes("read", "write", "trust")
                        .accessTokenValiditySeconds(accessTokenValiditySeconds);
            // @formatter:on
        }

    }

    @Configuration
    @EnableResourceServer
    protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

        @Autowired
        private ResourceServerTokenServices tokenService;

        @Override
        public void configure(ResourceServerSecurityConfigurer resources) {
            // @formatter:off
            resources           
                .resourceId(RESOURCE_ID)
                .tokenServices(tokenService);
            // @formatter:on
        }

        @Override
        public void configure(HttpSecurity http) throws Exception {
            // @formatter:off
            http                
                .antMatcher("/v1.0/**").authorizeRequests()
                .antMatchers("/v1.0/search/**").permitAll()
                .antMatchers("/v1.0/users/**").permitAll()
                .antMatchers("/v1.0/decisions/**").permitAll()
                .antMatchers("/v1.0/votes/**").permitAll()
                .antMatchers("/v1.0/likes/**").permitAll()
                .antMatchers("/v1.0/likeables/**").permitAll()
                .antMatchers("/v1.0/flags/**").permitAll()
                .antMatchers("/v1.0/flagtypes/**").permitAll()
                .antMatchers("/v1.0/flaggables/**").permitAll()
                .antMatchers("/v1.0/comments/**").permitAll()
                .antMatchers("/v1.0/commentables/**").permitAll()
                .antMatchers("/v1.0/subscribables/**").permitAll()
                .antMatchers("/v1.0/favoritables/**").permitAll()
                .antMatchers("/v1.0/import/**").permitAll()
                .antMatchers("/v1.0/tags/**").permitAll()
                .antMatchers("/v1.0/medias/**").permitAll()
                .antMatchers("/swagger**").permitAll()
                .anyRequest().authenticated()
                .and()
                .csrf().disable()
                .sessionManagement().sessionCreationPolicy(STATELESS); 
            // @formatter:on
        }

    }

}

WebMvcConfig

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("index");
        registry.addViewController("/login").setViewName("login");
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");

        registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");

        registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
    }

}

WebSecurityConfig

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;

    @Value("${logout.success.url}")
    private String logoutSuccessUrl;

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        // @formatter:off
        http
            .cors()
        .and()
            .csrf().ignoringAntMatchers("/v1.0/**", "/logout")
        .and()
            .authorizeRequests()

            .antMatchers("/oauth/authorize").authenticated()
            //Anyone can access the urls
            .antMatchers("/images/**").permitAll()
            .antMatchers("/signin/**").permitAll()
            .antMatchers("/v1.0/**").permitAll()
            .antMatchers("/auth/**").permitAll()
            .antMatchers("/actuator/health").permitAll()
            .antMatchers("/actuator/**").hasAuthority(Permission.READ_ACTUATOR_DATA)
            .antMatchers("/login").permitAll()
            .anyRequest().authenticated()
        .and()
            .formLogin()
                .loginPage("/login")
                .loginProcessingUrl("/login")
                .failureUrl("/login?error=true")
                .usernameParameter("username")
                .passwordParameter("password")
                .permitAll()
            .and()
                .logout()
                    .logoutUrl("/logout")
                    .logoutSuccessUrl(logoutSuccessUrl)
                    .permitAll();
        // @formatter:on
    }

    /**
     * Configures the authentication manager bean which processes authentication requests.
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
    }

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("*"));
        configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"));
        configuration.setAllowedHeaders(Arrays.asList("authorization", "content-type", "x-auth-token"));
        configuration.setExposedHeaders(Arrays.asList("x-auth-token"));
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }

}

Spring Boot 2와 함께 작동시키기 위해서는 무엇을 변경해야합니까?

해결법

  1. ==============================

    1.OAuth2 서버 구성

    OAuth2 서버 구성

    import static org.springframework.security.config.http.SessionCreationPolicy.STATELESS;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Primary;
    import org.springframework.security.authentication.AuthenticationManager;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.crypto.factory.PasswordEncoderFactories;
    import org.springframework.security.crypto.password.PasswordEncoder;
    import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
    import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
    import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
    import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
    import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
    import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
    import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
    import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter;
    import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
    import org.springframework.security.oauth2.provider.token.DefaultUserAuthenticationConverter;
    import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
    import org.springframework.security.oauth2.provider.token.TokenEnhancer;
    import org.springframework.security.oauth2.provider.token.TokenStore;
    import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
    import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
    
    import com.decisionwanted.api.security.authentication.UserAwareAccessTokenConverter;
    import com.decisionwanted.domain.service.user.social.UserDetailsService;
    
    @Configuration
    public class OAuth2ServerConfig {
    
        public static final String RESOURCE_ID = "restservice";
        public static final String CLIENT_ID = "client_id";
    
        @Value("${jwt.access.token.converter.signing.key}")
        private String jwtAccessTokenConverterSigningKey;
    
        @Value("${jwt.access.token.validity.seconds}")
        private int accessTokenValiditySeconds;
    
        @Autowired
        private UserDetailsService userDetailsService;
    
        @Bean
        @Primary
        public DefaultTokenServices tokenServices() {
            DefaultTokenServices tokenServices = new DefaultTokenServices();
            tokenServices.setTokenStore(tokenStore());
            tokenServices.setSupportRefreshToken(true);
            tokenServices.setAccessTokenValiditySeconds(accessTokenValiditySeconds);
            return tokenServices;
        }
    
        @Bean
        public JwtAccessTokenConverter accessTokenConverter() {
            JwtAccessTokenConverter converter = new UserAwareAccessTokenConverter();
    
            converter.setSigningKey(jwtAccessTokenConverterSigningKey);
    
            DefaultAccessTokenConverter accessTokenConverter = new DefaultAccessTokenConverter();
            DefaultUserAuthenticationConverter userTokenConverter = new DefaultUserAuthenticationConverter();
            userTokenConverter.setUserDetailsService(userDetailsService);
            accessTokenConverter.setUserTokenConverter(userTokenConverter);
    
            converter.setAccessTokenConverter(accessTokenConverter);
    
            return converter;
        }
    
        @Bean
        public TokenStore tokenStore() {
            return new JwtTokenStore(accessTokenConverter());
        }
    
        @Configuration
        @EnableAuthorizationServer
        protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
    
            @Autowired
            @Qualifier("authenticationManagerBean")
            private AuthenticationManager authenticationManager;
    
            @Value("${jwt.access.token.validity.seconds}")
            private int accessTokenValiditySeconds;
    
            @Autowired
            private TokenStore tokenStore;
    
            @Autowired
            private TokenEnhancer tokenEnhancer;
    
            private PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
    
            @Override
            public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
                // @formatter:off
                endpoints
                    .tokenStore(tokenStore)
                    .tokenEnhancer(tokenEnhancer)
                    .authenticationManager(this.authenticationManager);
                // @formatter:on
            }
    
            @Override
            public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
                // @formatter:off
                clients
                    .inMemory()
                        .withClient("clientapp")
                            .authorizedGrantTypes("password","refresh_token")
                            .authorities("ROLE_CLIENT")
                            .scopes("read", "write")
                            .resourceIds(RESOURCE_ID)
                            .secret(passwordEncoder.encode("changeit"))
                        .and()
                        .withClient(CLIENT_ID)
                            .authorizedGrantTypes("implicit")
                            .scopes("read", "write")
                            .autoApprove(true)
                        .and()
                            .withClient("my-trusted-client")
                            .authorizedGrantTypes("password", "authorization_code", "refresh_token", "implicit")
                            .authorities("ROLE_CLIENT", "ROLE_TRUSTED_CLIENT")
                            .scopes("read", "write", "trust")
                            .accessTokenValiditySeconds(accessTokenValiditySeconds);
                // @formatter:on
            }
    
        }
    
        @Configuration
        @EnableResourceServer
        protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
    
            @Autowired
            private ResourceServerTokenServices tokenService;
    
            @Override
            public void configure(ResourceServerSecurityConfigurer resources) {
                // @formatter:off
                resources           
                    .resourceId(RESOURCE_ID)
                    .tokenServices(tokenService);
                // @formatter:on
            }
    
            @Override
            public void configure(HttpSecurity http) throws Exception {
                // @formatter:off
                http                
                    .antMatcher("/v1.0/**").authorizeRequests()
                    .antMatchers("/v1.0/search/**").permitAll()
                    .antMatchers("/v1.0/users/**").permitAll()
                    .antMatchers("/v1.0/decisiongroups/**").permitAll()
                    .antMatchers("/swagger**").permitAll()
                    .anyRequest().authenticated()
                    .and()
                        .cors()
                    .and()
                        .csrf().disable()
                    .sessionManagement()
                        .sessionCreationPolicy(STATELESS); 
                // @formatter:on
            }
    
        }
    
    }
    

    WebMvcConfig

    @Configuration
    public class WebMvcConfig implements WebMvcConfigurer {
    
        @Override
        public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
            configurer.enable();
        }
    
        @Override
        public void addViewControllers(ViewControllerRegistry registry) {
            registry.addViewController("/").setViewName("index");
            registry.addViewController("/login").setViewName("login");
        }
    
        @Override
        public void addResourceHandlers(ResourceHandlerRegistry registry) {
            registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
    
            registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");
    
            registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
        }
    
    }
    

    WebSecurityConfig

    import java.util.Arrays;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.authentication.AuthenticationManager;
    import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.crypto.factory.PasswordEncoderFactories;
    import org.springframework.security.crypto.password.PasswordEncoder;
    import org.springframework.web.cors.CorsConfiguration;
    import org.springframework.web.cors.CorsConfigurationSource;
    import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
    
    import com.decisionwanted.domain.model.neo4j.security.permission.Permission;
    import com.decisionwanted.domain.service.user.social.UserDetailsService;
    
    @Configuration
    @EnableWebSecurity
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Autowired
        private UserDetailsService userDetailsService;
    
        @Value("${logout.success.url}")
        private String logoutSuccessUrl;
    
        PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
    
            // @formatter:off
            http
                .cors()
            .and()
                .csrf().ignoringAntMatchers("/v1.0/**", "/logout")
            .and()
                .authorizeRequests()
    
                .antMatchers("/oauth/authorize").authenticated()
                //Anyone can access the urls
                .antMatchers("/images/**").permitAll()
                .antMatchers("/signin/**").permitAll()
                .antMatchers("/v1.0/**").permitAll()
                .antMatchers("/auth/**").permitAll()
                .antMatchers("/actuator/health").permitAll()
                .antMatchers("/actuator/**").hasAuthority(Permission.READ_ACTUATOR_DATA)
                .antMatchers("/login").permitAll()
                .anyRequest().authenticated()
            .and()
                .formLogin()
                    .loginPage("/login")
                    .loginProcessingUrl("/login")
                    .failureUrl("/login?error=true")
                    .usernameParameter("username")
                    .passwordParameter("password")
                    .permitAll()
                .and()
                    .logout()
                        .logoutUrl("/logout")
                        .logoutSuccessUrl(logoutSuccessUrl)
                        .permitAll();
            // @formatter:on
        }
    
        /**
         * Configures the authentication manager bean which processes authentication requests.
         */
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
        }
    
        @Override
        @Bean
        public AuthenticationManager authenticationManagerBean() throws Exception {
            return super.authenticationManagerBean();
        }
    
        @Bean
        public CorsConfigurationSource corsConfigurationSource() {
            CorsConfiguration configuration = new CorsConfiguration();
            configuration.setAllowedOrigins(Arrays.asList("*"));
            configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"));
            configuration.setAllowedHeaders(Arrays.asList("authorization", "content-type", "x-auth-token"));
            configuration.setExposedHeaders(Arrays.asList("x-auth-token"));
            UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
            source.registerCorsConfiguration("/**", configuration);
            return source;
        }
    
    }
    

    SecurityTestUtils

    import java.util.Arrays;
    
    import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
    import org.springframework.security.oauth2.client.DefaultOAuth2ClientContext;
    import org.springframework.security.oauth2.client.OAuth2RestTemplate;
    import org.springframework.security.oauth2.client.token.grant.password.ResourceOwnerPasswordResourceDetails;
    
    public class SecurityTestUtils {
    
        public static final String AUTH_HEADER_NAME = "Authorization";
        public static final String AUTH_COOKIE_NAME = "AUTH-TOKEN";
    
        public static String loginAndGetAccessToken(String username, String password, int port) {
    
            ResourceOwnerPasswordResourceDetails resourceDetails = new ResourceOwnerPasswordResourceDetails();
            resourceDetails.setUsername(username);
            resourceDetails.setPassword(password);
            resourceDetails.setAccessTokenUri(String.format("http://localhost:%d/api/oauth/token", port));
            resourceDetails.setClientId("clientapp");
            resourceDetails.setClientSecret("changeit");
            resourceDetails.setGrantType("password");
            resourceDetails.setScope(Arrays.asList("read", "write"));
    
            DefaultOAuth2ClientContext clientContext = new DefaultOAuth2ClientContext();
    
            OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(resourceDetails, clientContext);
            restTemplate.setMessageConverters(Arrays.asList(new MappingJackson2HttpMessageConverter()));
    
            return restTemplate.getAccessToken().toString();
        }
    
    }
    
  2. ==============================

    2.이 url / api / oauth / token을 허용하도록 봄 보안 설정을 시도하십시오. 이걸로 무엇을 할지를 정의하지 않은 것으로 보입니다.

    이 url / api / oauth / token을 허용하도록 봄 보안 설정을 시도하십시오. 이걸로 무엇을 할지를 정의하지 않은 것으로 보입니다.

  3. from https://stackoverflow.com/questions/45391264/spring-boot-2-and-oauth2-jwt-configuration by cc-by-sa and MIT license