2017-06-06 41 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=/** 

應用程序和本地身份驗證。性能

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

當我wanto使用keycloak,我不得不忽視安全,以不會有問題,當我想使用基本身份驗證我必須排除爲了還可防止衝突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來實現這一點,但我無法理解它是如何工作的,但它似乎只是保護Rest API,它不允許瀏覽器創建會話並使用它適用於所有其他請求。

有人曾經實施過這樣的事情,可以給我一些更好的想法嗎?

+0

你有沒有解決你的問題?我有類似的問題。 – nejckorasa

回答

0

我設法解決這個問題。但是,我的解決方案有多美妙,可供討論。

我的用例是我需要使用Keycloak來保護我的大多數端點,但是一些(用於批處理)應該只使用基本認證。配置兩者都有缺點,Keycloak試圖驗證Authorization Header,即使它是Basic Auth,所以我需要做三件事情。

  1. 取消激活我的批處理路由的所有自動安全。
  2. 編寫一個自定義請求過濾器來確保批處理路由的安全。
  3. 操縱servlet請求對象,以使熱心的keycloak過濾器不會在其上跳過。

我的安全配置。

@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; 
    } 

} 
相關問題