How to integrate spring authorization server into keycloak as OIDC an identity proivider - spring-security

i have implemented a simple authorization server with Spring boot.my goal is to use it as an OIDC identity provider an register it with keycloak.
this is my configuration:
#Configuration
#Import(OAuth2AuthorizationServerConfiguration.class)
public class AuthorizationServerConfig {
#Bean
public RegisteredClientRepository registeredClientRepository() {
RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("client")
.clientSecret("{noop}secret")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.redirectUri("http://KEYCLOAK_URL:8082/*")
.scope(OidcScopes.OPENID)
.build();
return new InMemoryRegisteredClientRepository(registeredClient);
}
#Bean
UserDetailsService users() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("admin")
.password("admin")
.roles("ADMIN")
.build();
UserDetails user2 = User.withDefaultPasswordEncoder()
.username("test")
.password("test")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user,user2);
}
#Bean
#Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityFilterChain authServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
return http.formLogin(Customizer.withDefaults()).build();
}
#Bean
public JWKSource<SecurityContext> jwkSource() throws NoSuchAlgorithmException {
RSAKey rsaKey = generateRsa();
JWKSet jwkSet = new JWKSet(rsaKey);
return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
}
private static RSAKey generateRsa() throws NoSuchAlgorithmException {
KeyPair keyPair = generateRsaKey();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
return new RSAKey.Builder(publicKey)
.privateKey(privateKey)
.keyID(UUID.randomUUID().toString())
.build();
}
private static KeyPair generateRsaKey() throws NoSuchAlgorithmException {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
return keyPairGenerator.generateKeyPair();
}
#Bean
public ProviderSettings providerSettings() {
return ProviderSettings.builder()
.issuer("http://AUTH_SRVER_URL:9000")
.build();
}
}
#EnableWebSecurity
public class DefaultSecurityConfig {
#Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/login")
.permitAll()
.anyRequest()
.authenticated()
.and()
.formLogin();
return http.build();
}
}
then i registered the string authorization server as an idp in keycloak.
when i perform "login_with_spring" it redirects me to spring auth server login page. after submitting the form, it doesn't redirect me back to keycloak.

It looks like you've configured client_credentials as the grant type, but are attempting to use it with OpenID Connect 1.0. OIDC works with the authorization_code grant type. I haven't tested the setup you are describing here, but I believe you can simply use instead:
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)

Related

Spring authorization server redirect to login page when token is requested

We currently have 2 web applications that authenticate against a CAS. The communication between them is done via Basic Auth.
For security reasons we want to switch to OAuth2 to get rid of the Basic Auth. At the same time we want to get rid of the CAS because it does not meet our requirements.
So the goal is something along those lines:
I am at the point that I can log in to the user management and manipulate data. The 2nd application also correctly redirects me to the 1st application to authenticate me. Authentication also works. And the 1st application responds correctly to the 2nd application. The 2nd application then executes the expected POST to the url "oauth2/token". But the 1st application replies with a 302 to "/login" instead of a token.
Which point am I missing or do I still have to configure?
I have orientated myself on the following documentation:
https://docs.spring.io/spring-authorization-server/docs/current/reference/html/getting-started.html
My AuthorizationServerConfig looks like this:
#Configuration
public class AuthorizationServerConfig {
private final SecurityApp app;
public AuthorizationServerConfig(SecurityApp app) {
this.app = app;
}
#Bean
#Order(1)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http)
throws Exception {
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer();
authorizationServerConfigurer
.authorizationEndpoint(authorizationEndpoint ->
authorizationEndpoint.consentPage("/oauth2/authorize"))
.oidc(Customizer.withDefaults()); // Enable OpenID Connect 1.0
RequestMatcher endpointsMatcher = authorizationServerConfigurer
.getEndpointsMatcher();
http
.securityMatcher(endpointsMatcher)
.authorizeHttpRequests(authorize ->
authorize.anyRequest().authenticated()
)
.csrf(csrf -> csrf.ignoringRequestMatchers(endpointsMatcher))
.exceptionHandling(exceptions ->
exceptions.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"))
)
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
.apply(authorizationServerConfigurer);
return http.build();
}
#Bean
#Order(2)
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http)
throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
)
// Form login handles the redirect to the login page from the
// authorization server filter chain
.formLogin(Customizer.withDefaults());
return http.build();
}
#Bean
UserDetailsService users() {
return app::findUserByLogin;
}
// OVERWATCH
#Bean
public RegisteredClientRepository registeredClientRepository() {
return new RegisteredClientRepository() {
#Override
public void save(RegisteredClient registeredClient) {
throw new NotImplementedException();
}
#Override
public RegisteredClient findById(String id) {
return app.findByClientId(id);
}
#Override
public RegisteredClient findByClientId(String clientId) {
return app.findByClientId(clientId);
}
};
}
#Bean
public JWKSource<SecurityContext> jwkSource() {
RSAPublicKey publicKey = app.getPublicKey();
RSAPrivateKey privateKey = (RSAPrivateKey) app.getPrivateKey();
RSAKey rsaKey = new RSAKey.Builder(publicKey)
.privateKey(privateKey)
.keyID(UUID.randomUUID().toString())
.build();
JWKSet jwkSet = new JWKSet(rsaKey);
return new ImmutableJWKSet<>(jwkSet);
}
#Bean
public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
}
#Bean
public AuthorizationServerSettings authorizationServerSettings() {
return AuthorizationServerSettings.builder().build();
}
// region Password Authenticator
#Bean
public PasswordEncoder passwordEncoder() {
return app.passwordEncoder();
}
// endregion
}
And the Configuration from App 2 looks like this:
#Configuration
#EnableWebSecurity
public class AceSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/**")
.authorizeRequests()
.antMatchers("/oauth/authorize**", "/login**", "/error**")
.permitAll()
.and()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.oauth2Login( oauth2Login -> oauth2Login.defaultSuccessUrl("/index.html") );
}
}
application.properties from App2 is:
spring.security.oauth2.client.registration.<provider-name>.client-id=${app.uuid}
spring.security.oauth2.client.registration.<provider-name>.client-secret=${app.secret}
spring.security.oauth2.client.registration.<provider-name>.scope=openid
spring.security.oauth2.client.registration.<provider-name>.redirect-uri=http://127.0.0.1:8088/login/oauth2/code/<provider-name>
spring.security.oauth2.client.registration.<provider-name>.client-name=${app.name}
spring.security.oauth2.client.registration.<provider-name>.provider=${provider.name}
spring.security.oauth2.client.registration.<provider-name>.client-authentication-method=code
spring.security.oauth2.client.registration.<provider-name>.authorization-grant type=authorization_code
spring.security.oauth2.client.provider.<provider-name>.authorization-uri=http://localhost:8086/oauth2/authorize
spring.security.oauth2.client.provider.<provider-name>.token-uri=http://localhost:8086/oauth2/token
spring.security.oauth2.client.provider.<provider-name>.user-info-uri=http://localhost:8086/oauth2/userinfo?schema=openid
spring.security.oauth2.client.provider.<provider-name>.user-name-attribute=name
spring.security.oauth2.client.provider.<provider-name>.user-info-authentication-method=header
spring.security.oauth2.client.provider.<provider-name>.jwk-set-uri=http://localhost:8086/jwks
The request from App2 to App 1 that causes the redirect is:
POST http://localhost:8086/oauth2/token
Body:
grant_type=authorization_code,
code=Zls0ppjnS_RXyMVPB8fg_eQQgoiUAxRguOMsdyVYQpgd8eDkUDzgz813L0ybovTL7sNj0TDRUHibPfek9NzwULND1mty5WPW2DOtQjTAaEROL3qP7RvyTWXTEzzYe-o,
redirect_uri=[http://127.0.0.1:8088/login/oauth2/code/<provider-name>],
client_id=<app.uuid>
Header:
Accept:"application/json;charset=UTF-8",
Content-Type:"application/x-www-form-urlencoded;charset=UTF-8"

Spring Boot Authentication - oauth2 login with form logn and other oauth providers

I have following security config file in my authorization server :
#EnableWebSecurity
public class DefaultSecurityConfig {
#Autowired
MyUserDetailService myUserDetailService;
#Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http.authorizeRequests(authorizeRequests ->
authorizeRequests.anyRequest().authenticated()
).authenticationProvider(authenticationProvider())
.formLogin(withDefaults());
return http.build();
}
#Bean
UserDetailsService users() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("admin")
.password("password")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
#Bean
DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setPasswordEncoder(new BCryptPasswordEncoder());
authenticationProvider.setUserDetailsService(myUserDetailService);
return authenticationProvider;
}
}
#Configuration(proxyBeanMethods = false)
public class AuthorizationServerConfig {
#Bean
#Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityFilterChain authServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
return http.formLogin(Customizer.withDefaults()).build();
}
#Bean
public RegisteredClientRepository registeredClientRepository() {
RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("articles-client")
.clientSecret("{noop}secret")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.redirectUri("http://127.0.0.1:8080/login/oauth2/code/articles-client-oidc")
.redirectUri("http://127.0.0.1:8080/authorized")
.scope(OidcScopes.OPENID)
.scope("articles.read")
.build();
return new InMemoryRegisteredClientRepository(registeredClient);
}
#Bean
public JWKSource<SecurityContext> jwkSource() {
RSAKey rsaKey = generateRsa();
JWKSet jwkSet = new JWKSet(rsaKey);
return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
}
private static RSAKey generateRsa() {
KeyPair keyPair = generateRsaKey();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
return new RSAKey.Builder(publicKey)
.privateKey(privateKey)
.keyID(UUID.randomUUID().toString())
.build();
}
private static KeyPair generateRsaKey() {
KeyPair keyPair;
try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
keyPair = keyPairGenerator.generateKeyPair();
} catch (Exception ex) {
throw new IllegalStateException(ex);
}
return keyPair;
}
#Bean
public ProviderSettings providerSettings() {
return ProviderSettings.builder()
.issuer("http://auth-server:9000")
.build();
}
}
Form login is working fine but its doesnt show google sign. I have added the below properties in application yaml
server:
port: 9000
logging:
level:
root: INFO
org.springframework.web: INFO
org.springframework.security: INFO
org.springframework.security.oauth2: INFO
spring:
security:
oauth2:
client:
registration:
google:
client-secret: XXXXXXXXXXXXXXXXXXXXXXXX
client-id: XXXXXXXXXXXXXXXXXXXXXXXX
I wanted to implement all common oauth provider(google, facebook,linked in) along with some custom oauth provider from this list . I need an resources server which acts as generics for these providers.
Below is resource server security config:
#EnableWebSecurity
public class ResourceServerConfig {
#Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.mvcMatcher("/articles/**")
.authorizeRequests()
.mvcMatchers("/articles/**")
.access("hasAuthority('SCOPE_articles.read')")
.and()
.oauth2ResourceServer()
.jwt();
return http.build();
}
}
Please suggest how can i implement this at production level.

How can I login to custom authentication url in Spring Webflux Security?

I want to login to Spring Webflux Security through the changed authentication URL(/api/authenticate). So I made this code.
#Configuration
#EnableWebFluxSecurity
#EnableReactiveMethodSecurity
public class WebFluxSecurityConfig {
#Bean
public MapReactiveUserDetailsService userDetailsService() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("user")
.password("user")
.roles("USER")
.build();
UserDetails admin = User.withUsername("admin")
.password(passwordEncoder().encode("admin"))
.roles("ADMIN")
.build();
return new MapReactiveUserDetailsService(user, admin);
}
#Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
return http.csrf().disable()
.authorizeExchange()
.anyExchange().permitAll()
.and()
.formLogin()
.requiresAuthenticationMatcher(new PathPatternParserServerWebExchangeMatcher("/api/authenticate"))
.authenticationSuccessHandler((webFilterExchange, authentication) -> {
webFilterExchange.getExchange().getResponse().setStatusCode(HttpStatus.OK);
return Mono.empty();
})
.and()
.build();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
And then, I tried to login with user/user and admin/admin to localhost:8080/api/authenticate but it gives me response status code 302 and login?error.
How can I login with user/user and admin/admin to custom url localhost:8080/api/authenticate?

Spring Authorization Server with AuthorizationGrantType.PASSWORD

I want to build an authorization server with Spring Authorization Server project. For now I want to use AuthorizationGrantType.PASSWORD.
I developed a demo project from the samples of Spring Authorization Server project. However, when I try to get token with the http://localhost:9000/oauth2/token?grant_type=password&username=user&password=pass (I'm using client_id and client_secret as basic auth in Postman) request, I'm getting 403.
What I'm missing here?
Dependencies: spring-boot-starter-web, spring-boot-starter-security, spring-security-oauth2-authorization-server (version: 0.2.2)
AuthorizationServerConfig class:
import java.security.*;
import java.security.interfaces.*;
import java.util.UUID;
import org.springframework.context.annotation.*;
import org.springframework.security.oauth2.core.*;
import org.springframework.security.oauth2.server.authorization.client.*;
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.*;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
#Configuration(proxyBeanMethods = false)
public class AuthorizationServerConfig {
#Bean
public RegisteredClientRepository registeredClientRepository() {
RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("client1")
.clientSecret("{noop}secret")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.PASSWORD)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.build();
return new InMemoryRegisteredClientRepository(registeredClient);
}
#Bean
public ProviderSettings providerSettings() {
return ProviderSettings.builder()
.issuer("http://auth-server:9000")
.build();
}
#Bean
public JWKSource<SecurityContext> jwkSource() {
RSAKey rsaKey = generateRsa();
JWKSet jwkSet = new JWKSet(rsaKey);
return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
}
private static RSAKey generateRsa() {
KeyPair keyPair = generateRsaKey();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
return new RSAKey.Builder(publicKey)
.privateKey(privateKey)
.keyID(UUID.randomUUID().toString())
.build();
}
private static KeyPair generateRsaKey() {
KeyPair keyPair;
try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
keyPair = keyPairGenerator.generateKeyPair();
} catch (Exception ex) {
throw new IllegalStateException(ex);
}
return keyPair;
}
}
DefaultSecurityConfig class:
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.*;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
#EnableWebSecurity
public class DefaultSecurityConfig {
#Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.cors().disable()
.authorizeRequests(authorizeRequests ->
authorizeRequests
.anyRequest()
.authenticated()
);
return http.build();
}
#Bean
public UserDetailsService userDetailsService() {
UserDetails userDetails = User.withDefaultPasswordEncoder()
.username("user")
.password("pass")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(userDetails);
}
}
There is this message in the log that I found It may be related:
o.s.s.w.a.i.FilterSecurityInterceptor : Failed to authorize filter invocation [POST /oauth2/token?grant_type=password&username=user&password=pass] with attributes [authenticated]
Looks like password grant_type is not supported. Probably that's why I was getting error.
source: https://github.com/spring-projects/spring-authorization-server/issues/126

How to override the OAuth2AuthorizationServerSecurity's config?

For this project.I want to build a OAuth2 Server on separate front-end and back-end architecture. The back-end based on [spring-authorization-server] while front-end based on VUE.
In the OAuth2 login flow ,it redirects to /login page, but i need to redirect to the vue front-end's login page such as "http://front-end ip:port/loginPage" .
How can I customize the authenticationEntryPoint in org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerSecurity like this:
.formLogin(withDefaults()).exceptionHandling().authenticationEntryPoint(xxx)
Any idea ?
NOTE: OAuth2AuthorizationServerSecurity has been removed. The code below is latest master.
The sample authorization server application has the following default configuration:
#Configuration(proxyBeanMethods = false)
#Import(OAuth2AuthorizationServerConfiguration.class)
public class AuthorizationServerConfig {
#Bean
public RegisteredClientRepository registeredClientRepository() {
RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("messaging-client")
.clientSecret("secret")
.clientAuthenticationMethod(ClientAuthenticationMethod.BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.redirectUri("http://localhost:8080/authorized")
.scope("message.read")
.scope("message.write")
.clientSettings(clientSettings -> clientSettings.requireUserConsent(true))
.build();
return new InMemoryRegisteredClientRepository(registeredClient);
}
#Bean
public CryptoKeySource keySource() {
return new StaticKeyGeneratingCryptoKeySource();
}
}
In order to customize the default configuration, DO NOT #Import(OAuth2AuthorizationServerConfiguration.class) and instead provide the following:
#Configuration(proxyBeanMethods = false)
public class AuthorizationServerConfig {
#Bean
#Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
// TODO Customize http
return http.build();
}
#Bean
public RegisteredClientRepository registeredClientRepository() {
RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("messaging-client")
.clientSecret("secret")
.clientAuthenticationMethod(ClientAuthenticationMethod.BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.redirectUri("http://localhost:8080/authorized")
.scope("message.read")
.scope("message.write")
.clientSettings(clientSettings -> clientSettings.requireUserConsent(true))
.build();
return new InMemoryRegisteredClientRepository(registeredClient);
}
#Bean
public CryptoKeySource keySource() {
return new StaticKeyGeneratingCryptoKeySource();
}
}
This provides you access to HttpSecurity so you can customize anything you require.

Resources