java
47 lines · 7 steps
Method-level authorization with @PreAuthorize in Spring
A Spring REST controller guards each endpoint with a different SpEL security rule via @PreAuthorize.
Explained by
highlit
1@RestController
2@RequestMapping("/api/documents")
3@RequiredArgsConstructor
4public class DocumentController {
5
6 private final DocumentService documentService;
7
8 @GetMapping
9 @PreAuthorize("hasAnyRole('USER', 'ADMIN')")
10 public List<DocumentDto> list(@AuthenticationPrincipal UserPrincipal principal) {
11 return documentService.findAccessibleFor(principal.getId());
12 }
13
14 @GetMapping("/{id}")
15 @PreAuthorize("@documentSecurity.canRead(#id, authentication)")
16 public DocumentDto get(@PathVariable Long id) {
17 return documentService.findById(id);
18 }
19
20 @PostMapping
21 @PreAuthorize("hasRole('EDITOR')")
22 public ResponseEntity<DocumentDto> create(@Valid @RequestBody CreateDocumentRequest request,
23 @AuthenticationPrincipal UserPrincipal principal) {
24 DocumentDto created = documentService.create(request, principal.getId());
25 return ResponseEntity.status(HttpStatus.CREATED).body(created);
26 }
27
28 @PutMapping("/{id}")
29 @PreAuthorize("hasRole('EDITOR') and @documentSecurity.isOwner(#id, authentication)")
30 public DocumentDto update(@PathVariable Long id,
31 @Valid @RequestBody UpdateDocumentRequest request) {
32 return documentService.update(id, request);
33 }
34
35 @DeleteMapping("/{id}")
36 @PreAuthorize("hasRole('ADMIN')")
37 @ResponseStatus(HttpStatus.NO_CONTENT)
38 public void delete(@PathVariable Long id) {
39 documentService.delete(id);
40 }
41
42 @PostMapping("/{id}/publish")
43 @PreAuthorize("hasAnyRole('EDITOR', 'ADMIN') and #request.reviewed")
44 public DocumentDto publish(@PathVariable Long id, @RequestBody PublishRequest request) {
45 return documentService.publish(id);
46 }
47}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1@PreAuthorize accepts SpEL, so each endpoint can express its own authorization rule inline.
- 2Beans like @documentSecurity let you move per-resource ownership checks out of the controller body.
- 3Combining role checks with method arguments (#id, #request) enables fine-grained, context-aware access control.
Related explainers
java
public class SlidingLogRateLimiter { private final int maxRequests; private final long windowMillis;
How a sliding-log rate limiter works
rate-limiting
concurrency
sliding-window
Advanced
8 steps
typescript
import { Injectable, UnauthorizedException } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { ExtractJwt, Strategy } from 'passport-jwt'; import { ConfigService } from '@nestjs/config';
How a JWT strategy authenticates in NestJS
authentication
jwt
passport
Intermediate
7 steps
java
public final class Slugifier { private static final Pattern NON_LATIN = Pattern.compile("[^\\w-]"); private static final Pattern WHITESPACE = Pattern.compile("[\\s]+");
Building a URL slugifier in Java
regex
unicode-normalization
string-processing
Intermediate
8 steps
go
package auth import ( "net/http"
Setting and reading secure session cookies in Go
cookies
session-management
security
Intermediate
6 steps
java
@Repository public interface OrderRepository extends JpaRepository<Order, Long> { @Query("""
Keyset pagination with Spring Data JPA
pagination
cursor
jpa
Advanced
8 steps
typescript
import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common'; import { GqlExecutionContext } from '@nestjs/graphql'; import { Reflector } from '@nestjs/core'; import { JwtService } from '@nestjs/jwt';
How a GraphQL auth guard works in NestJS
authentication
authorization
jwt
Intermediate
7 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/method-level-authorization-with-preauthorize-in-spring-explained-java-b7c9/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.