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)?