使用Keycloak与Spring Security结合自定义身份验证系统?

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

Using Keycloak with Spring Security on top of a custom authentication system?

问题

这是我的问题:我目前正在开发一个应用程序,该应用程序具有由其他开发人员创建的自定义身份验证系统。以下是一个快速查看:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    // ...
}

@Component
public class LoginProvider {
    // ...
}

public class LoginFilterConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
    // ...
}

public class LoginFilter extends GenericFilterBean {
    // ...
}

Login 是一个自定义对象,用于存储用户信息。
目前在我们的应用程序中,用户可以创建一个帐户,该帐户存储在 Login 表中。他们使用该帐户连接到应用程序,并且使用 LoginFilter 等自定义身份验证系统来处理所有内容。

问题是:我们的一个客户想要使用 Keycloak 连接到应用程序,因为他们在 Azure AD 注册表中有自己的用户。我的问题是这样的:是否有办法在应用程序中有两个单独的身份验证流程?
一个将使用我上面发布的内容,允许其他客户使用我们的自定义身份验证系统进行连接,而我们将拥有另一个系统,允许人们通过 Keycloak 进行连接。

我感觉这是不可能的,Spring Security 只允许一种类型的身份验证,但我不能确定。
或者,Azure AD 用户是否可以使用 Keycloak 进行身份验证,然后我们可以只需检索令牌,将其存储在 Login 对象中,并自定义我们现有的流程以添加特定的 Keycloak 注销等等...
我不确定这是否有意义,我对 Spring Security 和 Keycloak 都很陌生。

感谢您提供的任何帮助!
谢谢!

英文:

Here's my problem : I'm currently working on an app that has a custom authentication system made by other developers. Here's a quick look at it :

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {


	private static final String LOGIN_URL = &quot;/login&quot;;
	@Autowired
	private LoginProvider loginProvider;

	@Autowired
	private CredentialsAuthenticationEntryPoint unauthorizedHandler;

	public WebSecurityConfig() {
		SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
	}

	@Override
	protected void configure(final HttpSecurity http) throws Exception {

		// Disable CSRF but protecting from XSS attacks (cross site scripting)
		http.csrf().disable();
		http
				.headers()
				.xssProtection()
				.and()
				.contentSecurityPolicy(&quot;script-src &#39;self&#39;&quot;);

		// No session will be created or used by spring security
		http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

		// Entry points
		http.authorizeRequests()//
				.antMatchers(&quot;/authentication/**&quot;).permitAll()//
				.antMatchers(&quot;/register/**&quot;).permitAll()//
				.antMatchers(&quot;/invitation/**&quot;).permitAll()//
				.antMatchers(&quot;/password/*/change/*&quot;).permitAll()//
				.antMatchers(&quot;/password/reset&quot;).permitAll()//
				.antMatchers(&quot;/company/**&quot;).permitAll()//
				.antMatchers(&quot;/actuator/**&quot;).permitAll()//
				.antMatchers(&quot;/zapier/**&quot;).permitAll()//
				.antMatchers(&quot;/redirect/**&quot;).permitAll()
				.antMatchers(&quot;/simple-captcha-endpoint/**&quot;).permitAll()
				.antMatchers(&quot;/core/**&quot;).access(&quot;hasIpAddress(&#39;127.0.0.1&#39;) or hasIpAddress(&#39;31.170.8.2&#39;) or hasIpAddress(&#39;31.170.8.3&#39;)&quot;)

				// Disallow everything else..
				.anyRequest().authenticated().and()//

				// If a user try to access a resource without having enough permissions
				.exceptionHandling().authenticationEntryPoint(unauthorizedHandler);

		// Apply API key
		http.apply(new LoginFilterConfigurer(loginProvider));

		// disable page caching
		http.headers().cacheControl();
	}

	@Override
	public void configure(final WebSecurity web) throws Exception {
		// Allow swagger to be accessed without authentication
		web.ignoring().antMatchers(&quot;/v2/api-docs&quot;)//
				.antMatchers(&quot;/swagger-resources/**&quot;)//
				.antMatchers(&quot;/swagger-ui.html&quot;)//
				// .antMatchers(&quot;/configuration/**&quot;)//
				.antMatchers(&quot;/webjars/**&quot;)//
				.antMatchers(&quot;/public&quot;);
	}

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

	@Autowired
	protected UiTokenRepository repository;

	@Autowired
	protected UiLoginRepository loginRepository;

	@Autowired
	protected OrganisationRepository organisationRepository;

	@Autowired
	protected WarningRepository warningRepository;

	@Autowired
	protected TrustabilityRepository trustabilityRepository;

	private static final Logger log = LogManager.getLogger(LoginProvider.class);

	public Authentication getAuthentication(final UiToken authorisation) {

		Login login = authorisation.getLogin();
		if (login != null) {
			final String name = login.getName();
			final String usernameOrEmail = name == null || name.trim().isEmpty() ? login.getEmail() : name;

			final CustomCredentialDetails userDetails = new CustomCredentialDetails(login,usernameOrEmail);
			UiUser uiUser;
			try{
				uiUser = new UiUser(
						usernameOrEmail,
						userDetails.getPassword(),
						userDetails.isEnabled(),
						userDetails.isAccountNonExpired(),
						userDetails.isCredentialsNonExpired(),
						userDetails.isAccountNonLocked(),
						userDetails.getAuthorities(),
						login
				);
			}catch (IllegalArgumentException exception){
				log.warn(&quot;Troubles with loginId={} and name={}&quot;,login.getUuid(), usernameOrEmail, exception);
				return null;
			}
			return new UsernamePasswordAuthenticationToken(uiUser, &quot;&quot;, uiUser.getAuthorities());
		}

		return null;
	}

	public String resolveApiKey(final HttpServletRequest req) {
		return req.getHeader(&quot;X-Auth-Token&quot;);
	}

	public UiToken findByAccessToken(final String token) {
		final String encryptedToken = UIRestTools.fixedEncrypt(token);
		return repository.findByTokenValueAndFetchUserAndFetchRoles(encryptedToken);
	}

	public void refreshToken(final UiToken authorisation) {
		authorisation.refreshToken();
		repository.save(authorisation);

		final Login login = authorisation.getLogin();
		login.setLastUseDate(new Date());
		loginRepository.save(login);
	}

}
public class LoginFilterConfigurer extends SecurityConfigurerAdapter&lt;DefaultSecurityFilterChain, HttpSecurity&gt; {

	private final LoginProvider loginProvider;

	public LoginFilterConfigurer(final LoginProvider loginProvider) {
		this.loginProvider = loginProvider;
	}

	@Override
	public void configure(final HttpSecurity http) throws Exception {
		final LoginFilter customFilter = new LoginFilter(loginProvider);
		http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
	}

}
public class LoginFilter extends GenericFilterBean {
	private final LoginProvider provider;

	public LoginFilter(final LoginProvider provider) {
		this.provider = provider;
	}

	@Override
	public void doFilter(final ServletRequest req, final ServletResponse res, final FilterChain filterChain)
			throws IOException, ServletException {


		final String token = provider.resolveApiKey((HttpServletRequest) req);
		if (token != null) {
			final UiToken authorisation = provider.findByAccessToken(token);
			if (authorisation != null &amp;&amp; !authorisation.isTokenExpired() &amp;&amp; authorisation.getState().equals(UiTokenStateEnum.ACTIVATED)) {
				provider.refreshToken(authorisation);
				final Authentication auth = provider.getAuthentication(authorisation);
				SecurityContextHolder.getContext().setAuthentication(auth);
			}
		}
		filterChain.doFilter(req, res);
	}

}

Login is a custom object that holds information about the user.
Right now in our app, users can create an account that is stored in the table Login. They connect to the app using that account, and the custom authentication system using the LoginFilter etc ... handles everything.

Here's the thing : one of our clients wants to use Keycloak to connect to the app because they have an Azure AD registry with their own users in it. My question is this one : is there anyway to have two separate authentication flows in the app ?
One would use what I posted above, allowing other clients to use our custom authentication system to connect, and we would have another system that would allow people to connect through Keycloak.

I feel like that's impossible and Spring Security only allows for one type of authentication but I cannot be sure.
Alternatively, would it be possible for the Azure AD users to authenticate using Keycloak, then we could just retrieve the token, store it in the Login object and customize our existing flow to add specific Keycloak logout etc ...
I'm not sure if that makes sense, I'm quite new to Spring Security and Keycloak as well.

Thanks for any help you can provide !
Cheers

答案1

得分: 1

我认为这可能是可行的,虽然不够干净/安全/可维护,但是可以做到。

我建议阅读文档的这一部分:多租户 Keycloak

这将允许您捕获传入的请求,选择哪些请求将进行 Keycloak 认证或您的自定义认证系统。您可以基于 URL 解析、头部等进行选择。

但是在这里,您应该非常小心您正在做的事情。

另一个选项是使用两个不同的 URL 和两个不同的身份验证流程来运行您的应用的两个实例。

对于 AD 用户,您可以检查 用户联合

希望对您有所帮助。

英文:

I think it mighy be doable, not clean / safe / maintainable.. but doable..

I would suggest to read this part of doc : Mutli-tenant Keycloak

This will allow you to catch incoming request, select which one will go for Keycloak auth or your custom auth system. You can base the selection by url parsing, header etc..

But you should be very carful here of what your doing.

Another option would be two instances of your app, with two different urls and two different authentication process..

For AD users you can check for user federation.
Hope it helps
++

huangapple
  • 本文由 发表于 2023年7月3日 21:28:58
  • 转载请务必保留本文链接:https://go.coder-hub.com/76605218.html
匿名

发表评论

匿名网友

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

确定