2017-11-17 7 views
5

Ich bin eine API für eine Angular 5-Anwendung. Ich möchte JWT für die Authentifizierung verwenden.
Ich möchte die Funktionen nutzen, die durch Feder Sicherheit vorgesehen sind, so kann ich mit Rollen leicht arbeiten.Frühling webflux benutzerdefinierte Authentifizierung für API

konnte ich die Standardauthentifizierung deaktivieren. Aber wenn ich http.authorizeExchange().anyExchange().authenticated(); benutze, bekomme ich immer noch eine Login-Eingabeaufforderung.
Ich möchte nur eine 403 anstelle der Eingabeaufforderung geben. Also überschreibt die Login-Eingabeaufforderung von einem "Ding" (Ist es ein Filter?), Die den Header Authorization für das Token überprüft.

Die Login Ich will nur in einem Controller tun, die eine JWT Token zurück. Aber welche Spring Security Bean sollte ich zum Überprüfen von Benutzerdaten verwenden? Ich kann meine eigenen Dienste und Repositories erstellen, aber ich möchte die Funktionen der Federsicherung so gut wie möglich nutzen. Die kurze Version dieser Frage lautet nur:
Wie kann ich die Authentifizierung der Federsicherheit anpassen?
Welche Bohnen zu tun haben, ich erstellen?
Wo muss ich die Konfiguration ablegen? (Ich habe jetzt eine Bohne von SecurityWebFilterChain)

Die einzigen Dokumentation I zur Authentifizierung in webflux mit Feder Sicherheit finden konnte, ist dies: https://docs.spring.io/spring-security/site/docs/5.0.0.BUILD-SNAPSHOT/reference/htmlsingle/#jc-webflux

Antwort

7

Nach vielen Ich denke, ich habe die Lösung gefunden:

Sie benötigen eine Bohne von SecurityWebFilterChain, die alle Konfiguration enthält.
Dies ist mein:

@Configuration 
public class SecurityConfiguration { 

    @Autowired 
    private AuthenticationManager authenticationManager; 

    @Autowired 
    private SecurityContextRepository securityContextRepository; 

    @Bean 
    public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { 
     // Disable default security. 
     http.httpBasic().disable(); 
     http.formLogin().disable(); 
     http.csrf().disable(); 
     http.logout().disable(); 

     // Add custom security. 
     http.authenticationManager(this.authenticationManager); 
     http.securityContextRepository(this.securityContextRepository); 

     // Disable authentication for `/auth/**` routes. 
     http.authorizeExchange().pathMatchers("/auth/**").permitAll(); 
     http.authorizeExchange().anyExchange().authenticated(); 

     return http.build(); 
    } 
} 

Ich habe deaktiviert Httpbasic, formLogin, csrf und Abmeldung, so konnte ich meine benutzerdefinierte Authentifizierung machen.

Mit den Einstellungen AuthenticationManager und SecurityContextRepository habe ich die Standard-Sicherheitskonfiguration für die Federung überschrieben, um zu überprüfen, ob ein Benutzer für eine Anforderung authentifiziert/autorisiert ist.

Der Authentifizierungsmanager:

@Component 
public class AuthenticationManager implements ReactiveAuthenticationManager { 

    @Override 
    public Mono<Authentication> authenticate(Authentication authentication) { 
     // JwtAuthenticationToken is my custom token. 
     if (authentication instanceof JwtAuthenticationToken) { 
      authentication.setAuthenticated(true); 
     } 
     return Mono.just(authentication); 
    } 
} 

Ich bin nicht ganz sicher, wo der Authentifizierungsmanager für ist, aber ich denke, die endgültige Authentifizierung zu tun, so authentication.setAuthenticated(true); eingestellt wird, wenn alles richtig ist.

SecurityContextRepository:

@Component 
public class SecurityContextRepository implements ServerSecurityContextRepository { 

    @Override 
    public Mono<Void> save(ServerWebExchange serverWebExchange, SecurityContext securityContext) { 
     // Don't know yet where this is for. 
     return null; 
    } 

    @Override 
    public Mono<SecurityContext> load(ServerWebExchange serverWebExchange) { 
     // JwtAuthenticationToken and GuestAuthenticationToken are custom Authentication tokens. 
     Authentication authentication = (/* check if authenticated based on headers in serverWebExchange */) ? 
      new JwtAuthenticationToken(...) : 
      new GuestAuthenticationToken(); 
     return new SecurityContextImpl(authentication); 
    } 
} 

In der Last werde ich auf den Header in der serverWebExchange Basis prüfen, ob der Benutzer authentifiziert ist. Ich benutze https://github.com/jwtk/jjwt. Ich gebe eine andere Art von Authentifizierungstoken zurück, wenn der Benutzer authentifiziert ist oder nicht.

3

In meinem alten Projekt, das ich diese Konfiguration verwendet:

@Configuration 
@EnableWebSecurity 
@Import(WebMvcConfig.class) 
@PropertySource(value = { "classpath:config.properties" }, encoding = "UTF-8", ignoreResourceNotFound = false) 
public class WebSecWebSecurityCfg extends WebSecurityConfigurerAdapter 
{ 
    private UserDetailsService userDetailsService; 
    @Autowired 
    @Qualifier("objectMapper") 
    private ObjectMapper mapper; 
    @Autowired 
    @Qualifier("passwordEncoder") 
    private PasswordEncoder passwordEncoder; 
    @Autowired 
    private Environment env; 

    public WebSecWebSecurityCfg(UserDetailsService userDetailsService) 
    { 
     this.userDetailsService = userDetailsService; 
    } 



    @Override 
    protected void configure(HttpSecurity http) throws Exception 
    {                
     JWTAuthorizationFilter authFilter = new JWTAuthorizationFilter 
                    ( authenticationManager(),//Auth mgr 
                     env.getProperty("config.secret.symmetric.key"), //Chiave simmetrica 
                     env.getProperty("config.jwt.header.string"), //nome header 
                     env.getProperty("config.jwt.token.prefix") //Prefisso token 
                    ); 
     JWTAuthenticationFilter authenticationFilter = new JWTAuthenticationFilter 
                    (
                     authenticationManager(), //Authentication Manager 
                     env.getProperty("config.secret.symmetric.key"), //Chiave simmetrica 
                     Long.valueOf(env.getProperty("config.jwt.token.duration")),//Durata del token in millisecondi 
                     env.getProperty("config.jwt.header.string"), //nome header 
                     env.getProperty("config.jwt.token.prefix"), //Prefisso token 
                     mapper 
                    ); 
     http   
     .cors() 
     .and() 
     .csrf() 
     .disable() 
     .authorizeRequests() 
     .anyRequest() 
     .authenticated() 
     .and() 
     .addFilter(authenticationFilter) 
     .addFilter(authFilter) 
     // Disabilitiamo la creazione di sessione in spring 
     .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); 
    } 

    @Override 
    public void configure(AuthenticationManagerBuilder auth) throws Exception 
    { 
     auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder); 
    } 

    @Bean 
    CorsConfigurationSource corsConfigurationSource() 
    { 
     final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); 
     source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues()); 
     return source; 
    } 
} 

wo JWTAuthorizationFilter ist:

public class JWTAuthorizationFilter extends BasicAuthenticationFilter 
{ 
    private static final Logger logger = LoggerFactory.getLogger(JWTAuthenticationFilter.class.getName()); 
    private String secretKey; 
    private String headerString; 
    private String tokenPrefix; 

    public JWTAuthorizationFilter(AuthenticationManager authenticationManager, AuthenticationEntryPoint authenticationEntryPoint, String secretKey, String headerString, String tokenPrefix) 
    { 
     super(authenticationManager, authenticationEntryPoint); 
     this.secretKey = secretKey; 
     this.headerString = headerString; 
     this.tokenPrefix = tokenPrefix; 
    } 
    public JWTAuthorizationFilter(AuthenticationManager authenticationManager, String secretKey, String headerString, String tokenPrefix) 
    { 
     super(authenticationManager); 
     this.secretKey = secretKey; 
     this.headerString = headerString; 
     this.tokenPrefix = tokenPrefix; 
    } 
    @Override 
    protected void onUnsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException 
    { 
     AuthenticationErrorEnum customErrorCode = null; 
     StringBuilder builder = new StringBuilder(); 
     if(failed.getCause() instanceof MissingJwtTokenException) 
     { 
      customErrorCode = AuthenticationErrorEnum.TOKEN_JWT_MANCANTE; 
     } 
     else if(failed.getCause() instanceof ExpiredJwtException) 
     { 
      customErrorCode = AuthenticationErrorEnum.TOKEN_JWT_SCADUTO; 
     } 
     else if(failed.getCause() instanceof MalformedJwtException) 
     { 
      customErrorCode = AuthenticationErrorEnum.TOKEN_JWT_NON_CORRETTO; 
     } 
     else if(failed.getCause() instanceof MissingUserSubjectException) 
     { 
      customErrorCode = AuthenticationErrorEnum.TOKEN_JWT_NESSUN_UTENTE_TROVATO; 
     } 
     else if((failed.getCause() instanceof GenericJwtAuthorizationException) || (failed.getCause() instanceof Exception)) 
     { 
      customErrorCode = AuthenticationErrorEnum.ERRORE_GENERICO; 
     } 
     builder.append("Errore duranre l'autorizzazione. "); 
     builder.append(failed.getMessage()); 
     JwtAuthApiError apiError = new JwtAuthApiError(HttpStatus.UNAUTHORIZED, failed.getMessage(), Arrays.asList(builder.toString()), customErrorCode); 
     String errore = (new ObjectMapper()).writeValueAsString(apiError); 
     response.setContentType(MediaType.APPLICATION_JSON_VALUE); 
     response.sendError(HttpStatus.UNAUTHORIZED.value(), errore); 
     request.setAttribute(IRsConstants.API_ERROR_REQUEST_ATTR_NAME, apiError); 
    } 

Und JWTAuthenticationFilter ist

public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter 
{ 
    private AuthenticationManager authenticationManager; 
    private String secretKey; 
    private long tokenDurationMillis; 
    private String headerString; 
    private String tokenPrefix; 
    private ObjectMapper mapper; 

    @Override 
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException 
    { 
     AuthenticationErrorEnum customErrorCode = null; 
     StringBuilder builder = new StringBuilder(); 
     if(failed instanceof BadCredentialsException) 
     { 
      customErrorCode = AuthenticationErrorEnum.CREDENZIALI_SERVIZIO_ERRATE; 
     } 

     else 
     { 
      //Teoricamente nella fase di autenticazione all'errore generico non dovrebbe mai arrivare 
      customErrorCode = AuthenticationErrorEnum.ERRORE_GENERICO; 
     }  
     builder.append("Errore durante l'autenticazione del servizio. "); 
     builder.append(failed.getMessage()); 
     JwtAuthApiError apiError = new JwtAuthApiError(HttpStatus.UNAUTHORIZED, failed.getMessage(), Arrays.asList(builder.toString()), customErrorCode); 
     String errore = mapper.writeValueAsString(apiError); 
     response.setContentType(MediaType.APPLICATION_JSON_VALUE); 
     response.sendError(HttpStatus.UNAUTHORIZED.value(), errore); 
     request.setAttribute(IRsConstants.API_ERROR_REQUEST_ATTR_NAME, apiError); 
    } 

    public JWTAuthenticationFilter(AuthenticationManager authenticationManager, String secretKey, long tokenDurationMillis, String headerString, String tokenPrefix, ObjectMapper mapper) 
    { 
     super(); 
     this.authenticationManager = authenticationManager; 
     this.secretKey = secretKey; 
     this.tokenDurationMillis = tokenDurationMillis; 
     this.headerString = headerString; 
     this.tokenPrefix = tokenPrefix; 
     this.mapper = mapper; 
    } 

    @Override 
    public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res) throws AuthenticationException 
    { 
     try 
     { 
      ServiceLoginDto creds = new ObjectMapper().readValue(req.getInputStream(), ServiceLoginDto.class); 

      return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(creds.getCodiceServizio(), creds.getPasswordServizio(), new ArrayList<>())); 
     } 
     catch (IOException e) 
     { 
      throw new RuntimeException(e); 
     } 
    } 

    @Override 
    protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res, FilterChain chain, Authentication auth) throws IOException, ServletException 
    { 
     DateTime dt = new DateTime(); 
     Date expirationTime = dt.plus(getTokenDurationMillis()).toDate(); 
     String token = Jwts 
         .builder() 
         .setSubject(((User) auth.getPrincipal()).getUsername()) 
         .setExpiration(expirationTime) 
         .signWith(SignatureAlgorithm.HS512, getSecretKey().getBytes()) 
         .compact(); 
     res.addHeader(getHeaderString(), getTokenPrefix() + token); 
     res.addHeader("jwtExpirationDate", expirationTime.toString()); 
     res.addHeader("jwtTokenDuration", String.valueOf(TimeUnit.MILLISECONDS.toMinutes(getTokenDurationMillis()))+" minuti"); 
    } 
    public String getSecretKey() 
    { 
     return secretKey; 
    } 

    public void setSecretKey(String secretKey) 
    { 
     this.secretKey = secretKey; 
    } 

    public long getTokenDurationMillis() 
    { 
     return tokenDurationMillis; 
    } 

    public void setTokenDurationMillis(long tokenDurationMillis) 
    { 
     this.tokenDurationMillis = tokenDurationMillis; 
    } 

    public String getHeaderString() 
    { 
     return headerString; 
    } 

    public void setHeaderString(String headerString) 
    { 
     this.headerString = headerString; 
    } 

    public String getTokenPrefix() 
    { 
     return tokenPrefix; 
    } 

    public void setTokenPrefix(String tokenPrefix) 
    { 
     this.tokenPrefix = tokenPrefix; 
    } 
} 

Der Benutzer Detail ist ein klassische userservicedetail

@Service 
public class UserDetailsServiceImpl implements UserDetailsService 
{ 
    @Autowired 
    private IServizioService service; 

    @Override 
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException 
    { 
     Service svc; 
     try 
     { 
      svc = service.findBySvcCode(username); 
     } 
     catch (DbException e) 
     { 
      throw new UsernameNotFoundException("Errore durante il processo di autenticazione; "+e.getMessage(), e); 
     } 
     if (svc == null) 
     { 
      throw new UsernameNotFoundException("Nessun servizio trovato per il codice servizio "+username); 
     } 
     else if(!svc.getAbilitato().booleanValue()) 
     { 
      throw new UsernameNotFoundException("Servizio "+username+" non abilitato"); 
     } 
     return new User(svc.getCodiceServizio(), svc.getPasswordServizio(), Collections.emptyList()); 
    } 
} 

Bitte beachten Sie, ich nicht Frühling

webflux

hätte verwende ich hoffe, es ist nützlich

Angelo

+0

Dank! Aber webflux security funktioniert ganz anders. Aber ich bin mir sicher, dass ich einige Teile verwenden kann. –

1

Dank Jan Sie haben mir mit Ihrem Beispiel sehr geholfen, die Authentifizierung in meiner Spring Webflux-Anwendung anzupassen und den Zugriff auf apis zu sichern.
In meinem Fall muss ich nur eine Kopfzeile lesen, um Benutzerrollen festzulegen, und ich möchte, dass die Spring-Sicherheit Benutzerberechtigungen überprüft, um den Zugriff auf meine Methoden zu sichern.
Sie gaben den Schlüssel mit benutzerdefinierten http.securityContextRepository(this.securityContextRepository); in SecurityConfiguration (keine Notwendigkeit eines benutzerdefinierten AuthenticationManager).

Dank dieses SecurityContextRepository konnte ich eine benutzerdefinierte Authentifizierung erstellen und einrichten (vereinfacht unten).

@Override 
public Mono<SecurityContext> load(ServerWebExchange serverWebExchange) { 
    String role = serverWebExchange.getRequest().getHeaders().getFirst("my-header"); 
    Authentication authentication = 
     new AnonymousAuthenticationToken("authenticated-user", someUser, AuthorityUtils.createAuthorityList(role)); 

    return Mono.just(new SecurityContextImpl(authentication)); 
} 

Und so kann ich meine Methoden mit diesen Rollen sichern:

@Component 
public class MyService { 
    @PreAuthorize("hasRole('ADMIN')") 
    public Mono<String> checkAdmin() { 
     // my secure method 
    } 
} 
Verwandte Themen