Spring Boot + OAuth2 + Google Login - How to implement logout - spring-security

I have a Auth Server implementation with Spring Boot + OAuth2 + Login through Google. And a resource server for my backend data services. I have used JDBC token store. Everything works great. But I am having difficulty in understanding logout implementation. Currently, whenever user clicks on logout I just delete the token from the browser local storage but the session stays active in the Auth server hence I don't need to login again. What I want is whenever use clicks on logout I want to invalidate the session and force him to login again.
Is there any nice way to do this? I currently don't have any logout configuration in my Spring Boot Auth server configuration.
Thanks

Try registering a LogoutSuccessHandler to do that. Something along the lines of:
#Configuration
#EnableWebSecurity
#EnableResourceServer
public class SecurityConfig extends ResourceServerConfigurerAdapter {
#Bean
public DefaultTokenServices tokenServices() {
return new DefaultTokenServices();
}
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId("myResourceId");
resources.tokenServices(tokenServices());
}
#Override
public void configure(HttpSecurity http) throws Exception {
// configure http security here...
http.logout().logoutSuccessHandler(new SimpleUrlLogoutSuccessHandler() {
#Override
public void onLogoutSuccess(HttpServletRequest request,
HttpServletResponse response,
Authentication authentication) {
OAuth2AccessToken token = tokenServices().getAccessToken((OAuth2Authentication) authentication);
tokenServices().revokeToken(token.getValue());
}
});
}
}

Related

Spring authorization server authenticate for each client

I'm trying to build an Identity Provider using Spring authorization-server that third party applications are going to use for FIM (federated identity management).
We want each OAuth client to require authentication (if a user tries to login with a different client they would need to authenticate for each client).
Out of the box the flow looks like this:
So there's 2 issues.
The /oauth2/authorize endpoint just checks whether or not the sessions principal is authenticated, it doesn't care or know which client the principal was meant for.
There's just a single /login endpoint, so during authentication it doesn't know which client is used.
My best bet here is that I should:
Make the oauth2/authorize endpoint redirection to /login include the query parameter client_id
Create a custom AuthenticationFilter that also adds the client_id to the User principal
Override the authorizationRequestConverter for the oauth2/authorize endpoint and validate that the client in the request is the same as the client stored on the authenticated principal
Am I missing anything or do anyone know of a simpler way of doing this?
Based on your last comment, it seems one possibility is to simply require authentication every time, or at least every time an authorization is requested. In that case, you could clear out the authentication after the authorization code is issued to the client, using a Filter. This doesn't seem ideal and will result in a poor user experience, but may achieve your requirement.
#Configuration
#EnableWebSecurity
public class SecurityConfig {
#Bean
#Order(1)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http)
throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
// ...
// Add filter to remove the SecurityContext after successful authorization
http.addFilterAfter(new RemoveSecurityContextOnAuthorizationFilter(), LogoutFilter.class);
return http.build();
}
private static final class RemoveSecurityContextOnAuthorizationFilter extends OncePerRequestFilter {
private SecurityContextHolderStrategy securityContextHolderStrategy =
SecurityContextHolder.getContextHolderStrategy();
private final LogoutHandler logoutHandler = new CompositeLogoutHandler(
new CookieClearingLogoutHandler("JSESSIONID"),
new SecurityContextLogoutHandler()
);
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
try {
filterChain.doFilter(request, response);
} finally {
String locationHeader = response.getHeader(HttpHeaders.LOCATION);
if (locationHeader != null) {
UriComponents uriComponents = UriComponentsBuilder.fromUriString(locationHeader).build();
if (uriComponents.getQueryParams().containsKey("code")) {
Authentication authentication = this.securityContextHolderStrategy.getContext().getAuthentication();
this.logoutHandler.logout(request, response, authentication);
}
}
}
}
}
// ...
}

How to properly configure spring-security with vaadin14 to handle 2 entry points - keyclaok and DB

I have a vaadin14 application that I want to enable different types of authentication mechanisms on different url paths. One is a test url, where authentication should use DB, and the other is the production url that uses keycloak.
I was able to get each authentication mechanism to work separately, but once I try to put both, I get unexpected results.
In both cases, I get login page, but the authentication doesn't work correctly. Here's my security configuration, what am I doing wrong?
#Configuration
#EnableWebSecurity
public class ApplicationSecurityConfiguration {
#Configuration
#Order(2)
public static class DBAuthConfigurationAdapter extends WebSecurityConfigurerAdapter {
private static final String LOGIN_PROCESSING_URL = "/login";
private static final String LOGIN_FAILURE_URL = "/login?error";
private static final String LOGIN_URL = "/login";
private static final String LOGOUT_SUCCESS_URL = "/login";
/**
* Require login to access internal pages and configure login form.
*/
#Override
protected void configure(HttpSecurity http) throws Exception {
// Not using Spring CSRF here to be able to use plain HTML for the login page
http.csrf().disable()
// Register our CustomRequestCache, that saves unauthorized access attempts, so
// the user is redirected after login.
.requestCache().requestCache(new CustomRequestCache())
// Restrict access to our application.
.and().antMatcher("/test**").authorizeRequests()
// Allow all flow internal requests.
.requestMatchers(SecurityUtils::isFrameworkInternalRequest).permitAll()
// Allow all requests by logged in users.
.anyRequest().hasRole("USER")
// Configure the login page.
.and().formLogin().loginPage(LOGIN_URL).permitAll().loginProcessingUrl(LOGIN_PROCESSING_URL)
.failureUrl(LOGIN_FAILURE_URL)
// Configure logout
.and().logout().logoutSuccessUrl(LOGOUT_SUCCESS_URL);
}
#Bean
#Override
public UserDetailsService userDetailsService() {
Properties users = null;
try {
users = PropertiesLoaderUtils.loadAllProperties("users.properties");
return new InMemoryUserDetailsManager(users);
} catch (IOException e) {
e.printStackTrace();
}
UserDetails user =
User.withUsername("user")
.password("{noop}password")
.roles("ACTOR")
.build();
return new InMemoryUserDetailsManager(user);
}
/**
* Allows access to static resources, bypassing Spring security.
*/
#Override
public void configure(WebSecurity web) {
web.ignoring().antMatchers(
// Vaadin Flow static resources
"/VAADIN/**",
// the standard favicon URI
"/favicon.ico",
// the robots exclusion standard
"/robots.txt",
// web application manifest
"/manifest.webmanifest",
"/sw.js",
"/offline-page.html",
// icons and images
"/icons/**",
"/images/**",
// (development mode) static resources
"/frontend/**",
// (development mode) webjars
"/webjars/**",
// (development mode) H2 debugging console
"/h2-console/**",
// (production mode) static resources
"/frontend-es5/**", "/frontend-es6/**",
"/resources/**");
}
}
#Order(1)
#Configuration
#ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
public static class AppKeycloakSecurity extends KeycloakWebSecurityConfigurerAdapter {
#Autowired
public void configureGlobal(
AuthenticationManagerBuilder auth) throws Exception {
KeycloakAuthenticationProvider keycloakAuthenticationProvider
= keycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(
new SimpleAuthorityMapper());
auth.authenticationProvider(keycloakAuthenticationProvider);
}
#Bean
public KeycloakSpringBootConfigResolver KeycloakConfigResolver() {
return new KeycloakSpringBootConfigResolver();
}
#Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(
new SessionRegistryImpl());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.httpBasic().disable();
http.formLogin().disable();
http.anonymous().disable();
http.csrf().disable();
http.headers().frameOptions().disable();
http
.antMatcher("/prod**")
.authorizeRequests()
.antMatchers("/vaadinServlet/UIDL/**").permitAll()
.antMatchers("/vaadinServlet/HEARTBEAT/**").permitAll()
.requestMatchers(SecurityUtils::isFrameworkInternalRequest).permitAll()
.anyRequest().hasRole("actor");
http
.logout()
.addLogoutHandler(keycloakLogoutHandler())
.logoutUrl("/logout").permitAll()
.logoutSuccessUrl("/");
http
.addFilterBefore(keycloakPreAuthActionsFilter(), LogoutFilter.class);
http
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint());
http
.sessionManagement()
.sessionAuthenticationStrategy(sessionAuthenticationStrategy());
}
}
}
Navigating within a Vaadin UI will change the URL in your browser, but it will not necessarily create a browser request to that exact URL, effectively bypassing the access control defined by Spring security for that URL. As such, Vaadin is really not suited for the request URL-based security approach that Spring provides. For this issue alone you could take a look at my add-on Spring Boot Security for Vaadin which I specifically created to close the gap between Spring security and Vaadin.
But while creating two distinct Spring security contexts based on the URL is fairly easy, this - for the same reason - will not work well or at all with Vaadin. And that's something even my add-on couldn't help with.
Update: As combining both security contexts is an option for you, I can offer the following solution (using my add-on):
Starting from the Keycloak example, you would have to do the following:
Change WebSecurityConfig to also add your DB-based AuthenticationProvider. Adding your UserDetailsService should still be enough. Make sure to give every user a suitable role.
You have to remove this line from application.properties: codecamp.vaadin.security.standard-auth.enabled = false
This will re-enable the standard login without Keycloak via a Vaadin view.
Adapt the KeycloakRouteAccessDeniedHandler to ignore all test views that shouldn't be protected by Keycloak.
I already prepared all this in Gitlab repo and removed everything not important for the main point of this solution. See the individual commits and their diffs to also help focus in on the important bits.

Spring Boot Security Configuration OAuthSSO and ResourceServer

I have a WebApp consisting of 2 parts.
One is with a frontend (Vaadin) where i want the user to be Logged-In via OAuth2. I then Check whether the user has a certain Role or not. --> If user opens the URL he shall be redirected to the OAuthLogin automatically. --> This is working with the #EnableOAuthSso.
Second Part is the REST-API of the Application, which is found by anything under /api/*. fE. /api/devices
should give me a list if the Get-Request has a valid Bearer-Token. If the GET Request has no Bearer-Token or a wrong Role (Authority) if want to get a 403.
Now this is my configuration:
#Configuration
#EnableOAuth2Sso
public class ProdWebSecurityConfiguration extends WebSecurityConfigurerAdapter {
private static final String ADMIN_ROLE= "role.global.admin";
private static final String READ_API_ROLE= "role.base.read.api";
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests()
.antMatchers("/login**", "/error**").permitAll()
.antMatchers("/*").hasAuthority(ADMIN_ROLE)
.antMatchers("/api/**").hasAnyAuthority(ADMIN_ROLE, READ_API_ROLE)
.and().logout().permitAll().logoutSuccessUrl(rootAuthUri + "/connect/endsession")
;
}
Now when opening for example /manageDevices in the Browser i get forced to be logged in via Auth-Code-Flow and everything works like as expected.
When i try to open /api/devices i also get forced to be logged in via Oauth. Even when i do send Http-Header with Authentication: Bearer xxxxx. Somehow it always forces me to the Login-Screen from my OAuth login.
application.properties these lines are defined:
base.rootauthuri=https://oauth2.mypage.ch
security.oauth2.client.clientId=client.base.parameters
security.oauth2.client.clientSecret=secret
security.oauth2.client.accessTokenUri=${base.rootauthuri}/connect/token
security.oauth2.client.userAuthorizationUri=${base.rootauthuri}/connect/authorize
security.oauth2.client.scope=openid,scope.base.parameters,role,offline_access
security.oauth2.client.clientAuthenticationScheme=form
security.oauth2.resource.userInfoUri=${base.rootauthuri}/connect/userinfo
How can i force everything under /api/* to not redirect to the AuthenticationForm but respond with 403 if no Bearer Token is sent. How can i make it to Check whether the Bearer-Token has Role "READ_API_ROLE" also.
I had the same question with SSO, I configured a ResourceServe for that:
#Configuration
#EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
#Autowired
private ResourceServerConfiguration configuration;
#PostConstruct
public void setSecurityConfigurerOrder() {
configuration.setOrder(3);
}
#Bean("resourceServerRequestMatcher")
public RequestMatcher resources() {
return new AntPathRequestMatcher("/api/**");
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.requestMatchers().antMatchers("/v1/**") // this is free resource
.and().authorizeRequests()
.antMatchers("/api/**").permitAll() // This is free resource for mvc calls
// Usado para paths que necessitam de token bearer
.and().requestMatchers().antMatchers("/integration/**")
.and().authorizeRequests()
.antMatchers("/integration/**").authenticated(); // this is protected resource, it's necessary token
}
}
I not configure WebSecurityConfigurerAdapter in my project;
Check this:
Spring Boot 1.3.3 #EnableResourceServer and #EnableOAuth2Sso at the same time
https://www.baeldung.com/spring-security-oauth2-enable-resource-server-vs-enable-oauth2-sso

log access denied events with Spring Security and J2EE container authentication

I've got spring security configured as
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = false)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.jee()
.mappableRoles("ROLE1", "ROLE2");
}
}
And then #Secured annotations with roles on the rest endpoints.
Doesn't matter what I do I don't seem to be able to create a custom handler for authorization (i.e. a user logged in successfully but doesn't have the right role to access a particular endpoint) error events.
What I tried was:
An exception handler with #ExceptionHandler(value = AccessDeniedException.class) - doesn't get called. I understand that's by design, ok.
AuthenticationEntryPoint configured as
http.exceptionHandling().authenticationEntryPoint(new RestAuthenticationEntryPoint())
#Component( "restAuthenticationEntryPoint" )
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
#Override
public void commence( HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException ) throws IOException {
// logging
}
}
-doesn't get called
ApplicationListener - I can see it's getting called on context closed, so it's registered correctly but not called on authorization error.
All I need is a simple handler to log unsuccessful authorization events.
It completely slipped my mind that the allowed roles are listed in web.xml as well for j2ee container authentication to work. So any user without a least one of those roles was just being rejected by the container.
Otherwise the first, simplest, method works fine. Hopefully my mistake will help someone in the future

Spring Session and Spring Security

I have questions on the following areas: spring-session and spring-security.
Spring Session
I have a application protected with Spring Security through basic in-memory authentication as provided in the example sample.
I see spring is creating session id's even the authentication is not successful, meaning I am seeing x-auth-token in my response header as well in the Redis DB even if I don't supply basic authentication credential details.
How do we avoid creating sessions for authentication failures?
Spring Security
Want to use spring security to protect resources assuming spring session creates session only for the protected resources.
Assuming a Signin API (/signin - HTTP Post) validates (username & password) credentials against a third-party REST API .
Once the external API validates the credentials, how do I update the spring security context on the successful authentication?
Access to other secured resources with the x-auth-token needs to be validated and based on the information access to the secured resource should be provided.
Do we need to have Spring Security in this case or shall I use a basic filter and spring session? What is recommended?
Typically it would be best to break your questions into multiple StackOverflow questions since you are more likely to find someone that knows the answer to a single question than both.
How do we avoid creating sessions for authentication failures ?
By default Spring Security will save the last unauthenticated request to session so that after you authenticate it can automatically make the request again. For example, in a browser if you request example.com/a/b/c and are not authenticated, it will save example.com/a/b/c to the HttpSession and then have the user authenticate. After you are authenticated, it will automatically give you the result of example.com/a/b/c. This provides a nice user experience so that your users do not need to type the URL again.
In the case of a REST service this is not necessary since the client would remember which URL needs to be re-requested. You can prevent the saving by modifying the configuration to use a NullRequestCache as shown below:
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.requestCache()
.requestCache(new NullRequestCache())
.and()
.httpBasic();
}
You can provide custom authentication by providing your own AuthenticationProvider. For example:
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.AuthorityUtils;
public class RestAuthenticationProvider implements AuthenticationProvider {
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) authentication;
String username = token.getName();
String password = (String) token.getCredentials();
// validate making REST call
boolean success = true;
// likely your REST call will return the roles for the user
String[] roles = new String[] { "ROLE_USER" };
if(!success) {
throw new BadCredentialsException("Bad credentials");
}
return new UsernamePasswordAuthenticationToken(username, null, AuthorityUtils.createAuthorityList(roles));
}
public boolean supports(Class<?> authentication) {
return (UsernamePasswordAuthenticationToken.class
.isAssignableFrom(authentication));
}
}
You can then configure your RestAuthenticationProvider using something like this:
#EnableWebSecurity
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
...
#Bean
public RestAuthenticationProvider restAuthenticationProvider() {
return new RestAuthenticationProvider();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth, AuthenticationProvider provider) throws Exception {
auth
.authenticationProvider(provider);
}
}
Session IDs are getting stored in Redis even when authentication fails.
Rob's answer of setting NullRequestCache didn't work for me. That is, there were redis entries even after setting request cache to NullRequestCache. To make it work, I had to use an authentication failure handler.
http.formLogin().failureHandler(authenticationFailureHandler()).permitAll();
private AuthenticationFailureHandler authenticationFailureHandler() {
return new AuthenticationFailureHandler();
}
public class AuthenticationFailureHandler
extends SimpleUrlAuthenticationFailureHandler {
}
Note that the failure handler does nothing but extend the default handler explicitly. It returns a 401 in case of failure. If you were doing redirects, you can configure it in the handler easily.

Resources