2017-06-06 6 views
1

本番環境では、プロジェクトの基本認証からKeycloakへの移行が完了しました。しかし、ローカル開発、スタンドアロンとデモのインストールでは、基本認証を引き続き使用したいと考えています。これは、プロファイルなどでトリガされる可能性があります。Angular JS/Spring起動アプリケーションでkeycloakの代わりに基本認証を行う方法

このプロジェクトでは、Java/Springブートで開発されたREST APIと、これらのAPIを使用するAngularJSアプリケーションがあります。 Keycloakを使用して、AngularJSアプリとAPIの両方を保護しています。

問題は、Spring SecurityとKeycloakを、同じアプリケーションで異なるプロファイルで「一緒に」動作させる方法です。後述のように、私は、これまでのところ、春のセキュリティとKeycloakの両方を設定することがわかった、とプロパティファイルと回避策を作っソリューション:

application-keycloak.properties

#Unactivate Basic Authentication 
security.ignored=/** 

アプリケーション-ローカルauth。プロパティ

#Unactivate Keycloak 
spring.autoconfigure.exclude=org.keycloak.adapters.springboot.KeycloakSpringBootConfiguration 

私はkeycloak使用するwanto

は、私が問題を抱えていないために、セキュリティを無視する必要があると私は基本認証を使用したいとき、私はまた、競合を防ぐためにkeycloak構成を除外しなければなりません。

@Configuration 
public class SecurityConfiguration extends WebSecurityConfigurerAdapter { 

@Override 
protected void configure(HttpSecurity http) throws Exception { 
    http.httpBasic().and() 
      .authorizeRequests() 
      .antMatchers("/","/scripts/**","/keycloak/isActive","/keycloak/config","/bower_components/**","/views/**","/fonts/**", 
        "/views/inventory/dialogs/**", "/services/**","/resources/**","/styles/**", "/info") 

      .permitAll() 
      .anyRequest() 
      .authenticated() 
      .and() 
      .csrf().disable(); 
} 


@Autowired 
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { 
    auth.inMemoryAuthentication().withUser("admin").password("admin").roles("ADMIN"); 
} 

そして、これが私のKeycloak春ブート構成です:

これは私のセキュリティ設定クラスです

# Keycloak 
keycloak.realm=local 
keycloak.realmKey=MIIBIjANBgkqhkiG9wsIIBCgKCAQEAuJYmaWvF3YhifflJhspXOs8RJn74w+eVD8PtpVbu2cYG9OIa49P8SwqVn/kyJQr7kT3OlCq3XMZWBHe+JSzSz7KttKkhfFSfzISdKDKlkPena2H/i3FKlRZIldbeeuQNYdD6nMpzU6QWLwGF1cUAo1M11f2p99QI1FOhVPJSErWsjDsKpWqG+rMMjT1eos0QCNP7krx/yfMdlUyaJCYiDvpOAoec3OWXvDJovEajBNAZMWVXgJF90wAVPRF6szraA2m7K2gG9ozaCNWB0v4Sy6czekbKjqEBPJo45uEmGHd92V//uf/WQG4HSiuv8CTV+b6TQxKtZCpQpqp2DyCLewIDAQAB 
keycloak.auth-server-url=http://localhost:8080/auth 
keycloak.ssl-required=none 
keycloak.resource=App-backend 
keycloak.bearer-only=true 
keycloak.credentials.secret=a714aede-5af9-4560-8c9d-d655c831772f 
keycloak.securityConstraints[0].securityCollections[0].name=Secured API 
keycloak.securityConstraints[0].securityCollections[0].authRoles[0]=ROLE_USER 
keycloak.securityConstraints[0].securityCollections[0].patterns[0]=/api/* 

それが動作している、しかし、私はそれはエレガントな解決策ではないと思います。私はKeycloakプロパティを使用してこれを実装しようとしましたenable-basic-auth、しかし私はそれがどのように動作するのか理解できませんでしたが、それはちょうどレストAPIを保護するために、それは他のすべての要求に対してです。

誰かがこのようなことを実行しなければならなかったことはありますか?

+0

問題が解決しましたか?私も同様の問題を抱えています。 – nejckorasa

答えて

0

私はこれを解決することができました。しかし、私の解決策がどれほど美しいかは議論の余地があります。

私のケースでは、Keycloakを使用してほとんどのエンドポイントを保護する必要がありますが、バッチ処理ではBasic認証を使用する必要があります。両方を設定することは、KeycloakがBasic AuthであってもAuthorization Headerを検証しようとするという欠点があるので、私は3つのことをする必要がありました。

  1. バッチルートの自動セキュリティをすべて無効にします。
  2. バッチルートを保護するカスタムリクエストフィルタを作成します。
  3. サークルリクエストオブジェクトを操作して、熱心なキークローキングフィルタがトリップしないようにします。

私のセキュリティ設定。

@EnableWebSecurity 
@EnableResourceServer 
public class SecurityConfiguration extends KeycloakWebSecurityConfigureAdapter { 

    @Override 
    public void configure(HttpSecurity http) throws Exception { 
     super.configure(http); 
     http.authorizeRequests() 
      // usual configuration ... 
      .antMatchers("/api/v1/batch/**").permitAll() // decouple security for this route 
      .anyRequest().denyAll(); 
    } 
} 

マイカスタム要求フィルタは、(春のセキュリティフィルタ、これ発注注釈の前に実行する必要がある):

@Component 
@Slf4j 
@Order(Ordered.HIGHEST_PRECEDENCE + 2) 
public class BasicAuthRequestFilter extends OncePerRequestFilter { 

    @Value("${batch.user}") 
    private String user; 

    @Value("${batch.password}") 
    private String password; 

    @Override 
    protected void doFilterInternal(
     HttpServletRequest request, 
     HttpServletResponse response, 
     FilterChain filterChain 
    ) throws ServletException, IOException { 
     if (isBatchRequest(request)) { 
      SimpleHttpFacade facade = new SimpleHttpFacade(request, response); 
      if (AuthOutcome.AUTHENTICATED.equals(auth(facade))) { 
       filterChain.doFilter(new AuthentifiedHttpServletRequest(request), response); 
      } 
      log.debug("Basic auth failed"); 
      SecurityContextHolder.clearContext(); 
      response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unable to authenticate with basic authentication"); 
      return; 
     } 
     filterChain.doFilter(request, response); 
    } 

    private boolean isBatchRequest(HttpServletRequest request) { 
     return request.getRequestURI().startsWith("/api/v1/batch/"); 
    } 

    private AuthOutcome auth(HttpFacade exchange) { 
     return extractToken(exchange.getRequest().getHeaders(HttpHeaders.AUTHORIZATION)) 
      .map(token -> extractUserPw(token) 
      .filter(userpw -> verify(userpw.getFirst(), userpw.getSecond())) 
      .map(userpw -> AuthOutcome.AUTHENTICATED) 
      .orElse(AuthOutcome.FAILED)) 
     .orElse(AuthOutcome.NOT_ATTEMPTED); 
    } 

    private Optional<String> extractToken(List<String> authHeaders) { 
     return authHeaders == null ? Optional.empty() : authHeaders.stream().map(authHeader -> authHeader.trim().split("\\s+")) 
      .filter(split -> split.length == 2) 
      .filter(split -> split[0].equalsIgnoreCase("Basic")) 
      .map(split -> split[1]) 
      .findFirst(); 
    } 

    private Optional<Pair<String, String>> extractUserPw(String token) { 
     try { 
      String userpw = new String(Base64.decode(token)); 
      String[] parts = userpw.split(":"); 
      if (parts.length == 2) { 
       return Optional.of(Pair.of(parts[0], parts[1])); 
      } 
     } catch (Exception e) { 
      log.debug("Basic Auth Token formatting error", e); 
     } 
     return Optional.empty(); 
    } 

    private boolean verify(String user, String password) { 
     return (this.user.equals(user) && this.password.equals(password)); 
    } 

} 

そして最後に包まれたServletRequest(あなたが要求からヘッダを削除することはできませんと):

public class AuthentifiedHttpServletRequest extends HttpServletRequestWrapper { 

    public AuthentifiedHttpServletRequest(HttpServletRequest request) { 
     super(request); 
    } 

    @Override 
    public boolean authenticate(HttpServletResponse response) throws IOException, ServletException { 
     return true; 
    } 

    @Override 
    public String getAuthType() { 
     return "Basic"; 
    } 

    @Override 
    public String getHeader(String name) { 
     if (!HttpHeaders.AUTHORIZATION.equalsIgnoreCase(name)) { 
      return super.getHeader(name); 
     } 
     return null; 
    } 

    @Override 
    public Enumeration<String> getHeaders(String name) { 
     if (!HttpHeaders.AUTHORIZATION.equalsIgnoreCase(name)) { 
      return super.getHeaders(name); 
     } 
     return Collections.enumeration(Collections.emptyList()); 
    } 

    @Override 
    public Enumeration<String> getHeaderNames() { 
     return Collections.enumeration(EnumerationUtils.toList(super.getHeaderNames()) 
      .stream() 
      .filter(s -> !HttpHeaders.AUTHORIZATION.equalsIgnoreCase(s)) 
      .collect(Collectors.toList())); 
    } 

    @Override 
    public int getIntHeader(String name) { 
     if (!HttpHeaders.AUTHORIZATION.equalsIgnoreCase(name)) { 
      return super.getIntHeader(name); 
     } 
     return -1; 
    } 

} 
関連する問題