package com.example.security;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequireRole {
String[] value();
boolean anyOf() default false;
}
package com.example.security;
import java.util.Arrays;
import java.util.Set;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class RoleGuardAspect {
private final CurrentUserProvider currentUser;
public RoleGuardAspect(CurrentUserProvider currentUser) {
this.currentUser = currentUser;
}
@Before("@annotation(requireRole)")
public void enforce(RequireRole requireRole) {
Set<String> granted = currentUser.grantedRoles();
String[] required = requireRole.value();
if (!isAllowed(granted, required, requireRole.anyOf())) {
throw new AccessDeniedException(
"Missing required role(s): " + Arrays.toString(required));
}
}
private boolean isAllowed(Set<String> granted, String[] required, boolean anyOf) {
if (anyOf) {
for (String role : required) {
if (granted.contains(role)) {
return true;
}
}
return false;
}
for (String role : required) {
if (!granted.contains(role)) {
return false;
}
}
return true;
}
}
package com.example.account;
import com.example.security.RequireRole;
import org.springframework.stereotype.Service;
@Service
public class AccountService {
private final AccountRepository accounts;
public AccountService(AccountRepository accounts) {
this.accounts = accounts;
}
@RequireRole("ADMIN")
public void closeAccount(long accountId, String reason) {
Account account = accounts.findByIdOrThrow(accountId);
account.markClosed(reason);
accounts.save(account);
}
@RequireRole(value = {"ADMIN", "AUDITOR"}, anyOf = true)
public Statement viewStatement(long accountId, int year, int month) {
Account account = accounts.findByIdOrThrow(accountId);
return account.statementFor(year, month);
}
}
package com.example.security;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
@Configuration
@EnableAspectJAutoProxy
public class SecurityConfig {
@Bean
public CurrentUserProvider currentUserProvider() {
return () -> {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth == null || !auth.isAuthenticated()) {
return Set.of();
}
return auth.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.map(a -> a.startsWith("ROLE_") ? a.substring(5) : a)
.collect(Collectors.toSet());
};
}
}
This snippet shows a small, self-contained role-based access control (RBAC) mechanism built with a custom annotation and a Spring AOP aspect, keeping authorization out of business logic. The pattern is useful when method-level checks are needed beyond URL-based security, or when the same guard must protect service methods invoked from multiple entry points (controllers, schedulers, messaging).
In RequireRole annotation, the marker is a plain @interface retained at RUNTIME and targeting METHOD. It carries a value() array of role names and an anyOf() flag that decides whether the caller must hold every listed role or just one. Because it is annotation-only, it stays a pure declaration of intent — no logic leaks into it.
RoleGuardAspect is where enforcement lives. The @Aspect is a Spring @Component so it is proxied and applied automatically. Its @Before("@annotation(requireRole)") pointcut binds the matched annotation instance directly into the advice parameter, so the aspect reads its value() and anyOf() without reflecting manually. The advice pulls the caller's granted authorities from an injected CurrentUserProvider, then delegates the decision to isAllowed. On failure it throws AccessDeniedException, which Spring maps to a 403 — the method body never runs.
The key trade-off is proxy-based interception: because Spring AOP wraps beans in proxies, the annotation is only honored on external calls into the bean, not on this.method() self-invocations. Guards therefore belong on public service methods reached through the proxy. Using @Before (rather than @Around) is deliberate — the check is a pre-condition with no need to alter the return value.
AccountService demonstrates realistic usage: closeAccount requires the single ADMIN role, while viewStatement accepts anyOf ADMIN or AUDITOR. The service reads cleanly because the cross-cutting concern is fully externalized. SecurityConfig completes the story by enabling @EnableAspectJAutoProxy and exposing a CurrentUserProvider bean that adapts the SecurityContextHolder. Pitfalls to watch: ensure roles are stored consistently (with or without a ROLE_ prefix) and that the aspect runs before transactional advice if it should block work early. This approach centralizes policy, makes intent declarative, and keeps tests focused on the aspect rather than every method.
Related snips
payload = {
sub: user.id,
iss: 'https://auth.example.com',
aud: 'codesnips-api',
exp: 15.minutes.from_now.to_i,
iat: Time.now.to_i,
JWT issuance and verification without common footguns
type User {
id: ID!
name: String!
email: String!
posts: [Post!]!
createdAt: String!
GraphQL API with Spring Boot
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
#!/usr/bin/env bash
set -euo pipefail
export VAULT_ADDR="https://vault.internal:8200"
export VAULT_TOKEN="${VAULT_TOKEN:?missing VAULT_TOKEN}"
Secrets management with environment isolation and Vault
import { randomBytes, createHash } from "crypto";
import jwt from "jsonwebtoken";
import { RefreshTokenStore } from "./store";
const ACCESS_SECRET = process.env.ACCESS_SECRET!;
const ACCESS_TTL = "15m";
JWT access + refresh token rotation (conceptual)
package files
import (
"context"
"time"
Presigned S3 upload URLs (AWS SDK v2)
Share this code
Here's the card — post it anywhere.