Migrating from WebSecurityConfigurerAdapter to SecurityFilterChain - spring

Here is my working security conf before migration :
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers("/auth/**")
.antMatchers("/swagger-ui/**")
.antMatchers("/swagger-ui.html")
.antMatchers("/swagger-resources/**")
.antMatchers("/v2/api-docs/**")
.antMatchers("/v3/api-docs/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedPortalRoleConverter);
http
.csrf().disable()
.cors()
.and()
.exceptionHandling()
.authenticationEntryPoint(new AuthenticationFallbackEntryPoint())
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests(authorize -> authorize.anyRequest().authenticated())
.oauth2ResourceServer()
.jwt().jwtAuthenticationConverter(jwtAuthenticationConverter);
}
And here is my Security chain config after migration :
#Bean
#Order(1)
public SecurityFilterChain ignorePathsSecurityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.antMatchers(
"/auth/**",
"/swagger-ui/**",
"/swagger-ui.html",
"/swagger-resources/**",
"/v3/api-docs/**")
.permitAll());
return http.build();
}
#Bean
#Order(2)
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http, GrantedPortalRoleConverter grantedPortalRoleConverter) throws Exception {
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedPortalRoleConverter);
http
.csrf().disable()
.cors(Customizer.withDefaults())
.exceptionHandling(configurer -> configurer.authenticationEntryPoint(new AuthenticationFallbackEntryPoint()))
.sessionManagement(configurer -> configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(authorize -> authorize.anyRequest().authenticated())
.oauth2ResourceServer(configurer -> configurer.jwt().jwtAuthenticationConverter(jwtAuthenticationConverter));
return http.build();
}
With the original conf, when I call a random non existing path :
#Test
void should_not_authenticate_or_return_not_found() throws Exception {
logger.info("should_not_authenticate_or_return_not_found");
mvc.perform(get("/toto/tata"))
.andExpect(status().isUnauthorized());
}
I get :
15:44:00.230 [main] DEBUG o.s.s.w.a.i.FilterSecurityInterceptor - Failed to authorize filter invocation [GET /toto/tata] with attributes [authenticated]
With the new conf, I'm just getting HTTP 404, what am I missing here please ? I can't see any difference and debug logs don't show much.
Here is the first line of log missing using the non working conf :
16:24:58.651 [main] DEBUG o.s.s.w.a.e.ExpressionBasedFilterInvocationSecurityMetadataSource - Adding web access control expression [authenticated] for any request
But in both logs, I can see (2 lines of this for the new conf since there are 2 security chains) :
o.s.s.web.DefaultSecurityFilterChain - Will secure any request with (...)

Explanation
When you have multiple SecurityFilterChains, you have to specify a request matcher, otherwise all requests will be processed by the first SecurityFilterChain, annotated with #Order(1), and never reach the second SecurityFilterChain, annotated with #Order(2).
In the code you shared above, this means configuring .requestMatchers() in ignorePathsSecurityFilterChain:
#Bean
#Order(1)
public SecurityFilterChain ignorePathsSecurityFilterChain(HttpSecurity http) throws Exception {
http
.requestMatchers(requests -> requests // add this block
.antMatchers(
"/auth/**",
"/swagger-ui/**",
"/swagger-ui.html",
"/swagger-resources/**",
"/v3/api-docs/**")
)
.authorizeHttpRequests(authorize -> authorize
.antMatchers(
"/auth/**",
"/swagger-ui/**",
"/swagger-ui.html",
"/swagger-resources/**",
"/v3/api-docs/**")
.permitAll());
return http.build();
}
This means that only the requests matching /auth/**, /swagger-ui/** etc will be processed by ignorePathsSecurityFilterChain, while the rest of the requests will move on to defaultSecurityFilterChain.
To understand the difference between requestMatchers and authorizeHttpRequests you can check out this StackOverflow question.
Solution
An even better option is to combine the SecurityFilterChains into a single one. I don't see any reason why you would separate them in this case.
The resulting configuration would be:
#Bean
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http, GrantedPortalRoleConverter grantedPortalRoleConverter) throws Exception {
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedPortalRoleConverter);
http
.authorizeHttpRequests(authorize -> authorize
.antMatchers(
"/auth/**",
"/swagger-ui/**",
"/swagger-ui.html",
"/swagger-resources/**",
"/v3/api-docs/**")
.permitAll()
.anyRequest().authenticated()
)
.csrf().disable()
.cors(Customizer.withDefaults())
.exceptionHandling(configurer -> configurer.authenticationEntryPoint(new AuthenticationFallbackEntryPoint()))
.sessionManagement(configurer -> configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.oauth2ResourceServer(configurer -> configurer.jwt().jwtAuthenticationConverter(jwtAuthenticationConverter));
return http.build();
}
Alternative
Alternatively you can use a WebSecurityCustomizer to ignore certain endpoints:
#Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return (web) -> web.ignoring().antMatchers(
"/auth/**",
"/swagger-ui/**",
"/swagger-ui.html",
"/swagger-resources/**",
"/v3/api-docs/**");
}
Then you would use defaultSecurityFilterChain as your only SecurityFilterChain.

Related

Spring Security in Spring Boot 3

I'm currently in the process of migrating our REST application from Spring Boot 2.7.5 to 3.0.0-RC2. I want everything to be secure apart from the Open API URL. In Spring Boot 2.7.5, we used to do this:
#Named
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/openapi/openapi.yml").permitAll()
.anyRequest().authenticated()
.and()
.httpBasic();
}
}
and it worked fine. In Spring Boot 3, I had to change it to
#Configuration
#EnableWebSecurity
public class WebSecurityConfig {
#Bean
public SecurityFilterChain configure(HttpSecurity http) throws Exception {
http.authorizeHttpRequests((requests) -> requests
.requestMatchers("/openapi/openapi.yml").permitAll()
.anyRequest()
.authenticated())
.httpBasic();
return http.build();
}
}
since WebSecurityConfigurerAdapter has been removed. It's not working though. The Open API URL is also secured via basic authentication. Have I made a mistake when upgrading the code or is that possibly an issue in Spring Boot 3 RC 2?
Update
Since most of the new API was already available in 2.7.5, I've updated our code in our 2.7.5 code base to the following:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig {
#Bean
public SecurityFilterChain configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeHttpRequests((requests) -> requests
.antMatchers(OPTIONS).permitAll() // allow CORS option calls for Swagger UI
.antMatchers("/openapi/openapi.yml").permitAll()
.anyRequest().authenticated())
.httpBasic();
return http.build();
}
}
In our branch for 3.0.0-RC2, the code is now as follows:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig {
#Bean
public SecurityFilterChain configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeHttpRequests((requests) -> requests
.requestMatchers(OPTIONS).permitAll() // allow CORS option calls for Swagger UI
.requestMatchers("/openapi/openapi.yml").permitAll()
.anyRequest().authenticated())
.httpBasic();
return http.build();
}
}
As you can see, the only difference is that I call requestMatchers instead of antMatchers. This method seems to have been renamed. The method antMatchers is no longer available. The end effect is still the same though. On our branch for 3.0.0-RC2, Spring Boot asks for basic authentication for the OpenAPI URL. Still works fine on 2.7.5.
Author: https://github.com/wilkinsona
#Bean
public SecurityFilterChain configure(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((requests) -> requests
.requestMatchers(new AntPathRequestMatcher("/openapi/openapi.yml")).permitAll()
.anyRequest().authenticated())
.httpBasic();
return http.build();
}
Source: https://github.com/spring-projects/spring-boot/issues/33357#issuecomment-1327301183
I recommend you use Spring Boot 3.0.0 (GA) right now, not RC version.
Inside my WebSecurityConfig, I did this:
private static final String[] AUTH_WHITELIST = {
// -- Swagger UI v2
"/v2/api-docs",
"v2/api-docs",
"/swagger-resources",
"swagger-resources",
"/swagger-resources/**",
"swagger-resources/**",
"/configuration/ui",
"configuration/ui",
"/configuration/security",
"configuration/security",
"/swagger-ui.html",
"swagger-ui.html",
"webjars/**",
// -- Swagger UI v3
"/v3/api-docs/**",
"v3/api-docs/**",
"/swagger-ui/**",
"swagger-ui/**",
// CSA Controllers
"/csa/api/token",
// Actuators
"/actuator/**",
"/health/**"
};
#Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests( auth -> auth
.requestMatchers(AUTH_WHITELIST).permitAll()
.anyRequest().authenticated()
)
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.httpBasic(withDefaults())
.addFilterBefore(authenticationJwtTokenFilter, UsernamePasswordAuthenticationFilter.class)
//.addFilterAfter(authenticationJwtTokenFilter, UsernamePasswordAuthenticationFilter.class)
.build();
}
#Bean
public SecurityFilterChain configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.authorizeHttpRequests((requests) -> requests
.requestMatchers( new AntPathRequestMatcher("swagger-ui/**")).permitAll()
.requestMatchers( new AntPathRequestMatcher("/swagger-ui/**")).permitAll()
.requestMatchers( new AntPathRequestMatcher("v3/api-docs/**")).permitAll()
.requestMatchers( new AntPathRequestMatcher("/v3/api-docs/**")).permitAll()
.anyRequest().authenticated())
.httpBasic();
return httpSecurity.build();
}
This and using Dockerfile (doing mvn clean package and running .jar from Docker) made me had no issues with authentication inside swagger ui.
Hope this can help you :)
Use
http.securityMatcher("<patterns>")...
to specify authentication for endpoints.
authorizeHttpRequests((requests) -> requests
.requestMatchers("<pattern>")
only works for authorization, if you don't set securityMatcher , SecurityFilterChain by default gets any request for authentication. And any request will be authenticated by an authentication provider.
In your case, you can define two security filter, chains: one for public endpoitns, another for secured. And give them proper order:
#Bean
#Order(1)
public SecurityFilterChain configure(HttpSecurity http) throws Exception {
http.securityMatcher(OPTIONS,"/openapi/openapi.yml").csrf().disable()
.authorizeHttpRequests((requests) -> requests
.anyRequest().permitAll() // allow CORS option calls for Swagger UI
);
return http.build();
}
#Bean
Order(2)
public SecurityFilterChain configure(HttpSecurity http) throws Exception {
http.securityMatcher("/**")
.csrf().disable()
.authorizeHttpRequests((requests) -> requests.anyRequest().authenticated())
.httpBasic();
return http.build();
}
The official documentation suggests an example which I have abridged here with your config:
http
.authorizeExchange((exchanges) ->
exchanges
.pathMatchers("/openapi/openapi.yml").permitAll()
.anyExchange().authenticated())
.httpBasic();
return http.build();
You could try this, since it changes the "request" for the "exchange" wording, in line with the migration to declarative clients (#PostExchange vs. #PostMapping) I suppose. Hope it helps.
My security cfg looks like:
Spring 3.0.0
#Bean
public SecurityFilterChain configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeHttpRequests(requests -> requests
.requestMatchers(HttpMethod.GET, "/", "/static/**", "/index.html", "/api/users/me").permitAll()
.requestMatchers(HttpMethod.POST, "/api/users").permitAll()
.requestMatchers(HttpMethod.GET, "/api/users/login", "/api/users/{username}", "/api/users/logout", "/api/costumers", "/api/storages").authenticated()
.requestMatchers(HttpMethod.POST, "/api/costumers", "/api/storages").authenticated()
.requestMatchers(HttpMethod.PUT, "/api/costumers/{id}", "/api/storages/{id}").authenticated()
.requestMatchers(HttpMethod.DELETE, "/api/users/{id}", "/api/storages/{id}", "/api/costumers/{id}").authenticated()
.anyRequest().denyAll())
.httpBasic();
return http.build();
}
it works
This seems to be a bug in Spring Boot 3. I've raised an issue.

Sleuth in Webflux Spring Security

I have a webflux app and need to have sleuth context in my authentication logs (move from DefaultWebFilterChain to SecurityWebFilterChain).
I have tried to add manually in my security chain:
public GwApiSecurityConfig(Tracer tracer, HttpServerHandler httpServerHandler, CurrentTraceContext currentTraceContext){
this.tracer = tracer;
this.httpServerHandler = httpServerHandler;
this.currentTraceContext = currentTraceContext;
}
#Bean
public SecurityWebFilterChain securityFilterChain(ServerHttpSecurity http) {
return http
.csrf().disable()
.authorizeExchange()
.pathMatchers(HttpMethod.GET,"/swagger", "/v3/**", "/webjars/**", "/actuator/**").permitAll()
.anyExchange().authenticated()
.and()
.httpBasic()
.authenticationEntryPoint((exchange, exception) -> Mono.error(new GwException(HttpStatus.UNAUTHORIZED, GwError.AUTHENTICATION)))
.and()
.formLogin().disable()
.redirectToHttps()
.and()
.addFilterBefore(new TraceWebFilter(tracer, httpServerHandler, currentTraceContext), SecurityWebFiltersOrder.HTTP_BASIC)
.build();
}
But I got an error:
09:11:06.117 ERROR[reactor-http-nio-2] [,] GwApiErrorHandler - null
java.lang.NullPointerException: null
at org.springframework.cloud.sleuth.instrument.web.TraceWebFilter.spanFromContextRetriever(TraceWebFilter.java:139)
Also I checked this property:
spring.sleuth.web.filter-order=1
but I think that only affect the DefaultWebFilterChain order, no SecurityWebFilterChain.
And how to remove the filter from DefaultWebFilterChain to avoid filtering twice?
Any ideas?
Thanks!

Spring Security - fallback to basic authentication if Kerberos fails

How can I enable fallback to basic auth if Kerberos authentication fails (e.g. client is not on the domain)? With the configuration below no browser authentication window appears and the following exceptions are thrown:
org.springframework.security.authentication.BadCredentialsException: Kerberos validation not successful
org.ietf.jgss.GSSException: Defective token detected (Mechanism level: GSSHeader did not find the right tag)
Relevant part of my WebSecurityConfigurerAdapter implementation:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.exceptionHandling()
.authenticationEntryPoint(spnegoEntryPoint())
.and()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.logout()
.permitAll()
.and()
.addFilterBefore(
spnegoAuthenticationProcessingFilter(),
BasicAuthenticationFilter.class);
}
#Bean
public SpnegoEntryPoint spnegoEntryPoint() {
return new SpnegoEntryPoint("/");
}
#Bean
public SpnegoAuthenticationProcessingFilter spnegoAuthenticationProcessingFilter() {
SpnegoAuthenticationProcessingFilter filter = new SpnegoAuthenticationProcessingFilter();
try {
filter.setAuthenticationManager(authenticationManagerBean());
} catch (Exception e) {
log.error("Failed to set AuthenticationManager on SpnegoAuthenticationProcessingFilter.", e);
}
return filter;
}

Spring Security custom authentication failure handler redirect with parameter

I have a problem with Spring Security authentication failure handler redirect with parameter.
In security config when I use
failureUrl("/login.html?error=true")
it works. But when I use custom authentication failure handler (as shown below), it always returns: url/login.html
getRedirectStrategy().sendRedirect(request, response, "/login.html?error=true");
or
response.sendRedirect(request.getContextPath() + "/login.html?error=true");
I don't know whats wrong. Why does it not show the parameter ?error=true?
Info: I am using Spring + JSF + Hibernate + Spring Security
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login.html")
.usernameParameter("j_username")
.passwordParameter("j_password")
.loginProcessingUrl("/j_spring_security_check")
.failureHandler(customAuthenticationFailureHandler)// .failureUrl("/login.html?error=true")//.successHandler(authSuccsessHandler)
.defaultSuccessUrl("/dashboard.html")
.permitAll()
.and()
.logout()
.invalidateHttpSession(true)
.logoutSuccessUrl("/")
.permitAll()
.and()
.exceptionHandling()
.accessDeniedPage("/access.html")
.and()
.headers()
.defaultsDisabled()
.frameOptions()
.sameOrigin()
.cacheControl();
http
.csrf().disable();
}
This is custom authentication failure handler:
#Component
public class CustomAuthFailureHandler extends SimpleUrlAuthenticationFailureHandler {
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
getRedirectStrategy().sendRedirect(request, response, "/login.html?error=true");
}
}
I will change parameter for some cases.
You didn't allow anonymous access to URL /login.html?error=true, so you are redirected to the login page (/login.html).
AbstractAuthenticationFilterConfigurer#permitAll allows access (for anyone) to failure URL but not for custom failure handler:
Ensures the urls for failureUrl(String) as well as for the HttpSecurityBuilder, the getLoginPage() and getLoginProcessingUrl() are granted access to any user.
You have to allow access explicitly with AbstractRequestMatcherRegistry#antMatchers:
Maps a List of AntPathRequestMatcher instances that do not care which HttpMethod is used.
and ExpressionUrlAuthorizationConfigurer.AuthorizedUrl#permitAll:
Specify that URLs are allowed by anyone.
You don't have to allow the exact URL /login.html?error=true, because AntPathRequestMatcher ignores the query string:
Matcher which compares a pre-defined ant-style pattern against the URL ( servletPath + pathInfo) of an HttpServletRequest. The query string of the URL is ignored and matching is case-insensitive or case-sensitive depending on the arguments passed into the constructor.
Your modified configuration:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/login.html").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login.html")
.usernameParameter("j_username")
.passwordParameter("j_password")
.loginProcessingUrl("/j_spring_security_check")
.failureHandler(customAuthenticationFailureHandler)// .failureUrl("/login.html?error=true")//.successHandler(authSuccsessHandler)
.defaultSuccessUrl("/dashboard.html")
.permitAll()
.and()
.logout()
.invalidateHttpSession(true)
.logoutSuccessUrl("/")
.permitAll()
.and()
.exceptionHandling()
.accessDeniedPage("/access.html")
.and()
.headers()
.defaultsDisabled()
.frameOptions()
.sameOrigin()
.cacheControl();
http
.csrf().disable();
}
In the case of OAuth token failure, I am getting below response, which is inconsistent with app response style.
{
"error": "invalid_token",
"error_description": "Invalid access token: 4cbc6f1c-4d47-44bd-89bc-92a8c86d88dbsdfsdfs"
}
I just wanted to use common response object for the consistency.
Following approach worked for me.
Build your resource server with your custom entry-point object
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.authenticationEntryPoint(new CustomOAuth2AuthenticationEntryPoint());
}
and here is your custom entry point
public class CustomOAuth2AuthenticationEntryPoint extends OAuth2AuthenticationEntryPoint{
public CustomOAuth2AuthenticationEntryPoint() {
super.setExceptionTranslator(new CustomOAuth2WebResponseExceptionTranslator());
}
}
here is your custom WebResponseExceptionTranslator, In my case I have just used a replica of DefaultWebResponseExceptionTranslator and rewritten handleOAuth2Exception method.
CustomOAuth2WebResponseExceptionTranslator implements WebResponseExceptionTranslator<Response> {
....
.....
private ResponseEntity<Response> handleOAuth2Exception(OAuth2Exception e) throws IOException {
int status = e.getHttpErrorCode();
HttpHeaders headers = new HttpHeaders();
headers.set("Cache-Control", "no-store");
headers.set("Pragma", "no-cache");
if (status == HttpStatus.UNAUTHORIZED.value() || (e instanceof InsufficientScopeException)) {
headers.set("WWW-Authenticate", String.format("%s %s", OAuth2AccessToken.BEARER_TYPE, e.getSummary()));
}
ResponseEntity<Response> response =new ResponseEntity<>(new Response().message(e.getMessage()).status(StatusEnum.ERROR)
.errorType(e.getClass().getName()), HttpStatus.UNAUTHORIZED);
return response;
}
Result looks like
{
"status": "error",
"message": "Invalid access token: 4cbc6f1c-4d47-44bd-89bc-92a8c86d88dbsdfsdfs",
"error_type": "org.springframework.security.oauth2.common.exceptions.InvalidTokenException"
}

Concurrent session management always redirecting to failureurl - Java Config

I am using Spring MVC with Spring Security ver4.0.1.RELEASE.
I was trying to control the concurrent user login to 1 and show an error message if user already logged In.
The Concurrent Session Management is working as expected but the expireUrl("") is not working. The .formLogin().loginPage("").failureUrl("") is always called instead of expireUrl(""). Please help.
Below is my SpringSecurityConfiguration.java which extends WebSecurityConfigurerAdapter
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/resources/**").permitAll()
.antMatchers("/", "/home").permitAll()
.antMatchers("/Access_Denied").permitAll()
.antMatchers("/login").permitAll()
.and().formLogin().loginPage("/login")
.failureUrl("/login?out=1")
.usernameParameter("userID").passwordParameter("password")
.and().csrf().and()
.logout()
.deleteCookies( "JSESSIONID" )
.logoutSuccessUrl( "/logout" )
.invalidateHttpSession( true )
.and().exceptionHandling().accessDeniedPage("/accessDenied.jsp")
.and()
.sessionManagement()
.maximumSessions(1)
expiredUrl("/login?time=1")
.sessionRegistry(sessionRegistry);
}
My Initializer class will looks like below -
protected Filter[] getServletFilters() {
return new Filter[] { new HiddenHttpMethodFilter() };
}
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
servletContext.addListener(new SessionListener());
servletContext.addListener(new CustomHttpSessionEventPublisher());
}
The below links provide extra info for this type of security configuration -
http://codehustler.org/blog/spring-security-tutorial-form-login-java-config/
https://gerrydevstory.com/2015/08/02/managing-spring-security-user-session/
Have you tried to move session management up on the chain?
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/resources/**").permitAll()
.antMatchers("/", "/home").permitAll()
.antMatchers("/Access_Denied").permitAll()
.antMatchers("/login").permitAll()
.and().sessionManagement()
.maximumSessions(1)
.expiredUrl("/login?time=1")
.sessionRegistry(sessionRegistry);
.and().formLogin().loginPage("/login")
.failureUrl("/login?out=1")
.usernameParameter("userID").passwordParameter("password")
.and().csrf().and()
.logout()
.deleteCookies( "JSESSIONID" )
.logoutSuccessUrl( "/logout" )
.invalidateHttpSession( true )
.and().exceptionHandling().accessDeniedPage("/accessDenied.jsp")
}

Resources