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