I created a Spring Boot app and have trouble with some endpoints that can be triggered manually or via #Scheduled annotation.
The issue I get is the one below:
org.springframework.security.authentication.AuthenticationCredentialsNotFoundException:
An Authentication object was not found in the SecurityContext
Is there a way to trigger SecurityContext if the process called via #Scheduled?
I am new to Spring Security and it is very hard for me to understand the reference guide. I found some similar questions but still cannot understand how to apply answers to my case.
Example of my MyController:
#Secured("ROLE_ADMIN")
#RestController
#RequestMapping(value = "/api")
public class MyController {
#Scheduled(cron = "* * * * * *")
#GetMapping(path="/data")
public void getData() {
// Do some operations
}
}
The #Scheduled method should not be #Secured. Scheduled methods are already in trusted code.
Refactor your code, e.g.
#Secured("ROLE_ADMIN")
#RestController
#RequestMapping(value = "/api")
public class MyController {
#Autowired
private MyService myService;
#PostMapping(path="/run")
public void runJobs() {
myService.runJobs();
}
}
#Service
public class MyService {
#Scheduled(cron = "* * * * * *")
public void runJobs() {
// Do some operations
}
}
Basically you have to configure your scheduler with the necessary authentication. Something in the lines of the following:
import com.google.common.collect.ImmutableList;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.concurrent.DelegatingSecurityContextScheduledExecutorService;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import java.util.List;
import java.util.concurrent.Executor;
import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
import static org.springframework.security.core.context.SecurityContextHolder.getContext;
#Configuration
public class SchedulerConfiguration implements SchedulingConfigurer {
private static final String KEY = "spring";
private static final String PRINCIPAL = "spring";
private static final List<SimpleGrantedAuthority> AUTHORITIES = ImmutableList.of(new SimpleGrantedAuthority("ADMIN"));
#Override
public void configureTasks(final ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(taskExecutor());
}
#Bean
public Executor taskExecutor() {
final AnonymousAuthenticationToken token = new AnonymousAuthenticationToken(KEY, PRINCIPAL, AUTHORITIES);
final SecurityContext securityContext = getContext();
securityContext.setAuthentication(token);
return new DelegatingSecurityContextScheduledExecutorService(newSingleThreadScheduledExecutor(), securityContext);
}
}
Then you can annotate your controller with #Secured("hasAuthority('ADMIN')").
Related
I like to have an implementation of one #Scheduled job using different configuration properties of .ymlfile.
Means in my yaml file I describe the cron expression as a list:
job:
schedules:
- 10 * * * * *
- 20 * * * * *
I read those values out using Configuration and created a #Bean named scheduled:
#Configuration
#ConfigurationProperties(prefix="job", locations = "classpath:cronjob.yml")
public class CronConfig {
private List<String> schedules;
#Bean
public List<String> schedules() {
return this.schedules;
}
public List<String> getSchedules() {
return schedules;
}
public void setSchedules(List<String> schedules) {
this.schedules = schedules;
}
}
In my Job class I want to start the execution of one method but for both of the schedules in my configuration.
#Scheduled(cron = "#{#schedules}")
public String execute() {
System.out.println(converterService.test());
return "success";
}
With this solution the application creates an error: (more or less clear)
Encountered invalid #Scheduled method 'execute': Cron expression must consist of 6 fields (found 12 in "[10 * * * * *, 20 * * * * *]")
Is there a way to configure the same scheduled job method with multiple declarations of cron expressions?
EDIT 1
After some try I just used a second annotation on the executer method.
#Scheduled(cron = "#{#schedules[0]}")
#Scheduled(cron = "#{#schedules[1]}")
public String execute() {
System.out.println(converterService.test());
return "success";
}
This solution works but is not really dynamic. Is there also a way to make this dynamic?
(edit since I found a way to perform this)
You can actually do this. Below I'm showcasing a working example:
cronjob.yaml
job:
schedules:
- 10 * * * * *
- 20 * * * * *
the actual task to perform MyTask:
package hello;
import org.springframework.stereotype.Component;
#Component
public class MyTask implements Runnable {
#Override
public void run() {
//complicated stuff
}
}
Your CronConfig as is:
package hello;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.List;
#Configuration
#ConfigurationProperties(prefix="job", locations = "classpath:cronjob.yml")
public class CronConfig {
private List<String> schedules;
#Bean
public List<String> schedules() {
return this.schedules;
}
public List<String> getSchedules() {
return schedules;
}
public void setSchedules(List<String> schedules) {
this.schedules = schedules;
}
}
The ScheduledTask bean that is responsible to schedule all crons:
package hello;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.stereotype.Component;
#Component
public class ScheduledTasks {
#Autowired
private TaskScheduler taskScheduler;
#Autowired
private CronConfig cronConfig;
#Autowired
private MyTask myTask;
private static final Logger log = LoggerFactory.getLogger(ScheduledTasks.class);
public void scheduleAllCrons() {
cronConfig.getSchedules().forEach( cron -> taskScheduler.schedule(myTask, new CronTrigger(cron)) );
}
}
The context/main class Application:
package hello;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.concurrent.ConcurrentTaskScheduler;
#SpringBootApplication
#EnableScheduling
#EnableAsync
public class Application {
#Bean
public TaskScheduler taskScheduler() {
return new ConcurrentTaskScheduler();
}
public static void main(String[] args) throws Exception {
ApplicationContext ctx = SpringApplication.run(Application.class);
ScheduledTasks scheduledTasks = ctx.getBean(ScheduledTasks.class);
scheduledTasks.scheduleAllCrons();
}
}
application.yaml:
crontab:
submitSubtask: "0 * * * * *"
updateBacktrace: "2/30 * * * * *"
java code:
#EnableScheduling
public class Demo{
#Scheduled(cron = "${crontab.updateBacktrace}")
private void updateBacktrace() {
...
}
#Scheduled(cron = "${crontab.submitSubtask}")
private void submitSubtask() {
...
}
}
it works for springboot 2.3.7.
A trick related to it:
while defining the corn job timing, attribute name should be in lower case
for example: if it is in Camel case, spring did not kick the job :(
application.yml:
common:
scheduler:
feedeErrorLogCleanUp: 0 0/5 * ? * *
WHEREAS BELOW STUFF WORKS
common:
scheduler:
feedeerrorlogcleanUp: 0 0/5 * ? * *
I created the following service interface:
import javax.validation.constraints.NotBlank;
import org.springframework.lang.NonNull;
import org.springframework.validation.annotation.Validated;
#Validated
public interface UserService {
User create(#NonNull Long telegramId, #NotBlank String name, #NonNull Boolean isBot);
}
but the following invocation:
userService.create(telegramId, "Mike", null);
passes the #NotNull validation for isBot parameter. How to correctly configure Spring Boot and my service in order to take into account #NonNull annotation and prevent method execution in case of null parameter?
I played around with this problem for a bit.
Your code looks fine to me: Make sure that the implementation of UserService also has the validation annotations present.
Ensure that you allow Spring to create the Bean; it should work as you expect.
Example
Service Definition
import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
#Validated
public interface GreetingService {
String greet(#NotNull #NotBlank String greeting);
}
Service Implementation
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
#Service
public class HelloGreetingService implements GreetingService {
public String greet(#NotNull #NotBlank String greeting) {
return "hello " + greeting;
}
}
Testcase
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import javax.validation.ConstraintViolationException;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
#SpringBootTest
class HelloGreetingServiceTest {
#Autowired
private GreetingService helloGreetingService;
#Test
void whenGreetWithStringInput_shouldDisplayGreeting() {
String input = "john doe";
assertEquals("hello john doe", helloGreetingService.greet(input));
}
#Test
void whenGreetWithNullInput_shouldThrowException() {
assertThrows(ConstraintViolationException.class, () -> helloGreetingService.greet(null));
}
#Test
void whenGreetWithBlankInput_shouldThrowException() {
assertThrows(ConstraintViolationException.class, () -> helloGreetingService.greet(""));
}
}
Testcases are green for me.
Github: https://github.com/almac777/spring-validation-playground
Source: https://www.baeldung.com/javax-validation-method-constraints
HTH!
Use the same thing in Implementation class instead interface.
Also can write one global exception like:
#Order(Ordered.HIGHEST_PRECEDENCE)
#RestControllerAdvice
public class GlobalRestException extends ResponseEntityExceptionHandler {
...
...
/**
* Handle MethodArgumentNotValidException. Triggered when an object fails #Valid
* validation.
*
* #param ex the MethodArgumentNotValidException that is thrown when #Valid
* validation fails
* #param headers HttpHeaders
* #param status HttpStatus
* #param request WebRequest
* #return the ApiException object
*/
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex,
HttpHeaders headers, HttpStatus status, WebRequest request) {
Error apiError = new Error(BAD_REQUEST);
apiError.setMessage("Validation error");
apiError.addValidationErrors(ex.getBindingResult().getFieldErrors());
apiError.addValidationError(ex.getBindingResult().getGlobalErrors());
return buildResponseEntity(apiError);
}
}
There are more method that can be override to handle different kind of exception like :
/**
* Handles javax.validation.ConstraintViolationException. Thrown when #Validated
* fails.
*
* #param ex the ConstraintViolationException
* #return the ApiException object
*/
#ExceptionHandler(javax.validation.ConstraintViolationException.class)
protected ResponseEntity<Object> handleConstraintViolation(javax.validation.ConstraintViolationException ex) {
Error apiError = new Error(BAD_REQUEST);
apiError.setMessage("Validation error");
apiError.addValidationErrors(ex.getConstraintViolations());
return buildResponseEntity(apiError);
}
You need to make sure that #Validated annotation is used on 'class' which method arguments will need to be validated and Spring configuration need to be added
#Configuration
public class MethodValidationConfig {
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
}
I completely new to Junit and I have to write Junit Test case for my Rest Controller but I am not getting from where should I start. Any help would be really appreciated.
This is My Rest Controller class.
#RestController
public class RecognitionController {
private FrameDecoder frameDecoder;
private TagEncoder tagEncoder;
private RecognitionService recognitionService;
#Autowired
public RecognitionController(FrameDecoder frameDecoder, TagEncoder tagEncoder,
RecognitionService recognitionService) {
this.frameDecoder = frameDecoder;
this.tagEncoder = tagEncoder;
this.recognitionService = recognitionService;
}
/**
*
* #param take the input as Json Frame and map the output at api/detection Url.
* #return List of Json tag in the Http response.
*/
#RequestMapping(value = "/api/detection", method = RequestMethod.POST)
public List<JsonTag> analyseframe(#RequestBody JsonFrame frame) {
SimpleFrame simpleFrame = frameDecoder.decodeFrame(frame);
List<OrientedTag> orientedTags = recognitionService.analyseFrame(simpleFrame);
return tagEncoder.encodeTag(orientedTags);
}
}
For testing Rest Controller you need:
JUnit
Mockito
Spring Test
JsonPath
Controller:
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.List;
#RestController
#RequestMapping(value = "/Entity")
public class EntityRestController {
private EntityService service;
#RequestMapping(value = "/entity/all", method = RequestMethod.GET)
public List<Entity> findAll() {
List<Entity> models = service.findAll();
return createEntities(models);
}
private List<EntityDTO> createDTOs(List<Entity> models) {
List<EntityDTO> dtos = new ArrayList<>();
for (Entitymodel: models) {
dtos.add(createDTO(model));
}
return dtos;
}
private EntityDTO createDTO(Entity model) {
EntityDTO dto = new EntityDTO();
dto.setId(model.getId());
dto.setDescription(model.getDescription());
dto.setTitle(model.getTitle());
return dto;
}
}
Test example:
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import java.util.Arrays;
import static org.hamcrest.Matchers.*;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {TestContext.class, WebAppContext.class})
#WebAppConfiguration
public class EntityRestControllerTest {
private MockMvc mockMvc;
#Autowired
private EntityService entityServiceMock;
//Add WebApplicationContext field here.
//The setUp() method is omitted.
#Test
public void findAllEntitiesTest() throws Exception {
Entity first = new Entity();
first.setId(1L);
first.setDescription("Lorem ipsum")
first.setTitle("Foo");
Entity second = new Entity();
second.setId(2L);
second.setDescription("Lorem ipsum")
second.setTitle("Bar");
when(entityServiceMock.findAll()).thenReturn(Arrays.asList(first, second));
mockMvc.perform(get("/entity/all"))
.andExpect(status().isOk())
.andExpect(content().contentType(TestUtil.APPLICATION_JSON_UTF8))
.andExpect(jsonPath("$", hasSize(2)))
.andExpect(jsonPath("$[0].id", is(1)))
.andExpect(jsonPath("$[0].description", is("Lorem ipsum")))
.andExpect(jsonPath("$[0].title", is("Foo")))
.andExpect(jsonPath("$[1].id", is(2)))
.andExpect(jsonPath("$[1].description", is("Lorem ipsum")))
.andExpect(jsonPath("$[1].title", is("Bar")));
verify(entityServiceMock, times(1)).findAll();
verifyNoMoreInteractions(entityServiceMock);
}
}
Please follow the full tutorial for more details.
___EDIT_1___
I didn't understand from where "thenReturn" Method came
static method Mockito.when() has the following signature:
public static <T> OngoingStubbing<T> when(T methodCall)
When you mocking some service and putting it inside when as parameter - it returns object which IS OngoingStubbing<T>. All classes which implement OngoingStubbing<T> have thenReturn(T value) method and it's called.
I know I should not be testing void methods like this, but I am just testing Mockito.doNothing() as of now with a simple example.
My Service class:
#Service
public class Service{
#Autowired
private Consumer<String, String> kafkaConsumer;
public void clearSubscribtions(){
kafkaConsumer.unsubscribe();
}
}
My Test class:
#MockBean
private Consumer<String, String> kafkaConsumer;
#Test
public void testClearSubscriptions() {
Service service = new Service();
Mockito.doNothing().when(kafkaConsumer).unsubscribe();
service.clearSubscriptions();
}
The test keeps failing with a null pointer exception. When I debugged it, it goes into the clearSubscription method of the service class, and there on the line of kafkaConsumer.unsubscribe(), kafkaConsumer is null. But I mocked the consumer, why is it throwing null pointer exception and I should be skipping over that method, right?
Edit:
All the declarations of the class:
#Autowired
private Consumer<String, String> kafkaConsumer;
#Autowired
private Service2 service2;
private final Object lock = new Object();
private static Logger logger = LoggerFactory.getLogger(Service.class);
private HashMap<String, String> subscribedTopics = new HashMap<>();
Figured out what was wrong, I needed to auto wire the service
You are instantiating a new service Service service = new Service(); but from what I can see you are never injecting the mock bean into the new service.
Here is a sample of what I think you could do if you are using mockito only and dont need to instantiate a spring container (used a single class for ease of example dont do this in actual code):
package com.sbp;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
#RunWith(MockitoJUnitRunner.class) // run with mockitos runner so annotations are processed
public class MyServiceTest {
public interface Consumer<T, R> {
public void unsubscribe();
}
#Service
public class KafkaConsumer implements Consumer<String, String> {
#Override
public void unsubscribe() {
}
}
#Service
public class MyService {
#Autowired
private Consumer<String, String> kafkaConsumer;
public void clearSubscriptions() {
kafkaConsumer.unsubscribe();
}
}
#Mock // tell mockito that this is a mock class - it will instantiate for you
private Consumer<String, String> kafkaConsumer;
#InjectMocks // tell mockito to inject the above mock into the class under test
private MyService service = new MyService();
#Test
public void testClearSubscriptions() {
service.clearSubscriptions();
Mockito.verify(kafkaConsumer, Mockito.times(1)).unsubscribe();
}
}
If you need an example via Spring using MockBean or without and dependencies, let me know and I can post.
UPDATED: adding sample using spring junit runner and using spring boot's mockbean annotation
package com.sbp;
import com.sbp.MyServiceTest.TestContext.MyService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Service;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
#RunWith(SpringJUnit4ClassRunner.class) // run with spring
#SpringBootTest(classes = MyServiceTest.TestContext.class) // make it a spring boot test so #MockBean annotation is processed, provide a dummy test context class
public class MyServiceTest {
public interface Consumer<T, R> {
public void unsubscribe();
}
#Configuration
public static class TestContext {
#Service
public class KafkaConsumer implements Consumer<String, String> {
#Override
public void unsubscribe() {
}
}
#Service
public class MyService {
#Autowired
private Consumer<String, String> kafkaConsumer;
public void clearSubscriptions() {
kafkaConsumer.unsubscribe();
}
}
}
#MockBean // this will create a mockito bean and put it in the application context in place of the Kafka consumer bean defined in the TestContext class
private Consumer<String, String> kafkaConsumer;
#Autowired // inject the bean from the application context that is wired with the mock bean
private MyService myService;
#Test
public void testClearSubscriptions() {
myService.clearSubscriptions();
Mockito.verify(kafkaConsumer, Mockito.times(1)).unsubscribe();
}
}
I want to retrieve the current user in my controller methods with the #AuthenticationPrincipal annotation. The docs state the following:
Annotation that binds a method parameter or method return value to the Authentication.getPrincipal().
But in fact I get the Authentication object instead of Authentication.getPrincipal().
This is my simple controller method:
#RequestMapping("/")
public #ResponseBody String index(#AuthenticationPrincipal final WindowsAuthenticationToken user) {
return String.format("Welcome to the home page, %s!", user.getName());
}
WindowsAuthenticationToken implements Authentication. In this implementation getPrincipal returns a WindowsPrincipal.
The controller method above works, but when I change the arguments type to WindowsPrincipal and try to access the website, I get the following error page:
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Tue Mar 03 15:13:52 CET 2015
There was an unexpected error (type=Internal Server Error, status=500).
argument type mismatch HandlerMethod details: Controller [pkg.HomeController] Method [public java.lang.String pkg.HomeController.index(waffle.servlet.WindowsPrincipal)] Resolved arguments: [0] [type=waffle.spring.WindowsAuthenticationToken] [value=waffle.spring.WindowsAuthenticationToken#121a2581]
This is my configuration file:
package pkg;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import waffle.servlet.spi.BasicSecurityFilterProvider;
import waffle.servlet.spi.NegotiateSecurityFilterProvider;
import waffle.servlet.spi.SecurityFilterProvider;
import waffle.servlet.spi.SecurityFilterProviderCollection;
import waffle.spring.NegotiateSecurityFilter;
import waffle.spring.NegotiateSecurityFilterEntryPoint;
import waffle.windows.auth.impl.WindowsAuthProviderImpl;
#Configuration
#EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private NegotiateSecurityFilterEntryPoint negotiateSecurityFilterEntryPoint;
#Autowired
private NegotiateSecurityFilter waffleNegotiateSecurityFilter;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.exceptionHandling().authenticationEntryPoint(negotiateSecurityFilterEntryPoint).and()
.addFilterBefore(waffleNegotiateSecurityFilter, BasicAuthenticationFilter.class).authorizeRequests()
.anyRequest().fullyAuthenticated();
}
#Bean
public WindowsAuthProviderImpl waffleAuthProvider() {
return new WindowsAuthProviderImpl();
}
#Bean
public NegotiateSecurityFilterProvider negotiateSecurityFilterProvider(
final WindowsAuthProviderImpl waffleAuthProvider) {
return new NegotiateSecurityFilterProvider(waffleAuthProvider);
}
#Bean
public BasicSecurityFilterProvider basicSecurityFilterProvider(final WindowsAuthProviderImpl waffleAuthProvider) {
return new BasicSecurityFilterProvider(waffleAuthProvider);
}
#Bean
public SecurityFilterProviderCollection waffleSecurityFilterProviderCollection(
final NegotiateSecurityFilterProvider negotiateSecurityFilterProvider,
final BasicSecurityFilterProvider basicSecurityFilterProvider) {
final SecurityFilterProvider[] providers = { negotiateSecurityFilterProvider, basicSecurityFilterProvider };
return new SecurityFilterProviderCollection(providers);
}
#Bean
public NegotiateSecurityFilterEntryPoint negotiateSecurityFilterEntryPoint(
final SecurityFilterProviderCollection waffleSecurityFilterProviderCollection) {
final NegotiateSecurityFilterEntryPoint entryPoint = new NegotiateSecurityFilterEntryPoint();
entryPoint.setProvider(waffleSecurityFilterProviderCollection);
return entryPoint;
}
#Bean
public NegotiateSecurityFilter waffleNegotiateSecurityFilter(
final SecurityFilterProviderCollection waffleSecurityFilterProviderCollection) {
final NegotiateSecurityFilter filter = new NegotiateSecurityFilter();
filter.setProvider(waffleSecurityFilterProviderCollection);
return filter;
}
}
Why is the behaviour different from how it should be?
My principal object did not implement UserDetails. Because WindowsPrincipal is a class of an external library I could not make any changes to it. In the end I created a new filter that wraps the WindowsPrincipal in a class that implements UserDetails. Now I get the correct principal object using #AuthenticationPrincipal.
It is because your WindowsPrincipal implements Principal. Remove the implements clause and it will work again. I had the same problem and this resolved it.