Spring security concurrency control with custom UsernamePasswordAuthenticationFilter - spring-security

As per the new requirement i have created custom UsernamePasswordAuthenticationFilter to capture additional parameters from the login page. As expected my config was working fine. I'm able to retrieve additional parameters in filter and saving to session. But after adding my custom filter to config, the session management is not working. Previous i was allowing only one session per user by setting max sessions values to 1. It is not working now, application is allowing same user to login multiple times. I'm sure that it is happening only after integrating custom UsernamePasswordAuthenticationFilter to my config. Below is my spring security config.
http.formLogin()
.loginPage("/login.html")
.loginProcessingUrl("/login.html")
.usernameParameter("username")
.passwordParameter("password")
.and()
.logout()
.logoutSuccessUrl("/login.html")
.logoutRequestMatcher(new AntPathRequestMatcher("/logout.html"))
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID")
.and()
.sessionManagement()
.maximumSessions(1)
.expiredUrl("/multiplesessions.html")
.sessionRegistry(getSessionRegistry());
http.addFilterBefore(customUsernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
#Bean
public SessionRegistry getSessionRegistry() {
return new SessionRegistryImpl();
}
#Autowired
public void configAuthentication(AuthenticationManagerBuilder auth) throws Exception {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(dsnyUserDetailsService);
provider.setPasswordEncoder(passwordEncoder());
auth.authenticationProvider(provider);
}
#Bean
public PasswordEncoder passwordEncoder() {
return new StandardPasswordEncoder();
}
#Bean(name = "myAuthenticationManager")
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
DsnyUsernamePasswordAuthenticationFilter customUsernamePasswordAuthenticationFilter() throws Exception {
DsnyUsernamePasswordAuthenticationFilter customUsernamePasswordAuthenticationFilter = new DsnyUsernamePasswordAuthenticationFilter();
customUsernamePasswordAuthenticationFilter.setAuthenticationManager(authenticationManagerBean());
customUsernamePasswordAuthenticationFilter.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/login.html", "POST"));
return customUsernamePasswordAuthenticationFilter;
}
Am i missing any thing here?

I resolved this problem by adding custom ConcurrentSessionFilter. Here is the code if any one wants.
http.sessionManagement().sessionAuthenticationStrategy(concurrentSession());
http.addFilterBefore(concurrentSessionFilter(), ConcurrentSessionFilter.class);
#Bean
public CompositeSessionAuthenticationStrategy concurrentSession() {
ConcurrentSessionControlAuthenticationStrategy concurrentAuthenticationStrategy = new ConcurrentSessionControlAuthenticationStrategy(getSessionRegistry());
concurrentAuthenticationStrategy.setMaximumSessions(1);
//concurrentAuthenticationStrategy.setExceptionIfMaximumExceeded(true);
List<SessionAuthenticationStrategy> delegateStrategies = new ArrayList<SessionAuthenticationStrategy>();
delegateStrategies.add(concurrentAuthenticationStrategy);
delegateStrategies.add(new SessionFixationProtectionStrategy());
delegateStrategies.add(new RegisterSessionAuthenticationStrategy(getSessionRegistry()));
CompositeSessionAuthenticationStrategy authenticationStrategy = new CompositeSessionAuthenticationStrategy(delegateStrategies);
return authenticationStrategy;
}
#Bean
ConcurrentSessionFilter concurrentSessionFilter() {
CustomSessionInformationExpiredStrategy redirectStrategy = new CustomSessionInformationExpiredStrategy("/pub/multiplesessions.html");
CustomConcurrentSessionFilter concurrentSessionFilter = new CustomConcurrentSessionFilter(getSessionRegistry(), redirectStrategy);
return concurrentSessionFilter;
}
CustomSessionInformationExpiredStrategy.java
public class CustomSessionInformationExpiredStrategy implements SessionInformationExpiredStrategy {
private Logger log = Logger.getLogger(this.getClass().getName());
private String expiredUrl = "";
public CustomSessionInformationExpiredStrategy(String expiredUrl) {
this.expiredUrl = expiredUrl;
}
#Override
public void onExpiredSessionDetected(SessionInformationExpiredEvent sessionInformationExpiredEvent) throws IOException, ServletException {
log.info("Redirecting to session expired page");
HttpServletRequest request = sessionInformationExpiredEvent.getRequest();
HttpServletResponse response = sessionInformationExpiredEvent.getResponse();
request.getSession();// creates a new session
response.sendRedirect(request.getContextPath() + expiredUrl);
}
}
CustomConcurrentSessionFilter.java, no custom code here.
public class CustomConcurrentSessionFilter extends ConcurrentSessionFilter {
public CustomConcurrentSessionFilter(SessionRegistry sessionRegistry) {
super(sessionRegistry);
}
public CustomConcurrentSessionFilter(SessionRegistry sessionRegistry, SessionInformationExpiredStrategy sessionInformationExpiredStrategy) {
super(sessionRegistry, sessionInformationExpiredStrategy);
}
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
super.doFilter(req, res, chain);
}
}

Related

Spring Boot KeyCloak not invoking success handler

I am using Spring Boot KeyCloak in my application to connect with KeyCloak. However I have a custom success handler which is not being invoked. I am not sure why. here is my code:
SecurityConfiguration.java:
#KeycloakConfiguration
public class SecurityConfiguration extends KeycloakWebSecurityConfigurerAdapter {
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
auth.authenticationProvider(keycloakAuthenticationProvider);
}
#Bean
public KeycloakSpringBootConfigResolver keycloakConfigResolver() {
return new KeycloakSpringBootConfigResolver();
}
#Bean
#Primary
#Override
protected KeycloakAuthenticationProcessingFilter keycloakAuthenticationProcessingFilter() throws Exception {
KeycloakAuthenticationProcessingFilter filter = new KeycloakAuthenticationProcessingFilter(authenticationManagerBean());
filter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy());
filter.setAuthenticationSuccessHandler(successHandler());
filter.setAuthenticationFailureHandler(failureHandler());
return filter;
}
#Bean
#Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.csrf().disable().authorizeRequests()
.antMatchers("/**").authenticated();
}
#NotNull
#Bean
public KeyCloakAuthSuccessHandler successHandler() {
return new KeyCloakAuthSuccessHandler(new SavedRequestAwareAuthenticationSuccessHandler());
}
#NotNull
#Bean
public KeyCloakAuthFailureHandler failureHandler() {
return new KeyCloakAuthFailureHandler();
}
}
And in my KeyCloakAuthSuccessHandler.java, I have:
#Slf4j
public class KeyCloakAuthSuccessHandler extends KeycloakAuthenticationSuccessHandler {
#Autowired
ObjectMapper mapper;
public KeyCloakAuthSuccessHandler(AuthenticationSuccessHandler fallback) {
super(fallback);
}
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
log.error("inside success handler");
if (authentication.getPrincipal() instanceof KeycloakPrincipal) {
AccessToken token = ((KeycloakPrincipal<?>) authentication.getPrincipal()).getKeycloakSecurityContext().getToken();
// do other stuff
}
}
}
The above code doesn't invoked the success handler however a similar failure handler is working and getting invoked.
As discussed over the comments, this is because the success handler is not invoked during non-interactive(non-human ways - bearer/basic) login of Keycloak. If you want to invoke success handler each time irrespective of the way of login, write a custom KeycloakAuthencticationProcessingFilter by extending the same and change this line from the original one by over-riding it.
A rough example will look like:
public class CustomKeycloakAuthenticationProcessingFilter extends KeycloakAuthenticationProcessingFilter {
public CustomKeycloakAuthenticationProcessingFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
public CustomKeycloakAuthenticationProcessingFilter(AuthenticationManager authenticationManager, RequestMatcher requiresAuthenticationRequestMatcher) {
super(authenticationManager, requiresAuthenticationRequestMatcher);
}
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
Authentication authResult) throws IOException, ServletException {
// Line of importance down here
if (authResult instanceof KeycloakAuthenticationToken) {
super.successfulAuthentication(request, response, chain, authResult);
return;
}
// whatever spring-boot-keycloak does copy paste here
}
}
I think you have to write your own KeycloakAuthenticationProcessingFilter using your KeyCloakAuthSuccessHandler.
https://github.com/keycloak/keycloak/blob/main/adapters/oidc/spring-security/src/main/java/org/keycloak/adapters/springsecurity/filter/KeycloakAuthenticationProcessingFilter.java
You can't inject your KeyCloakAuthSuccessHandler.

SPRING-SECURITY Credential is not supported if the CORS header ‘Access-Control-Allow-Origin’ is ‘*’

I have my service work and deployed with no problem, but i need to add more feature [Realtime Notification]. I'm using SockJS, StompJS, Spring security, and LDAP authentication.
Here is my AuthenticationFilter, i'm using token as my passcode it generated after login into LDAP.
#Log4j2
public class AuthenticationFilter extends OncePerRequestFilter {
#Autowired
private JWTUtil jwtUtil;
private final String authHeader = "token";
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
//CORS
response.addHeader("Access-Control-Allow-Origin", "*");
if (request.getHeader("Access-Control-Request-Method") != null && "OPTIONS".equalsIgnoreCase(request.getMethod())) {
response.addHeader("Access-Control-Allow-Headers", "token");
response.addHeader("Access-Control-Allow-Headers", "Content-Type");
response.addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
}
final String authHeader = request.getHeader(this.authHeader);
if (authHeader != null) {
String token = authHeader;
try {
Claims claims = jwtUtil.getAllClaimsFromToken(token);
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
new User(claims.getSubject()),
null,
authorities
);
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
} catch (Exception e) {
log.debug("Error ", e);
}
}
if (!request.getMethod().equalsIgnoreCase("OPTIONS")) {
chain.doFilter(request, response);
}
}
}
Here is my WebSecurityConfig
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
public UnauthorizedHandler unauthorizedHandler() throws Exception {
return new UnauthorizedHandler();
}
#Bean
public ForbiddenHandler forbiddenHandler() throws Exception {
return new ForbiddenHandler();
}
#Bean
public AuthenticationFilter authenticationFilterBean() throws Exception {
return new AuthenticationFilter();
}
#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()
.exceptionHandling().accessDeniedHandler(forbiddenHandler()).and()
// don't create session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
// allow auth url
.antMatchers("/login","/v2/api-docs", "/configuration/ui", "/swagger-resources/**", "/configuration/**", "/swagger-ui.html", "/webjars/**", "/notif/**", "/mealnotif/**", "/topic/**", "/websocket/**", "/resources/**", "/META-INF/resources/**").permitAll()
.anyRequest().authenticated();
// custom JWT based security filter
httpSecurity.addFilterBefore(authenticationFilterBean(), UsernamePasswordAuthenticationFilter.class);
// disable page caching
httpSecurity.headers().cacheControl();
}
}
And the last one is my WebSocketConfig
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/mealnotif");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/notif").setAllowedOrigins("*")
.withSockJS();
}
}
But in return My Angular project always return Cross-Origin Request Blocked
CORS Reason ERROR
How do i solve this?
i can solve this by adding whitelist of allowed origin in my application.properties
management.endpoints.web.cors.allowed-origins=http://localhost,http://localhost:4200
by this properties i managed to giving properly response to client
response

Spring Security Access Denied Handler is called when the HttpServletResponse is already committed

I have an HTTP API, protected with Spring Security and JWT.
I get a 401 when I'm trying to access a protected resource.
I get the resource if I'm authenticated (JWT is valid) and I have the correct role. The resource is protected with #PreAuthorize("hasRole('USER')").
The issue I have is that when I don't have the correct role I'd like to return a 403 (in the following code it is a 401 for the sake of testing).
But right know I get a 500 because of the AccessDeniedException which is thrown when the role is incorrect.
The weird thing is that it goes to my JwtAccessDeniedHandler custom code but the response is already committed (isCommitted() == true) so whenever I try to set the status etc it does nothing.
Do you have any ideas about what could be misconfigured or missing?
Config:
#Slf4j
#EnableWebSecurity
#EnableGlobalMethodSecurity(
prePostEnabled = true,
securedEnabled = true,
jsr250Enabled = true
)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private ObjectMapper objectMapper;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterBefore(
jwtAuthenticationFilter(joseHelper(jsonWebKey())),
UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.antMatchers("/auth/**").permitAll()
.anyRequest().authenticated()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf().disable()
.exceptionHandling()
.authenticationEntryPoint(new JwtAuthenticationEntryPoint())
.accessDeniedHandler(new JwtAccessDeniedHandler());
}
#Bean
public JwtAuthenticationFilter jwtAuthenticationFilter(JoseHelper joseHelper) {
return new JwtAuthenticationFilter(joseHelper);
}
#Bean
public JoseHelper joseHelper(PublicJsonWebKey key) {
return new JoseHelper(key);
}
#Bean
public PublicJsonWebKey jsonWebKey() throws IOException, JoseException {
return RsaJwkGenerator.generateJwk(2048);
}
private void sendUnauthorized(HttpServletResponse httpServletResponse) throws IOException {
httpServletResponse.setContentType("application/json");
httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
ApiError apiError = ApiError.builder()
.code(HttpStatus.UNAUTHORIZED.name())
.message(HttpStatus.UNAUTHORIZED.getReasonPhrase())
.httpStatus(HttpStatus.UNAUTHORIZED)
.build();
httpServletResponse.getWriter().print(objectMapper.writeValueAsString(apiError));
}
private class JwtAccessDeniedHandler implements AccessDeniedHandler {
#Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
log.info("accessDeniedHandler", e);
sendUnauthorized(httpServletResponse);
}
}
private class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
AuthenticationException e) throws IOException, ServletException {
sendUnauthorized(httpServletResponse);
}
}
}
Filter:
#Slf4j
#Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private static final String BEARER = "Bearer ";
private JoseHelper joseHelper;
#Autowired
public JwtAuthenticationFilter(JoseHelper joseHelper) {
this.joseHelper = joseHelper;
}
#Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
String header = httpServletRequest.getHeader("Authorization");
if (header == null || !header.startsWith(BEARER)) {
log.error("JWT token is not valid");
filterChain.doFilter(httpServletRequest, httpServletResponse);
return;
}
final String encryptedToken = header.substring(BEARER.length());
try {
final String decryptedJwt = joseHelper.decryptJwt(encryptedToken);
final String verifiedJwt = joseHelper.verifyJwt(decryptedJwt);
final JwtClaims jwtClaims = joseHelper.parse(verifiedJwt);
List<SimpleGrantedAuthority> authorities = jwtClaims.getStringListClaimValue("userRoles")
.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());
JwtAuthenticationToken jwtAuthenticationToken = new JwtAuthenticationToken(jwtClaims, null, authorities);
SecurityContextHolder.getContext().setAuthentication(jwtAuthenticationToken);
filterChain.doFilter(httpServletRequest, httpServletResponse);
} catch (JoseException | InvalidJwtException | MalformedClaimException e) {
log.error("JWT token is not valid", e);
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
}
}
The issue was because I use Jersey apparently. I didn't really had time to investigate the why right now.
Once I registered an exception mapper in my JerseyConfig I was able to capture and handle the AccessDeniedException correctly.
And from that point the access denied handler is not called anymore and becomes useless.
A bit weird, but there is probably a good reason.

Unable to redirect to CAS for single log out from spring

I am developing an application which has an angular2 frontend and a spring boot backend. For the authentication I am using CAS. Single sign on is working properly, but the single log out is not working and not even redirecting to cas/logout endpoint.(I am sending a POST to my spring boot app's /logout url from angular app )
CAS version - 4.2.2
CAS client core - 3.4
I followed http://docs.spring.io/spring-security/site/migrate/current/3-to-4/html5/migrate-3-to-4-jc.html#m3to4-filter-urls-cas and did necessary changes to Authentication Filter and Logout filter. But still I couldn't identify the issue. Any help is much appreciated.
Security Config
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true, jsr250Enabled = true)
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private AuthProperties properties;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterAfter(csrfHeaderFilter(), CsrfFilter.class)
.addFilterBefore(requestSingleLogoutFilter(), LogoutFilter.class)
.addFilterBefore(singleSignOutFilter(), CasAuthenticationFilter.class)
.addFilterBefore(casAuthenticationFilter(), BasicAuthenticationFilter.class)
.exceptionHandling()
.authenticationEntryPoint(casAuthenticationEntryPoint())
.and()
.logout()
.logoutUrl("/logout")
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID")
.permitAll()
.and()
.authorizeRequests()
.antMatchers("//**").authenticated()
.antMatchers("/test").permitAll()
.and()
.csrf()
.csrfTokenRepository(csrfTokenRepository());
}
/***
* Service Properties refer to the application which is being authenticated
* Typically in this case, the service is the authentication engine or auth app
*/
#Bean
public ServiceProperties serviceProperties() {
ServiceProperties sp = new ServiceProperties();
sp.setService(properties.getAppServiceSecurity());
sp.setArtifactParameter("casTicket");
sp.setAuthenticateAllArtifacts(true);
sp.setSendRenew(false);
return sp;
}
#Bean
public CasAuthenticationProvider casAuthenticationProvider() throws Exception {
CasAuthenticationProvider casAuthenticationProvider = new CasAuthenticationProvider();
casAuthenticationProvider.setAuthenticationUserDetailsService(userService());
casAuthenticationProvider.setServiceProperties(serviceProperties());
if (properties.isCasProxyTicket()) {
casAuthenticationProvider.setTicketValidator(cas30ServiceProxyTicketValidator());
casAuthenticationProvider.setStatelessTicketCache(ehManager());
} else {
casAuthenticationProvider.setTicketValidator(cas30ServiceTicketValidator());
}
casAuthenticationProvider.setKey(properties.getProviderKey());
return casAuthenticationProvider;
}
#Bean
public SessionAuthenticationStrategy sessionStrategy() {
SessionAuthenticationStrategy sessionStrategy = new SessionFixationProtectionStrategy();
return sessionStrategy;
}
#Bean
public Cas30ServiceTicketValidator cas30ServiceTicketValidator() {
return new Cas30ServiceTicketValidator(properties.getCasUrlPrefix());
}
#Bean
public CasAuthenticationFilter casAuthenticationFilter() throws Exception {
CasAuthenticationFilter casAuthenticationFilter = new CasAuthenticationFilter();
casAuthenticationFilter.setAuthenticationManager(authenticationManager());
casAuthenticationFilter.setSessionAuthenticationStrategy(sessionStrategy());
casAuthenticationFilter.setProxyGrantingTicketStorage(pgtStorage());
casAuthenticationFilter.setFilterProcessesUrl("/login/cas");
casAuthenticationFilter.setProxyReceptorUrl(properties.getCasProxyReceptor());
return casAuthenticationFilter;
}
#Bean
public CasAuthenticationEntryPoint casAuthenticationEntryPoint() {
CasAuthenticationEntryPoint casAuthenticationEntryPoint = new TBXCasAuthenticationEntryPoint();
casAuthenticationEntryPoint.setLoginUrl(properties.getCasLoginUrl());
casAuthenticationEntryPoint.setServiceProperties(serviceProperties());
return casAuthenticationEntryPoint;
}
#Bean
public LogoutFilter requestSingleLogoutFilter() {
StringBuffer stringBuffer = new StringBuffer("");
stringBuffer.append(properties.getCasLogoutUrl());
LogoutFilter logoutFilter = new LogoutFilter("https://localhost:9443/cas/logout", new SecurityContextLogoutHandler());//env.getRequiredProperty(CAS_URL_LOGOUT) + "?service="+ env.getRequiredProperty(APP_SERVICE_HOME)
logoutFilter.setFilterProcessesUrl("/logout");
return logoutFilter;
}
#Bean
public SingleSignOutFilter singleSignOutFilter() {
SingleSignOutFilter filter = new SingleSignOutFilter();
filter.setArtifactParameterName(Protocol.CAS3.getArtifactParameterName());
filter.setCasServerUrlPrefix("https://localhost:9443/cas");
filter.setIgnoreInitConfiguration(true);
return filter;
}
#Bean
public Filter csrfHeaderFilter() {
return new OncePerRequestFilter() {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
if (csrf != null) {
Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN");
if (cookie != null) {
} else {
}
String token = csrf.getToken();
if (cookie == null || token != null && !token.equals(cookie.getValue())) {
cookie = new Cookie("XSRF-TOKEN", token);
cookie.setPath("/");
response.addCookie(cookie);
}
}
filterChain.doFilter(request, response);
}
};
}
#Bean
public CsrfTokenRepository csrfTokenRepository() {
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
repository.setHeaderName("X-XSRF-TOKEN");
return repository;
}
/**
* configure via WebSecurity provides the fonts, images, scripts, styles and views to be removed
* from the security features, because, access to these scripts is a must regarding the user experience
**/
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers("/fonts*//**")
.antMatchers("/images1*//**")
.antMatchers("/scripts*//**")
.antMatchers("/styles*//**")
.antMatchers("/views*//**")
.antMatchers("/i18n*//**");
}
#Bean
public UserPrincipleHandler userService() {
UserPrincipleHandler userPrincipleServiceHanlder = new UserPrincipleHandler();
return userPrincipleServiceHanlder;
}
#Bean
public RequestContextListener requestContextListener() {
return new RequestContextListener();
}
#Bean
public RequestContextFilter requestContextFilter() {
return new RequestContextFilter();
}
#Bean
public ServiceAuthenticationDetailsSource serviceAuthenticationDataSource() {
ServiceAuthenticationDetailsSource serviceDetailSource = new ServiceAuthenticationDetailsSource(serviceProperties());
return serviceDetailSource;
}
#Bean
public SimpleUrlAuthenticationFailureHandler simpleUrlAuthentication() {
SimpleUrlAuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler();
failureHandler.setDefaultFailureUrl(properties.getCasFailureUrl());
return failureHandler;
}
#Bean
public ProxyList proxyChainList() {
List<String> list = properties.getProxyList();
String[] stringArray = Arrays.copyOf(list.toArray(), list.size(), String[].class);
List<String[]> urls = new ArrayList<String[]>();
urls.add(stringArray);
ProxyList proxyList = new ProxyList(urls);
return proxyList;
}
#Bean
public ProxyGrantingTicketStorageImpl pgtStorage() {
ProxyGrantingTicketStorageImpl pgtImpl = new ProxyGrantingTicketStorageImpl();
return pgtImpl;
}
#Bean
public SpringCacheBasedTicketCache ehManager() throws Exception {
SpringCacheBasedTicketCache ehmanager = new SpringCacheBasedTicketCache(cacheMap());
return ehmanager;
}
#Bean
public ConcurrentMapCache cacheMap() {
ConcurrentMapCache conCacheMap = new ConcurrentMapCache("casTickets");
conCacheMap.put("casTickets", 50);
return conCacheMap;
}
#Bean
public Cas30ProxyTicketValidator cas30ServiceProxyTicketValidator() {
Cas30ProxyTicketValidator validator = new Cas30ProxyTicketValidator(properties.getCasUrlPrefix());//env.getRequiredProperty(CAS_URL_PREFIX)
StringBuffer stringBuffer = new StringBuffer("");
stringBuffer.append(properties.getAppServiceHome()).append(properties.getCasProxyReceptor());
validator.setProxyCallbackUrl(stringBuffer.toString());//env.getRequiredProperty(APP_SERVICE_HOME)+"login/cas/proxyreceptor"
validator.setProxyGrantingTicketStorage(pgtStorage());
validator.setAllowedProxyChains(proxyChainList());
validator.setAcceptAnyProxy(false);
return validator;
}
}

How to set a custom invalid session strategy in Spring Security

I'm developing a web application, based on Spring-Boot - 1.1.6, Spring -Security -3.2.5 and more.
I'm using Java based configuration:
#Configuration
#EnableWebMvcSecurity
public class SecurityCtxConfig extends WebSecurityConfigurerAdapter {
#Bean
DelegatingAuthenticationEntryPoint delegatingAuthenticationEntryPoint() {
LinkedHashMap<RequestMatcher, AuthenticationEntryPoint> map = new LinkedHashMap<RequestMatcher, AuthenticationEntryPoint>();
Http403ForbiddenEntryPoint defaultEntryPoint = new Http403ForbiddenEntryPoint();
map.put(AnyRequestMatcher.INSTANCE, defaultEntryPoint);
DelegatingAuthenticationEntryPoint retVal = new DelegatingAuthenticationEntryPoint(map);
retVal.setDefaultEntryPoint(defaultEntryPoint);
return retVal;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
ExceptionHandlingConfigurer<HttpSecurity> exceptionHandling = http.exceptionHandling();
exceptionHandling.authenticationEntryPoint(delegatingAuthenticationEntryPoint());
http.logout().logoutSuccessHandler(new LogoutSuccessHandler() {
#Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication arg2)
throws IOException, ServletException {
response.setStatus(HttpServletResponse.SC_OK);
}
});
}
}
The requirement is to return Http status 401 in case that the session cookie is invalid or missing(no matter the reason)
I see the InvalidSessionStrategy but I don't find a way to set it on the SessionManagementFilter.
Can some one please instract me how to implement my plan or another one that will fulfill the requirement
Using SpringBoot this works for me:
#Configuration
#EnableWebSecurity
public class UISecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
...
http.addFilterAfter(expiredSessionFilter(), SessionManagementFilter.class);
...
}
private Filter expiredSessionFilter() {
SessionManagementFilter smf = new SessionManagementFilter(new HttpSessionSecurityContextRepository());
smf.setInvalidSessionStrategy((request, response) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Session go BOOM!"));
return smf;
}
}
We had the exact same problem and I did this hack to solve it (yes I know, this is a hack, therefore the name...).
I create a BeanPostProcessor and search for the SessionManagementFilter to reconfigure it...
#Bean
public HackyBeanPostProcessor myBeanPostProcessor() {
return new HackyBeanPostProcessor();
}
protected static class HackyBeanPostProcessor implements BeanPostProcessor {
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
// FIXME check if a new spring-security version allows this in an
// other way (current: 3.2.5.RELEASE)
if (bean instanceof SessionManagementFilter) {
SessionManagementFilter filter = (SessionManagementFilter) bean;
filter.setInvalidSessionStrategy(new InvalidSessionStrategy() {
#Override
public void onInvalidSessionDetected(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
}
});
}
return bean;
}
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
return bean;
}
}
Since I'm using AspectJ (I mean, compile time weaving and not Spring AOP), it was quite easy to hack the SessionManagementFilter creation by setting my custom InvalidSessionStrategy after the SessionManagementFilter is constructed:
#Aspect
public class SessionManagementAspect {
private static final Log logger = LogFactory.getLog();
#AfterReturning("execution( org.springframework.security.web.session.SessionManagementFilter.new(..))&&this(smf)")
public void creation(JoinPoint pjp, SessionManagementFilter smf) throws Throwable {
logger.debug("Adding/Replacing the invalid session detection policy to return 401 in case of an invalid session");
smf.setInvalidSessionStrategy(new InvalidSessionStrategy() {
#Override
public void onInvalidSessionDetected(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
logInvalidSession(request, "invalid cookie");
if (!response.isCommitted())
response.sendError(HttpStatus.UNAUTHORIZED.value());
}
});
}
}
If you are not using AspectJ, try adding #Component and add this Aspect to your context, it might work if the SessionManagementFilter is a bean (Since Spring-AOP applias only on spring beans)

Resources