Как создать собственный фильтр с помощью 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};
}
}
Ответы
Ответ 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
делает.