簡體   English   中英

如何使用 Spring MVC 創建我自己的過濾器?

[英]How to create my own filter with Spring MVC?

我使用 Spring MVC (4.0.1) 作為休息服務的后端,使用 angularjs 作為前端。

對我的服務器后端的每個請求都有一個帶有會話 ID 的 http 標頭

我可以使用以下代碼在我的服務器后端讀取此標頭:

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

替代過濾器,您可以使用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
    }

}

以下是執行您提到的邏輯的過濾器

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

Spring可以使用過濾器,但他們建議您使用他們的過濾器版本,稱為攔截器

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

快速瀏覽它們的工作方式。 它們幾乎與過濾器相同,但旨在在 Spring MVC 生命周期內工作。

我假設您正在嘗試實現某種基於 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);
    }
}

是一個指向 GitHub 的鏈接,感謝nielsutrecht 所做的出色工作,我以這個項目為基礎,它運行良好。

您還可以使用具有針對某個注釋的切入點的方面來實現它。 我編寫了一個庫,使您能夠使用基於 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會檢查“aud jwt claim”中的值是否與使用@ClaimValue 進行注解的 clientId 參數匹配。 如果匹配,則可以輸入該方法。 否則拋出異常。

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

}

您可以通過執行以下步驟來創建和配置您自己的過濾器。

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,然后繼續到相應的控制器。

希望這將是必需的答案。

你的方法看起來是正確的。

一旦我使用了類似於以下內容(刪除了大部分行並保持簡單)。

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,您可以使用FilterRegistrationBean 它和FilterRegistration.Dynamic做同樣的事情(我認為是這樣)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM