From e18e3cca3e05335725365b3c00c4da769145ac21 Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Nakazone Batista Date: Mon, 27 May 2024 18:37:56 -0300 Subject: [PATCH] Better UserGuard hasAccess Implementation --- .../auth/security/config/SecurityConfig.java | 8 +- .../UserResourceAccessInterceptor.java | 12 +- .../guard/UserResourceEndpointManager.java | 113 ++++++++++++++++ .../util/guard/UserResourceGuardEnum.java | 68 +--------- .../auth/util/guard/UserResourceHandler.java | 128 ++++++------------ 5 files changed, 166 insertions(+), 163 deletions(-) create mode 100644 src/main/java/br/com/hideyoshi/auth/util/guard/UserResourceEndpointManager.java diff --git a/src/main/java/br/com/hideyoshi/auth/security/config/SecurityConfig.java b/src/main/java/br/com/hideyoshi/auth/security/config/SecurityConfig.java index 0c4dd94..b0773a6 100644 --- a/src/main/java/br/com/hideyoshi/auth/security/config/SecurityConfig.java +++ b/src/main/java/br/com/hideyoshi/auth/security/config/SecurityConfig.java @@ -7,7 +7,7 @@ import br.com.hideyoshi.auth.security.oauth2.repository.OAuthRequestRepository; import br.com.hideyoshi.auth.security.service.AuthService; import br.com.hideyoshi.auth.service.UserService; import br.com.hideyoshi.auth.util.exception.AuthenticationInvalidExceptionDetails; -import br.com.hideyoshi.auth.util.guard.UserResourceHandler; +import br.com.hideyoshi.auth.util.guard.UserResourceEndpointManager; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; @@ -44,7 +44,7 @@ public class SecurityConfig { private final AuthService authService; private final UserService userService; private final OAuthRequestRepository oAuthRequestRepository; - private final UserResourceHandler userResourceHandler; + private final UserResourceEndpointManager userResourceEndpointManager; @Bean public AuthenticationProvider authenticationProvider() { @@ -86,11 +86,11 @@ public class SecurityConfig { UsernamePasswordAuthenticationFilter.class ); - for (String endpoint : this.userResourceHandler.getOpenPaths()) { + for (String endpoint : this.userResourceEndpointManager.getOpenPaths()) { http.authorizeRequests().antMatchers(endpoint).permitAll(); } - for (String endpoint : this.userResourceHandler.getGuardedPaths()) { + for (String endpoint : this.userResourceEndpointManager.getGuardedPaths()) { http.authorizeRequests().antMatchers(endpoint).hasAnyAuthority("ROLE_USER", "ROLE_ADMIN"); } diff --git a/src/main/java/br/com/hideyoshi/auth/security/interceptor/UserResourceAccessInterceptor.java b/src/main/java/br/com/hideyoshi/auth/security/interceptor/UserResourceAccessInterceptor.java index ddfa0e3..2a3972e 100644 --- a/src/main/java/br/com/hideyoshi/auth/security/interceptor/UserResourceAccessInterceptor.java +++ b/src/main/java/br/com/hideyoshi/auth/security/interceptor/UserResourceAccessInterceptor.java @@ -1,10 +1,8 @@ package br.com.hideyoshi.auth.security.interceptor; -import br.com.hideyoshi.auth.service.UserService; -import br.com.hideyoshi.auth.util.exception.AuthenticationInvalidException; import br.com.hideyoshi.auth.util.exception.AuthorizationException; import br.com.hideyoshi.auth.util.guard.UserResourceGuard; -import com.fasterxml.jackson.databind.ObjectMapper; +import br.com.hideyoshi.auth.util.guard.UserResourceHandler; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.springframework.stereotype.Component; @@ -20,7 +18,7 @@ import java.util.Objects; @RequiredArgsConstructor public class UserResourceAccessInterceptor implements HandlerInterceptor { - private final UserService userService; + private final UserResourceHandler userResourceHandler; public boolean preHandle(final HttpServletRequest request, final HttpServletResponse response, final Object handler) { @@ -32,8 +30,10 @@ public class UserResourceAccessInterceptor implements HandlerInterceptor { .getMethodAnnotation(UserResourceGuard.class); if (Objects.nonNull(annotation)) { - Boolean accessPermission = - annotation.accessType().hasAccess(this.userService, request); + Boolean accessPermission = this.userResourceHandler.hasAccess( + annotation.accessType(), request + ); + if (!accessPermission) { throw new AuthorizationException(annotation.denialMessage()); } diff --git a/src/main/java/br/com/hideyoshi/auth/util/guard/UserResourceEndpointManager.java b/src/main/java/br/com/hideyoshi/auth/util/guard/UserResourceEndpointManager.java new file mode 100644 index 0000000..6bd823c --- /dev/null +++ b/src/main/java/br/com/hideyoshi/auth/util/guard/UserResourceEndpointManager.java @@ -0,0 +1,113 @@ +package br.com.hideyoshi.auth.util.guard; + +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.stereotype.Component; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.*; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + + +@Component +@RequiredArgsConstructor +public class UserResourceEndpointManager { + private final ListableBeanFactory beanFactory; + + public List getGuardedPaths() { + return this.extractPathsFromMethods(this.getGuardedResources()); + } + + public List getOpenPaths() { + return this.extractPathsFromMethods(this.getOpenResources()); + } + + private List extractPathsFromMethods(List methods) { + final List paths = new ArrayList<>(); + for (final Method method : methods) { + String[] parentPath = new String[0]; + + RequestMapping classAnnotation = method.getDeclaringClass().getAnnotation(RequestMapping.class); + if (classAnnotation != null) { + parentPath = this.getPathFromAnnotation(classAnnotation); + } + + List annotations = List.of(method.getAnnotations()); + + for (Annotation annotation : annotations) { + final String[] path = this.getPathFromAnnotation(annotation); + + if (path != null) + paths.add(String.join("/", parentPath) + String.join("/", path)); + } + } + return paths; + } + + private List getGuardedResources() { + final List guardedAccessTypes = Arrays.asList( + UserResourceGuardEnum.USER, + UserResourceGuardEnum.SAME_USER, + UserResourceGuardEnum.ADMIN_USER + ); + final List methods = new ArrayList<>(); + + for (final Class controllerClass : this.getControllerClasses()) { + methods.addAll(this.getMethodsByAccessType(controllerClass, guardedAccessTypes)); + } + + return methods; + } + + private List getOpenResources() { + final List openAccessTypes = List.of(UserResourceGuardEnum.OPEN); + final List methods = new ArrayList<>(); + + for (final Class controllerClass : this.getControllerClasses()) { + methods.addAll(this.getMethodsByAccessType(controllerClass, openAccessTypes)); + } + + return methods; + } + + private List getMethodsByAccessType(final Class controllerClass, List accessTypes) { + final List methods = new ArrayList<>(); + for (final Method method : controllerClass.getDeclaredMethods()) { + if (!method.isAnnotationPresent(UserResourceGuard.class)) { + continue; + } + UserResourceGuard annotation = method.getAnnotation(UserResourceGuard.class); + if (!accessTypes.contains(annotation.accessType())) { + continue; + } + + methods.add(method); + } + return methods; + } + + private List> getControllerClasses() { + final List> controllerClasses = new ArrayList<>(); + for (final String beanName : this.beanFactory.getBeanNamesForAnnotation(Controller.class)) { + controllerClasses.add(this.beanFactory.getType(beanName)); + } + return controllerClasses; + } + + private String[] getPathFromAnnotation(Annotation annotation) { + String[] path; String[] value; + + try { + value = (String[]) annotation.annotationType().getMethod("value").invoke(annotation); + path = (String[]) annotation.annotationType().getMethod("path").invoke(annotation); + } catch (Exception e) { + return null; + } + + return value.length > 0 ? value : path; + } +} diff --git a/src/main/java/br/com/hideyoshi/auth/util/guard/UserResourceGuardEnum.java b/src/main/java/br/com/hideyoshi/auth/util/guard/UserResourceGuardEnum.java index 57d208b..9b345b0 100644 --- a/src/main/java/br/com/hideyoshi/auth/util/guard/UserResourceGuardEnum.java +++ b/src/main/java/br/com/hideyoshi/auth/util/guard/UserResourceGuardEnum.java @@ -15,41 +15,13 @@ import java.util.HashMap; @Getter public enum UserResourceGuardEnum { - USER("user") { - @Override - public Boolean hasAccess( - UserService userService, - HttpServletRequest request) { - return UserResourceGuardEnum.justUser(userService, request); - } - }, + USER("user"), - SAME_USER("same_user") { - @Override - public Boolean hasAccess( - UserService userService, - HttpServletRequest request) { - return UserResourceGuardEnum.sameUser(userService, request); - } - }, + SAME_USER("same_user"), - ADMIN_USER("admin_user") { - @Override - public Boolean hasAccess( - UserService userService, - HttpServletRequest request) { - return UserResourceGuardEnum.adminUser(userService, request); - } - }, + ADMIN_USER("admin_user"), - OPEN("open") { - @Override - public Boolean hasAccess( - UserService userService, - HttpServletRequest request) { - return openAccess(userService, request); - } - }; + OPEN("open"); private final String accessType; @@ -66,36 +38,4 @@ public enum UserResourceGuardEnum { throw new IllegalArgumentException("Argument not valid."); } - private static boolean justUser(UserService userService, HttpServletRequest request) { - UserDTO userLogged = (UserDTO) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); - - return userLogged.getAuthorities().contains(new SimpleGrantedAuthority(Role.USER.getDescription())); - } - - private static boolean sameUser(UserService userService, HttpServletRequest request) { - UserDTO userLogged = (UserDTO) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); - - Object requestPathVariable = request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE); - - ObjectMapper objectMapper = new ObjectMapper(); - HashMap pathVariable = objectMapper.convertValue(requestPathVariable, HashMap.class); - UserDTO userInfo = userService.getUser(Long.parseLong(pathVariable.get("id"))); - - return userLogged.getUsername().equals(userInfo.getUsername()); - - } - - private static boolean adminUser(UserService userService, HttpServletRequest request) { - UserDTO userLogged = (UserDTO) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); - return userLogged.getAuthorities().contains(new SimpleGrantedAuthority(Role.ADMIN.getDescription())); - } - - private static Boolean openAccess(UserService userService, HttpServletRequest request) { - return true; - } - - public abstract Boolean hasAccess( - UserService userService, - HttpServletRequest request); - } diff --git a/src/main/java/br/com/hideyoshi/auth/util/guard/UserResourceHandler.java b/src/main/java/br/com/hideyoshi/auth/util/guard/UserResourceHandler.java index a568801..a7f3eee 100644 --- a/src/main/java/br/com/hideyoshi/auth/util/guard/UserResourceHandler.java +++ b/src/main/java/br/com/hideyoshi/auth/util/guard/UserResourceHandler.java @@ -1,113 +1,63 @@ package br.com.hideyoshi.auth.util.guard; + +import br.com.hideyoshi.auth.enums.Role; +import br.com.hideyoshi.auth.model.UserDTO; +import br.com.hideyoshi.auth.service.UserService; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.*; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; +import org.springframework.web.servlet.HandlerMapping; +import javax.servlet.http.HttpServletRequest; +import java.util.HashMap; @Component @RequiredArgsConstructor public class UserResourceHandler { - private final ListableBeanFactory beanFactory; + private final ObjectMapper objectMapper = new ObjectMapper(); - public List getGuardedPaths() { - return this.extractPathsFromMethods(this.getGuardedResources()); + private final UserService userService; + + public Boolean hasAccess(UserResourceGuardEnum userResourceGuardEnum, HttpServletRequest request) { + return switch (userResourceGuardEnum) { + case USER -> justUser(); + case SAME_USER -> sameUser(request); + case ADMIN_USER -> adminUser(); + case OPEN -> openAccess(); + default -> false; + }; } - public List getOpenPaths() { - return this.extractPathsFromMethods(this.getOpenResources()); + private boolean justUser() { + UserDTO userLogged = (UserDTO) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); + + return userLogged.getAuthorities().contains(new SimpleGrantedAuthority(Role.USER.getDescription())); } - private List extractPathsFromMethods(List methods) { - final List paths = new ArrayList<>(); - for (final Method method : methods) { - String[] parentPath = new String[0]; + private boolean sameUser(HttpServletRequest request) { + UserDTO userLogged = (UserDTO) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); - RequestMapping classAnnotation = method.getDeclaringClass().getAnnotation(RequestMapping.class); - if (classAnnotation != null) { - parentPath = this.getPathFromAnnotation(classAnnotation); - } - - List annotations = List.of(method.getAnnotations()); - - for (Annotation annotation : annotations) { - final String[] path = this.getPathFromAnnotation(annotation); - - if (path != null) - paths.add(String.join("/", parentPath) + String.join("/", path)); - } - } - return paths; - } - - private List getGuardedResources() { - final List guardedAccessTypes = Arrays.asList( - UserResourceGuardEnum.USER, - UserResourceGuardEnum.SAME_USER, - UserResourceGuardEnum.ADMIN_USER + Object requestPathVariable = request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE); + HashMap pathVariable = this.objectMapper.convertValue( + requestPathVariable, new TypeReference<>() {} ); - final List methods = new ArrayList<>(); - for (final Class controllerClass : this.getControllerClasses()) { - methods.addAll(this.getMethodsByAccessType(controllerClass, guardedAccessTypes)); - } + UserDTO userInfo = this.userService.getUser(Long.parseLong(pathVariable.get("id"))); + + return userLogged.getUsername().equals(userInfo.getUsername()); - return methods; } - private List getOpenResources() { - final List openAccessTypes = List.of(UserResourceGuardEnum.OPEN); - final List methods = new ArrayList<>(); - - for (final Class controllerClass : this.getControllerClasses()) { - methods.addAll(this.getMethodsByAccessType(controllerClass, openAccessTypes)); - } - - return methods; + private boolean adminUser() { + UserDTO userLogged = (UserDTO) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); + return userLogged.getAuthorities().contains(new SimpleGrantedAuthority(Role.ADMIN.getDescription())); } - private List getMethodsByAccessType(final Class controllerClass, List accessTypes) { - final List methods = new ArrayList<>(); - for (final Method method : controllerClass.getDeclaredMethods()) { - if (!method.isAnnotationPresent(UserResourceGuard.class)) { - continue; - } - UserResourceGuard annotation = method.getAnnotation(UserResourceGuard.class); - if (!accessTypes.contains(annotation.accessType())) { - continue; - } - - methods.add(method); - } - return methods; - } - - private List> getControllerClasses() { - final List> controllerClasses = new ArrayList<>(); - for (final String beanName : this.beanFactory.getBeanNamesForAnnotation(Controller.class)) { - controllerClasses.add(this.beanFactory.getType(beanName)); - } - return controllerClasses; - } - - private String[] getPathFromAnnotation(Annotation annotation) { - String[] path; String[] value; - - try { - value = (String[]) annotation.annotationType().getMethod("value").invoke(annotation); - path = (String[]) annotation.annotationType().getMethod("path").invoke(annotation); - } catch (Exception e) { - return null; - } - - return value.length > 0 ? value : path; + private Boolean openAccess() { + return true; } }