Keycloak虚拟配置

huangapple go评论67阅读模式
英文:

Keycloak dummy configuration

问题

我有一个使用Keycloak作为身份验证提供者的Spring Boot项目。是否可以为特定的Spring配置文件创建一个“虚拟身份验证(dummy auth)”?我想要一个名为“dummy-auth”的配置文件,它将始终将KeycloakPrincipal设置为某个虚拟用户。也许可以使用一些过滤器来进行替换?以下是我的Keycloak配置:

@KeycloakConfiguration
public class KeycloakSecurityConfiguration extends KeycloakWebSecurityConfigurerAdapter {

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) {
        auth.authenticationProvider(keycloakAuthenticationProvider());
    }

    @Bean
    public KeycloakSpringBootConfigResolver keycloakSpringBootConfigResolver() {
        return new KeycloakSpringBootConfigResolver();
    }

    @Override
    protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
        return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        super.configure(http);
        http.authorizeRequests()
            .anyRequest()
            .fullyAuthenticated();
    }
}

我真的不知道如何实现这个虚拟身份验证。也许更好的选择是一种嵌套的Keycloak实例?

英文:

I have Spring Boot project with Keycloak as authentication provider. Is it possible to create a 'dummy auth' for a specific spring profile? I would like to have profile 'dummy-auth' which will always set KeycloakPrincipal as some dummy user. Maybe some kind of filter to replace? I have my keycloak config:

@KeycloakConfiguration
public class KeycloakSecurityConfiguration extends KeycloakWebSecurityConfigurerAdapter {

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) {
        auth.authenticationProvider(keycloakAuthenticationProvider());
    }

    @Bean
    public KeycloakSpringBootConfigResolver keycloakSpringBootConfigResolver() {
        return new KeycloakSpringBootConfigResolver();
    }

    @Override
    protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
        return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        super.configure(http);
        http.authorizeRequests()
                .anyRequest()
                .fullyAuthenticated();
    }
}

and do not really have idea how to implement this dummy auth. Maybe better option is some kind of nested keycloak instance?

答案1

得分: 3

几点建议:

1. 拥有多个配置文件

我会为不同的用途建议不同的配置文件:keycloak,local,keycloak-local。

Keycloak

在您想要与身份提供者集成并将从Keycloak检索的已认证对象设置到Spring上下文中时,请使用此配置文件。

@Configuration
@EnableWebSecurity
@ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
@Profile("keycloak")
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
    // ... 其他代码 ...
}

Local

也可以创建一个本地配置文件,您不想要与身份提供者一起提供,但仍想将已认证的用户注入到您的上下文中。

@Configuration
@EnableWebSecurity
@ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
@Profile("local")
public class LocalSecurityConfig extends WebSecurityConfigurerAdapter {
    // ... 其他代码 ...
}

Keycloak-Local

或者,更好的方法是,在特定的配置文件中将真实的Keycloak认证对象设置到上下文中。如果您需要在上下文中有更复杂的认证对象并且使用KeycloakAuthenticationToken,则使用此方法。否则,local配置文件也是可以的,因为它需要更多的编码,但这也是一个工作示例。

创建一个过滤器,我们称之为LocalFilter。这个过滤器将创建一个KeycloakAuthenticationToken并将其设置到Spring上下文中:

public class LocalFilter extends GenericFilterBean {
    // ... 其他代码 ...
}

并在您的SecurityConfig中配置该过滤器:

@Configuration
@EnableWebSecurity
@ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
@Profile("local-keycloak")
public class LocalKeycloakSecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
    // ... 其他代码 ...
}

2. 如果是资源服务器:无需与身份提供者交互的令牌

如果您将客户端用作Keycloak的资源服务器,有一种方法可以在不与身份提供者交互的情况下验证身份。按照以下步骤操作;

  • 生成JWT(您可以创建一个虚拟客户端以Keycloak并复制它,如果需要一些示例,或者在任何地方生成它,记下您的算法和公钥)
  • 如果您使用Keycloak生成jwt,请从Realm Settings -> Keys -> 复制属于算法的公钥(默认为RS256),并将其保存在应用程序中的某个位置,例如:src/main/test/resources
  • 配置您的服务时,不要提供issuer-uri,而是使用public-keyjws-algorithm。通过这样做,您的服务将不会请求您的身份提供者来验证Bearer令牌。
  • 对于Spring Security OAuth2库,以下是一个示例配置:
spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          public-key-location: classpath:jwt_public_key.json
          jws-algorithm: RS256 # 这是默认值,您可以省略设置它
  • 现在将您的令牌作为Bearer头发送。
英文:

A couple of suggestions;

1. Have multiple profiles

I will suggest different profiles for different purposes; keycloak, local, keycloak-local.

Keycloak

Use this profile when you want to integrate with Identity Provider and set Authenticated Object retrieved from Keycloak to your Spring context.

@Configuration
@EnableWebSecurity
@ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
@Profile( "keycloak" )
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter
{
	@Bean
	public KeycloakSpringBootConfigResolver keycloakConfigResolver() {
		return new KeycloakSpringBootConfigResolver();
	}
	@Bean
	@Override
	protected SessionAuthenticationStrategy sessionAuthenticationStrategy( )
	{
		return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
	}

	@Autowired
	public void configureGlobal( AuthenticationManagerBuilder auth) throws Exception {
		KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
		keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
		auth.authenticationProvider(keycloakAuthenticationProvider);
	}

	@Override
	protected void configure( HttpSecurity http) throws Exception {
		super.configure(http);
		http.authorizeRequests()
			.antMatchers("/hello")
			.authenticated()
			.anyRequest()
			.permitAll();
	}

}

Local

Create also a local profile where you don't want to ship with Identity Provider but still want to inject an authenticated user to your context

@Configuration
@EnableWebSecurity
@ComponentScan( basePackageClasses = KeycloakSecurityComponents.class )
@Profile( "local" )
public class LocalSecurityConfig extends WebSecurityConfigurerAdapter
{

	@Override
	protected void configure( HttpSecurity http ) throws Exception
	{
		http.authorizeRequests( )
			.antMatchers( "/hello" )
			.authenticated( )
			.anyRequest( )
			.permitAll( )
			.and( )
			.formLogin( );
	}

	@Autowired
	public void configureGlobal( AuthenticationManagerBuilder auth )
		throws Exception
	{
		auth
			.inMemoryAuthentication( )
			.withUser( "user" )
			.password( passwordEncoder().encode( "password") )
			.roles( "USER" );
	}

	@Bean
	public PasswordEncoder passwordEncoder() {
		return new BCryptPasswordEncoder();
	}

}

Keycloak-Local

Or even better we can set real Keycloak Authenticated object to the context in specific profile. Use this approach if you need more complicated Authentication object in the context and you ship with KeycloakAuthenticationToken. Otherwise local profile is also fine since this requires more coding, but this is also working example:

Create a Filter, let's call it LocalFilter. This filter will create a KeycloakAuthenticationToken and set it to the spring context:

public class LocalFilter extends GenericFilterBean
{
	@Override
	public void doFilter( ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain )
		throws IOException, ServletException
	{
		SecurityContextHolder.getContext( ).setAuthentication( generateKeycloakToken( "stackoverflow.com", "ROLE_USER", "" ) );
		filterChain.doFilter(servletRequest, servletResponse);
	}

	public static KeycloakAuthenticationToken generateKeycloakToken( String org, String roles, String permissions )
	{
		AccessToken accessToken = new AccessToken( );
		if ( org != null && !org.isEmpty( ) )
		{
			accessToken.setOtherClaims( "org", org );
		}
		if ( permissions != null && !permissions.isEmpty( ) )
		{
			accessToken.setOtherClaims( "permissions", permissions );
		}
		RefreshableKeycloakSecurityContext rksc =
			new RefreshableKeycloakSecurityContext( null, null, UUID.randomUUID( ).toString( ), accessToken, null, null,
				null );
		Set<String> rolesSet = new HashSet<>( );
		String[] roleArr = roles.split( "," );
		for ( String role : roleArr )
		{
			rolesSet.add( role.trim( ) );
		}
		KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal = new KeycloakPrincipal<>( "name", rksc );
		Collection<GrantedAuthority> authorities = generateGrantedAuthority( roles );
		return new KeycloakAuthenticationToken( new SimpleKeycloakAccount( principal, rolesSet, rksc ), false,
			authorities );
	}

	public static Collection<GrantedAuthority> generateGrantedAuthority( String roles )
	{
		Collection<GrantedAuthority> authorities = new ArrayList<>( );
		for ( String role : roles.split( "," ) )
		{
			authorities.add( new SimpleGrantedAuthority( role.trim( ) ) );
		}
		return authorities;
	}
}

And configrue the Filter in your SecurityConfig:

@Configuration
@EnableWebSecurity
@ComponentScan( basePackageClasses = KeycloakSecurityComponents.class )
@Profile( "local-keycloak" )
public class LocalKeycloakSecurityConfig extends KeycloakWebSecurityConfigurerAdapter
{
	@Bean
	public KeycloakSpringBootConfigResolver keycloakConfigResolver( )
	{
		return new KeycloakSpringBootConfigResolver( );
	}

	@Bean
	@Override
	protected SessionAuthenticationStrategy sessionAuthenticationStrategy( )
	{
		return new RegisterSessionAuthenticationStrategy( new SessionRegistryImpl( ) );
	}

	@Autowired
	public void configureGlobal( AuthenticationManagerBuilder auth ) throws Exception
	{
		KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider( );
		keycloakAuthenticationProvider.setGrantedAuthoritiesMapper( new SimpleAuthorityMapper( ) );
		auth.authenticationProvider( keycloakAuthenticationProvider );
	}

	@Override
	protected void configure( HttpSecurity http ) throws Exception
	{
		super.configure( http );
		http.addFilterBefore(new LocalFilter(), UsernamePasswordAuthenticationFilter.class)
			.authorizeRequests( )
			.antMatchers( "/hello" )
			.authenticated( )
			.anyRequest( )
			.permitAll( );
	}
}

2. If Resource Server: Token without interacting Identity Provider

If you are using your client as a resource server to Keycloak there is a way to validate your authentication without interacting with your Identity Provider. Follow the steps;

  • Generate a JWT (You can make a dummy client to Keycloak and copy it if you need some sample or generate it anywhere, note down your algorithm and public key)
  • If you used Keycloak to generate jwt, copy your public key from Realm Settings -> Keys -> Copy public key belong to algorithm (Default is RS256) and save it to somewher in your application, e.g: src/main/test/resources
  • Instead of giving issuer-uri configure your service with public-key and jws-algorithm. By doing this, your service will not request to your Identity Provider to validate the Bearer.
  • For spring security oauth2 library here is a sample configuration:
spring
  security:
    oauth2:
      resourceserver:
        jwt:
          public-key-location: classpath:jwt_public_key.json
          jws-algorithm: RS256 # this is default, you can skip setting it
  • Now send your token as a Bearer header.

答案2

得分: 0

如果目标是对单个@Component进行单元测试,我不会触碰配置(也许只需 @MockBean JwtDecoder jwtDecoder; ),并且只需在安全上下文中注入一个模拟的身份验证,可以使用手动方式 SecurityContextHolder.getContext().setAuthentication(auth) 或者使用类似于我编写的 这个库中的 @WithMockKeycloakAuth 。我提供了各种带有单元测试的示例应用,包括一个使用 KeycloakAuthenticationToken 作为 Authentication 实现的示例。我的单元测试中没有任何需要运行的授权服务器。

如果您的意图是在没有访问公司Keycloak实例的情况下启动应用程序,
也许您应该考虑在开发机器上运行一个“独立”的Keycloak实例。在不同配置文件之间进行更多更改(指向Keycloak服务器A或B)往往是我经常遇到的令人痛苦的经历的根源...

英文:

If the aim is unit-testing a single @Component, I wouldn't touch the conf (maybe just @MockBean JwtDecoder jwtDecoder;) and simply inject a mocked authentication in the security context, either manually with SecurityContextHolder.getContext().setAuthentication(auth) or using something like @WithMockKeycloakAuth from this lib I wrote. I provide various sample apps with unit tests, including one using KeycloakAuthenticationToken as Authentication impl. None of my unit tests require any running authorization-server.

If your intention is to start the app without an access to your company Keycloak instances,
maybe should you consider running a "standalone" Keycloak instance on your dev machine. Changing more than properties values across profiles (pointing to Keycloak server A or B) has too often been the source of painful experience to me...

huangapple
  • 本文由 发表于 2020年9月7日 01:34:26
  • 转载请务必保留本文链接:https://go.coder-hub.com/63767061.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定