How to get Spring Security to respond to Pre-Flight CORS request with OAUTH2 - spring

I am not a Java programmer by profession. I am working on building an app for our lab. It started out as a Spring MVC app with JSP files. It has now migrated to a Spring REST API that uses OAUTH2 as a standalone authentication and authorization server. I am using a MySQL database to store my data and users. I can successfully get an access_token issued if I use Postman to access the server. However if I use the Angular 2 client I setup I cannot get past the pre-flight OPTIONS request sent by the client to the server. I have tried several different ways to configure a CORS filter, but I cannot get that filter to get implemented and I always get a 401 code returned for the pre-flight request. I have tried implementing the suggested solution in the Spring blog https://spring.io/blog/2015/06/08/cors-support-in-spring-framework but that hasn't worked yet.
In my pom.xml file I am using these versions of Spring
Spring Framework - 4.3.2.RELEASE
Spring Security - 4.1.2.RELEASE
Spring Security OAUTH2 - 2.0.10.RELEASE
I also included Spring Boot 1.4.0 when trying to use FilterRegistrationBean
Here is my code as it is right now.
WebConfig Class:
#EnableWebMvc
#Configuration
#ComponentScan(basePackages={"eng.lab"})
#Import({ApplicationContext.class})
#PropertySource("classpath:application.properties")
public class WebConfig extends WebMvcConfigurerAdapter{
/* #Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**");
}*/
}
SecurityConfig Class:
#Configuration
#EnableWebSecurity
#PropertySource("classpath:application.properties")
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
DataSource dataSource;
#Autowired
public void configAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().dataSource(dataSource)
.usersByUsernameQuery(
"select username,password, enabled from user where username=?")
.authoritiesByUsernameQuery(
"select u.username, r.role from User u, Role r where r.id=u.roleId and u.userName=?");
}
#Autowired
private ClientDetailsService clientDetailsService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.anonymous().disable()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/oauth/token").permitAll()
//.requestMatchers(CorsUtils::isCorsRequest).permitAll()
.antMatchers("/oauth/token").permitAll();
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
#Bean
#Autowired
public TokenStoreUserApprovalHandler userApprovalHandler(TokenStore tokenStore){
TokenStoreUserApprovalHandler handler = new TokenStoreUserApprovalHandler();
handler.setTokenStore(tokenStore);
handler.setRequestFactory(new DefaultOAuth2RequestFactory(clientDetailsService));
handler.setClientDetailsService(clientDetailsService);
return handler;
}
#Bean
#Autowired
public ApprovalStore approvalStore(TokenStore tokenStore) throws Exception {
TokenApprovalStore store = new TokenApprovalStore();
store.setTokenStore(tokenStore);
return store;
}
}
MethodSecurityConfig Class:
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true, proxyTargetClass = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
#SuppressWarnings("unused")
#Autowired
private SecurityConfig securityConfig;
#Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
return new OAuth2MethodSecurityExpressionHandler();
}
}
WebAppInitializer Class:
public class WebAppInitializer implements WebApplicationInitializer{
#Override
public void onStartup(ServletContext container) throws ServletException {
AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
rootContext.register(WebConfig.class);
container.addListener(new ContextLoaderListener(rootContext));
DelegatingFilterProxy filter = new DelegatingFilterProxy("springSecurityFilterChain");
DispatcherServlet dispatcherServlet = new DispatcherServlet(rootContext);
ServletRegistration.Dynamic registration = container.addServlet("dispatcherServlet", dispatcherServlet);
container.addFilter("springSecurityFilterChain", filter).addMappingForUrlPatterns(null, false, "/*");
registration.setLoadOnStartup(1);
registration.addMapping("/");
}
}
SimpleCorsFilter Class:
#Component
#Order(Ordered.HIGHEST_PRECEDENCE)
public class SimpleCorsFilter implements Filter {
public SimpleCorsFilter() {
}
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
HttpServletRequest request = (HttpServletRequest) req;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "x-requested-with, authorization");
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
} else {
chain.doFilter(req, res);
}
}
#Override
public void init(FilterConfig filterConfig) {
}
#Override
public void destroy() {
}
}
If I include a second WebSecurityConfig class I do get a 403 code instead.
MyWebSecurity Class:
#Configuration
#EnableWebSecurity
#Order(-1)
public class MyWebSecurity extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/oauth/token").permitAll();
}
}
Any suggestions as to how to get past the pre-flight issue beyond waiting for the DATAREST-573 bug to be fixed?
UPDATE: I tried Benjamin's suggestion, but not sure I implemented the solution properly. I have edited my WebConfig class as folows but the pre-flight still fails. The filter still isn't getting applied.
package eng.lab.config;
import javax.servlet.Filter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
#EnableWebMvc
#Configuration
#ComponentScan(basePackages={"eng.lab"})
#Import({ApplicationContext.class})
#PropertySource("classpath:application.properties")
public class WebConfig extends WebMvcConfigurerAdapter{
//more custome rule beans
#Bean
public FilterRegistrationBean simpleCORSFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new SimpleCorsFilter());
registration.addUrlPatterns("/*");
//registration.addInitParameter("paramName", "paramValue");
registration.setName("simpleCorsFilter");
registration.setOrder(0);
return registration;
}
#Bean(name = "simpleCorsFilter")
public Filter simpleCorsFilter() {
return new SimpleCorsFilter();
}
/* #Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**");
}*/
}

Your CORS filter is not registered. As this is a standard javax.servlet.Filter instance, you need to register it against a FilterRegistrationBean or declare it in your web.xml file.
You can refer to this SO question for more details: How to add a filter class in Spring Boot?

Related

PrincipalExtractor and AuthoritiesExtractor doesn't hit

I have a project with Spring security and Oauth2.
On the resource server I have the following configuration:
#Configuration
public class SecurityConfiguration extends ResourceServerConfigurerAdapter {
#Override
public void configure(final HttpSecurity http) throws Exception {
http.antMatcher("/**")
.authorizeRequests().antMatchers("/info", "/health", "/h2-console/**").permitAll()
.anyRequest().authenticated()
.and().headers().frameOptions().disable();
}
}
I have the following extractors:
#Component
public class InsurancePrincipalExtractor implements PrincipalExtractor {
#Override
public Object extractPrincipal(Map<String, Object> map) {
return map.get("username");
}
}
#Component
public class InsuranceAuthoritiesExtractor implements AuthoritiesExtractor {
#Override
public List<GrantedAuthority> extractAuthorities(Map<String, Object> map) {
//Logic
}
I set the user-info-uri: http://localhost:8081/uaa/v1/me
The problem is that it does not hit my extractor methods at runtime, so nothing happens. As I know I just need to annotate it with the #Component and the Spring boot and will use it auto.
UPDATE:
Solution founded.
I had to add this to my configuration as well:
#Bean
protected ResourceServerTokenServices resourceServerTokenServices(ResourceServerProperties sso,
OAuth2ClientContext oauth2ClientContext,
UserInfoRestTemplateFactory restTemplateFactory) {
UserInfoTokenServices services = new UserInfoTokenServices(sso.getUserInfoUri(), sso.getClientId());
services.setRestTemplate(restTemplateFactory.getUserInfoRestTemplate());
services.setTokenType(sso.getTokenType());
return services;
}

spring oauth2, unable to retrieve user info

couldn't find a doc which illustrates how to use access_token to get user info.
How can I make a such endpoint?
I see when I access the /user endpoint OAuth2AuthenticationProcessingFilter is not included.
I think I messed up something below
#Configuration
#EnableWebSecurity
#Order(2)
// after ResourceServerConfigurerAdapter
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
// https://stackoverflow.com/questions/26387003/how-to-make-spring-boot-never-issue-session-cookie
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.antMatchers("/", "index", "/login**", "/webjars/**")
.permitAll()
.and().exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.accessDeniedHandler(oAuth2FailedHandler)
.and().csrf().disable().authorizeRequests()
.and().addFilterBefore(new CORSFilter(), BasicAuthenticationFilter.class); }
}
I also have..
#Order(3)
#Configuration
#EnableResourceServer
#Slf4j
public class OAuth2ResourceServerConfig extends ResourceServerConfigurerAdapter {
#Override
public void configure(final ResourceServerSecurityConfigurer config) {
config.tokenServices(tokenServices());
}
#Override
public void configure(HttpSecurity http) throws Exception {
// super.configure(http);
log.info("hello OAuth2ResourceServerConfig:configure");
http
.authorizeRequests().anyRequest().authenticated();
}
#Bean
#Primary
public DefaultTokenServices tokenServices() {
final DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
return defaultTokenServices;
}
// JDBC token store configuration
#Bean
public DataSource dataSource() {
final DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(env.getProperty("spring.datasource.driver-class-name"));
dataSource.setUrl(env.getProperty("spring.datasource.url"));
dataSource.setUsername(env.getProperty("spring.datasource.username"));
dataSource.setPassword(env.getProperty("spring.datasource.password"));
return dataSource;
}
#Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource());
}
}
#Configuration
//#PropertySource({ "classpath:persistence.properties" }) #EnableAuthorizationServer
#Slf4j
public class OAuth2AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
// ....
}
Add a REST controller with something like this:
#RequestMapping("/user")
public #ResponseBody Principal user(Principal user) {
return user;
}
Then you can call it, using your access token in the Authorization header.

Spring Security + REST + postgreSQL

I did a tutorial https://auth0.com/blog/securing-spring-boot-with-jwts/ about authorization using Spring Security, but this example use hardcoded user data. I would like to authorize using database PostgreSQL. How can I do that? Or do you know some examples on github using Spring REST Security and PostgreSQL?
package com.example.security;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers(HttpMethod.POST, "/login").permitAll()
.anyRequest().authenticated()
.and()
// We filter the api/login requests
.addFilterBefore(new JWTLoginFilter("/login", authenticationManager()),
UsernamePasswordAuthenticationFilter.class)
// And filter other requests to check the presence of JWT in header
.addFilterBefore(new JWTAuthenticationFilter(),
UsernamePasswordAuthenticationFilter.class);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// Create a default account
auth.inMemoryAuthentication()
.withUser("admin")
.password("password")
.roles("ADMIN");
}
}
You can use it with your custom userdetailservice like this:
#Autowired
private CustomUserDetailService userDetailsService;
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsService)
;
}
and add customuserdetail service :
#Service
public class CustomUserDetailService implements UserDetailsService {
#Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
User user = getUserFromDatabase();
UserItem userItem = new UserItem(user.getUsername(),user.getPassword(),true,true,true,true, new ArrayList<GrantedAuthority>());;
userItem.setAuthorities(AuthorityUtils.createAuthorityList("ROLE_ADMIN", "ROLE_USER"));
return userItem;
}
}
You need to create a bean for dataSource like this
#Bean
public DriverManagerDataSource dataSource() {
DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();
driverManagerDataSource.setDriverClassName("org.postgresql.Driver");
driverManagerDataSource.setUrl("jdbc:postgresql://127.0.0.1:5432/mydb");
driverManagerDataSource.setUsername("postgres");
driverManagerDataSource.setPassword("root");
return driverManagerDataSource;
}
And then autowire javax.sql.DataSource inside your WebSecurityConfig class:
#Autowired
DataSource dataSource;
if your password is Bcrypt encoded then create a bean for passwordEncoder
#Bean(name="passwordEncoder")
public PasswordEncoder passwordencoder(){
return new BCryptPasswordEncoder();
}
Configure authentication like this:
public void configAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().dataSource(dataSource)
.usersByUsernameQuery(
"select email,password from users where email=?").passwordEncoder(passwordencoder());
}
and finally hit the /login route.

multiple oauth2 services and configurations

I have an existing Spring application which is using configuration A extending
ResourceServerConfigureAdapter to secure APIs against an internal oauth service A.
I am trying to add another configuration B extending WebSecurityConfigurerAdapter which authenticates against an external oauth provider.
The aim is to continue with B determine authentication for /api/ related endpoints while A determines overall login to the web application.
Following is the existing code using ResourceServerConfigureAdapter:-
#Configuration
#EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Value("${oauth.clientId}")
private String clientId;
#Value("${oauth.clientSecret}")
private String clientSecret;
#Autowired
private RestTemplate restTemplate;
#Bean
public RemoteTokenServices remoteTokenServices() {
RemoteTokenServices remoteTokenServices = new RemoteTokenServices();
remoteTokenServices.setRestTemplate(restTemplate);
remoteTokenServices.setClientId(clientId);
remoteTokenServices.setClientSecret(clientSecret);
remoteTokenServices.setCheckTokenEndpointUrl("srvc://myservice/api/v2/oauth/check_token");
return remoteTokenServices;
}
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId(null);
resources.tokenServices(remoteTokenServices());
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.anonymous()
.and().authorizeRequests()
.antMatchers("/api/secured/**").authenticated()
.antMatchers("/api/**").permitAll();
}}
Following is the code using WebSecurityConfigurerAdapter:-
#SpringBootApplication
#EnableOAuth2Client
#EnableMBeanExport(registration = RegistrationPolicy.IGNORE_EXISTING)
public class DemoService extends WebSecurityConfigurerAdapter {
#Autowired
OAuth2ClientContext oauth2ClientContext;
#Override
protected void configure(HttpSecurity http) throws Exception {
// #formatter:off
http.antMatcher("/**").authorizeRequests().antMatchers("/", "/login**", "/webjars/**").permitAll().anyRequest()
.authenticated().and().exceptionHandling()
.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/")).and().logout()
.logoutSuccessUrl("/").permitAll().and().csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()).and()
.addFilterBefore(ssoFilter(), BasicAuthenticationFilter.class);
// #formatter:on
}
public static void main(String[] args) {
SpringApplication.run(DemoService.class, args);
}
#Bean
public FilterRegistrationBean oauth2ClientFilterRegistration(OAuth2ClientContextFilter filter) {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(filter);
registration.setOrder(-100);
return registration;
}
private Filter ssoFilter() {
OAuth2ClientAuthenticationProcessingFilter googleFilter = new OAuth2ClientAuthenticationProcessingFilter(
"/login/google");
OAuth2RestTemplate googleTemplate = new OAuth2RestTemplate(google(), oauth2ClientContext);
googleFilter.setRestTemplate(googleTemplate);
UserInfoTokenServices tokenServices = new UserInfoTokenServices(googleResource().getUserInfoUri(),
google().getClientId());
tokenServices.setRestTemplate(googleTemplate);
googleFilter.setTokenServices(
new UserInfoTokenServices(googleResource().getUserInfoUri(), google().getClientId()));
return googleFilter;
}
#Bean
#ConfigurationProperties("google.client")
public AuthorizationCodeResourceDetails google() {
return new AuthorizationCodeResourceDetails();
}
#Bean
#ConfigurationProperties("google.resource")
public ResourceServerProperties googleResource() {
return new ResourceServerProperties();
}
}
Both of them individually run fine but put together in the same project, problems start showing up. Everything compiles and runs fine but when I hit localhost:8080/ following happens -
The page loads fine but when I hit localhost:8080/login/google, it shows me a whitelabel error page like following
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Thu Apr 06 13:22:27 IST 2017
There was an unexpected error (type=Not Found, status=404).
Not Found
I try to read a bit about ResourceServerConfigureAdapter vs WebSecurityConfigurerAdapter and I could only understand that there is some kind of filter-order that determines the priority of each configurer. But that hasn't helped me fix the issue. Any pointers?
Update:
There's another adapter for Swagger integration that is also part of the project.
#EnableSwagger2
#Configuration
public class SwaggerConfiguration extends WebMvcConfigurerAdapter {
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addRedirectViewController("/docs", "/swagger-ui.html");
registry.addRedirectViewController("/docs/", "/swagger-ui.html");
registry.addRedirectViewController("/docs.json", "/v2/api-docs");
}
#Bean
public Docket swaggerSpringMvcPlugin() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(new ApiInfoBuilder()
.title("Spring Boot Service")
.description("Sample project documentation")
.contact("a#b.com")
.version("1.0")
.license("Apache")
.build())
.forCodeGeneration(true)
.ignoredParameterTypes(Principal.class)
.useDefaultResponseMessages(false)
.select()
.paths(documentedPaths())
.build();
}
private Predicate<String> documentedPaths() {
return or(
regex("/api.*"));
}
}
.addFilterBefore(ssoFilter(), BasicAuthenticationFilter.class);
The OAuth2ClientAuthenticationProcessingFilter must after OAuth2ClientContextFilter, OAuth2ClientAuthenticationProcessingFilter will throw a redirect exception when the request is wrong(no code, etc...),
and the OAuth2ClientContextFilter will catch it and redirect to the userAuthorizationUri;
The BasicAuthenticationFilter is before OAuth2ClientContextFilter normal, , so you should change the order:
#Autowired
private OAuth2ClientContextFilter oAuth2ClientContextFilter;
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterAfter(oAuth2ClientContextFilter, ExceptionTranslationFilter.class)
.addFilterAfter(ssoFilter(), OAuth2ClientContextFilter.class);
}
UPDATE:
There is another place need to be updated, if you have multi chains, you should define the request match, the default value is '/**', and the default order of ResourceServerConfiguration is 3, the default order of WebSecurityConfigurerAdapter is 100, ResourceServerConfiguration has high priority.
// only handle the request start with `/api`
http.requestMatcher(new AntPathRequestMatcher("/api/**"))
http.anonymous()
.and().authorizeRequests()
.antMatchers("/api/secured/**").authenticated()
.antMatchers("/api/**").permitAll();
If you put the WebSecurityConfigurerAdapter before ResourceServerConfiguration by change the order, you should also config the WebSecurityConfigurerAdapter not to handler /api/**
// skip the request start with '/api'
http.requestMatcher(new RegexRequestMatcher("^(?!/api).*$", null))
.authorizeRequests().antMatchers("/", "/login/**", "/webjars/**").permitAll().anyRequest()
Update2:
.authorizeRequests().antMatchers("/", "/login**")
This matcher doesn't match /login/google, please update to /login/**, so it should be
.authorizeRequests().antMatchers("/", "/login/**").permitAll()

#EventListener for AuthenticationSuccessEvent or InteractiveAuthenticationSuccessEvent not fired

I have this listener in the context of Spring:
package listeners;
import java.util.Date;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.EventListener;
import org.springframework.security.authentication.event.AbstractAuthenticationEvent;
import org.springframework.security.authentication.event.AuthenticationSuccessEvent;
import org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent;
import org.springframework.stereotype.Component;
import services.UserService;
import services.security.CustomUserDetails;
/**
*
* #author sergio
*/
#Component
public class AuthenticationSuccessEventHandler{
private static Logger logger = LoggerFactory.getLogger(AuthenticationSuccessEventHandler.class);
#Autowired
private UserService userService;
#EventListener({AuthenticationSuccessEvent.class, InteractiveAuthenticationSuccessEvent.class})
public void processAuthenticationSuccessEvent(AbstractAuthenticationEvent e) {
logger.info("Autenticación realizada ....");
// Actualizamos la útltima fecha de acceso
String username = ((CustomUserDetails) e.getAuthentication().getPrincipal()).getUsername();
logger.info("Actualizando último acceso para user: " + username);
userService.updateLastLoginAccess(username, new Date());
}
}
This is successfully created in context, according to the Spring debug messages.
DEBUG DefaultListableBeanFactory:448 - Creating instance of bean 'authenticationSuccessEventHandler'
2016-12-11 11:33:29 DEBUG InjectionMetadata:72 - Registered injected element on class [listeners.AuthenticationSuccessEventHandler]: AutowiredFieldElement for private services.UserService listeners.AuthenticationSuccessEventHandler.userService
When I authenticate correctly in the application, no event is released by Spring Security and therefore this Event Listener is not called.
My Spring Security configuration is this
#Configuration
#EnableWebSecurity
#ComponentScan(basePackageClasses = CustomUserDetailsService.class)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Bean
public PasswordEncoder passwordEncoder() {
PasswordEncoder encoder = new BCryptPasswordEncoder();
return encoder;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/signup").anonymous()
.antMatchers("/admin/**").authenticated()
.anyRequest().permitAll()
.and()
.formLogin().loginPage("/admin/login").permitAll()
.usernameParameter("username").passwordParameter("password")
.and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/admin/logout"))
.logoutSuccessUrl("/admin/login?logout")
.and()
.exceptionHandling().accessDeniedPage("/403")
.and()
.csrf();
}
}
SecurityWebApplicationInitializer
package config;
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
/**
*
* #author sergio
*/
public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer {
}
I am using Spring Security 4.2.0.RELEASE.
You may need to register the event-publishing infrastructure (eg. by configuring a DefaultAuthenticationEventPublisher).
#EnableWebSecurity
class SecurityConfig extends WebSecurityConfigurerAdapter {
...
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.authenticationEventPublisher(authenticationEventPublisher())
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
#Bean
public DefaultAuthenticationEventPublisher authenticationEventPublisher() {
return new DefaultAuthenticationEventPublisher();
}
}
This is how i achieved it.
1) In your Application class, expose your application listener like
#Bean
public ApplicationListener applicationListener(){
return new AuthSuccessApplicationListener();
}
2) Implement AuthSuccessApplicationListener for example
public class AuthSuccessApplicationListener implements
ApplicationListener<InteractiveAuthenticationSuccessEvent>{
#Autowired(required=false)
HttpSession httpSession;
#Autowired
Environment env;
/**
* Handle an application event.
*
* #param appEvent the event to respond to
*/
#Override
public void onApplicationEvent(InteractiveAuthenticationSuccessEvent appEvent) {
if (appEvent!=null) {
LdapUserDetailsImpl ldapUserDetailsImpl = (LdapUserDetailsImpl) appEvent.getAuthentication().getPrincipal();
try {
if (ldapUserDetailsImpl != null) {
logger.info("Session Created for " + ldapUserDetailsImpl.getUsername());
if (httpSession.getAttribute("adminUser") == null) {
// check user is admin and set into session
if (isAdminUser(ldapUserDetailsImpl.getUsername())) {
httpSession.setAttribute("adminUser", "ADMIN_USER");
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>(auth.getAuthorities());
// Add the ROLE_ADMIN into Authorities
authorities.add(new SimpleGrantedAuthority(SecurityConfig.ADMIN));
// Create a new Authentication based on current principal and authorities and set into Security Context
Authentication newAuth = new UsernamePasswordAuthenticationToken(auth.getPrincipal(), auth.getCredentials(), authorities);
SecurityContextHolder.getContext().setAuthentication(newAuth);
}
}
}
} catch (Exception e) {
logger.error("Exception occurred : " + e.getMessage());
}
}
}
Here's how the Spring Security docs explain it (at the time of writing, Spring Security is at version 5.6.1):
To listen for these events, you must first publish an AuthenticationEventPublisher. Spring Security’s DefaultAuthenticationEventPublisher will probably do fine:
#Bean
public AuthenticationEventPublisher authenticationEventPublisher(ApplicationEventPublisher appEventPublisher) {
return new DefaultAuthenticationEventPublisher(appEventPublisher);
}
See https://docs.spring.io/spring-security/reference/servlet/authentication/events.html
In spring-security version 5.6.0 only UsernamePasswordAuthenticationFilter fires InteractiveAuthenticationSuccessEvent. As option You may extends AbstractAuthenticationProcessingFilter or do it by own a success handler implementation. Example:
class LoggingAuthenticationSuccessHandler extends WebFilterChainServerAuthenticationSuccessHandler {
#Autowired
private AuthenticationEventPublisher eventPublisher;
#Override
public Mono<Void> onAuthenticationSuccess(WebFilterExchange webFilterExchange, Authentication authentication) {
this.eventPublisher.publishAuthenticationSuccess(authentication);
return super.onAuthenticationSuccess(webFilterExchange,authentication);
}
}

Resources