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

时间:2017-06-06 20:25:49

标签: java authentication spring-boot spring-security keycloak

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

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

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

application-keycloak.properties

@csrf_protect
@login_required  # redirects to login page if user.is_active is false
def render_fim_table(request):

    table = FimTable(Compound.objects.all())

    table.paginate(page=request.GET.get('page', 1), per_page=20)

    response = render(request, 'fim_table.html', {'table': table})
    return response

application-local-auth.properties

#Unactivate Basic Authentication
security.ignored=/**

当我想要使用keycloak时,我必须忽略安全性以避免出现问题,当我想使用基本身份验证时,我必须排除Keycloak配置以防止冲突。

这是我的安全配置类:

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

这是我的Keycloak Spring Boot配置:

@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属性 enable-basic-auth 来实现这一点,但我无法理解它是如何工作的,但它似乎只是为了保护Rest API,它不允许浏览器创建会话并将其用于所有其他请求。

有人曾经不得不实施这样的事情并能给我一些更好的想法吗?

2 个答案:

答案 0 :(得分:2)

我设法解决了这个问题。但是,我的解决方案有多漂亮,值得辩论。

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

  1. 取消批量路由的所有自动安全性。
  2. 编写一个自定义请求过滤器,用于保护批次路由。
  3. 操纵servlet请求对象,使狂热的密钥泄露过滤器不会绊倒。
  4. 我的安全配置。

    @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(因为你无法从请求中删除Headers):

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

答案 1 :(得分:1)

不太确定这是否仍然有用,但是也许有人会觉得有用。

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

幸运的是,使用Keycloak,可以同时使用OAuth2和BasicAuth启用身份验证,我想这就是您要在dev / localhost环境中启用的身份验证。

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

keycloak.enable-basic-auth=true

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

您可以通过连接到本地Keycloak服务器上的Keycloak管理控制台并为客户端启用Direct Access Grant来实现:

Enabling Basic Auth in Keycloak

之后,您可以使用承载者令牌基本身份验证进行身份验证。