Подтвердить что ты не робот

Как создать собственный фильтр с помощью Spring MVC?

Я использую Spring MVC (4.0.1) в качестве бэкэнд для служб отдыха и angularjs как интерфейс.

каждый запрос на мой серверный сервер имеет http-заголовок с идентификатором сеанса

Я могу прочитать этот заголовок на моем сервере с помощью следующего кода:

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

Теперь я вызываю этот метод getPermission(xHeader), он возвращает только true или false. Если пользователь существует в моем БД, он возвращает true else false!

Теперь я хочу создать фильтр с таким поведением, который проверяет каждый запрос, если у пользователя есть разрешение на доступ к моим контроллерам! Но если метод возвращает false, он должен отправить обратно ошибку 401 и не дойти до моего контроллера!

Как я могу это сделать и создать свой собственный фильтр? Я использую только Java Config и не XML.

Думаю, я должен добавить фильтр здесь:

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

Ответ 1

Альтернатива фильтрам вы можете использовать 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
    }

}

Ответ 2

Ниже приведен фильтр для выполнения указанной вами логики

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

И вы поняли, что должен быть конфигуратор spring.

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

Ответ 3

Spring могут использовать фильтры, но они рекомендуют использовать свою версию фильтров, известных как перехватчик

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

Быстро просматривается, как они работают. Они почти идентичны фильтрам, но предназначены для работы внутри жизненного цикла Spring MVC.

Ответ 4

Я предполагаю, что вы пытаетесь реализовать некоторую защиту OAuth, которая основана на токене jwt.

В настоящее время есть несколько способов сделать это, но вот мой любимый:

Вот как выглядит фильтр:

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 за отличную работу, которую я использовал в этом проекте как основа и отлично работает.

Ответ 5

Вы можете создать и настроить свой собственный фильтр, выполнив следующие шаги.

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) Теперь перезагрузите сервер и проверьте, что весь веб-запрос сначала появится в MyFilter, а затем перейдите к соответствующему контроллеру.

Надеюсь, это будет необходимый ответ.

Ответ 6

Вы также можете реализовать его, используя аспект с pointcut, который нацелен на определенную аннотацию. Я написал библиотеку, которая позволяет использовать аннотации, которые выполняют проверки авторизации на основе токена JWT.

Вы можете найти проект со всей документацией: https://github.com/nille85/jwt-aspect. Я использовал этот подход несколько раз, чтобы защитить REST Backend, который используется одностраничным приложением.

Я также зарегистрировал в своем блоге, как вы можете использовать его в Spring приложении MVC: http://www.nille.be/security/creating-authorization-server-using-jwts/

Ниже приводится выдержка из примера проекта на https://github.com/nille85/auth-server

В примере ниже содержится защищенный метод getClient. Аннотация @Authorize, в которой аспект использует проверки, если значение из "id jwt претензии" соответствует параметру 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);


    }

}

Сам аспект можно настроить следующим образом:

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

}

Ответ 7

Ваш подход выглядит правильно.

Как только я использовал что-то похожее на следующее (Удалено большинство строк и было просто).

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

    }

Надеюсь, что это поможет.

Кроме того, вы можете использовать FilterRegistrationBean, если вы Spring Boot. Он делает то же самое (я так думаю), что FilterRegistration.Dynamic делает.