I'm developing a web application unsing Spring Security 4.0.0's Java Config instead of xml config. I'm using ObjectPostProcessors to customize the some of Spring Security's beans, notably the session consurrency ones (to achive immediate invalidation of a session as soon as a user logs in again, as opposed to Spring's standard behavior of invalidating at the next request).
It's working as expected most of the times, but sometimes when I restart the application it seems like not all beans get modified as I want.
Are SecurityBuilders processed in a specific order or are they instead processed with a ramdom order?
EDIT:
My Config
#EnableWebSecurity
public class SecurityConfig extends AbstractCASWebSecurityConfigurerAdapter {
public SecurityConfig() {
super(true, false, true);
}
#Autowired
private Environment env;
// we need a custom SessionRegistry as there's no way to get ahold of the one created by the configurer.
#Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
// we need a custom HttpSessionCsrfTokenRepository as there's no way to get ahold of the one created by the configurer.
#Bean
public CsrfTokenRepository csrfTokenRepository() {
return new HttpSessionCsrfTokenRepository();
}
// our custom ConcurrentSessionControlAuthenticationStrategy that invalidates session immediately
#Bean
public SessionInvalidatingConcurrentSessionControlAuthenticationStrategy myConcurrentSessionControlAuthenticationListener()
{
// we have to recreate the LogoutHandlers because we need to call them
// before invalidating the session
final LogoutHandler [] logoutHandlers = new LogoutHandler [] {
new CookieClearingLogoutHandler("JSESSIONID"),
new CsrfLogoutHandler(csrfTokenRepository())
//, new SecurityContextLogoutHandler() // seems to create problems with redirecting to the same page that caused the login request
};
SessionInvalidatingConcurrentSessionControlAuthenticationStrategy mine = new SessionInvalidatingConcurrentSessionControlAuthenticationStrategy(sessionRegistry(), logoutHandlers);
mine.setExceptionIfMaximumExceeded(false);
mine.setMaximumSessions(1);
return mine;
}
#Override
public void configure(WebSecurity web) throws Exception {
super.configure(web);
boolean devMode = this.env.acceptsProfiles("development");
final String [] ignoredPaths = devMode
? new String [] {"/webjars/**", "/static/**", "/bower_components/**" }
: new String [] {"/webjars/**", "/static/**" };
web
.ignoring()
.antMatchers(ignoredPaths)
.and()
.debug(false)
;
}
protected void configure(final HttpSecurity http) throws Exception {
super.configure(http);
http
.sessionManagement()
.maximumSessions(73467436) // this is just to trigger the ConcurrencyControlConfigurer
.sessionRegistry(sessionRegistry())
.and()
.withObjectPostProcessor(new ObjectPostProcessor<ConcurrentSessionControlAuthenticationStrategy>() {
#SuppressWarnings("unchecked")
#Override
public <O extends ConcurrentSessionControlAuthenticationStrategy> O postProcess(O concurrentSessionControlAS) {
// substitute the ConcurrentSessionControlAuthenticationStrategy created by
// ConcurrencyControlConfigurer with our own
return (O) myConcurrentSessionControlAuthenticationListener();
}
})
.and()
// we need to ignore the stomp endpoint to allow SockJS javascript client to issue POST requests
// to /push/../../.. when using trasports which are not WebSocket;
// at that time, protection is given by Stomp CSRF headers
.csrf()
.csrfTokenRepository(csrfTokenRepository())
.ignoringAntMatchers("/push/**")
.and()
// allow same origin to frame our site to support iframe SockJS
.headers()
.frameOptions().sameOrigin()
.and()
.authorizeRequests()
.antMatchers("/help/**").permitAll() // help redirects do not require authentication
.antMatchers("/push/info").permitAll() // do not require being authenticated for the /info request by SockJS
.anyRequest().authenticated()
.and()
// remove the session cookie when logging out
.logout()
.deleteCookies("JSESSIONID") // see: http://docs.spring.io/autorepo/docs/spring-security/current/reference/htmlsingle/#detecting-timeouts
.and()
;
}
}
AbstractCASWebSecurityConfigurerAdapter is an AbstractWebSecurityConfigurerAdapter that configures CAS.
Related
Preconditions
I have two Java Spring applications(App 'A' and App 'B') that were created via JHipster(monolithic application). Both applications uses keycloak for authentication/authorization.
Both applications have an angular frontend and support login via ouath (spring-security). Here ist my SecurityConfiguration of Application A and B:
#Configuration
#Import(SecurityProblemSupport.class)
#EnableOAuth2Sso
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private final CorsFilter corsFilter;
private final SecurityProblemSupport problemSupport;
public SecurityConfiguration(CorsFilter corsFilter, SecurityProblemSupport problemSupport) {
this.corsFilter = corsFilter;
this.problemSupport = problemSupport;
}
#Bean
public AjaxLogoutSuccessHandler ajaxLogoutSuccessHandler() {
return new AjaxLogoutSuccessHandler();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public SecurityEvaluationContextExtension securityEvaluationContextExtension() {
return new SecurityEvaluationContextExtension();
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers(HttpMethod.OPTIONS, "/**")
.antMatchers("/app/**/*.{js,html}")
.antMatchers("/i18n/**")
.antMatchers("/content/**")
.antMatchers("/swagger-ui/index.html")
.antMatchers("/test/**")
.antMatchers("/h2-console/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.and()
.addFilterBefore(corsFilter, CsrfFilter.class)
.exceptionHandling()
.authenticationEntryPoint(problemSupport)
.accessDeniedHandler(problemSupport)
.and()
.logout()
.logoutUrl("/api/logout")
.logoutSuccessHandler(ajaxLogoutSuccessHandler())
.permitAll()
.and()
.headers()
.frameOptions()
.disable()
.and()
.authorizeRequests()
.antMatchers("/api/profile-info").permitAll()
.antMatchers("/api/**").authenticated()
.antMatchers("/management/health").permitAll()
.antMatchers("/management/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/v2/api-docs/**").permitAll()
.antMatchers("/swagger-resources/configuration/ui").permitAll()
.antMatchers("/swagger-ui/index.html").hasAuthority(AuthoritiesConstants.ADMIN);
}
}
In App B i also have an ResourceServerConfiguration. This checks if the header contains an "Authorization" key. If true, the user can login via JWT(Bearer Authentication). I tested this via Postman and it works fine:
#Configuration
#EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Override
public void configure(final HttpSecurity http) throws Exception {
http
.requestMatcher(new RequestHeaderRequestMatcher("Authorization")).authorizeRequests()
.anyRequest().authenticated();
}
}
Further more both apps are in the same keycloak realm and have the access-type "public".
Problem:
Now i want to call an endpoint of App B via an Spring RestTemplate from App A. The problem is, that i do not have an access_token that i can put in my rest request/restTemplate. When i look in my request that is send from my frontend, i only got an JSESSIONID. There is no access_token/JWT in the header.
Question
Is there a way to get the access_token of the current user out of the JSESSIONID/the HttpSession or the spring security context? Do i need something like a Tokenstore where i store every token that comes from keycloak?
Did anyone else have similar problems or any idea how i could solve that problem?
After some research it turns out that the problem lies within the generated jhipster code.
I followed the authentication process in the application and saw, that there was a call to the /account endpoint directly after authentication, where the user information were retrieved. The call is triggerd by the frontend. First time this endpoint is called, there is a principal with a bearer token available. Within the /account endpoint, a call to the userService with the principal object is performed. More precisley
getUserFromAuthentication(OAuth2Authentication authentication)
is called. Within this method there is a part that replaces the OAuth2Authentication with a new UsernamePasswordAuthenticationToken and inserts it into the SecurityContext:
UsernamePasswordAuthenticationToken token = getToken(details, user,
grantedAuthorities);
authentication = new OAuth2Authentication(authentication.getOAuth2Request(), token);
SecurityContextHolder.getContext().setAuthentication(authentication);
So after that, the access_token is lost. I am not quite sure, why it was replaced with the new OAuth2Authentication, but i tend to extend this part and keep the access_token in my securityContext for further restcalls.
I have a spring-boot application using spring-security. The security configuration is split into multiple instances of WebSecurityConfigurerAdapter.
I have one where I configure logout in general:
#Override
protected void configure(HttpSecurity http) throws Exception {
// configure logout
http
.logout()
.logoutUrl("/logout")
.invalidateHttpSession(true)
.addLogoutHandler((request, response, authentication) -> {
System.out.println("logged out 1!");
})
.permitAll();
// ... more security configuration, e.g. login, CSRF, rememberme
}
And there is another WebSecurityConfigurerAdapter, where I want to do almost nothing, except adding another LogoutHandler:
#Override
protected void configure(HttpSecurity http) throws Exception {
// configure logout
http
.logout()
.logoutUrl("/logout")
.addLogoutHandler((request, response, authentication) -> {
System.out.println("logged out 2!");
});
}
Both configure() methods are called. However, if I do log out, only the first LogoutHandler is called. Changing the #Order of both configurations does not change the result.
What is missing in my configuration?
When you create several security configurations Spring Boot will create a separate SecurityFilterChain for each of them. See WebSecurity:
#Override
protected Filter performBuild() throws Exception {
// ...
for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) {
securityFilterChains.add(securityFilterChainBuilder.build());
}
// ...
}
When application gets logout request FilterChainProxy will return only one SecurityFilterChain:
private List<Filter> getFilters(HttpServletRequest request) {
for (SecurityFilterChain chain : filterChains) {
// Only the first chain that matches logout request will be used:
if (chain.matches(request)) {
return chain.getFilters();
}
}
return null;
}
If you really need modular security configuration I would suggest to create a separate security configuration for logout and other realms. You can define logout handlers as beans (using #Bean annotation) in different configuration classes and collect these handlers in logout configuration:
WebSecurityLogoutConfiguration.java
#Configuration
#Order(99)
public class WebSecurityLogoutConfiguration extends WebSecurityConfigurerAdapter {
// ALL YOUR LOGOUT HANDLERS WILL BE IN THIS LIST
#Autowired
private List<LogoutHandler> logoutHandlers;
#Override
protected void configure(HttpSecurity http) throws Exception {
// configure only logout
http
.logout()
.logoutUrl("/logout")
.invalidateHttpSession(true)
// USE CompositeLogoutHandler
.addLogoutHandler(new CompositeLogoutHandler(logoutHandlers));
http.csrf().disable(); // for demo purposes
}
}
WebSecurity1Configuration.java
#Configuration
#Order(101)
public class WebSecurity1Configuration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
// ... more security configuration, e.g. login, CSRF, rememberme
http.authorizeRequests()
.antMatchers("/secured/**")
.authenticated();
}
// LOGOUT HANDLER 1
#Bean
public LogoutHandler logoutHandler1() {
return (request, response, authentication) -> {
System.out.println("logged out 1!");
};
}
}
WebSecurity2Configuration.java
#Configuration
#Order(102)
public class WebSecurity2Configuration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/api/**")
.permitAll();
}
// LOGOUT HANDLER 2
#Bean
public LogoutHandler logoutHandler2() {
return (request, response, authentication) -> {
System.out.println("logged out 2!");
};
}
}
You should be solving this problem with the CompositeLogoutHandler on your single /logout operation endpoint.
You can still keep two WebSecurityConfigurerAdapter's as desired, but you'll be conglomerating the logout functionality for two LogoutHandlers into a single composite action:
new CompositeLogoutHandler(loggedOutHandler1, loggedOutHandler2);
The keypoint is you should create separated instance of AuthenticationManger.
Here is an sample for multiples WebSecurityAdapter
I have a spring MVC 4 with spring security application deployed on a websphere 8.5 shared server say server123. I access the application as https app on corporate F5 domain name.
I have this strange issue where session id keeps changing on every servlet request. This causes an infinite redirect loop on IE. However this works on chrome and firefox.
I invoke my application as below where apps/MyApp/ is the context root an MainPage is the controller request mapping url https://example.server.com/apps/MyApp/MainPage .
I have also configured an SSO authentication with UserNamePasswordAuthenticationFilter that intercepts the spring redirect auth url /loginSSO. Once the authentication is successful, the forward path /MainPage is lost in IE and redirects to https://example.server.com/apps/MyApp/ and https://example.server.com/apps/MyApp/loginSSO repeatedly.Below is my security config details.
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/loginSSO").permitAll();
http.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.formLogin()
.loginPage("/loginSSO")
.successHandler(successHandler())
.and()
.csrf()
.csrfTokenRepository(csrfTokenRepository())
.and()
.addFilterBefore(new CookieFilter(),
ChannelProcessingFilter.class)
.addFilterAfter(new CSRFFilter(), CsrfFilter.class)
.addFilterBefore(authFilter(),
UsernamePasswordAuthenticationFilter.class)
.requiresChannel()
.channelProcessors(
Arrays.<ChannelProcessor> asList(
new InsecureChannelProcessor(),
new SecureChannelProcessor()));
http.portMapper().http(8080).mapsTo(8443).http(80).mapsTo(44)
.http(9080).mapsTo(9443).http(7777).mapsTo(7443);
}
/**
* Auth filter.
*
* #return the auth filter
*/
#Bean
public AuthFilter authFilter() {
AuthFilter authFilter = new AuthFilter();
try {
authFilter
.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher(
"/loginSSO"));
authFilter.setAuthenticationManager(authenticationManager());
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return authFilter;
}
#Autowired
#Qualifier("customUserDetailsService")
UserDetailsService userDetailsService;
#Bean
public SavedRequestAwareAuthenticationSuccessHandler successHandler() {
SavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
successHandler.setTargetUrlParameter("targetUrl");
return successHandler;
}
#Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
/**
* Csrf token repository.
*
* #return the csrf token repository
*/
private CsrfTokenRepository csrfTokenRepository() {
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
repository.setSessionAttributeName("_csrf");
repository.setHeaderName("X-XSRF-TOKEN");
return repository;
}
}
This issue was resolved. It happened to be the problem with the application context root configurations in Websphere application.xml file.
I need to configure expired-url in my Spring MVC application. Here is my effort, but has no effect:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterBefore(adminAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(customerAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.csrf()
.disable()
.authorizeRequests()
.antMatchers("...", "...", "...").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/admin/login")
.and()
.logout()
.addLogoutHandler(customLogoutHandler())
.logoutSuccessHandler(customLogoutSuccessHandler())
.logoutUrl("/logout")
.deleteCookies("remove")
.invalidateHttpSession(true)
.permitAll()
.and()
.sessionManagement()
.maximumSessions(1)
.expiredUrl("/expired");
}
This does not have any effect and when the user's session times out, spring does not redirect him to /expired url and just redirects him to /admin/login url.
Update:
I tried suggested solutions in the comments and answer, but did not see any effect. Also I removed addLogoutHandler(), logoutSuccessHandler() and two addFilterBefore() at beginning of method, but not working.
Also I tried another solution in this way:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterBefore(sessionManagementFilter(), SessionManagementFilter.class)
.csrf()
.disable()
.authorizeRequests()
.antMatchers("...", "...", "...").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/admin/login")
.and()
.logout()
.logoutUrl("/logout")
.deleteCookies("remove")
.invalidateHttpSession(true)
.permitAll();
}
#Bean
public SessionManagementFilter sessionManagementFilter() {
SessionManagementFilter sessionManagementFilter = new SessionManagementFilter(httpSessionSecurityContextRepository());
sessionManagementFilter.setInvalidSessionStrategy(simpleRedirectInvalidSessionStrategy());
return sessionManagementFilter;
}
#Bean
public SimpleRedirectInvalidSessionStrategy simpleRedirectInvalidSessionStrategy() {
SimpleRedirectInvalidSessionStrategy simpleRedirectInvalidSessionStrategy = new SimpleRedirectInvalidSessionStrategy("/expired");
return simpleRedirectInvalidSessionStrategy;
}
#Bean
public HttpSessionSecurityContextRepository httpSessionSecurityContextRepository(){
HttpSessionSecurityContextRepository httpSessionSecurityContextRepository = new HttpSessionSecurityContextRepository();
return httpSessionSecurityContextRepository;
}
Could anyone help me to solve this problem?
ConcurrentSessionFilter will redirect to expiredUrl, if the valid session ID is marked as expired in SessionRegistry, see Spring Security reference:
- expired-url The URL a user will be redirected to if they attempt to use a session which has been "expired" by the concurrent session controller because the user has exceeded the number of allowed sessions and has logged in again elsewhere. Should be set unless exception-if-maximum-exceeded is set. If no value is supplied, an expiry message will just be written directly back to the response.
SessionManagementFilter will redirect to invalidSessionUrl, if the session ID is not valid (timeout or wrong ID), see Spring Security reference:
If the user is not currently authenticated, the filter will check whether an invalid session ID has been requested (because of a timeout, for example) and will invoke the configured InvalidSessionStrategy, if one is set. The most common behaviour is just to redirect to a fixed URL and this is encapsulated in the standard implementation SimpleRedirectInvalidSessionStrategy. The latter is also used when configuring an invalid session URL through the namespace,as described earlier.
Both URLs (expiredUrl, invalidSessionUrl) have to be configured as permitAll().
BTW: If you want to use Concurrent Session Control with maximumSessions you have to add HttpSessionEventPublisher to your web.xml:
Concurrent Session Control
If you wish to place constraints on a single user’s ability to log in to your application, Spring Security supports this out of the box with the following simple additions. First you need to add the following listener to your web.xml file to keep Spring Security updated about session lifecycle events:
<listener>
<listener-class>
org.springframework.security.web.session.HttpSessionEventPublisher
</listener-class>
</listener>
I tried the Ali Dehghani's solution(in the comments) in this way:
.sessionManagement().maximumSessions(1).and().invalidSessionUrl("/expired");
And as The Coder said, add "/expired" in the permitted urls and the problem solved. Thank you everybody who has paid attention to my problem, especially Ali Dehghani and The Coder, for their useful comments.
Ideally your UX should simply redirect your user back to the login page. I guess you see the requirement of having a dedicated /expired page because of Spring MVC - change security settings dynamically where you informed about your need of having separate login masks. If the workaround (the one that I described in my answer on your other question) works for you, you could maybe drop your requirement of having a dedicated /expired page and redirect the user to the correct login page directly using teh solution approach number (2). How about that?
Nevertheless, to answer your current question...
I'm not sure if it works but give it a try and change your code
//...
.sessionManagement()
.maximumSessions(1)
.expiredUrl("/expired");
}
to
//...
.sessionManagement().sessionFixation().newSession().maximumSessions(1)
.expiredUrl("/expired")
.sessionRegistry(sessionRegistry());
}
#Bean
public SessionRegistry sessionRegistry() {
SessionRegistry sessionRegistry = new SessionRegistryImpl();
return sessionRegistry;
}
In case it doesn't work, could you then post the code of your customLogoutHandler() and customLogoutSuccessHandler()? You are using Spring MVC outside of Spring Boot, correct?
If you use UserDetails and UserDetailsService then it should be because your UserDetails implementation class there is no Override hashCode () and equals (Object obj) method. This is my implementation class for UserDetails:
public class MyUser implements UserDetails {
private String username;
private String password;
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
#Override
public String getPassword() {
return null;
}
#Override
public String getUsername() {
return null;
}
#Override
public boolean isAccountNonExpired() {
return false;
}
#Override
public boolean isAccountNonLocked() {
return false;
}
#Override
public boolean isCredentialsNonExpired() {
return false;
}
#Override
public boolean isEnabled() {
return false;
}
#Override
public int hashCode() {
return username.hashCode();
}
#Override
public boolean equals(Object obj) {
return this.toString().equals(obj.toString());
}
}
Form based security is redirection to the login page if a user is not authenticated and tries to access a protected action. Instead of the redirect I want it to return HTTP code 403.
As far as I understand, I have to register some kind of entry point for this. Unfortunately I don't undertand how I can set this up for a java based configuration.
This is my security config:
#Configuration
#EnableWebMvcSecurity
pubfooc class FOOSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
//#formatter:off
protected void configure(HttpSecurity http) throws Exception {
http
.authenticationProvider(aS400AuthenticationProvider())
.formLogin()
.loginProcessingUrl("/authorized")
.passwordParameter("password")
.usernameParameter("cfooentId")
.successHandler(foorAuthenticationSuccessHandler())
.failureHandler(foorAuthenticationFailureHandler())
.and()
.csrf().disable()
.rememberMe()
.rememberMeServices(foorRememberMeServices())
.key(CookieService.FOO_SESSION_COOKIE_NAME)
.and()
.sessionManagement().sessionCreationPofoocy(SessionCreationPofoocy.STATELESS)
;
}
//#formatter:on
#Bean
pubfooc FOORememberMeServices foorRememberMeServices() {
return new FOORememberMeServices();
}
#Bean
pubfooc AS400AuthenticationProvider aS400AuthenticationProvider() {
return new AS400AuthenticationProvider();
}
#Bean
pubfooc CookieService cookieService() {
return new CookieService.Impl();
}
#Bean
pubfooc FOOAuthenticationSuccessHandler foorAuthenticationSuccessHandler() {
return new FOOAuthenticationSuccessHandler();
}
#Bean
pubfooc FOOAuthenticationFailureHandler foorAuthenticationFailureHandler() {
return new FOOAuthenticationFailureHandler();
}
}
You should try http.exceptionHandling().authenticationEntryPoint(new org.springframework.security.web.authentication.Http403ForbiddenEntryPoint()) (assuming you are using Spring Security 2.0 or higher).