Search code examples
springspring-securityoauth-2.0spring-java-config

Spring security oauth2 and form login configuration


My project consists exposes two different parts, a JSF admin panel and a RESTfull service. I am trying to setup spring security to use different authentication methods depending on the URL the user navigates.

The requirements are

  • Users navigating to the JSF page get a login screen where they authentication using form authentication.
  • Users navigating to the REST service use OAuth2 implicit authentication with basic authentication for the token granting.

The seperate configurations work by themselves, the problem is when I try to combine both of them in one configuration, in that case it seems like the REST provider gets in the way and authenticates each request even if the requests go to the admin url (this is documented from spring security ordering).

My sample configurations are as shown:

  • For the form login (JSF)

    @Override
    @Order(1)
    protected void configure(HttpSecurity http) throws Exception {
    http
            .csrf().disable()
            .authorizeRequests()
            .antMatchers("/resources/**").permitAll()
            .antMatchers("/templates/**").permitAll()
            .antMatchers("/401.html").permitAll()
            .antMatchers("/404.html").permitAll()
            .antMatchers("/500.html").permitAll()
            .antMatchers("/api/**").permitAll()
            .antMatchers("/ui/admin.xhtml").hasAnyAuthority("admin", "ADMIN")
            .antMatchers("/thymeleaf").hasAnyAuthority("admin", "ADMIN")
            //.anyRequest().authenticated()
            .and()
            .formLogin()
            .loginPage("/login")
            .defaultSuccessUrl("/ui/index.xhtml")
            .failureUrl("/login?error=1")
            .permitAll()
            .and()
            .logout()
            .permitAll()
            .and()
            .rememberMe()
            .and().exceptionHandling().accessDeniedPage("/error/403");
    
  • OAuth2 security config (REST)

    @EnableResourceServer
    @Order(2)
    public class RestSecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Inject
        private UserRepository userRepository;
    
        @Inject
        private PasswordEncoder passwordEncoder;
    
        @Bean
        ApplicationListener<AbstractAuthorizationEvent> loggerBean() {
            return new AuthenticationLoggerListener();
        }
    
        @Bean
        AccessDeniedHandler accessDeniedHandler() {
            return new AccessDeniedExceptionHandler();
        }
    
        @Bean
        AuthenticationEntryPoint entryPointBean() {
            return new UnauthorizedEntryPoint();
        }
    
        /*Override
        public void configure(WebSecurity web) throws Exception {
            web.ignoring()
                    .antMatchers(
                            "/resources/**"
                            , "/templates/**"
                            , "/login"
                            , "/logout"
                            , "/ui/**"
                            , "/401.html"
                            , "/404.html"
                            , "/500.html"
                    );
        }*/
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            ContentNegotiationStrategy contentNegotiationStrategy = http.getSharedObject(ContentNegotiationStrategy.class);
            if (contentNegotiationStrategy == null) {
                contentNegotiationStrategy = new HeaderContentNegotiationStrategy();
            }
            MediaTypeRequestMatcher preferredMatcher = new MediaTypeRequestMatcher(contentNegotiationStrategy,
                    MediaType.APPLICATION_FORM_URLENCODED,
                    MediaType.APPLICATION_JSON,
                    MediaType.MULTIPART_FORM_DATA);
    
            http.authorizeRequests()
                    .antMatchers("/ui/**").permitAll()
                    .and()
                    .anonymous().disable()
                    .sessionManagement()
                    .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                    .and().httpBasic()
                    .and()
                    .exceptionHandling()
                    .accessDeniedHandler(accessDeniedHandler()) // handle access denied in general (for example comming from @PreAuthorization
                    .authenticationEntryPoint(entryPointBean()) // handle authentication exceptions for unauthorized calls.
                    .defaultAuthenticationEntryPointFor(entryPointBean(), preferredMatcher)
                    .and()
                    .authorizeRequests()
                    .antMatchers("/api/**").fullyAuthenticated();
    
        }
    
        @Override
        @Bean
        public AuthenticationManager authenticationManagerBean() throws Exception {
            return super.authenticationManagerBean();
        }
    
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.userDetailsService(new UserDetailsService() {
                @Override
                public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
                    User user = userRepository.findOneByUsername(s);
    
                    if (null == user) {
                        // leave that to be handled by log listener
                        throw new UsernameNotFoundException("The user with email " + s + " was not found");
                    }
    
                    return (UserDetails) user;
                }
            }).passwordEncoder(passwordEncoder);
        }
    
    
        @Configuration
        @EnableAuthorizationServer
        protected static class OAuth2Config extends AuthorizationServerConfigurerAdapter {
    
    
    
            @Autowired
            private AuthenticationManager authenticationManager;
    
    
            @Bean
            public JwtAccessTokenConverter accessTokenConverter() {
                return new JwtAccessTokenConverter();
            }
    
            @Override
            public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
                oauthServer.tokenKeyAccess("isAnonymous() || hasAuthority('ROLE_TRUSTED_CLIENT')").checkTokenAccess("hasAuthority('ROLE_TRUSTED_CLIENT')");
            }
    
            @Override
            public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
                endpoints.authenticationManager(authenticationManager).accessTokenConverter(accessTokenConverter());
            }
    
    
            @Override
            public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
                clients.inMemory()
                        .withClient("xxx")
                        .resourceIds(xxx)
                        .authorizedGrantTypes("password", "authorization_code", "refresh_token", "implicit")
                        .authorities("ROLE_CLIENT", "ROLE_TRUSTED_CLIENT")
                        .scopes("read", "write", "trust", "update")
                        .accessTokenValiditySeconds(xxx)
                        .refreshTokenValiditySeconds(xxx)
                        .secret("xxx")
    
            }
        }
    }
    

These configurations exist on different classes and the ordering is set manually.

Has anyone any solutions to this issue?

Best,


Solution

  • I tried to adapt your security configuration. Unfortunately, I can not validate this configuration due to missing reference application.

    Maybe it can help you:

    @Configuration
    @EnableWebSecurity
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Autowired
        private UserRepository userRepository;
    
        @Autowired
        private PasswordEncoder passwordEncoder;
    
        @Autowired
        protected void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
            auth.userDetailsService(new UserDetailsService() {
                @Override
                public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
                    User user = userRepository.findOneByUsername(s);
    
                    if (null == user) {
                        throw new UsernameNotFoundException("The user with email " + s + " was not found");
                    }
    
                    return (UserDetails) user;
                }
            }).passwordEncoder(passwordEncoder);
        }
    
        @Override
        public void configure(WebSecurity webSecurity) throws Exception {
            webSecurity
                    .ignoring()
                    .antMatchers("/resources/**"
                            , "/templates/**"
                            , "/login"
                            , "/logout"
                            , "/ui/**"
                            , "/401.html"
                            , "/404.html"
                            , "/500.html");
        }
    
        @Configuration
        @EnableAuthorizationServer
        public static class OAuth2Configuration extends AuthorizationServerConfigurerAdapter {
    
            @Autowired
            private AuthenticationManager authenticationManager;
    
            @Bean
            public JwtAccessTokenConverter accessTokenConverter() {
                return new JwtAccessTokenConverter();
            }
    
            @Override
            public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
                oauthServer.tokenKeyAccess("isAnonymous() || hasAuthority('ROLE_TRUSTED_CLIENT')").checkTokenAccess("hasAuthority('ROLE_TRUSTED_CLIENT')");
            }
    
            @Override
            public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
                endpoints.authenticationManager(authenticationManager).accessTokenConverter(accessTokenConverter());
            }
    
    
            @Override
            public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
                clients.inMemory()
                        .withClient("xxx")
                        .resourceIds("xxx")
                        .authorizedGrantTypes("password", "authorization_code", "refresh_token", "implicit")
                        .authorities("ROLE_CLIENT", "ROLE_TRUSTED_CLIENT")
                        .scopes("read", "write", "trust", "update")
                        .accessTokenValiditySeconds(xxx)
                        .refreshTokenValiditySeconds(xxx)
                        .secret("xxx");
    
            }
        }
    
        @Configuration
        @Order(1)
        public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
    
            @Override
            public void configure(HttpSecurity http) throws Exception {
                http
                        .csrf().disable()
                        .authorizeRequests()
                        .antMatchers("/ui/admin.xhtml").hasAnyAuthority("admin", "ADMIN")
                        .antMatchers("/thymeleaf").hasAnyAuthority("admin", "ADMIN")
                        .and()
                        .formLogin()
                        .loginPage("/login")
                        .defaultSuccessUrl("/ui/index.xhtml")
                        .failureUrl("/login?error=1")
                        .permitAll()
                        .and()
                        .logout()
                        .permitAll()
                        .and()
                        .rememberMe()
                        .and().exceptionHandling().accessDeniedPage("/error/403");
            }
        }
    
        @Order(2)
        @Configuration
        @EnableResourceServer
        public static class CustomResourceServerConfigurerAdapter extends ResourceServerConfigurerAdapter {
    
            @Bean
            ApplicationListener<AbstractAuthorizationEvent> loggerBean() {
                return new AuthenticationLoggerListener();
            }
    
            @Bean
            AccessDeniedHandler accessDeniedHandler() {
                return new AccessDeniedExceptionHandler();
            }
    
            @Bean
            AuthenticationEntryPoint entryPointBean() {
                return new UnauthorizedEntryPoint();
            }
    
            @Override
            public void configure(HttpSecurity http) throws Exception {
                ContentNegotiationStrategy contentNegotiationStrategy = http.getSharedObject(ContentNegotiationStrategy.class);
                if (contentNegotiationStrategy == null) {
                    contentNegotiationStrategy = new HeaderContentNegotiationStrategy();
                }
                MediaTypeRequestMatcher preferredMatcher = new MediaTypeRequestMatcher(contentNegotiationStrategy,
                        MediaType.APPLICATION_FORM_URLENCODED,
                        MediaType.APPLICATION_JSON,
                        MediaType.MULTIPART_FORM_DATA);
    
                http.authorizeRequests()
                        .and()
                        .anonymous().disable()
                        .sessionManagement()
                        .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                        .and().httpBasic()
                        .and()
                        .exceptionHandling()
                        .accessDeniedHandler(accessDeniedHandler()) // handle access denied in general (for example comming from @PreAuthorization
                        .authenticationEntryPoint(entryPointBean()) // handle authentication exceptions for unauthorized calls.
                        .defaultAuthenticationEntryPointFor(entryPointBean(), preferredMatcher)
                        .and()
                        .authorizeRequests()
                        .antMatchers("/api/**").fullyAuthenticated();
            }
        }
    }