Search code examples
javaspring-bootspring-mvcaopdry

How do I add a permission check for many methods in a controller? (Filter, Action)


I have the following at the beginning of many methods.

    if ((user.privilege & User.Privilege.WRITE) == 0) {
        session.setAttribute("alert", "You do not have permission to save.");
        return new ModelAndView("redirect:/admin/home");
    }

How can I extract this and put it into a separate method that is called before many other controller methods, similar to Ruby on Rails before_action :validate_privilege, [:save, :weekly_report, ...]?

I found this in the docs, but it doesn't give any examples.

https://docs.spring.io/spring-boot/docs/1.5.19.RELEASE/reference/htmlsingle/#boot-features-embedded-container-servlets-filters-listeners


I found a way to do it based on @Deadpool's answer. It seems like it is more complicated than a simple annotation would be.

@Bean
public GenericFilterBean beforeAction() {
    String[] actions = new String[]{"/admin/censor", "/admin/weekly-report", "/admin/email-blast-submit"};
    return new GenericFilterBean() {
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
            HttpServletRequest req = (HttpServletRequest) request;
//              System.out.println("requestURI:"+req.getRequestURI());
            boolean found = Arrays.stream(actions).anyMatch(req.getRequestURI()::equals);
            if (found) {
                User user = (User) req.getSession().getAttribute("user");
                if (user != null && (user.privilege & User.Privilege.WRITE) == 0) {
                    req.getSession().setAttribute("alert", "You do not have permission to save.");
                    HttpServletResponse res = (HttpServletResponse) response;
                    res.sendRedirect("/admin/home");
                    return;
                }
            }
            chain.doFilter(request, response);
        }
    };
}

For the API using OAuth2, I used

@Override
public ResponseEntity<...> delete(Principal principal, @RequestBody ...) throws ApiException {
    isAuthorized(principal, "ROLE_USER");
    ...

/** If not authorized, throw ApiException. */
private void isAuthorized(Principal principal, String role) throws ApiException {
    OauthClientDetail cd = userMapper.getOauthClientDetails(principal.getName());
    if (cd == null || cd.authorities == null || !cd.authorities.equals(role)) {
        throw new ApiException(HttpStatus.UNAUTHORIZED, ...);
    }
}

(ApiException, OauthClientDetail (POJO), and UserMapper (MyBatis) are custom classes.)


Solution

  • If you are looking for Filter to check each request for authorization you can use Filter

    @Component
    public class AuthFilter implements Filter {
    
    @Override
    public void doFilter
      ServletRequest request, 
      ServletResponse response, 
      FilterChain chain) throws IOException, ServletException {
    
        HttpServletRequest req = (HttpServletRequest) request;
        LOG.info(
          "Starting a transaction for req : {}", 
          req.getRequestURI());
    
        chain.doFilter(request, response);
        LOG.info(
          "Committing a transaction for req : {}", 
          req.getRequestURI());
        }
    
         // other methods 
      }
    

    or GenericFilterBean

    public class CustomFilter extends GenericFilterBean {
    
    @Override
    public void doFilter(
      ServletRequest request, 
      ServletResponse response,
      FilterChain chain) throws IOException, ServletException {
        chain.doFilter(request, response);
           }
       }