简体   繁体   中英

Spring Boot and JPA Repository — how to filter a GET by ID

I'm rewriting an application, this time using a RESTful interface from Spring. I'm presuming that server-side authorization is best. That is:

  • Supppose user 1 works this REST repository. He/she accesses mysite.com/heroes/1 and gets the (id = 1) hero from the hero table.

  • User 2 doesn't have rights to see the (id = 1) hero, but could craft a cURL statement to try anyway. I claim the server should prevent user 2 from accessing the (id = 1) hero.

I believe that the server can extract a JWT payload that gives me the user name or password (I put it in there). From that payload the server fetches the user's account and knows what heroes he/she is entitled to see.

I have already accomplished this goal through services and DAO classes. However, the Spring Boot and JPA tutorials I see promote using CrudRepository implementations to reduce coding. I'd like to know how to do my filtering using this technology.

Here is an example from the web:

@RepositoryRestResource(collectionResourceRel = "heroes", path = "heroes")
public interface HeroRepository extends CrudRepository<Hero, Long> {
}

When mysite.com/heroes/1 is accessed it automagically returns the data from hero (id = 1). I'd like to instruct it to let me choose which ID values to permit. That is, at runtime a query parameter is provided to it through code.

As a test I provided this code:

@RepositoryRestResource(collectionResourceRel = "heroes", path = "heroes")
public interface HeroRepository extends CrudRepository<Hero, Long> {

    @Query ("from Hero h where id in (1, 3, 5)")
    public Hero get();

}

However, it doesn't block mysite.com/heroes/2 from returning the (id = 2) hero.

How do I get to my desired goal?

Thanks, Jerome.

UPDATE 5/13, 5:50 PM

My request is being misunderstood, so I further explain my intent.

  • Users 1 and 2 are ordinary users, accessing their accounts.
  • Each user must be confined to his/her own account.
  • A user can't cheat by crafting requests for other peoples' data.

Thus the need for the server to extract a user ID, or such, from a JWT token and apply it in code to whatever causes the /heroes query to work.

My original example originated with this tutorial . In it the only Java classes are Hero and HeroRepository. There are no explicit classes for DAO, services or controllers. The included Spring libraries let all of the /heroes fetching occur without further coding.

Thanks again for all of your interest and help. Jerome.

I have created HeroRepository to resolve all the queries up to my understanding.

I'd like to instruct it to let me choose which ID values to permit.

You can achieve the same using.

List<Hero> findByIdIn(List<Long> ids);

Or, if you prefer Query

@Query("SELECT H FROM Hero H WHERE H.id IN :ids")
List<Hero> alternativeFindByIdIn(@Param("ids") List<Long> ids);

it doesn't block mysite.com/heroes/2 from returning the (id = 2) hero.

I cannot see your Controller/Service methods, so I am assuming that findOne() is being called. You can prevent it using..

// Disallow everybody to use findOne()
default Hero findOne(Long id) {
    throw new RuntimeException("Forbidden !!");
} 

OR, if you want more control over your method invocations, you can also use @PreAuthorize from spring-security .

// Authorization based method call
@PreAuthorize("hasRole('ADMIN')")
Optional<Hero> findById(Long id);

Summary

public interface HeroRepository extends CrudRepository<Hero, Long> {

    // Disallow everybody to use findOne()
    default Hero findOne(Long id) {
        throw new RuntimeException("Forbidden !!");
    }

    // If u want to pass ids as a list
    List<Hero> findByIdIn(List<Long> ids);

    // Alternative to above one
    @Query("SELECT H FROM Hero H WHERE H.id IN :ids")
    List<Hero> alternativeFindByIdIn(@Param("ids") List<Long> ids);

    // Authorization based method call
    @PreAuthorize("hasRole('ADMIN')")
    Optional<Hero> findById(Long id);

}

PS: Note that I am returning Optional<Hero> from the method. Optional.empty() will be returned if query produces no results. This will force us to check if the value is present before doing any operation, thereby avoiding NullPointerException .

You can create a custom @Query , that uses informations (here: id ) of the logged in user. With this solution an user have only access to an entity with the same id as he has.

@Override
@Query("SELECT h FROM Hero h WHERE h.id=?1 AND h.id=?#{principal.id}")
public Hero findOne(Long id);

You need to enable SpEl for @Query ( link ) and create an custom UserDetailsService ( link ) with custom UserDetails, that contains the id of the user, so you can do principal.id .

In the same way you should secure the findAll() method.

use this code for Controller : -

@RestController
@RequestMapping("/cities")
public class CityController {

private static final Logger logger = LoggerFactory.getLogger(CityController.class);

@Autowired
private CityService cityService;


@RequestMapping(value = "/{id}", method = RequestMethod.GET)
public RestResponse find(@PathVariable("id") Long id) {
.
.
}

use below code for Repo :-

 public interface CityRepo extends JpaRepository<FCity, Long> {

@Query("select e from FCity e where  e.cityId = :id")
FCity findOne(@Param("id") Long id);

}

use below code for service :-

@Service
@Transactional
public class CityService {

@Autowired(required = true)
private CityRepo cityRepo;

public FCity findOne(Long id) {
    return cityRepo.findOne(id);
}

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM