我使用Spring MVC(4.0.1)作为休息服务的后端和angularjs作为前端。
我的服务器后端的每个请求都有一个带有会话ID的http-header
我可以使用以下代码在服务器后端读取此标头:
@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};
}
}
答案 0 :(得分:28)
替代过滤器,您可以使用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
}
}
答案 1 :(得分:8)
以下是执行您提到的逻辑的过滤器
@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()};
}
}
答案 2 :(得分:6)
Spring 可以使用过滤器,但是他们建议您使用他们的过滤器版本,称为拦截器
http://viralpatel.net/blogs/spring-mvc-interceptor-example/
快速了解它们的工作原理。它们几乎与过滤器完全相同,但设计用于在Spring MVC生命周期内工作。
答案 3 :(得分:6)
我假设您正在尝试实现某种基于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);
}
}
Here是指向GitHub的链接所有感谢 nielsutrecht ,因为我使用这个项目作为基础的伟大工作,它运作良好。
答案 4 :(得分:2)
您还可以使用具有针对特定注释的切入点的方面来实现它。我编写了一个库,使您可以使用基于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 检查来自&#34; aud jwt claim&#34; 的值是否与使用 @注释的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);
}
}
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;
}
}
答案 5 :(得分:1)
您可以通过执行以下步骤来创建和配置自己的过滤器。
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,然后继续进入相应的控制器。
希望这将是必要的答案。
答案 6 :(得分:1)
您的方法看起来是正确的。
一旦我使用了类似于下面的内容(删除了大部分行并保持简单)。
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
做了同样的事情(我认为是这样)。