san*_*sto 5 java authentication spring-security spring-boot keycloak
我们已经在生产环境的项目中从基本身份验证迁移到 Keycloak 方法。但是,我们希望继续使用基本身份验证,用于本地开发、独立和演示安装,这可以由配置文件或类似的东西触发。
在这个项目中,我们有使用 Java/Spring boot 开发的 REST API 和使用这些 API 的 AngularJS 应用程序。我们正在使用 Keycloak 来保护 AngularJS 应用程序和 API。
问题是如何使 Spring Security 和 Keycloak 在具有不同配置文件的同一应用程序中“一起”工作。到目前为止,我找到的解决方案是配置 Spring Security 和 Keycloak,并使用属性文件进行了解决方法,如下所述:
应用程序keycloak.properties
#Unactivate Basic Authentication
security.ignored=/**
Run Code Online (Sandbox Code Playgroud)
应用程序本地auth.properties
#Unactivate Keycloak
spring.autoconfigure.exclude=org.keycloak.adapters.springboot.KeycloakSpringBootConfiguration
Run Code Online (Sandbox Code Playgroud)
当我想使用 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");
}
Run Code Online (Sandbox Code Playgroud)
这是我的 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/*
Run Code Online (Sandbox Code Playgroud)
它正在工作,但我认为这不是一个优雅的解决方案。我尝试使用 Keycloak 属性enable-basic-auth来实现这一点,但我无法理解它是如何工作的,但它似乎只是为了保护 Rest API,它不允许浏览器创建会话并将其用于所有其他请求。
有没有人必须实现这样的东西并且可以给我一些更好的主意?
我设法解决了这个问题。然而,我的解决方案有多漂亮还有待商榷。
我的用例是,我需要使用 Keycloak 保护大部分端点,但有些端点(用于批处理)应该只使用基本身份验证。配置两者都有一个缺点,即 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();
}
}
Run Code Online (Sandbox Code Playgroud)
我的自定义请求过滤器(需要在 spring security 过滤器之前运行,因此是排序注释):
@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));
}
}
Run Code Online (Sandbox Code Playgroud)
最后是包装的 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;
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
5086 次 |
| 最近记录: |