I have declared the following functions in an open source core banking solution based on Spring boot (Fineract) to limit the number of concurrent sessions per user to 1. My WebSecurity.java file is as follows:
#EnableWebSecurity
#Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/**").permitAll()
.anyRequest().authenticated()
.and()
.sessionManagement()
.maximumSessions(1)
.maxSessionsPreventsLogin(true)
.sessionRegistry(sessionRegistry());
}
// Work around https://jira.spring.io/browse/SEC-2855
#Bean
public SessionRegistry sessionRegistry() {
SessionRegistry sessionRegistry = new SessionRegistryImpl();
return sessionRegistry;
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("mifos").password("password").roles("USER");
}
// Register HttpSessionEventPublisher
#Bean
public static ServletListenerRegistrationBean httpSessionEventPublisher() {
return new ServletListenerRegistrationBean(new HttpSessionEventPublisher());
}
}
The SecurityWebApplicationInitializer.java is as follows:
public class SecurityWebApplicationInitializer
extends AbstractSecurityWebApplicationInitializer {
protected Class<?>[] getRootConfigClasses() {
return new Class[] { WebSecurityConfig.class };
}
}
However, I am still able to log into the system with multiple private browser windows opened. My assumption is that the problem is either with the SpringSecurityFilterChain not being registered with war, or with the way I am chaining the functions of the HttpSecurity object. Since I did not declare a customized login form or have defined any expired URL pages, I had to edit the steps shown in the following link: https://github.com/spring-projects/spring-boot/issues/1537 . Any leads on how to diagnose this issue? Thanks in advance.
Related
I have a Spring Boot Vaadin web app with Spring Security and keycloak-spring-security-adapter.
This works fine on the root page. From the root page I can access the child page /edit/{id} without problems. But I want authenticated users to be able to call the child page with parameter directly. This is important, because another Vaadin web app with the same security should be able to access the edit page directly. This should work due to SSO.
I searched in books, tutorials and of cause Stackoverflow. But no solution did work. I read about CORS, but I must admit, I don't understand that enough.
This are my security configuration classes:
#KeycloakConfiguration
public class UiSecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
#Autowired
private KeycloakClientRequestFactory factory;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
KeycloakAuthenticationProvider keyCloakAuthProvider = keycloakAuthenticationProvider();
keyCloakAuthProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
auth.authenticationProvider(keyCloakAuthProvider);
}
#Bean
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public KeycloakRestTemplate restTemplate() {
return new KeycloakRestTemplate(factory);
}
#Bean
#Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
}
#Override
public void configure(HttpSecurity http) throws Exception {
super.configure(http);
http
.cors().disable()
.csrf().disable()
.anonymous().disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.and()
.authorizeRequests()
.requestMatchers(SecurityUtils::isFrameworkInternalRequest).permitAll()
.anyRequest().hasAnyRole("ROLE_trainer", "ROLE_admin");
}
#Override
public void configure(WebSecurity web) throws Exception {
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",
// (development mode) static resources //
"/frontend/**",
// (development mode) webjars //
"/webjars/**",
// (production mode) static resources //
"/frontend-es5/**", "/frontend-es6/**");
}
#Bean
public static KeycloakConfigResolver keycloakConfigResolver() {
return new KeycloakSpringBootConfigResolver();
}
}
#Component
public class ConfigureUIServiceInitListener implements VaadinServiceInitListener {
#Override
public void serviceInit(ServiceInitEvent event) {
event.getSource().addUIInitListener(uiEvent -> {
final UI ui = uiEvent.getUI();
ui.addBeforeEnterListener(this::beforeEnter);
});
}
private void beforeEnter(BeforeEnterEvent event) {
Class<?> navigationTarget = event.getNavigationTarget();
if (isSecureAndNotAuthentificated(navigationTarget)) {
event.rerouteTo("");
}
}
private boolean isSecureAndNotAuthentificated(Class<?> navigationTarget) {
boolean userLoggedIn = SecurityUtils.isUserLoggedIn();
return MainView.class.equals(navigationTarget) && !userLoggedIn;
}
}
This is my keycloak development setting:
These are the versions I use:
Spring Boot Starter: 2.6.2,
Keycloak: 11.0.1,
Vaadin: 14.4.2
How can I make other routes accessible for authenticated users via SSO?
I'm trying to setup a simple HTTP basic authentication mechanism for accessing REST endpoints in an application.
Basically, all endpoints starting with /api/internal shall be secured with HTTP basic authentication, while further configurations shall secure other paths with e.g. OAuth2.
The problem is that, for example, a GET request to /api/internal/test is allowed even when the client does not provide any credentials in the request header.
This is my current security configuration class:
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Order(1)
#Configuration
#EnableWebSecurity
public static class InternalApiSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
final PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
auth
.inMemoryAuthentication()
.passwordEncoder(passwordEncoder)
.withUser("user")
.password(passwordEncoder.encode("password"))
.roles("USER");
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.csrf().disable()
.mvcMatcher("/api/internal/**")
.authorizeRequests().anyRequest().authenticated()
.and()
.httpBasic();
}
}
// Other security configuration follow here...
}
After having spent some more time on this problem, I found that the authentication works when adding the following to the chain:
sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
I have a Spring Boot app where I have custom pre authentication filter. I want to ignore security for health URL but I am not able to do it. Below is my configuration.
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
#Order(1000)
public class UserSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> userDetailsService;
#Autowired
private IUserIdentityService iUserIdentityService;
#Value("${spring.profiles.active}")
private String profileType;
#Autowired
#Qualifier("publicEndpoints")
private Map<String, String> publicEndpoints;
#Autowired
private GenericDataService genericDataService;
#Bean(name = "preAuthProvider")
PreAuthenticatedAuthenticationProvider preauthAuthProvider() {
PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider();
provider.setPreAuthenticatedUserDetailsService(userDetailsService);
return provider;
}
#Bean
AppPreAuthenticatedProcessingFilter appPreAuthenticatedProcessingFilter() throws Exception {
appPreAuthenticatedProcessingFilter filter = new appPreAuthenticatedProcessingFilter(iUserIdentityService, genericDataService);
filter.setAuthenticationManager(super.authenticationManagerBean());
filter.setContinueFilterChainOnUnsuccessfulAuthentication(false);
filter.setCheckForPrincipalChanges(true);
return filter;
}
/**
* Uses JEE pre-authentication filter, that assumes that the user has been
* pre-authenticated into the container.
*/
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/health/e2e").permitAll()
.and()
.addFilter(appPreAuthenticatedProcessingFilter())
.authorizeRequests()
.anyRequest().authenticated()
.and()
.authenticationProvider(preauthAuthProvider())
.csrf()
.csrfTokenRepository(this.csrfTokenRepository())
.and()
.httpBasic().disable();
// Disabling the CSRF implementation, if "csrf.disabled" property set to "true"
// in System Properties.
if (!StringUtils.isEmpty(profileType) && profileType.equals("local")) {
http.csrf().disable();
}
}
/**
* Method to ignore web security for urls
*/
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("*/api-docs", "/configuration/ui", "/swagger-resources/**", "/configuration/**", "/swagger-ui.html", "/webjars/**", "/health/e2e", "*/health/e2e", "**/health/e2e");
}
/**
* Method to to return CsrfTokenRepository
*/
private CsrfTokenRepository csrfTokenRepository() {
CookieCsrfTokenRepository tokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse();
tokenRepository.setCookiePath("/");
return tokenRepository;
}
}
Custom authentication filter looks like
#Slf4j
public class AppPreAuthenticatedProcessingFilter extends AbstractPreAuthenticatedProcessingFilter {
private IUserIdentityService iUserIdentityService;
private GenericDataService genericDataService;
public AppPreAuthenticatedProcessingFilter(IUserIdentityService iUserIdentityService, GenericDataService genericDataService) {
this.iUserIdentityService = iUserIdentityService;
this.genericDataService = genericDataService;
}
#Override
protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request));
return iUserIdentityService.getUserName();
}
#Override
protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
return AppConst.DEFAULT_CREDENTIAL;
}
}
I am not sure why /health/e2e is secured?
P.S. I tried removing #Bean from pre auth filter but in that case, filter never gets called for any request.
The problem is two fold
Your security setup contains an error
The filter is added to the regular filter bean as well.
With your current security setup the AppPreAuthenticatedProcessingFilter is added only to the /health/e2d URL. Your attempt to fix something has actually broken things instead.
Your configuration should be something along the lines of
http.authorizeRequests().anyRequest().authenticated()
.and().httpBasic()
.and().authenticationProvider(preauthAuthProvider())
.csrf().csrfTokenRepository(this.csrfTokenRepository())
.and().addFilterBefore(appPreAuthenticatedProcessingFilter(), UsernamePasswordAuthenticationFilter.class);
// in System Properties.
if (!StringUtils.isEmpty(profileType) && profileType.equals("local")) {
http.csrf().disable();
}
Spring Boot will by default register an javax.servlet.Filter in the normal filter chain, to disable this you need to add a FilterRegistrationBean to disable this.
#Bean
public FilterRegistrationBean<AppPreAuthenticatedProcessingFilter> preAuthenticationFilterRegistrationBean(AppPreAuthenticatedProcessingFilter filter) {
FilterRegistrationBean<AppPreAuthenticatedProcessingFilter> frb = new FilterRegistrationBean<>(filter);
frb.setEnabled(false);
return frb;
}
At a glance, I have API Back-end App written in Spring Boot which uses JWT for secured data transmission. I want to add 3rd parameter for authorization, so I should have login, password and storeID parameters. I am inspired by this answer How implement Spring security when login page having more field apart from user name and password? but when I followed proposed solution my 3rd parameter in not used. My impression is that I am missing something important in Security Config. Could you please point to my mistake?
SecurityConfig
#SuppressWarnings("SpringJavaAutowiringInspection")
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private JwtAuthenticationEntryPoint unauthorizedHandler;
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private AuthenticationDetailsSource<HttpServletRequest, ?> webAuthenticationDetailsSourceImpl;
#Autowired
public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder
.authenticationProvider(myAuthProvider());
}
#Bean
public CustomUserDetailsAuthenticationProvider myAuthProvider() throws Exception {
CustomUserDetailsAuthenticationProvider provider = new CustomUserDetailsAuthenticationProvider();
provider.setPasswordEncoder(passwordEncoder());
provider.setUserDetailsService(userDetailsService);
return provider;
}
#Bean
public UsernamePasswordAuthenticationFilter usernamePasswordAuthenticationFilter() throws Exception {
UsernamePasswordAuthenticationFilter usernamePasswordAuthenticationFilter = new UsernamePasswordAuthenticationFilter();
usernamePasswordAuthenticationFilter.setAuthenticationManager(authenticationManager());
usernamePasswordAuthenticationFilter.setAuthenticationDetailsSource(webAuthenticationDetailsSourceImpl);
return usernamePasswordAuthenticationFilter;
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public JwtAuthenticationTokenFilter authenticationTokenFilterBean() throws Exception {
JwtAuthenticationTokenFilter authenticationTokenFilter = new JwtAuthenticationTokenFilter();
authenticationTokenFilter.setAuthenticationManager(authenticationManagerBean());
return authenticationTokenFilter;
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
// we don't need CSRF because our token is invulnerable
.csrf().disable()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
// don't create session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
// allow anonymous resource requests
.antMatchers(
HttpMethod.GET,
"/",
"/*.html",
"/favicon.ico",
"/**/*.html",
"/**/*.css",
"/**/*.js"
).permitAll()
.antMatchers("/auth/**").permitAll()
.anyRequest().authenticated();
// Custom JWT based security filter
httpSecurity
.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
// disable page caching
httpSecurity.headers().cacheControl();
}
}
I was under impression I can check against storeID field in WebAuthenticationDetailsSourceImpl, but looks like it has never been executed because I don't see anything related in log.
WebAuthenticationDetailsSourceImpl:
#Component
public class WebAuthenticationDetailsSourceImpl implements AuthenticationDetailsSource<HttpServletRequest, JwtAuthenticationRequest> {
#Override
public JwtAuthenticationRequest buildDetails(HttpServletRequest context) {
System.out.println("___#####_____");
System.out.println(context);
System.out.println("___#####_____");
return new JwtAuthenticationRequest();
}
}
cuz you don't insert "your" usernamePasswordAuthenticationFilter that set webAuthenticationDetailsSourceImpl to Spring Security's authentication filter chain.
perhaps current your authentication filter chain is
~
JwtAuthenticationTokenFilter
(Spring Security's original)UsernamePasswordAuthenticationFilter
~
hence,if you want to retrieve your additional parameter in "your" usernamePasswordAuthenticationFilter add this filter too like a JwtAuthenticationTokenFilter
but , if you want to simply retrieve parameter at JwtAuthenticationTokenFilter
use setAuthenticationDetailsSource at there
#Bean
public JwtAuthenticationTokenFilter authenticationTokenFilterBean() throws Exception {
JwtAuthenticationTokenFilter authenticationTokenFilter = new JwtAuthenticationTokenFilter();
authenticationTokenFilter.setAuthenticationManager(authenticationManagerBean());
authenticationTokenFilter.setAuthenticationDetailsSource(webAuthenticationDetailsSourceImpl);
return authenticationTokenFilter;
}
Using Spring boot - After successfully authenticating with GitHub OAuth, the Audit listener is not being triggered.
public class AuthenticationListener implements ApplicationListener<InteractiveAuthenticationSuccessEvent> {
#Override
public void onApplicationEvent(final InteractiveAuthenticationSuccessEvent event) {
System.out.println("+++++++ ================ ------------");
}
}
Do I need to register it anywhere else? I have tried as suggested else where on Stackoverflow to create a #Bean, but this made no difference.
Full code https://github.com/DashboardHub/PipelineDashboard/tree/feature/178-login-github
Update
SecurityConfig class
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
OAuth2ClientContext oauth2ClientContext;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**")
.authorizeRequests()
.antMatchers("/", "/login**", "/webjars/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")//.failureUrl("/login?error")
.permitAll()
.and().logout().logoutSuccessUrl("/").permitAll()
.and().addFilterBefore(ssoFilter(), BasicAuthenticationFilter.class)
;
}
#Bean
#ConfigurationProperties("security.oauth2")
ClientResourcesConfig github() {
return new ClientResourcesConfig();
}
private Filter ssoFilter() {
CompositeFilter filter = new CompositeFilter();
List<Filter> filters = new ArrayList<>();
filters.add(ssoFilter(this.github(), "/login/github"));
filter.setFilters(filters);
return filter;
}
private Filter ssoFilter(ClientResourcesConfig client, String path) {
OAuth2ClientAuthenticationProcessingFilter githubFilter = new OAuth2ClientAuthenticationProcessingFilter(
path);
OAuth2RestTemplate githubTemplate = new OAuth2RestTemplate(client.getClient(),
oauth2ClientContext);
githubFilter.setRestTemplate(githubTemplate);
githubFilter.setTokenServices(new UserInfoTokenServices(
client.getResource().getUserInfoUri(), client.getClient().getClientId()));
return githubFilter;
}
}
I got this working by injecting the default ApplicationEventPublisher into your security config bean. Then setting this as the application event publisher on the processingfilter:
#Autowired
private ApplicationEventPublisher applicationEventPublisher;
...
githubFilter.setApplicationEventPublisher(applicationEventPublisher);
For some reason, the application event publisher on the filter is a NullEventPublisher by default.
Use #Configuration annotation on AuthenticationListener class to register it in your application context.
EDIT
As I could not figure out why event wasn't fired, I present alternative solution for this problem. You have to create class that implements AuthenticationSuccessHanddler and implement its onAuthenticationSuccess method:
#Component(value="customAuthenticationSuccessHandler")
public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler{
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
//implementation
}
}
Then add it to your configuration like:
#Resource
private CustomAuthenticationSuccessHandler customAuthenticationSuccessHandler;
#Override
protected void configure(HttpSecurity http) throws Exception {
...
.loginPage("/login").and()
.successHandler(getCustomAuthenticationSuccessHandler())
.permitAll()
...
}
public CustomAuthenticationSuccessHandler getCustomAuthenticationSuccessHandler() {
return customAuthenticationSuccessHandler;
}
It's not exactly what you wanted, but should solve your problem.
Instead of an InteractiveAuthenticationSuccessEvent, listening for an AuthenticationSuccessEvent did the trick for me.
However, the listener is called twice: first one's event's Authentication is an UsernamePasswordAuthenticationToken, while the second one is an OAuth2Authentication