Method security not working if I implement UserDetailsService in #Service - spring

I've implemented Spring Security on my project. But method security annotations ignored if I implement UserDetailsService on #Service.
What's wrong with this code?
#Transactional
public interface UserService extends UserDetailsService {
#PreAuthorize("hasRole('ROLE_SUPER')") /* it's ignored. */
void update(UserEditForm form);
#Override
User loadUserByUsername(String username) throws UsernameNotFoundException;
}
#Service
public class SimpleUserService implements UserService {
// ommitted
}
#Transactional
public interface SomeService{
#PreAuthorize("hasRole('ROLE_SUPER')") /* it's working fine */
void doSomething();
}
#Service
public class SimpleSomeService implements SomeService {
// ommitted
}
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
PasswordEncoder passwordEncoder;
#Autowired
UserService userService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService).passwordEncoder(passwordEncoder);
}
// ommitted
}
ps: Sorry I don't have well enough english knowledge.

You need to make changes in SimpleUserService like this
#Service("simpleUserService")
public class SimpleUserService implements UserService {
// ommitted
}
and in SecurityConfig, Autowire UserService with #Qualifier("simpleUserService")
#Autowired
#Qualifier("simpleUserService")
UserService userService;

Do not annotate the interfaces with Spring annotations(#Component, #Service, #Transactional, #Repository), Add the annotations on the implementation classes.
Remove #Transactional on UserService interface, Create new class UserServiceImpl and add annotation #Transactional.
and also move the #PreAuthorize("hasRole('ROLE_SUPER')") annotation on to the implementation method.

Related

AuthenticationFailureBadCredentialsEvent never called

I use spring-boot 2.6.8 with spring security
When user don't enter correct information, i would like to do an operation.
So I created this class.
#Component
public class AuthenticationFailureEventListener implements ApplicationListener<AuthenticationFailureBadCredentialsEvent {
private LoginAttemptService loginAttemptService;
#Override
public void onApplicationEvent(AuthenticationFailureBadCredentialsEvent e) {
WebAuthenticationDetails auth = (WebAuthenticationDetails) e.getAuthentication().getDetails();
loginAttemptService.loginFailed(e.getAuthentication().getName(), auth.getRemoteAddress());
}
}
If a user enter a bad password, this event is never called
Edit
For the security, I have this
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private AuthenticationEventPublisher authenticationEventPublisher;
#Autowired
private PasswordEncoder passwordEncoder;
#Autowired
private CustomAuthenticationSuccessHandler customAuthenticationSuccessHandler;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationEventPublisher(authenticationEventPublisher).userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder);
}
...
}
The events are not published out of the box. You need to also declare an AuthenticationEventPublisher with code like this:
#Bean
public AuthenticationEventPublisher authenticationEventPublisher(
ApplicationEventPublisher applicationEventPublisher
) {
return new DefaultAuthenticationEventPublisher(applicationEventPublisher);
}
Please also have a look at this part of the reference documentation: https://docs.spring.io/spring-security/reference/servlet/authentication/events.html

Why isn't #Service creating bean?

I have implemented custom UserDetailsService but I can't autowire it because bean is not created. Other services in same package work with same anotations.
#Service
#NoArgsConstructor
public class JwtUserDetailService implements UserDetailsService {
#Autowired
UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return new UserPrincipal(userRepository.findByUsername(username));
}
}

Test spring boot controllers with JUnit5+Spring Security

I have a spring boot application and want to write integration tests for controllers. It is my SecurityConfig:
#Configuration
#EnableWebSecurity
#RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final MyUserDetailsService userDetailsService;
private final SessionAuthenticationProvider authenticationProvider;
private final SessionAuthenticationFilter sessionAuthenticationFilter;
#Override
public void configure(WebSecurity web) {
//...
}
#Override
protected void configure(HttpSecurity http) throws Exception {
/...
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider);
auth.userDetailsService(userDetailsService);
}
}
It is my controller:
#RestController
public class MyController {
//...
#GetMapping("/test")
public List<TestDto> getAll(){
List<TestDto> tests= testService.findAll(authService.getLoggedUser().getId());
return mapper.toTestDtos(tests);
}
}
I Created a test(JUnit 5):
#WebMvcTest(TestController.class)
class TestControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean(name = "mockTestService")
private TestService testService;
#Autowired
private TestMapper mapper;
#MockBean(name = "mockAuthService")
private AuthService authService;
private Test test;
#BeforeEach
void setUp() {
User user = new Test();
user.setId("userId");
when(authService.getLoggedUser()).thenReturn(user);
test = new Facility();
test.setId("id");
test.setName("name");
when(testService.findAll("userId")).thenReturn(singletonList(test));
}
#Test
void shouldReturnAllIpaFacilitiesForCurrentTenant() throws Exception {
mockMvc.perform(get("/test").contentType(MediaType.APPLICATION_JSON))
.andDo(print())
.andExpect(status().isOk())
.andExpect(jsonPath("$..id").value(test.getId()))
.andExpect(jsonPath("$..timeOut").value(test.getName()));
}
}
When I start the test I get an exception: Consider defining a bean of type 'com.auth.MyUserDetailsService' in your configuration.
It happens because I have not UserDetailsService bean in the test. What should I do:
Add 3 beans are required for SecurityConfig, like:
#MockBean
MyUserDetailsService userDetailsService;
#MockBean
SessionAuthenticationProvider authenticationProvider;
#MockBean
SessionAuthenticationFilter sessionAuthenticationFilter;
Add test implementation of SecurityConfig
something else
Which approach is better?
For writing tests for your controller endpoints using #WebMvcTest I would use the great integration from MockMvc and Spring Security.
Make sure you have the following dependency:
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
Next, you can mock the authenticated user for your MockMvc requests and also set the username, roles, etc.
Either use an annotation to populate the authenticated user inside the SecurityContext for the test:
#Test
#WithMockUser("youruser")
public void shouldReturnAllIpaFacilitiesForCurrentTenant() throws Exception {
// ...
}
Or use one of the SecurityMockMvcRequestPostProcessors:
this.mockMvc
.perform(
post("/api/tasks")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"taskTitle\": \"Learn MockMvc\"}")
.with(csrf())
.with(SecurityMockMvcRequestPostProcessors.user("duke"))
)
.andExpect(status().isCreated());
You can find more information on this here.

Bean initialisation, PreAuthorize and GlobalAuthenticationConfigurerAdapter

follow configuration is not work, since i have used the #PreAuthorize annotation.
I would like to inject a service in my own AuthenticationProvider. If my service not use the #PreAuthorize annotation, it will work. If i use this annotation, the "my service " bean will be null at the "MyGlobalAuthenticationConfigurerAdapter", because when the my service bean is created the authentification provider is created too (to early). So what can i do?
MyService:
interface MyService{
#PreAuthorize()
void foo(){
}
Config 1:
#Configuration
#ComponentScan
#EnableAutoConfiguration
public class MyConfiguration {
#Bean
public MyService myService() {
return new MyServiceimpl();
}
Config2:
#Configuration
#ComponentScan
#EnableAutoConfiguration
#Order(Ordered.HIGHEST_PRECEDENCE)
public class MyGlobalAuthenticationConfigurerAdapter extends GlobalAuthenticationConfigurerAdapter {
#Autowired
private MyService myService;
#Override
public void configure(final AuthenticationManagerBuilder auth) throws Exception {
final MyAuthenticationProvider myAuthenticationProvider = myAuthenticationProvider ();
auth.authenticationProvider(myAuthenticationProvider );
}
#Bean
public MyAuthenticationProvider myAuthenticationProvider () {
return new MyAuthenticationProvider (myService);
}

Wiring ClientRegistrationService with jdbc datasource

I could successfully set the jdbc datasource to Spring OAuth2 using the following configuration. However I am struggling to wire ClientRegistrationService while it was easy to wire ClientDetailsService.
#Configuration
#EnableAuthorizationServer
protected static class OAuth2AuthorizationConfig extends AuthorizationServerConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private DataSource dataSource;
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource);
}
.....
}
Here is what I tried
Below code fails to find the ClientDetailsService is not instanceof or of assignableFrom JdbcClientDetailsService or ClientRegistrationService
#Controller
public class ClientPortalApplication {
private ClientRegistrationService clientService;
#Autowired
public void setClientDetailsService(ClientDetailsService clientDetailsService) {
if (clientDetailsService instanceof JdbcClientDetailsService)) {
clientService = (ClientRegistrationService) clientDetailsService;
}
}
......
}
Below code wiring fails on finding a bean of type ClientRegistrationService
:
#Controller
public class ClientPortalApplication {
#Autowired
private ClientRegistrationService clientService;
......
}
The ClientDetailsService created in yout AuthorizationServerConfigurerAdapter is not a bean therefore can't be injected. A solution is to create a bean JdbcClientDetailsService inject it in the AuthorizationServerConfigurerAdapter and you will be able to inject it anywhere else:
#Configuration
public class MyConfiguration {
#Autowired
private DataSource dataSource;
#Bean
public JdbcClientDetailsService jdbcClientDetailsService() {
return new JdbcClientDetailsService(dataSource);
}
#Configuration
#EnableAuthorizationServer
protected class OAuth2AuthorizationConfig extends AuthorizationServerConfigurerAdapter {
#Autowired
private JdbcClientDetailsService jdbcClientDetailsService;
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(jdbcClientDetailsService);
}
}
}

Resources