Spring Security with OAuth2(Keycloak) disable default login page - spring

I have successfully configured Spring Boot Spring Security with Keycloak. Everything works fine. In order to login, I use the following URL: http://localhost:8081/realms/MY_REALM_NAME
But when I try to access the following page: http://localhost:8080/login I see the following page:
I'd like to disable/remove this page. How to properly configure it with Spring Security?
UPDATED
My SpringSecurity configuration:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true)
public class SecurityConfiguration extends VaadinWebSecurityConfigurerAdapter {
private final ClientRegistrationRepository clientRegistrationRepository;
private final GrantedAuthoritiesMapper authoritiesMapper;
private final ProfileService profileService;
SecurityConfiguration(ClientRegistrationRepository clientRegistrationRepository,
GrantedAuthoritiesMapper authoritiesMapper, ProfileService profileService) {
this.clientRegistrationRepository = clientRegistrationRepository;
this.authoritiesMapper = authoritiesMapper;
this.profileService = profileService;
SecurityContextHolder.setStrategyName(VaadinAwareSecurityContextHolderStrategy.class.getName());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http
// Enable OAuth2 login
.oauth2Login(oauth2Login ->
oauth2Login
.clientRegistrationRepository(clientRegistrationRepository)
.userInfoEndpoint(userInfoEndpoint ->
userInfoEndpoint
// Use a custom authorities mapper to get the roles from the identity provider into the Authentication token
.userAuthoritiesMapper(authoritiesMapper)
)
// Use a Vaadin aware authentication success handler
.successHandler(new KeycloakVaadinAuthenticationSuccessHandler(profileService))
)
// Configure logout
.logout(logout ->
logout
// Enable OIDC logout (requires that we use the 'openid' scope when authenticating)
.logoutSuccessHandler(logoutSuccessHandler())
// When CSRF is enabled, the logout URL normally requires a POST request with the CSRF
// token attached. This makes it difficult to perform a logout from within a Vaadin
// application (since Vaadin uses its own CSRF tokens). By changing the logout endpoint
// to accept GET requests, we can redirect to the logout URL from within Vaadin.
.logoutRequestMatcher(new AntPathRequestMatcher("/logout", "GET"))
);
}
#Bean
#Primary
public SpringViewAccessChecker springViewAccessChecker(AccessAnnotationChecker accessAnnotationChecker) {
return new KeycloakSpringViewAccessChecker(accessAnnotationChecker, "/oauth2/authorization/keycloak");
}
private OidcClientInitiatedLogoutSuccessHandler logoutSuccessHandler() {
var logoutSuccessHandler = new OidcClientInitiatedLogoutSuccessHandler(clientRegistrationRepository);
logoutSuccessHandler.setPostLogoutRedirectUri("{baseUrl}");
return logoutSuccessHandler;
}
#Override
public void configure(WebSecurity web) throws Exception {
super.configure(web);
// Don't apply security rules on our static pages
web.ignoring().antMatchers("/session-expired");
}
#Bean
public PolicyFactory htmlSanitizer() {
// This is the policy we will be using to sanitize HTML input
return Sanitizers.FORMATTING.and(Sanitizers.BLOCKS).and(Sanitizers.STYLES).and(Sanitizers.LINKS);
}
}

Have tried formLogin().disable() method?
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http
//your config here
.and().formLogin().disable();
}

Related

How to configure ldap in spring-security 5.7 while retaining basic form login

I'm trying to configure my webSecurity to use both ldap and basic authentication (jdbc) with the new component-based security configuration (no WebSecurityConfigurerAdapter) but I can't get it to use both.
The required result is for spring to first attempt ldap, and if it doesn't find (or just fails for now is good enough) attempt to login using basic autentication.
The project is a migration from an older Spring-Boot version and with WebSecurityConfigurerAdapter the following code is what worked:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter
{
#Override
protected void configure(HttpSecurity http) throws Exception
{
http.authorizeRequests().antMatchers("/services/**").permitAll().anyRequest().authenticated();
http.httpBasic();
http.formLogin().permitAll().loginPage("/login").defaultSuccessUrl("/customer/overview", true);
http.logout().permitAll();
http.csrf().disable();
http.headers().frameOptions().disable();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception
{
auth.userDetailsService(userDetails);
//#formatter:off
auth.ldapAuthentication()
.userSearchFilter("(uid={0})")
.userSearchBase("ou=people")
.groupSearchFilter("(uniqueMember={0})")
.groupSearchBase("ou=groups")
.groupRoleAttribute("cn")
.rolePrefix("ROLE_")
.userDetailsContextMapper(customLdapUserDetailsContextMapper())
.contextSource()
.url(ldapUrl);
//#formatter:on
}
#Bean
CustomLdapUserDetailsContextMapper customLdapUserDetailsContextMapper()
{
CustomLdapUserDetailsContextMapper mapper = new CustomLdapUserDetailsContextMapper();
mapper.setCustomUserDetailsService(userDetailsService());
return mapper;
}
//Implementation of custom contextMapper is not relevant for example i believe, basicly it maps some ldap roles, but for testing i don't use roles yet
}
and this is what my conversion to the new style looks like:
#Configuration
public class WebSecurityConfig
{
#Bean
public SecurityFilterChain filterChain(HttpSecurity http, AuthenticationManager ldapAuthenticationManager) throws Exception
{
// #formatter:off
http.authorizeRequests()
.mvcMatchers("/services/**").permitAll()
.mvcMatchers("/resources/**").permitAll()
.mvcMatchers("/webjars/**").permitAll()
.anyRequest().authenticated();
http.httpBasic();
http.formLogin().permitAll().loginPage("/login").defaultSuccessUrl("/customer/overview", true);
http.logout().permitAll();
http.csrf().disable();
http.authenticationManager(ldapAuthenticationManager); //THIS LINE SEEMS TO BE PROBLEMATIC
// #formatter:on
return http.build();
}
#Bean
public AuthenticationManager ldapAuthenticationManager(BaseLdapPathContextSource ldapContextSource, UserDetailsService userDetailsService)
{
LdapBindAuthenticationManagerFactory factory = new LdapBindAuthenticationManagerFactory(ldapContextSource);
UserDetailsServiceLdapAuthoritiesPopulator ldapAuthoritiesPopulator = new UserDetailsServiceLdapAuthoritiesPopulator(userDetailsService);
factory.setUserSearchFilter("(uid={0})");
factory.setUserSearchBase("ou=people");
factory.setLdapAuthoritiesPopulator(ldapAuthoritiesPopulator);
return factory.createAuthenticationManager();
}
}
when in the above new code the line http.authenticationManager(ldapAuthenticationManager); is enabled ldap login works fine (and it even binds roles from database user), but basic login doesn't work. however when the line is disabled basic login works but ldap does not.
Any help on how to get spring to use both logins would be much appreciated.
Instead of creating a custom AuthenticationManager, you can create the AuthenticationProvider that will be used for LDAP authentication.
You can configure the provider on HttpSecurity:
#Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http, LdapAuthenticator authenticator) throws Exception {
// ...
http.authenticationProvider(
new LdapAuthenticationProvider(authenticator, ldapAuthoritiesPopulator));
// ...
return http.build();
}
#Bean
BindAuthenticator authenticator(BaseLdapPathContextSource contextSource) {
BindAuthenticator authenticator = new BindAuthenticator(contextSource);
authenticator.setUserSearch(
new FilterBasedLdapUserSearch("ou=people", "(uid={0})", contextSource));
return authenticator;
}

automate the OAuth2 refresh_token process with SpringBoot 2

I have a SpringBoot2 application, a MainApp as a resource-server, KeyCloak as AuthorizationServer and a maven module, which is related to the MainApp, as a OAuth2LoginClient.
In other words, in MavenModule I have the follow SecurityConfig:
#Configuration
#PropertySource("classpath:idm.properties")
public class Auth0Provider extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests(authorizeRequests ->
authorizeRequests
.requestMatchers(PROTECTED_URLS).authenticated()
.anyRequest().authenticated()
)
.oauth2Login().redirectionEndpoint().baseUri("/callback*");
http.csrf().disable();
}
private static final RequestMatcher PROTECTED_URLS = new OrRequestMatcher(
new AntPathRequestMatcher("/idmauth/**")
);
}
There is also a controller that intercepts the protected call:
#Value("${oauth.redirectURL}")
private String redirectURL;
#Autowired
private OAuth2AuthorizedClientService clientService;
#RequestMapping(method = RequestMethod.GET, path = "/redirect")
public RedirectView redirectWithUsingRedirectView(OAuth2AuthenticationToken oauthToken, RedirectAttributes attributes) {
OAuth2AuthorizedClient client =
clientService.loadAuthorizedClient(
oauthToken.getAuthorizedClientRegistrationId(),
oauthToken.getName());
String token = client.getAccessToken().getTokenValue();
attributes.addAttribute("jwt", token);
return new RedirectView(redirectURL);
}
This return the AccessToken to my frontend. Clearly in my idm.properties file I have the spring.oauth2.client.provider and spring.oauth2.client.registration info.
Now the MainApp is a SpringBoot2 WebApp with this simple SecurityConfig:
#EnableWebSecurity
#Configuration
public class Oauth2RestApiSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors()
.and()
.requestMatchers().antMatchers("/api/**")
.and()
.authorizeRequests().anyRequest().authenticated()
.and()
.oauth2ResourceServer().jwt();
}
}
And in it's application.properties just the line:
spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://<host>/protocol/openid-connect/certs
All works fine but, when the token expire, the only way I have currently found to refresh my token
is to manually do this HTTP-POST:
POST /auth/realms/<audience>/protocol/openid-connect/token HTTP/1.1
Host: <host>
Content-Type: application/x-www-form-urlencoded
Content-Length: 844
client_id=<my_client_id>
&client_secret=<my_client_secret>
&refresh_token=<refresh_token_previously_obtained>
&grant_type=refresh_token
Is there a better way to do this? Maybe inside the SecurityConfig or with a specific path inside spring.oauth2.x properties?
Note that refreshing an access token is done on the OAuth 2.0 client side.
This is done automatically by Spring Security if you have configured a WebClient to be used when requesting protected resources.
#Bean
WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
return WebClient.builder()
.apply(oauth2Client.oauth2Configuration())
.build();
}
When you have done so, the expired OAuth2AccessToken will be refreshed (or renewed) if an OAuth2AuthorizedClientProvider is available to perform the authorization.

Can't disable default login page of spring security

I'm using spring boot 2.5.5 with spring cloud 2020.0.4 and in my gateway I want to apply a filter to check the token(jwt) is valid or not ? and authorize just one path before the first connection.
and even if I enable the web security or (exclude = { SecurityAutoConfiguration.class }) or http.httpBasic().disabled().. I'm always redirected to spring security login page (localhost:9092/login)
#Configuration
#EnableWebSecurity
public class SecurityTokenConfig extends WebSecurityConfigurerAdapter {
private static Logger logger = LoggerFactory.getLogger(SecurityTokenConfig.class);
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.exceptionHandling().authenticationEntryPoint((req, rsp, e) -> rsp.sendError(HttpServletResponse.SC_UNAUTHORIZED)).and()
.addFilterAfter(new JwtTokenAuthenticationFilter(jwtConfig()), UsernamePasswordAuthenticationFilter.class)=
.authorizeRequests().antMatchers("/**/auth-api/**")
.permitAll();
// http.authorizeRequests().antMatchers("/resources/**", "/favicon.ico", "/logout").permitAll();
if (http.cors() != null) {
logger.info(new StringBuilder("Config").append(http.cors().toString()).toString());
} else {
logger.info(" Security Config cors conf is null");
}

Spring Security: Multiple OpenID Connect Clients for Different Paths?

Using Spring Boot 2.1.5 and Spring Security 5, I'm trying to use two different OpenID clients (based in Keycloak). Here is what we have in application.properties.
spring.security.oauth2.client.registration.keycloak-endusersclient.client-id=endusersclient
spring.security.oauth2.client.registration.keycloak-endusersclient.client-secret=7b41aaa4-277f-47cf-9eab-91afacd55d2c
spring.security.oauth2.client.provider.keycloak-endusersclient.issuer-uri=https://mydomain/auth/realms/endusersrealm
spring.security.oauth2.client.registration.keycloak-employeesclient.client-id=employeesclient
spring.security.oauth2.client.registration.keycloak-employeesclient.client-secret=7b41aaa4-277f-47cf-9eab-91afacd55d2d
spring.security.oauth2.client.provider.keycloak-employeesclient.issuer-uri=https://mydomain/auth/realms/employeesrealm
You can see from the snippet above, we are trying to use one OpenID client for endusers (customers) and another for employees.
In the security configuration class, we see how to configure security on different patterns as follows:
public class OpenIDConnectSecurityConfig extends
WebSecurityConfigurerAdapter
{
#Override
protected void configure(HttpSecurity http) throws Exception {
// avoid multiple concurrent sessions
http.sessionManagement().maximumSessions(1);
http.authorizeRequests()
.antMatchers("/endusers/**").authenticated()
.antMatchers("/employees/**").authenticated()
.anyRequest().permitAll().and()
.oauth2Login()
.successHandler(new OpenIDConnectAuthenticationSuccessHandler())
.and()
.logout().logoutSuccessUrl("/");
What I don't understand is how to configure each OpenID client to fire on a separate URL pattern. In the example above, we would like to see the endusers client be used when hitting URL's starting with "/endusers", and to use the employees client when hitting URL's starting with "/employees".
Can this be done?
You need to use AuthenticationManagerResolver for the multi-tenant case, in which endusersclient and employeesclient are your tenants.
public class CustomAuthenticationManagerResolver implements AuthenticationManagerResolver<HttpServletRequest> {
#Override
public AuthenticationManager resolve(HttpServletRequest request) {
return fromTenant();
}
private AuthenticationManager fromTenant(HttpServletRequest request) {
String[] pathParts = request.getRequestURI().split("/");
//TODO find your tanent from the path and return the auth manager
}
// And in your class, it should be like below
private CustomAuthenticationManagerResolver customAuthenticationManagerResolver;
http.authorizeRequests()
.antMatchers("/endusers/**").authenticated()
.antMatchers("/employees/**").authenticated()
.anyRequest().permitAll().and().oauth2ResourceServer().authenticationManagerResolver(this.customAuthenticationManagerResolver);
For Opaque Token (Multitenant Configuration)
#Component
public class CustomAuthenticationManagerResolver implements AuthenticationManagerResolver {
#Override
public AuthenticationManager resolve(HttpServletRequest request) {
String tenantId = request.getHeader("tenant");
OpaqueTokenIntrospector opaqueTokenIntrospector;
if (tenantId.equals("1")) {
opaqueTokenIntrospector = new NimbusOpaqueTokenIntrospector(
"https://test/authorize/oauth2/introspect",
"test",
"test"
);
} else {
opaqueTokenIntrospector = new NimbusOpaqueTokenIntrospector(
"https://test/authorize/oauth2/introspect",
"test",
"test");
}
return new OpaqueTokenAuthenticationProvider(opaqueTokenIntrospector)::authenticate;
}
}
Web Security Configuration
#Autowired
private CustomAuthenticationManagerResolver customAuthenticationManagerResolver;
#Override
public void configure(HttpSecurity http) throws Exception {
http.anyRequest()
.authenticated().and().oauth2ResourceServer()
.authenticationEntryPoint(restEntryPoint).authenticationManagerResolver(customAuthenticationManagerResolver);
}

disable spring formlogin and basic auth

I have the following spring boot 2.0 config but I am still getting the basic auth login screen. I DO NOT want to disable all spring security like almost every post on the internet suggests. I only want to stop the form login page and basic auth so I can use my own.
I have seen all the suggestions with permitAll and exclude = {SecurityAutoConfiguration.class} and a few others that I can't remember anymore. Those are not what I want. I want to use spring security but I wan my config not Spring Boots. Yes I know many people are going to say this is a duplicate but I disagree because all the other answers are to disable spring security completely and not just stop the stupid login page.
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true)
public class CustomSecurity extends WebSecurityConfigurerAdapter {
private final RememberMeServices rememberMeService;
private final AuthenticationProvider customAuthProvider;
#Value("${server.session.cookie.secure:true}")
private boolean useSecureCookie;
#Inject
public CustomSecurity(RememberMeServices rememberMeService, AuthenticationProvider customAuthProvider) {
super(true);
this.rememberMeService = rememberMeService;
this.bouncerAuthProvider = bouncerAuthProvider;
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/v2/**").antMatchers("/webjars/**").antMatchers("/swagger-resources/**")
.antMatchers("/swagger-ui.html");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic().disable().formLogin().disable();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).headers().frameOptions().disable();
http.authenticationProvider(customAuthProvider).authorizeRequests().antMatchers("/health").permitAll()
.anyRequest().authenticated();
http.rememberMe().rememberMeServices(rememberMeService).useSecureCookie(useSecureCookie);
http.exceptionHandling().authenticationEntryPoint(new ForbiddenEntryPoint());
}
}
If you want to redirect to your own login page, i can show your sample code and configuration
remove the http.httpBasic().disable().formLogin().disable();, you should set your own login page to redirect instead of disable form login
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/my_login").permitAll().and().authorizeRequests().anyRequest().authenticated();
http.formLogin().loginPage("/my_login");
}
then create your own LoginController
#Controller
public class LoginController {
#RequestMapping("/my_login")
public ModelAndView myLogin() {
return new ModelAndView("login");
}
}
you can specified the login with thymeleaf view resolver

Resources