Delegating Authentication to Microservices with Custom Spring Annotations
This article explains how to move authentication from the gateway to individual Spring Cloud microservices by defining three custom annotations and an AOP aspect, while also covering Feign‑based calls and providing practical code examples for implementation.
Implementation Idea
Previous articles placed authentication and authorization together at the gateway using ReactiveAuthorizationManager . The new approach delegates authentication to downstream microservices, letting the gateway only perform routing.
To achieve this, modify the gateway configuration to permit all requests:
http
....
// whitelist directly permitted
.pathMatchers(ArrayUtil.toArray(whiteUrls.getUrls(), String.class)).permitAll()
// all other requests also permitted
.anyExchange().permitAll()
.....1. Remove the Authorization Manager
The gateway no longer uses the ReactiveAuthorizationManager ; all requests bypass it.
2. Define Three Custom Annotations
Spring Security already provides @Secured, @PreAuthorize, and @PostAuthorize, but this article creates its own: @RequiresLogin – only logged‑in users can access the annotated method. @RequiresPermissions – only users with specified permission codes can access. @RequiresRoles – only users with specified roles can access.
Annotation definitions:
/**
* @author 公众号:码猿技术专栏
* @url www.java-family.cn
* @description 登录认证的注解,标注在controller方法上,一定要是登录才能的访问的接口
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface RequiresLogin {} /**
* @author 公众号:码猿技术专栏
* @url www.java-family.cn
* @description 标注在controller方法上,确保拥有指定权限才能访问该接口
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface RequiresPermissions {
String[] value() default {};
Logical logical() default Logical.AND;
} /**
* @author 公众号:码猿技术专栏
* @url www.java-family.cn
* @description 标注在controller方法上,确保拥有指定的角色才能访问该接口
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface RequiresRoles {
String[] value() default {OAuthConstant.ROLE_ROOT_CODE, OAuthConstant.ROLE_ADMIN_CODE};
Logical logical() default Logical.AND;
}3. Annotation Aspect Definition
The aspect intercepts methods annotated with any of the three custom annotations and performs the corresponding checks:
@Aspect
@Component
public class PreAuthorizeAspect {
public static final String POINTCUT_SIGN = " @annotation(com.mugu.blog.common.annotation.RequiresLogin) || @annotation(com.mugu.blog.common.annotation.RequiresPermissions) || @annotation(com.mugu.blog.common.annotation.RequiresRoles)";
@Pointcut(POINTCUT_SIGN)
public void pointcut() {}
@Around("pointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
checkMethodAnnotation(signature.getMethod());
try {
return joinPoint.proceed();
} catch (Throwable e) {
throw e;
}
}
public void checkMethodAnnotation(Method method) {
RequiresLogin loginAnno = method.getAnnotation(RequiresLogin.class);
if (loginAnno != null) { doCheckLogin(); }
RequiresRoles rolesAnno = method.getAnnotation(RequiresRoles.class);
if (rolesAnno != null) { doCheckRole(rolesAnno); }
RequiresPermissions permAnno = method.getAnnotation(RequiresPermissions.class);
if (permAnno != null) { doCheckPermissions(permAnno); }
}
private void doCheckLogin() {
LoginVal loginVal = SecurityContextHolder.get();
if (Objects.isNull(loginVal)) {
throw new ServiceException(ResultCode.INVALID_TOKEN.getCode(), ResultCode.INVALID_TOKEN.getMsg());
}
}
private void doCheckRole(RequiresRoles requiresRoles) {
String[] roles = requiresRoles.value();
LoginVal loginVal = OauthUtils.getCurrentUser();
String[] authorities = loginVal.getAuthorities();
boolean match = false;
if (requiresRoles.logical() == Logical.AND) {
match = Arrays.stream(authorities).filter(StrUtil::isNotBlank).allMatch(item -> CollectionUtil.contains(Arrays.asList(roles), item));
} else {
match = Arrays.stream(authorities).filter(StrUtil::isNotBlank).anyMatch(item -> CollectionUtil.contains(Arrays.asList(roles), item));
}
if (!match) {
throw new ServiceException(ResultCode.NO_PERMISSION.getCode(), ResultCode.NO_PERMISSION.getMsg());
}
}
private void doCheckPermissions(RequiresPermissions requiresPermissions) {
// TODO: implement permission check based on business needs
}
}“ @RequiresPermissions logic is not implemented here; you need to complete it according to your own business requirements.”
4. Using the Annotations
Example: only users with admin or root roles can add an article:
@RequiresRoles
@AvoidRepeatableCommit
@ApiOperation("Add Article")
@PostMapping("/add")
public ResultMsg<Void> add(@RequestBody @Valid ArticleAddReq req) {
// ...
}When a Feign client calls a downstream service that is protected by @RequiresRoles, the call will be rejected for users lacking the required role, returning “No permission”.
Conclusion
The article demonstrates how to shift authentication from the gateway to individual microservices using custom annotations and an AOP aspect. In production, keep authentication centralized at the gateway unless specific business scenarios require decentralization.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Su San Talks Tech
Su San, former staff at several leading tech companies, is a top creator on Juejin and a premium creator on CSDN, and runs the free coding practice site www.susan.net.cn.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
