如何使基本身份验证作为 Angular JS/Spring 启动应用程序中 keycloak 的替代品

Posted

技术标签:

【中文标题】如何使基本身份验证作为 Angular JS/Spring 启动应用程序中 keycloak 的替代品【英文标题】:How to make Basic Authentication work as an alternative for keycloak in a Angular JS/Spring boot app 【发布时间】:2017-11-08 00:21:16 【问题描述】:

我们已经在生产环境的项目中从 Basic Authentication 迁移到 Keycloak 方法。但是,我们希望继续使用基本身份验证,用于本地开发、独立和演示安装,这可能由配置文件或类似的东西触发。

在这个项目中,我们有使用 Java/Spring 引导开发的 REST API 和一个使用这些 API 的 AngularJS 应用程序。我们使用 Keycloak 来保护 AngularJS 应用程序和 API。

问题是如何使 Spring Security 和 Keycloak 在具有不同配置文件的同一应用程序中“一起”工作。到目前为止,我找到的解决方案是同时配置 Spring Security 和 Keycloak,并使用属性文件进行了解决,如下所述:

application-keycloak.properties

#Unactivate Basic Authentication
security.ignored=/**

application-local-auth.properties

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

当我想使用 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 Spring Boot 配置:

# 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,它不允许浏览器创建一个会话并将其用于所有其他请求。

有没有人必须实现这样的东西并且可以给我一些更好的主意?

【问题讨论】:

你的问题解决了吗?我也有类似的问题。 【参考方案1】:

我设法解决了这个问题。但是,我的解决方案有多漂亮还有待商榷。

我的用例是我需要使用 Keycloak 保护我的大多数端点,但一些(用于批处理)应该只使用基本身份验证。配置两者都有一个缺点,即 Keycloak 会尝试验证授权标头,即使它是基本身份验证,所以我需要做三件事。

    停用我的批处理路由的所有自动安全功能。 编写一个自定义请求过滤器来保护批处理路由。 操纵 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();
    

我的自定义请求过滤器(需要在spring安全过滤器之前运行,因此是排序注释):

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


【讨论】:

【参考方案2】:

不太确定这是否仍然相关,但也许有人会发现它有帮助。

默认情况下,Keycloak 会覆盖大量配置。它正在拦截所有 Auth 请求(OAuth2、BasicAuth 等)

幸运的是,使用 Keycloak,可以同时使用 OAuth2 和 BasicAuth 启用身份验证,我假设这是您想要在您的开发/本地主机环境中启用的。

为此,您首先需要将以下属性添加到您的 application-local-auth.properties

keycloak.enable-basic-auth=true

此属性将在您的开发环境中启用基本身份验证。但是,您还需要在 Keycloak 中的客户端上启用基本身份验证。

您可以通过连接到本地 Keycloak 服务器上的 Keycloak 管理控制台并为您的客户端启用直接访问授权来完成此操作:

Enabling Basic Auth in Keycloak

之后,您可以使用 Bearer TokenBasic Auth 进行身份验证。

【讨论】:

以上是关于如何使基本身份验证作为 Angular JS/Spring 启动应用程序中 keycloak 的替代品的主要内容,如果未能解决你的问题,请参考以下文章

无法使用 Angular 2 作为前端并使用基本 Http 身份验证获取有关 Spring 安全性的登录详细信息

如何使用 Angular 2 进行 oAuth2 基本身份验证?

Angular ngIf firebase 身份验证指令使 DOM 无响应

如何使用 Spring Boot + Angular 处理外部 OAuth2 身份验证

带有 HTTP 基本身份验证的 Angular 6 HTTP Get 请求

如何使用基本身份验证保护 Spring Cloud Eureka 服务?