java 92 lines · 3 tabs

Guard a Checkout Endpoint with @PreAuthorize and a Custom PermissionEvaluator in Spring Boot

Shared by codesnips Aug 2026
3 tabs
package com.shop.security;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;

@Configuration
@EnableMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig {

    @Bean
    MethodSecurityExpressionHandler methodSecurityExpressionHandler(PermissionEvaluator permissionEvaluator) {
        DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();
        handler.setPermissionEvaluator(permissionEvaluator);
        return handler;
    }
}
3 files · java Explain with highlit

Spring Security's method-level annotations let authorization live next to the code it protects instead of being scattered across URL matchers. In MethodSecurityConfig, @EnableMethodSecurity(prePostEnabled = true) turns on the @PreAuthorize/@PostAuthorize machinery, and a MethodSecurityExpressionHandler bean wires in a custom PermissionEvaluator. Registering the handler as a bean is important: the SpEL hasPermission(...) function is inert until an evaluator is attached, so without this step the expression silently fails closed.

The real logic lives in CartPermissionEvaluator, which implements PermissionEvaluator. Spring calls hasPermission(auth, targetId, targetType, permission) — the four-argument overload — when an expression references a domain object by id and type rather than by instance. Here the evaluator loads the Cart by id, then checks two things: that the authenticated principal actually owns the cart, and that the requested permission (a string like "CHECKOUT") is satisfied. Ownership is the classic gap that role checks miss — a user with the CUSTOMER role must still only be able to check out their own cart, which is a per-object decision that no hasRole call can express. The instance-based overload is left unsupported to keep the contract narrow and avoid accidentally trusting a detached object.

CheckoutController shows the payoff. The @PreAuthorize("hasPermission(#cartId, 'CART', 'CHECKOUT')") on checkout runs before the method body, so an unauthorized request never reaches the service and never begins a transaction. The #cartId reference pulls the argument straight from the method signature, and the string literals map to the targetType and permission arguments the evaluator receives.

The trade-off is that this evaluator issues a database read on every guarded call, so it is worth caching or scoping to cheap lookups for hot paths. A subtle pitfall is exception handling: throwing a plain runtime error from the evaluator surfaces as a 500 rather than a clean 403, so CartPermissionEvaluator returns false for missing carts and lets Spring translate the denial into an AccessDeniedException. This pattern fits any endpoint where authorization depends on data ownership rather than static roles, keeping controllers declarative while centralizing the messy rules in one testable evaluator.


Related snips

graphql
type User {
    id: ID!
    name: String!
    email: String!
    posts: [Post!]!
    createdAt: String!

GraphQL API with Spring Boot

java graphql spring-boot
by David Kumar 3 tabs
java
package com.example.starter.config;

import com.example.starter.properties.CustomProperties;
import com.example.starter.service.CustomService;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;

Custom Spring Boot starters

java spring-boot starter
by David Kumar 4 tabs
erb
<%# private stream: turbo signs the serialized record name %>
<%= turbo_stream_from current_user %>

<section class="notifications">
  <h1>Notifications</h1>

Turbo Streams + authorization: signed per-user stream name

rails turbo hotwire
by codesnips 3 tabs
java
package com.example.demo.config;

import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.common.serialization.StringDeserializer;
import org.apache.kafka.common.serialization.StringSerializer;

Messaging with Apache Kafka

java kafka messaging
by David Kumar 3 tabs
ruby
class Document < ApplicationRecord
  belongs_to :owner, class_name: "User"
  has_many :visibilities, class_name: "DocumentVisibility", dependent: :delete_all

  scope :public_documents, -> { where(is_public: true) }

Polymorphic “Visible To” Scope with Arel

rails activerecord arel
by codesnips 3 tabs
java
package com.example.demo.controller;

import com.example.demo.dto.FileMetadata;
import com.example.demo.service.FileStorageService;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;

File upload and download handling

java spring-boot file-upload
by David Kumar 2 tabs

Share this code

Here's the card — post it anywhere.

Guard a Checkout Endpoint with @PreAuthorize and a Custom PermissionEvaluator in Spring Boot — share card
Link copied