Search code examples
spring-securityspring-data-jpaspring-el

Spring 4 Rest @Query annotation not working with security Principal


I have a simple (still a demo program at this point) Spring program (Spring Rest and Security) that works in a plain way. Through JpaRepository I can GET all (/option handled by findAll()), GET one (/option/1 handled by findOne(Long id)), PUT and POST.

My business model says that a logged-in user should see only the records they have rights to. So a findAll() should return maybe three records, a findOne(id) should return one or zero.

I believe I must tie these to the Principal object in an annotation. I've tried a variety of schemes but haven't yet figured what Spring wants of me. For the findAll() I've tried this:

@Override
@Query("from Option o where o.id = 11")
public List<Option> findAll();

However, findAll() still returns many records.

Then I tried to tie my query to the Principal:

@Override
@Query("from Option o where o.id = ?#{ principal.id}")
public List<Option> findAll(); 

(I've also tried ?#{#principal.id} )

This fails in interesting ways.

  • When it returns HTTP 500 I'm told that "principal" can't be found.
  • Once I reconfigure my program I get a HTTP 404, meaning I'm asking something wrong. But since I don't know how to dump the principal.id value (logging from an interface?) I can't see what I'm doing wrong.

Here is how I expose a UserDetailsService:

@Service
public class JwtUserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

Here is how I configure the principal in the pom.xml:

    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-data</artifactId>
        <!-- <version>4.2.1.RELEASE</version> -->
    </dependency>

and in Java:

@SuppressWarnings("SpringJavaAutowiringInspection")
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private JwtAuthenticationEntryPoint unauthorizedHandler;

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
        authenticationManagerBuilder
                .userDetailsService(this.userDetailsService)
                .passwordEncoder(passwordEncoder());
    }

My questions are:

  • Why doesn't the @Query("from Option o where o.id = 11") return only one record?
  • How can I tell the attributes of "principal", so I can tell if its ID is what I think it is?
  • Are there other questions I should be asking?

Thanks, Jerome.


Solution

  • Why doesn't the @Query("from Option o where o.id = 11") return only one record?

    Because this is not a valid JPQL-query. Your hardcoded test-query must be @Query("SELECT o FROM Option o WHERE o.id = 11")

    How can I tell the attributes of "principal", so I can tell if its ID is what I think it is?

    Your must enable the Spring Security SpEL functions for queries.

    @Configuration
    @EnableJpaRepositories
    class SecurityConfiguration {
    
        @Bean
        EvaluationContextExtension securityExtension() {
            return new SecurityEvaluationContextExtension();
        }
    }
    

    It`s explained here.

    Now ?#{principal.id} will work, if you have a custom UserDetails-Implementation with an id-field. For example:

    import org.springframework.security.core.userdetails.User;
    
    public class HapaUserDetails extends User { //User implements UserDetails
    
        private final long id;
    
        //constructor and getters
    }