0

I mostly followed https://www.youtube.com/watch?v=EoK5a99Bmjc&list=WL&index=12 (summary: https://netcrash.wordpress.com/2017/08/21/summary-of-josh-long-youtube-video-about-security-oauth/#comment-39 until ResourceServer comes into play).

I have read Access sensitive Spring boot actuator endpoints via tokens in browser as it comes close to what I need, yet I do not necessarily need a browser and that question is not answered.

In contrast to the tutorial I included spring-boot-starter-actuator as a dependency in my pom.xml.

My application.properties looks like this:

logging.level=DEBUG
server.port=9191
endpoints.health.enabled=true
endpoints.health.sensitive=false
endpoints.shutdown.enabled=true
endpoints.shutdown.sensitive=true
management.contextPath: /manage
management.security.enabled=true

My AccountUserDetailsService looks like this:

package com.divstar.particle.authservice.rest;
import java.text.MessageFormat;

import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

@Service
public class AccountUserDetailsService implements UserDetailsService {

    private final IAccountRepository accountRepository;

    public AccountUserDetailsService(final IAccountRepository accountRepository) {
        this.accountRepository = accountRepository;
    }

    @Override
    public UserDetails loadUserByUsername(final String username) {
        return accountRepository.findByUsername(username)
                                .map(account -> new User(account.getUsername(), account.getPassword(), account.isEnabled(),
                                        account.isNonExpired(), account.isCredentialsNonExpired(), account.isNonLocked(),
                                        AuthorityUtils.createAuthorityList("ROLE_ADMIN", "ROLE_USER", "ROLE_ACTUATOR")))
                                .orElseThrow(() -> new UsernameNotFoundException(
                                        MessageFormat.format("Could not find the user {0}!", username)));
    }
}

I included the role "ROLE_ACTUATOR", because it seems that that's what the actuator requires in order to access the management service endpoints.

The main application class looks rather simple:

package com.divstar.particle.authservice;

import java.util.stream.Stream;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

import com.divstar.particle.authservice.rest.Account;
import com.divstar.particle.authservice.rest.IAccountRepository;

@SpringBootApplication
public class Application {
    @Bean
    CommandLineRunner initUsers(final IAccountRepository userRepository) {
        return args -> Stream.of("test,long", "blame,short", "pass,secure")
                             .map(tpl -> tpl.split(","))
                             .forEach(tpl -> userRepository.save(new Account(tpl[0], tpl[1], "sample@mail.com", true, true, true, true)));
    }

    public static void main(final String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

IAccountRepository extends JpaRepository and defines "findByUsername", which returns Optional.

My AuthorizationServerConfigurationAdapter looks like this:

package com.divstar.particle.authservice;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
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.configurers.AuthorizationServerEndpointsConfigurer;

@Configuration
@EnableAuthorizationServer
public class AuthServiceConfiguration extends AuthorizationServerConfigurerAdapter {
    private final AuthenticationManager authenticationManager;

    public AuthServiceConfiguration(final AuthenticationManager authenticationManager) {
        super();
        this.authenticationManager = authenticationManager;
    }

    @Override
    public void configure(final ClientDetailsServiceConfigurer clients) throws Exception {
        clients
               .inMemory()
               .withClient("html5")
               .secret("clientsecret")
               .authorizedGrantTypes("password")
               .scopes("openid");
    }

    @Override
    public void configure(final AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(authenticationManager);
    }
}

After starting up the authentication server I use the following cURL-command to obtain a token:

 curl html5:clientsecret@localhost:9191/oauth/token -d grant_type=password -d username=test -d password=long

It returns the access_token as well as further information in a JSON reply.

Yet if I try to access a sensitive endpoint (e.g. /manage/shutdown) like this:

curl -X POST -H"Authorization: bearer b8412762-af80-4b7d-9eb9-3fa472dd37c9" localhost:9191/manage/shutdown

I receive the following error message:

{"timestamp":1508029184236,"status":401,"error":"Unauthorized","message":"Full authentication is required to access this resource","path":"/manage/shutdown"}

What am I missing / doing wrong? How can I let spring-boot actuator recognize JWTs and execute a given sensitive endpoint if the token is valid (given the proper authority / role)?

Igor
  • 1,582
  • 6
  • 19
  • 49

1 Answers1

0

Based off of the information you shared, it looks like you are not actually telling Spring to use your custom UserDetailsService. In order to do that you have to have a class that extends WebSecurityConfigurerAdapter and then override the configure(AuthenticationManagerBuilder auth) method like so:

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  auth.userDetailsService(customUserDetailsService);
}

See here for more info on OAuth2 and custom UserDetailsService: https://stackoverflow.com/a/43245998/1320627

The easiest way to ensure you have your user details service configured correctly is to add a REST endpoint, protect with authentication, and then try to hit it.

TddOrBust
  • 450
  • 2
  • 9
  • I indeed did not extend WebSecurityConfigurerAdapter nor inject my custom UserDetailsService there. I tried this now, but it does not seem to change anything. If I have custom security for actuator endpoints, do I need to turn **"management.security.enabled" on or off**? I will update my question with the newly created class. I also did not entirely understand what you mean by "add a REST endpoint" - I thought that's what actuator already did? Except I do not know how to protect it properly. – Igor Oct 15 '17 at 09:41
  • Intriguing. My point was to suggest you add a new controller get mapping so that you can test to see if your authentication process is working correctly. You are right that Actuator does the same thing. If you had an existing controller mapping that worked, then you would just have more information for tracking down the problem. I suspect the problem is still with your authorization configuration. More in a moment. – TddOrBust Oct 15 '17 at 17:06