How to create my own filter with Spring MVC?

前端 未结 7 1521
名媛妹妹
名媛妹妹 2020-12-14 07:40

I use Spring MVC (4.0.1) as a backend for rest services and angularjs as frontend.

every request to my server backend has a http-header with a session id

I c

相关标签:
7条回答
  • 2020-12-14 07:45

    I assume that you are trying to implement some kind of OAuth security which is based on jwt token.

    Nowdays there are several ways to do so but here is my favourite one:

    Here is how the filter looks like:

    import java.io.IOException;
    
    import javax.servlet.FilterChain;
    import javax.servlet.ServletException;
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    import javax.servlet.http.HttpServletRequest;
    
    import org.springframework.web.filter.GenericFilterBean;
    
    import io.jsonwebtoken.Claims;
    import io.jsonwebtoken.Jwts;
    import io.jsonwebtoken.SignatureException;
    
    public class JwtFilter extends GenericFilterBean {
    
        @Override
        public void doFilter(final ServletRequest req,
                             final ServletResponse res,
                             final FilterChain chain) throws IOException, ServletException {
            final HttpServletRequest request = (HttpServletRequest) req;
    
            final String authHeader = request.getHeader("Authorization");
            if (authHeader == null || !authHeader.startsWith("Bearer ")) {
                throw new ServletException("Missing or invalid Authorization header.");
            }
    
            final String token = authHeader.substring(7); // The part after "Bearer "
    
            try {
                final Claims claims = Jwts.parser().setSigningKey("secretkey")
                    .parseClaimsJws(token).getBody();
                request.setAttribute("claims", claims);
            }
            catch (final SignatureException e) {
                throw new ServletException("Invalid token.");
            }
    
            chain.doFilter(req, res);
        }
    
    }
    

    Pretty simple there is the user controller also where you can find the login method:

    import java.util.Arrays;
    import java.util.Date;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    import javax.servlet.ServletException;
    
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.RestController;
    
    import io.jsonwebtoken.Jwts;
    import io.jsonwebtoken.SignatureAlgorithm;
    
    @RestController
    @RequestMapping("/user")
    public class UserController {
    
        private final Map<String, List<String>> userDb = new HashMap<>();
    
        public UserController() {
            userDb.put("tom", Arrays.asList("user"));
            userDb.put("sally", Arrays.asList("user", "admin"));
        }
    
        @RequestMapping(value = "login", method = RequestMethod.POST)
        public LoginResponse login(@RequestBody final UserLogin login)
            throws ServletException {
            if (login.name == null || !userDb.containsKey(login.name)) {
                throw new ServletException("Invalid login");
            }
            return new LoginResponse(Jwts.builder().setSubject(login.name)
                .claim("roles", userDb.get(login.name)).setIssuedAt(new Date())
                .signWith(SignatureAlgorithm.HS256, "secretkey").compact());
        }
    
        @SuppressWarnings("unused")
        private static class UserLogin {
            public String name;
            public String password;
        }
    
        @SuppressWarnings("unused")
        private static class LoginResponse {
            public String token;
    
            public LoginResponse(final String token) {
                this.token = token;
            }
        }
    }
    

    Of course we have Main where you can see the filter bean:

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
    import org.springframework.boot.context.embedded.FilterRegistrationBean;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    
    @EnableAutoConfiguration
    @ComponentScan
    @Configuration
    public class WebApplication {
        @Bean
        public FilterRegistrationBean jwtFilter() {
            final FilterRegistrationBean registrationBean = new FilterRegistrationBean();
            registrationBean.setFilter(new JwtFilter());
            registrationBean.addUrlPatterns("/api/*");
    
            return registrationBean;
        }
    
        public static void main(final String[] args) throws Exception {
            SpringApplication.run(WebApplication.class, args);
        }
    
    }
    

    Last but not least there is an example controller:

    import io.jsonwebtoken.Claims;
    
    import java.util.List;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    @RequestMapping("/api")
    public class ApiController {
        @SuppressWarnings("unchecked")
        @RequestMapping(value = "role/{role}", method = RequestMethod.GET)
        public Boolean login(@PathVariable final String role,
                final HttpServletRequest request) throws ServletException {
            final Claims claims = (Claims) request.getAttribute("claims");
    
            return ((List<String>) claims.get("roles")).contains(role);
        }
    }
    

    Here is a link to GitHub all thanks goes to nielsutrecht for the great work I have used this project as base and it works perfectly.

    0 讨论(0)
  • 2020-12-14 07:45

    You can create and configure your own filter by doing following steps.

    1) Create your class by implementing the filter interface and override its methods.

    public class MyFilter implements javax.servlet.Filter{
    
    
    public void destroy(){}
    public void doFilter(Request, Response, FilterChain){//do what you want to filter
    }
    ........
    }
    

    2) Now configure your filter in web.xml

    <filter>
      <filter-name>myFilter</filter-name>
      <filter-class>MyFilter</filter-class>
    </filter>
    

    3) Now provide url mapping of the filter.

    <filter-mapping>
       <filter-name>myFilter</filter-name>
       <url-pattern>*</url-pattern>
    </filter-mapping>
    

    4) Now restart your server and check all the web request will first come to MyFilter and then proceed to the respective controller.

    Hopefully it will be the required answer.

    0 讨论(0)
  • 2020-12-14 07:49

    Your approach looks correct.

    Once I have used something similar to following (Removed most of the lines and kept it simple).

    public class MvcDispatcherServletInitializer  extends AbstractAnnotationConfigDispatcherServletInitializer {
    
        @Override
        public void onStartup(ServletContext servletContext) throws ServletException {
            super.onStartup(servletContext);
    
            EnumSet<DispatcherType> dispatcherTypes = EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.ERROR);
    
            FilterRegistration.Dynamic monitoringFilter = servletContext.addFilter("monitoringFilter", MonitoringFilter.class);
            monitoringFilter.addMappingForUrlPatterns(dispatcherTypes, false, "/api/admin/*");
        }
    
        @Override
        protected Class<?>[] getRootConfigClasses() {
            return new Class[] { WebMvcConfig.class };
        }
    
        @Override
        protected Class<?>[] getServletConfigClasses() {
            return null;
        }
    
        @Override
        protected String[] getServletMappings() {
            return new String[] { "/" };
        }
    
    }
    

    Also you need a custom filter looks like below.

    public class CustomXHeaderFilter implements Filter {
    
            @Override
            public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
                HttpServletRequest request = (HttpServletRequest) req;
                HttpServletResponse response = (HttpServletResponse) res;
    
                String xHeader = request.getHeader("X-Auth-Token");
                if(YOUR xHeader validation fails){
                    //Redirect to a view
                    //OR something similar
                    return;
                }else{
                    //If the xHeader is OK, go through the chain as a proper request
                    chain.doFilter(request, response);
                }
    
            }
    
            @Override
            public void destroy() {
            }
    
            @Override
            public void init(FilterConfig arg0) throws ServletException {
            }
    
        }
    

    Hope this helps.

    Additionally you can use FilterRegistrationBean if you Spring Boot. It does the same thing (I think so) which FilterRegistration.Dynamic does.

    0 讨论(0)
  • 2020-12-14 07:50

    Alternative to Filters, you can use HandlerInterceptor.

    public class SessionManager implements HandlerInterceptor{
    
        // This method is called before the controller
        @Override
        public boolean preHandle(HttpServletRequest request,
                HttpServletResponse response, Object handler) throws Exception {
    
            String xHeader = request.getHeader("X-Auth-Token");
            boolean permission = getPermission(xHeader);
            if(permission) {
                return true;
            }
            else {
                response.setStatus(HttpStatus.UNAUTHORIZED.value());
                return false;
                // Above code will send a 401 with no response body.
                // If you need a 401 view, do a redirect instead of
                // returning false.
                // response.sendRedirect("/401"); // assuming you have a handler mapping for 401
    
            }
            return false;
        }
    
        @Override
        public void postHandle(HttpServletRequest request,
                HttpServletResponse response, Object handler,
                ModelAndView modelAndView) throws Exception {
    
        }
    
        @Override
        public void afterCompletion(HttpServletRequest request,
                HttpServletResponse response, Object handler, Exception ex)
                throws Exception {
    
        }
    }
    

    And then add this interceptor to your webmvc config.

    @EnableWebMvc
    @Configuration
    public class WebConfig extends WebMvcConfigurerAdapter {
    
        @Bean
        SessionManager getSessionManager() {
             return new SessionManager();
        }
    
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(getSessionManager())
            .addPathPatterns("/**")
            .excludePathPatterns("/resources/**", "/login");
         // assuming you put your serve your static files with /resources/ mapping
         // and the pre login page is served with /login mapping
        }
    
    }
    
    0 讨论(0)
  • 2020-12-14 07:57

    Spring can use filters, but they recommend that you use their version of filters, known as an interceptor

    http://viralpatel.net/blogs/spring-mvc-interceptor-example/

    There is a quick run through of how they work. They are nearly identical to filters, but designed to work inside the Spring MVC lifecycle.

    0 讨论(0)
  • 2020-12-14 08:00

    You can also implement it using an aspect with a pointcut that targets a certain annotation. I have written a library that enables you to use annotations that perform authorization checks based on a JWT token.

    You can find the project with all the documentation on: https://github.com/nille85/jwt-aspect. I have used this approach multiple times in order to secure a REST Backend that is consumed by a single page application.

    I have also documented on my blog how you can use it in a Spring MVC Application: http://www.nille.be/security/creating-authorization-server-using-jwts/

    The following is an extract from the example project on https://github.com/nille85/auth-server

    The example underneath contains a protected method getClient. The annotation @Authorize that the aspect uses checks if the value from the "aud jwt claim" matches the clientId parameter that is annotated with @ClaimValue. If it matches, the method can be entered. Otherwise an exception is thrown.

    @RestController
    @RequestMapping(path = "/clients")
    public class ClientController {
    
        private final ClientService clientService;
    
        @Autowired
        public ClientController(final ClientService clientService) {
            this.clientService = clientService;
        }
    
        @Authorize("hasClaim('aud','#clientid')")
        @RequestMapping(value = "/{clientid}", method = RequestMethod.GET, produces = "application/json")
        @ResponseStatus(value = HttpStatus.OK)
        public @ResponseBody Client getClient(@PathVariable(value = "clientid") @ClaimValue(value = "clientid") final String clientId) {
            return clientService.getClient(clientId);
        }
    
        @RequestMapping(value = "", method = RequestMethod.GET, produces = "application/json")
        @ResponseStatus(value = HttpStatus.OK)
        public @ResponseBody List<Client> getClients() {
            return clientService.getClients();
        }
    
    
        @RequestMapping(path = "", method = RequestMethod.POST, produces = "application/json")
        @ResponseStatus(value = HttpStatus.OK)
        public @ResponseBody Client registerClient(@RequestBody RegisterClientCommand command) {
            return clientService.register(command);
    
    
        }
    
    }
    

    The Aspect itself can be configured like:

    @Bean
    public JWTAspect jwtAspect() {
        JWTAspect aspect = new JWTAspect(payloadService());
        return aspect;
    }
    

    The PayloadService that is needed can for example be implemented like:

    public class PayloadRequestService implements PayloadService {
    
        private final JWTVerifier verifier;
    
        public PayloadRequestService(final JWTVerifier verifier){
            this.verifier = verifier;
        }
    
        @Override
        public Payload verify() {
            ServletRequestAttributes t = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
            HttpServletRequest request = t.getRequest();
    
            final String jwtValue = request.getHeader("X-AUTH");
            JWT jwt = new JWT(jwtValue);
            Payload payload =verifier.verify(jwt);
    
            return payload;
        }
    
    }
    
    0 讨论(0)
提交回复
热议问题