Search code examples
javaspring-bootspring-securityspring-security-oauth2okta

Spring Boot 2.5.3 OAuth2 - Auth-Server and Webservice separate, after Login no endpont works


Following the example on https://developer.okta.com/blog/2019/03/12/oauth2-spring-security-guide using the projects Create an OAuth 2.0 Server and Build Your Client App after the login, any endpoint accessed jumps to the root endpoint localhost:8082.

I don't use Thymeleaf, as my webservice returns data, not a page.

OAuth 2.0 Server project

@SpringBootApplication
@EnableResourceServer
public class Demo2Application {
    public static void main(String[] args) {
        SpringApplication.run(Demo2Application.class, args);
    }
}

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    private final PasswordEncoder passwordEncoder;
    
    public AuthorizationServerConfig(PasswordEncoder passwordEncoder) {
        this.passwordEncoder = passwordEncoder;
    }    
    
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
            .withClient("abcd")
            .secret(passwordEncoder.encode("fDw7Mpkk5czHNuSRtmhGmAGL42CaxQB9"))
            .authorizedGrantTypes("authorization_code")
            .scopes("user_info")
            .autoApprove(true)
            .redirectUris("http://localhost:8082/login/oauth2/code/");
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security
            .tokenKeyAccess("permitAll()")
            .checkTokenAccess("isAuthenticated()");;
    }
}


@Configuration
@Order(1)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.requestMatchers()
            .antMatchers("/login", "/oauth/authorize")
            .and()
            .authorizeRequests()
            .anyRequest().authenticated()
            .and()
            .formLogin().permitAll();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
            .withUser("john")
            .password(passwordEncoder().encode("doe"))
            .roles("USER");
    }
    
    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }    
}

@RestController
public class UserController {
    @GetMapping("/user/me")
    public Principal user(Principal principal) {
        return principal;
    }    
}

application.properties
server.port=8090

pom.xml
<dependency>
    <groupId>org.springframework.security.oauth</groupId>
    <artifactId>spring-security-oauth2</artifactId>
    <version>2.5.1.RELEASE</version>
</dependency>     
<dependency>
    <groupId>org.springframework.security.oauth.boot</groupId>
    <artifactId>spring-security-oauth2-autoconfigure</artifactId>
    <version>2.5.2</version>
</dependency>           
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

I omit the context path, which the project originally uses.

Webservice project

@RestController
public class MyRESTController {

    @GetMapping("/securedPage")
    public String securedPage(Principal principal) {
        return "securedPage";
    }

    @GetMapping("/")
    public String index(Principal principal) {
        return "index";
    }   
}

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.antMatcher("/**").authorizeRequests()
            .antMatchers("/", "/login**").permitAll()
            .anyRequest().authenticated()
            .and()
            .oauth2Login();
    }
}


application.properties
server.port=8082
server.servlet.session.cookie.name=UISESSION

spring.security.oauth2.client.registration.custom-client.client-id=abcd
spring.security.oauth2.client.registration.custom-client.client-secret=fDw7Mpkk5czHNuSRtmhGmAGL42CaxQB9
spring.security.oauth2.client.registration.custom-client.client-name=Auth Server
spring.security.oauth2.client.registration.custom-client.provider=custom-provider
spring.security.oauth2.client.registration.custom-client.scope=user_info
spring.security.oauth2.client.registration.custom-client.redirect-uri=http://localhost:8082/login/oauth2/code/
spring.security.oauth2.client.registration.custom-client.client-authentication-method=basic
spring.security.oauth2.client.registration.custom-client.authorization-grant-type=authorization_code

spring.security.oauth2.client.provider.custom-provider.authorization-uri=http://localhost:8090/oauth/authorize
spring.security.oauth2.client.provider.custom-provider.token-uri=http://localhost:8090/oauth/token
spring.security.oauth2.client.provider.custom-provider.user-info-uri=http://localhost:8090/user/me
spring.security.oauth2.client.provider.custom-provider.user-name-attribute=name

pom.xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

That works fine.

Now, when I use the OAuth 2.0 Server project with my webservice project, means I add the application properties from the demo webservice project into my application.properties I cannot access any other endpoints but the root endpoint http://localhost:8082.

I assume that the WebSecurityConfig in my webservice project is the reason for that. It looks like this:

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    public static final String ROLE_MYUSER = "MYUSER";
    public static final String ROLE_MYADMIN = "MYADMIN";
    
    private MyUserProperties myUserProperties;

    public WebSecurityConfig(MyUserProperties myUserProperties) {
        // Load usernames and passwords from properties file
        this.myUserProperties = myUserProperties;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            //.httpBasic()
            .and()
            .authorizeRequests()
                .antMatchers("/").permitAll()
            
                .antMatchers("/abc/**").hasRole(ROLE_MYUSER)
                .mvcMatchers(HttpMethod.GET, "/def1/{name}", "/def2/{name}").hasRole(ROLE_MYUSER)
                .mvcMatchers(HttpMethod.PATCH, "/def/{name}", "/def2/{name}").hasRole(ROLE_MYUSER)           
                .antMatchers("/ghi/**").hasRole(ROLE_MYUSER)
                //... and so on
            
                .antMatchers("/**").hasRole(ROLE_MYADMIN)
            .and()
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            .oauth2Login();    
            //.csrf().disable()
            //.formLogin().disable();
    }    

    @Bean
    @Override
    public UserDetailsService userDetailsService() {
        UserDetails myuser = User
            .withUsername(myUserProperties.getPortal().get("user"))
            .password("{noop}" + myUserProperties.getPortal().get("pass"))
            .roles(ROLE_MYUSER)
            .build();

        UserDetails myadmin = User
            .withUsername(myUserProperties.getAdmin().get("user"))
            .password("{noop}" + myUserProperties.getAdmin().get("pass"))
            .roles(ROLE_MYUSER, ROLE_MYADMIN)
            .build();

        InMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager();

        userDetailsManager.createUser(myuser);
        userDetailsManager.createUser(myadmin);

        return userDetailsManager;
    }   
}

I had Basic Auth so far, I defined my two users with the two roles in a

@Bean
@Override
public UserDetailsService userDetailsService() {

Now switching to OAuth2 I removed in

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

the lines

.httpBasic()
.csrf().disable()
.formLogin().disable()

and added

.oauth2Login()

The behavior in the web browser is the following

http://localhost:8082

delivers correctly the data from that root endpoint, without any login.

Any other endpoint, like for example

http://localhost:8082/abc

first leads to the login page, where I enter the defined user john / doe from the OAuth 2.0 Server project, after that it doesn't show the expected data from the endpoint http://localhost:8082/abc , but jumps back to the root endpoint http://localhost:8082.

The first step I tried is to place

@Bean
@Override
public UserDetailsService userDetailsService() {

in the OAuth 2.0 Server project, into the class

@Configuration
@Order(1)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

but with that the login didn't work, it only accepted john / doe.

The next stept was to remove

@Bean
@Override
public UserDetailsService userDetailsService() {

also from that class and define my two users in

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {

where john / doe was defined.

The login with one of my users works now, but the wrong behavior still remains. What else has to be altered, and where?

The access to the endpoints in my webservice project depends on the roles the user has.

My pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency> 

Solution

  • The solution here OAuth2 Client Principal do not have GrantedAuthorities when authenticated by Other Custom Authorization Server (SpringBoot2 & OAuth2)

    solved it.

    CustomOAuth2User 
    CustomOAuth2UserService 
    CustomAuthoritiesExtractor