java - ejemplo - ¿Cómo crear mi propio filtro con Spring MVC?
spring security filter chain (7)
A continuación se muestra el filtro para realizar la lógica que ha mencionado.
@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);
}
}
}
Y lo hiciste bien, la configuración de primavera debería estar siguiendo.
public class MyWebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Filter[] getServletFilters() {
return new Filter[]{new AuthTokenFilter()};
}
}
Uso Spring MVC (4.0.1) como backend para servicios de descanso y angularjs como frontend.
cada solicitud a mi servidor backend tiene un encabezado http con un ID de sesión
Puedo leer este encabezado en el servidor de mi servidor con el siguiente código:
@Autowired
protected HttpServletRequest request;
String xHeader=request.getHeader("X-Auth-Token"); //returns the sessionID from the header
Ahora llamo a este método getPermission(xHeader)
que devuelve solo verdadero o falso. Si el usuario existe en mi base de datos, devuelve true o false.
Ahora quiero crear un filtro con este comportamiento, que verifique todas las solicitudes si el usuario tiene permiso para acceder a mis controladores. ¡Pero si el método devuelve falso, debería devolver un error 401 y no alcanzar mi controlador!
¿Cómo puedo hacer esto y crear mi propio filtro? Solo uso Java Config y no XML.
Creo que debo agregar el filtro aquí:
public class WebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Filter[] getServletFilters() {
MyOwnFilter=new MyOwnFilter();
return new Filter[] {MyOwnFilter};
}
}
Alternativa a los filtros, puede utilizar 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 {
}
}
Y luego agregue este interceptor a su configuración 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
}
}
Puede crear y configurar su propio filtro siguiendo estos pasos.
1) Cree su clase implementando la interfaz de filtro y anule sus métodos.
public class MyFilter implements javax.servlet.Filter{
public void destroy(){}
public void doFilter(Request, Response, FilterChain){//do what you want to filter
}
........
}
2) Ahora configura tu filtro en web.xml
<filter>
<filter-name>myFilter</filter-name>
<filter-class>MyFilter</filter-class>
</filter>
3) Ahora proporcione la asignación de URL del filtro.
<filter-mapping>
<filter-name>myFilter</filter-name>
<url-pattern>*</url-pattern>
</filter-mapping>
4) Ahora reinicie su servidor y verifique que todas las solicitudes web lleguen primero a MyFilter y luego al controlador respectivo.
Esperemos que sea la respuesta requerida.
Spring puede usar filtros, pero recomiendan que uses su versión de filtros, conocida como un interceptor
http://viralpatel.net/blogs/spring-mvc-interceptor-example/
Hay una rápida descripción de cómo funcionan. Son casi idénticos a los filtros, pero están diseñados para funcionar dentro del ciclo de vida de Spring MVC.
Su enfoque se ve correcto.
Una vez usé algo similar a seguir (eliminé la mayoría de las líneas y lo mantuve 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[] { "/" };
}
}
También necesitas un filtro personalizado que se ve a continuación.
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 {
}
}
Espero que esto ayude.
Además, puede usar FilterRegistrationBean
si FilterRegistrationBean
Spring Boot. Hace lo mismo (creo que sí) que FilterRegistration.Dynamic
hace.
Supongo que está intentando implementar algún tipo de seguridad OAuth que se basa en el token jwt.
Hoy en día hay varias formas de hacerlo, pero aquí está mi favorita:
Así es como se ve el filtro:
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);
}
}
Bastante simple es el controlador de usuario también donde puede encontrar el método de inicio de sesión:
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;
}
}
}
Por supuesto tenemos Main donde puedes ver el bean de filtro:
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);
}
}
Por último, pero no menos importante, hay un controlador de ejemplo:
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 hay un enlace a GitHub. Gracias a nielsutrecht por el gran trabajo que he usado este proyecto como base y funciona perfectamente.
También puede implementarlo utilizando un aspecto con un punto de corte que apunte a una anotación determinada. He escrito una biblioteca que le permite utilizar anotaciones que realizan comprobaciones de autorización basadas en un token JWT.
Puede encontrar el proyecto con toda la documentación en: https://github.com/nille85/jwt-aspect . He utilizado este enfoque varias veces para asegurar un Backend REST que consume una aplicación de una sola página.
También he documentado en mi blog cómo puede usarlo en una aplicación Spring MVC: http://www.nille.be/security/creating-authorization-server-using-jwts/
Lo siguiente es un extracto del proyecto de ejemplo en https://github.com/nille85/auth-server
El siguiente ejemplo contiene un método protegido getClient. La anotación @Authorize que utiliza el aspecto comprueba si el valor de "aud jwt claim" coincide con el parámetro clientId que se anota con @ClaimValue . Si coincide, se puede introducir el método. De lo contrario se lanza una excepción.
@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);
}
}
El Aspecto en sí puede configurarse como:
@Bean
public JWTAspect jwtAspect() {
JWTAspect aspect = new JWTAspect(payloadService());
return aspect;
}
El PayloadService que se necesita se puede implementar, por ejemplo, como:
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;
}
}