web-dev-qa-db-fra.com

Comment créer mon propre filtre avec Spring MVC?

J'utilise Spring MVC (4.0.1) comme backend pour les services de repos et angularjs comme frontend.

chaque demande à mon serveur a un en-tête http avec un identifiant de session

Je peux lire cet en-tête dans mon serveur avec le code suivant:

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

J'appelle maintenant cette méthode getPermission(xHeader), elle ne renvoie que vrai ou faux. Si l'utilisateur existe dans ma base de données, la valeur renvoyée est true sinon false!

Je veux maintenant créer un filtre avec ce comportement, qui vérifie chaque demande si l'utilisateur a la permission d'accéder à mes contrôleurs! Mais si la méthode retourne false, elle devrait renvoyer une erreur 401 et ne pas atteindre mon contrôleur!

Comment puis-je faire cela et créer mon propre filtre? J'utilise uniquement Java Config et pas de XML.

Je pense que je dois ajouter le filtre ici:

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

Alternative aux filtres, vous pouvez utiliser 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 {

    }
}

Et ajoutez ensuite cet intercepteur à votre configuration 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
    }

}
26
ares

Ci-dessous le filtre pour exécuter la logique que vous avez mentionnée

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

Et vous avez bien compris, la configuration du printemps devrait suivre.

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

Spring can peut utiliser des filtres, mais ils vous recommandent d'utiliser leur version de filtres, appelée interceptor.

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

Leur fonctionnement est rapide. Ils sont presque identiques aux filtres, mais conçus pour fonctionner dans le cycle de vie de Spring MVC.

6
Ben Potter

Je suppose que vous essayez d'implémenter une sorte de sécurité OAuth basée sur le jeton JWT.

De nos jours, il y a plusieurs façons de le faire, mais voici ma préférée: 

Voici à quoi ressemble le filtre: 

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

}

Assez simple, il y a le contrôleur d'utilisateur où vous pouvez trouver la méthode de connexion:

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

Bien sûr, nous avons Main où vous pouvez voir le haricot filtre: 

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

}

Enfin, il y a un exemple de contrôleur: 

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

Ici est un lien vers GitHub tous nos remerciements vont à nielsutrecht pour le bon travail que j’ai fait de ce projet et qui fonctionne parfaitement.

4
Lazar Lazarov

Vous pouvez également l'implémenter en utilisant un aspect avec un pointcut qui cible une annotation donnée. J'ai écrit une bibliothèque qui vous permet d'utiliser des annotations qui effectuent des contrôles d'autorisation en fonction d'un jeton JWT. 

Vous pouvez trouver le projet avec toute la documentation sur: https://github.com/nille85/jwt-aspect . J'ai utilisé cette approche plusieurs fois afin de sécuriser un backend REST consommé par une application à une seule page.

J'ai également expliqué sur mon blog comment l'utiliser dans une application Spring MVC: http://www.nille.be/security/creating-authorization-server-using-jwts/

Ce qui suit est un extrait du projet exemple sur https://github.com/nille85/auth-server

L'exemple ci-dessous contient une méthode protégée getClient. L'annotation @Authorize utilisée par l'aspect vérifie si la valeur de "aud jwt claim" correspond au paramètre clientId annoté avec @ClaimValue. Si cela correspond, la méthode peut être entrée. Sinon, une exception est levée.

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


    }

}

L'aspect lui-même peut être configuré comme suit:

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

Le PayloadService nécessaire peut par exemple être implémenté comme:

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

}
2
nille85

Vous pouvez créer et configurer votre propre filtre en procédant comme suit.

1) Créez votre classe en implémentant l’interface de filtre et substituez ses méthodes.

public class MyFilter implements javax.servlet.Filter{


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

2) Configurez maintenant votre filtre dans web.xml

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

3) Fournissez maintenant un mappage d'URL du filtre.

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

4) Maintenant, redémarrez votre serveur et vérifiez que toute la requête Web parviendra d'abord à MyFilter, puis au contrôleur correspondant.

Espérons que ce sera la réponse requise.

1
Web Clerisy

Votre approche semble correcte.

Une fois que j'ai utilisé quelque chose de similaire à suivre (Supprimer la plupart des lignes et le garder 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[] { "/" };
    }

}

Aussi, vous avez besoin d'un filtre personnalisé comme ci-dessous.

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

    }

J'espère que cela t'aides.

De plus, vous pouvez utiliser FilterRegistrationBean si vous démarrez Spring Boot. Il fait la même chose (je pense que oui) que FilterRegistration.Dynamic fait.

1
sura2k