[英]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.