2016-12-22 2 views
1

Das Szenario wir suchen ist wie folgt:Spring Security unerwartetes Verhalten für REST-Endpunkte Authentifizierung?

  1. Client mit REST-URL zu einer REST Login
  2. Frühling Micro (mit Spring Security) verbindet
  3. Token 200 OK und ein Login zurückgeben sollte
  4. der Client hält das Token
  5. der Client ruft andere REST-Endpunkte mit demselben Token auf.

Allerdings sehe ich, dass der Client 302 und einen Location Header wird immer zusammen mit dem Token. Es tut also authentifizieren, aber mit unerwünschten HTTP-Antwort Statuscode und Header.

Die Spring Security-Konfiguration wie folgt aussieht:

@Configuration 
@EnableWebSecurity 
public class WebSecurityConfig extends WebSecurityConfigurerAdapter { 
    @Override 
    protected void configure(HttpSecurity http) throws Exception { 
     http 
      .csrf() 
       .disable() // Refactor login form 
       // See https://jira.springsource.org/browse/SPR-11496 
      .headers() 
       .addHeaderWriter(new XFrameOptionsHeaderWriter(XFrameOptionsHeaderWriter.XFrameOptionsMode.SAMEORIGIN)) 
       .and() 
      .formLogin() 
       .loginPage("/signin") 
       .permitAll() 
       .and() 
      .logout() 
       .logoutUrl("/signout") 
       .permitAll() 
       .and() 
      .authorizeRequests() 
       .antMatchers("/", "/home").permitAll() 
       .anyRequest().authenticated(); 
... 
} 

Ich habe versucht, Abfangjäger und Hinzufügen von Filtern, aber nicht sehen kann, wo 302 und Ort festgelegt und werden im Frühjahr Seite hinzugefügt. jedoch die Location Header in den Antwort-Header nicht zeigen, auf der Client-Seite empfing (zusammen mit dem Rest des Spring Security Header LINK):

Server=Apache-Coyote/1.1 
X-Content-Type-Options=nosniff 
X-XSS-Protection=1; mode=block 
Cache-Control=no-cache, no-store, max-age=0, must-revalidate 
Pragma=no-cache 
Expires=0 
X-Frame-Options=DENY, SAMEORIGIN 
Set-Cookie=JSESSIONID=D1C1F1CE1FF4E1B3DDF6FA302D48A905; Path=/; HttpOnly 
Location=http://ec2-35-166-130-246.us-west-2.compute.amazonaws.com:8108/ <---- ouch 
Content-Length=0 
Date=Thu, 22 Dec 2016 20:15:20 GMT 

Jeder Vorschlag, wie man es wie erwartet funktioniert ("200 OK ", keine Location-Header und das Token)?

HINWEIS: Verwenden von Spring Boot, Spring Security, keine Benutzeroberfläche, nur Client-Code, der REST-Endpunkte aufruft.

+0

Wir versuchen, eine URL zu erstellen, wenn sich die App mit POST und Anmeldeinformationen anmeldet und ein Anmelde-Token erhält. mir ist nicht klar, warum es ein 302 sein sollte. Ich vermisse vielleicht etwas ... – Roy

+0

// client calls: new RestTemplate(). execute (SIGIN_URL, HttpMethod.POST, \t neu RequestCallback() {... }, \t neue ResponseExtractor ... { \t public Object extractData (...) {// Statuscode 302 jetzt \t headersToUpdate.add ("Cookie", response.getHeaders(). getFirst ("Set- Cookie ")); \t return null;} (No UI und Tasten) – Roy

Antwort

-1

Sie können verwenden und dann diese Methode verketten, um die gewünschten Header hinzuzufügen.

+1

versucht, aber nach wie vor Ort zeigt – Roy

0

Es ist eine 302 Antwort, die den Browser anweist, auf Ihre Anmeldeseite umzuleiten. Was erwartest du zu passieren? 302 Antwort muss einen Location-Header haben.

+0

der Client conne. cts to/signin ist die Login-URL. also erwarte ich tatsächlich, 200 zu sehen. fehle ich etwas? können Sie weitere Informationen teilen? – Roy

+0

vielleicht werde ich repharese - was soll ich ändern, um 200 Statuscode und keinen Location-Header zu bekommen? – Roy

+1

@Roy du kannst nicht. Ist die '/ signin'-URL für Browser gedacht oder ist dies ein Anmeldeendpunkt für API-Clients, um eine Art Token zu erhalten? Wenn es ein Token erhalten soll, wie soll dieses Token dann verwendet werden, nachdem der Client es erhalten hat? – Strelok

0

Wenn Sie einen Rest api benötigen, dürfen Sie http.formLogin() nicht verwenden. Es generiert Formular-basierte Anmeldung wie beschrieben here.

Stattdessen können Sie diese Konfiguration

httpSecurity 
       .csrf() 
        .disable() 
       .exceptionHandling() 
        .authenticationEntryPoint(authenticationEntryPoint) 
       .and() 
       .sessionManagement() 
        .sessionCreationPolicy(SessionCreationPolicy.STATELESS) 
       .and() 
       .authorizeRequests() 
        .antMatchers(HttpMethod.OPTIONS, "/**").permitAll() 
        .antMatchers("/login").permitAll() 
        .anyRequest().authenticated() 
       .and() 
       .logout() 
        .disable() 
       .addFilterBefore(authTokenFilter, UsernamePasswordAuthenticationFilter.class); 

Erstellen Sie eine Klasse haben, AuthTokenFilter die Feder erstreckt UsernamePasswordAuthenticationFilter und doFilter Methode überschreiben, die in jeder Anforderung für einen Authentifizierungs-Token prüft und setzt die SecurityContextHolder entsprechend.

@Override 
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
      throws IOException, ServletException { 
     HttpServletResponse resp = (HttpServletResponse) response; 
     resp.setHeader("Access-Control-Allow-Origin", "*"); 
     resp.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS"); 
     resp.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, " + tokenHeader); 

     HttpServletRequest httpRequest = (HttpServletRequest) request; 
     String authToken = httpRequest.getHeader(tokenHeader); 
     String username = this.tokenUtils.getUsernameFromToken(authToken); // Create some token utility class to manage tokens 

     if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { 

      UsernamePasswordAuthenticationToken authentication = 
          new UsernamePasswordAuthenticationToken(-------------); 
      // Create an authnetication as above and set SecurityContextHolder 
      authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpRequest)); 
     SecurityContextHolder.getContext().setAuthentication(authentication); 
     } 
     chain.doFilter(request, response); 
} 

Dann ein AuthenticationController, abgebildet mit /login URL erstellen, die Anmeldeinformationen überprüft und gibt Token.

/* 
* Perform the authentication. This will call Spring UserDetailsService's loadUserByUsername implicitly 
* BadCredentialsException is thrown if username and password mismatch 
*/ 
Authentication authentication = this.authenticationManager.authenticate(
    new UsernamePasswordAuthenticationToken(
      authenticationRequest.getUsername(), 
      authenticationRequest.getPassword() 
    ) 
); 
SecurityContextHolder.getContext().setAuthentication(authentication);   
UserDetailsImp userDetails = (UserDetailsImp) authentication.getPrincipal(); 
// Generate token using some Token Utils class methods, using this principal 

Um zu verstehen, loadUserByUsername, UserDetailsService und UserDetails finden Sie Spring security docs }

Zum besseren Verständnis bitte obigen Link und den folgenden Kapiteln gründlich lesen.

+0

@Ramanujan, ich habe eine erste funktionierende Grundlinie. Ich werde die von Ihnen gesendeten Links lesen, den Code überprüfen und aktualisieren. Vielen Dank! – Roy

+0

immer noch mit der langen und nicht so klaren Dokumentation zu kämpfen. In der Zwischenzeit könnten Sie bitte auch Ratschläge zum Umgang mit der Abmeldung hinzufügen (z. B. Ungültigkeitserklärung des authToken, keine Weiterleitung usw. verwenden)? danke! – Roy

0

Sie können Ihre benutzerdefinierte AuthenticationSuccessHandler-Methode und die Override-Methode "onAuthenticationSuccess" implementieren, um den Antwortstatus gemäß Ihren Anforderungen zu ändern.

Beispiel:

@Override 
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, 
     Authentication authentication) throws IOException, ServletException { 
    ObjectMapper mapper = new ObjectMapper(); 
    Map<String, String> tokenMap = new HashMap<String, String>(); 
    tokenMap.put("token", accessToken.getToken()); 
    tokenMap.put("refreshToken", refreshToken.getToken()); 
    response.setStatus(HttpStatus.OK.value()); 
    response.setContentType(MediaType.APPLICATION_JSON_VALUE); 
    mapper.writeValue(response.getWriter(), tokenMap); 
} 
1

http.formLogin()

ist für die formularbasierte Anmeldung. Daher wird der Header 302 status und Location in der Antwort erwartet, wenn Sie versuchen, auf eine geschützte Ressource zuzugreifen, ohne authentifiziert zu sein.

Basierend auf Ihrer Anforderung/Szenario

  1. Client eine Verbindung mit REST zu einem Login-REST-URL

haben Sie für die Authentifizierung unter Verwendung von HTTP Basic in Betracht gezogen?

http.httpBasic()

über die HTTP-Basic, können Sie den Authorization-Header mit dem Benutzername/Passwort bevölkern und die BasicAuthenticationFilter kümmern sich um die Anmeldeinformationen zur Authentifizierung und die Security entsprechend bevölkern.

Ich habe eine working example dieser mit Angular auf der Client-Seite und Spring Boot-Spring Security auf Back-End. Wenn Sie sich security-service.js ansehen, sehen Sie eine Fabrik namens securityService, die eine login() Funktion bietet. Diese Funktion ruft die /principal Endpunkt mit dem Header Authorization mit dem Benutzernamen/Passwort bevölkerten per HTTP Basic-Format, zum Beispiel:

Authorization : Basic base64Encoded(username:passsword)

Die BasicAuthenticationFilter diese Anforderung verarbeiten, indem sie die Anmeldeinformationen zu extrahieren und schließlich der Authentifizierung des Benutzers und Füllen der SecurityContext mit dem authentifizierten Prinzipal. Nach erfolgreicher Authentifizierung wird die Anforderung zum angegebenen Endpunkt /principal weitergeleitet, der auf SecurityController.currentPrincipal abgebildet ist, der einfach eine JSON-Repräsentation des authentifizierten Prinzipals zurückgibt.

Für weitere Anforderungen:

  1. Frühling Micro (mit Spring Security) sollte Token
  2. 200 OK und eine Anmeldung Rückkehr hält der Kunde das Token
  3. Der Client ruft andere REST-Endpunkte mit demselben Token auf.

Sie können eine Sicherheits/Login-Token erzeugen und zurückgeben, dass anstelle der Benutzer-Info. Ich würde jedoch wärmstens empfehlen, Spring Security OAuth zu betrachten, wenn Sie eine Anzahl von REST-Endpunkten in verschiedenen Microservices bereitgestellt haben, die über ein Sicherheitstoken geschützt werden müssen. Der Aufbau eines eigenen STS (Security Token Service) kann sehr kompliziert werden und wird daher nicht empfohlen.

Verwandte Themen