如何使用Spring MVC创建自己的过滤器?

时间:2014-08-18 22:31:16

标签: java spring spring-mvc

我使用Spring MVC(4.0.1)作为休息服务的后端和angularjs作为前端。

我的服务器后端的每个请求都有一个带有会话ID的http-header

我可以使用以下代码在服务器后端读取此标头:

@Autowired
protected HttpServletRequest request;
String xHeader=request.getHeader("X-Auth-Token"); //returns the sessionID from the header

现在我调用此方法getPermission(xHeader)它只返回true或false。如果用户存在于我的数据库中,则返回true,否则为false!

我现在想要创建一个具有此行为的过滤器,如果用户有权访问我的控制器,则会检查每个请求!但是如果方法返回false,它应该发回401错误而不能到达我的控制器!

我该怎么做并创建自己的过滤器?我只使用Java Config而不使用XML。

我想我必须在这里添加过滤器:

public class WebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    @Override
    protected Filter[] getServletFilters() {
        MyOwnFilter=new MyOwnFilter();
        return new Filter[] {MyOwnFilter};
    }
}

7 个答案:

答案 0 :(得分:28)

替代过滤器,您可以使用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 {

    }
}

然后将此拦截器添加到您的webmvc配置中。

@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
    }

}

答案 1 :(得分:8)

以下是执行您提到的逻辑的过滤器

@WebFilter("/*")
public class AuthTokenFilter implements Filter {

    @Override
    public void destroy() {
        // ...
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        //
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        String xHeader = ((HttpServletRequest)request).getHeader("X-Auth-Token");
        if(getPermission(xHeader)) {
            chain.doFilter(request, response);
        } else {
            request.getRequestDispatcher("401.html").forward(request, response);
        }
    }
}

你做对了,弹簧配置应该跟随。

public class MyWebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    @Override
    protected Filter[] getServletFilters() {
        return new Filter[]{new AuthTokenFilter()};
    }
}

答案 2 :(得分:6)

Spring 可以使用过滤器,但是他们建议您使用他们的过滤器版本,称为拦截器

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

快速了解它们的工作原理。它们几乎与过滤器完全相同,但设计用于在Spring MVC生命周期内工作。

答案 3 :(得分:6)

我假设您正在尝试实现某种基于jwt令牌的OAuth安全性。

现在有几种方法可以这样做,但这是我最喜欢的方法:

以下是过滤器的外观:

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);
    }

}

非常简单,还有用户控制器,您可以在其中找到登录方法:

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;
        }
    }
}

当然我们有Main你可以在哪里看到过滤器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);
    }

}

最后但并非最不重要的是有一个示例控制器:

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是指向GitHub的链接所有感谢 nielsutrecht ,因为我使用这个项目作为基础的伟大工作,它运作良好。

答案 4 :(得分:2)

您还可以使用具有针对特定注释的切入点的方面来实现它。我编写了一个库,使您可以使用基于JWT令牌执行授权检查的注释。

您可以找到包含所有文档的项目:https://github.com/nille85/jwt-aspect。我多次使用这种方法来保护单页应用程序使用的REST后端。

我还在博客中记录了如何在Spring MVC应用程序中使用它:http://www.nille.be/security/creating-authorization-server-using-jwts/

以下是https://github.com/nille85/auth-server

上示例项目的摘录

下面的示例包含受保护的方法getClient。方面使用的注释 @Authorize 检查来自&#34; aud jwt claim&#34; 的值是否与使用 @注释的clientId参数匹配ClaimValue 即可。如果匹配,则可以输入方法。否则抛出异常。

@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);


    }

}

Aspect本身可以配置为:

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

所需的PayloadService可以实现为:

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;
    }

}

答案 5 :(得分:1)

您可以通过执行以下步骤来创建和配置自己的过滤器。

1)通过实现过滤器接口并覆盖其方法来创建您的类。

public class MyFilter implements javax.servlet.Filter{


public void destroy(){}
public void doFilter(Request, Response, FilterChain){//do what you want to filter
}
........
}

2)现在在web.xml中配置过滤器

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

3)现在提供过滤器的url映射。

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

4)现在重新启动服务器并检查所有Web请求将首先进入MyFilter,然后继续进入相应的控制器。

希望这将是必要的答案。

答案 6 :(得分:1)

您的方法看起来是正确的。

一旦我使用了类似于下面的内容(删除了大部分行并保持简单)。

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[] { "/" };
    }

}

您还需要一个自定义过滤器,如下所示。

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 {
        }

    }

希望这有帮助。

此外,如果您使用Spring Boot,则可以使用FilterRegistrationBeanFilterRegistration.Dynamic做了同样的事情(我认为是这样)。