#EventListener for AuthenticationSuccessEvent or InteractiveAuthenticationSuccessEvent not fired - spring

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);
}
}

Related

Default admin user in config and other users from database

I'm using spring boot with spring security and jpa. I would like to have admin user in config and other users in database. The problem is when I added UserDetailsService, the admin user from application.properties stop working.
application.properties:
spring.security.user.name=admin
spring.security.user.password={bcrypt}$2a$10$7F7xGm8iuzafFA7RPS8gzeKJm9qNBwtejT4hSuBKXMNlc/4NxOy1G
spring.security.user.roles=admin
Spring Security config class:
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Qualifier("myUserDetailsService")
#Autowired
private UserDetailsService userDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(encodePWD());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
.antMatchers("/").permitAll()
.antMatchers("/uzytkownik/pacjent/rejestracja").anonymous()
.anyRequest().hasRole("admin")
.and().formLogin().permitAll()
;
}
#Bean
public BCryptPasswordEncoder encodePWD() {
return new BCryptPasswordEncoder();
}
}
Without any configuration Spring Security reads user from properties file by default. But if you add an UserDetailsService it means you want to read users using the interface's method loadUserByUsername. This method can be implemented for example to read users from database
If you want your admin user from application.properties to be considered as a user, you can create it in your implementation of UserDetailsService :
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
#Service
public class MyUserDetailsService implements UserDetailsService {
#Value("${spring.security.user.name}")
private String adminUserName;
#Value("${spring.security.user.password}")
private String adminPassword;
#Value("${spring.security.user.roles}")
private String adminRole;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
if(username.equals(adminPassword)) {
return User.builder().username(adminUserName).password(adminPassword).roles(adminRole).build();
}
//return your other users from database here
return null;
}
}

How to combine two different configuration files in Spring boot?

I have multiproject Spring application.
Project A - responsible for LDAP authentication
Project B - responsible for Database authentication
Project MAIN - can use both of them or one of them.
If we use only Project A - we have LDAP auth
If we use only Project B - we have JDBC auth
If we use both of them - first goes LDAP auth, if it failures, then goes JDBC auth. And if Project B is included, it adds some filters
Project MAIN does not have #Configuration file, but Projects A and B has it.
Project A #Configuration
#Configuration
#EnableWebSecurity
#Order(1)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
/**адрес сервера LDAP*/
#Value("${ldap.server}")
private String ldapServer;
/**номер порта LDAP сервера*/
#Value("${ldap.port}")
private int ldapPort;
/**домен для LDAP*/
#Value("${ldap.suffix}")
private String suffix;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(adAuthProvider());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic()
.and()
.authorizeRequests().antMatchers("/**").authenticated()
.and()
.csrf().disable();
}
/**провайдер для аутентификации через LDAP*/
#Bean
public ActiveDirectoryLdapAuthenticationProvider adAuthProvider() {
String ldapUrl = String.format("ldap://%s:%s", ldapServer, ldapPort);
ActiveDirectoryLdapAuthenticationProvider adAuthProvider = new
ActiveDirectoryLdapAuthenticationProvider(suffix, ldapUrl);
adAuthProvider.setConvertSubErrorCodesToExceptions(true);
adAuthProvider.setUseAuthenticationRequestCredentials(true);
return adAuthProvider;
}
}
and Project B Configuration file.
#Configuration
#EnableWebSecurity
public class ECommonConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(jdbcAuthProvider());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic()
.and()
.authorizeRequests().antMatchers("/**").authenticated()
.and()
.csrf().disable();
http.addFilterAt(ldapAuthenticationFilter(), LDAPAuthenticationFilter.class);
http.authorizeRequests().antMatchers("/**").access("#requestAuthorization.checkRequestPermissions(authentication, request)");
}
/**провайдер для аутентификации через базу данных*/
#Bean
public DaoAuthenticationProvider jdbcAuthProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
/**бин для шифрования паролей*/
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**бин для фильтра проверки наличия LDAP-пользователя в базе данных*/
#Bean
public LDAPAuthenticationFilter ldapAuthenticationFilter() throws Exception {
return new LDAPAuthenticationFilter(authenticationManager());
}
#Bean
#Override
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
/**бин для инициализации базы данных по умолчанию - описание параметров подключения к БД в файле application.yml*/
#Bean
public DataSource dataSource() {
return datasourceConnectionManager().getDataSource("test");
}
/**бин создания менеджера подключения к нескольким базам данных*/
#Bean
public DatasourceConnectionManager datasourceConnectionManager() {
return new DatasourceConnectionManager();
}
}
I need these two configurations works together or only one oh them
To combine this 2 ways of authentication you can create a custom authentication provider ( more details here: https://www.baeldung.com/spring-security-authentication-provider )
The implementation of the auth provider would look something like this:
#Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
private ActiveDirectoryLdapAuthenticationProvider ldapAuthenticationProvider;
private DaoAuthenticationProvider daoAuthenticationProvider;
// env variable to help you choose which auth provider should be enabled
#Value("${ldap.enabled}")
private int ldapEnabled;
// env variable to help you choose which auth provider should be enabled
#Value("${daoAuth.enabled}")
private int daoAuthEnabled;
#Autowired
public CustomAuthenticationProvider(ActiveDirectoryLdapAuthenticationProvider ldapAuthenticationProvider, DaoAuthenticationProvider daoAuthenticationProvider) {
this.ldapAuthenticationProvider = ldapAuthenticationProvider;
this.daoAuthenticationProvider = daoAuthenticationProvider;
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication);
// if both enabled then first try with ldap, if not successful try with dao
if (ldapEnabled && daoAuthEnabled ) {
Authentication authenticate = ldapAuthenticationManager.authenticate(authentication);
if(!authenticate.isAuthenticated()) {
authenticate = ldapAuthenticationManager.authenticate(authentication);
}
return authenticate;
}
// if only ldap enabled
if(ldapEnabled) {
return ldapAuthenticationManager.authenticate(authentication);
}
// if only dao enabled
return daoAuthenticationProvider.authenticate(authentication);
}
#Override
public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
}
You can use Spring profiling for this. Just Add #Profile annotation along with name on the configuration class as shown below.
Configuration for ProjectA
#Profile("ProjectA")
#Configuration
#EnableWebSecurity
#Order(1)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
...
And Configuration for ProjectB
#Profile("ProjectB")
#Configuration
#EnableWebSecurity
public class ECommonConfig extends WebSecurityConfigurerAdapter {
...
Then at the time of execution of application you can specify active profile by passing following parameter to java.
#In case of need of only ProjectA then
-Dspring.profiles.active=ProjectA
#In case of need of only ProjectB then
-Dspring.profiles.active=ProjectB
#In case of need of both projects then
-Dspring.profiles.active=ProjectA,ProjectB
Same thing you can define in application.properties file with required profile
spring.profiles.active=ProjectA,ProjectB
This way you can dynamically decide which Project configuration should be included.

Spring Security Always returning 403 forbidden, Access denied

I want to enable admin to access admin page and do admin stuff, but when I try to do that by setting that the url with /admin/** can only be accessed by user with role admin, it returns 403 Forbidden, access denied. But the user has authorities set to ROLE_ADMIN I checked. What am I doing wrong?
My Controller for user login
#RestController
public class UserController {
#Autowired
AuthenticationManager authenticationManager;
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private AuthorityService authorityService;
#Autowired
private UserAuthorityService userAuthorityService;
#Autowired
TokenUtils tokenUtils;
#Autowired
private UserService userService;
#RequestMapping(value = "/api/login", method = RequestMethod.POST, produces = "text/html")
public ResponseEntity<String> login(#RequestBody LoginDTO loginDTO) {
try {
// System.out.println(loginDTO.getUsername() + " " + loginDTO.getPassword());
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
loginDTO.getUsername(), loginDTO.getPassword());
Authentication authentication = authenticationManager.authenticate(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
UserDetails details = userDetailsService.loadUserByUsername(loginDTO.getUsername());
return new ResponseEntity<String>(tokenUtils.generateToken(details), HttpStatus.OK);
} catch (Exception ex) {
return new ResponseEntity<String>("Invalid login", HttpStatus.BAD_REQUEST);
}
}
#RequestMapping(value = "/api/register", method = RequestMethod.POST, produces = "text/html")
public ResponseEntity<String> register(#RequestBody RegisterDTO registerDTO) {
try {
System.out.println(registerDTO);
User user = userService.findUserByUsername(registerDTO.getUsername());
// // Check if user with that username exists
if(user != null){
// User with that username is found
return new ResponseEntity<String>("User with that username exists", HttpStatus.BAD_REQUEST);
}
// We need to save the user so his ID is generated
User newUser = userService.saveUser(new User(registerDTO));
UserAuthority userAuthority = userAuthorityService.save(new UserAuthority(newUser, authorityService.findOneByName("User")));
Set<UserAuthority> authorities = new HashSet<>();
authorities.add(userAuthority);
newUser.setUserAuthorities(authorities);
User savedUser = userService.save(newUser);
return new ResponseEntity<String>("You have registered successfully with username " + savedUser.getUsername(), HttpStatus.OK);
} catch (Exception ex) {
return new ResponseEntity<String>("Invalid register", HttpStatus.BAD_REQUEST);
}
}
}
I can say that I test my app with postman and login and registration are working fine. When the user is logged in I can the token with the correct data and users authorities, but why when I try to access /admin/building/add url it is returning 403 error?
My Controller for adding building for admin page:
#RestController
public class BuildingController {
#Autowired
private BuildingService buildingService;
#RequestMapping(value = "/admin/building/add", method = RequestMethod.POST, produces = "text/html")
public ResponseEntity<String> addBuilding(#RequestBody BuildingDTO buildingDTO) {
try{
Building newBuilding = new Building(buildingDTO);
return new ResponseEntity<String>(newBuilding.getName(), HttpStatus.OK);
}catch (Exception ex) {
return new ResponseEntity<String>("Data was not valid", HttpStatus.BAD_REQUEST);
}
}
}
My SecurityConfiguration.java
#Configuration
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Autowired
public void configureAuthentication(
AuthenticationManagerBuilder authenticationManagerBuilder)
throws Exception {
authenticationManagerBuilder
.userDetailsService(this.userDetailsService).passwordEncoder(
passwordEncoder());
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public AuthenticationTokenFilter authenticationTokenFilterBean()
throws Exception {
AuthenticationTokenFilter authenticationTokenFilter = new AuthenticationTokenFilter();
authenticationTokenFilter
.setAuthenticationManager(authenticationManagerBean());
return authenticationTokenFilter;
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/index.html", "/view/**", "/app/**", "/", "/api/login", "/api/register").permitAll()
// defined Admin only API area
.antMatchers("/admin/**").hasRole("ADMIN")
.anyRequest()
.authenticated()
.and().csrf().disable();
//if we use AngularJS on client side
// .and().csrf().csrfTokenRepository(csrfTokenRepository());
//add filter for adding CSRF token in the request
httpSecurity.addFilterAfter(new CsrfHeaderFilter(), CsrfFilter.class);
// Custom JWT based authentication
httpSecurity.addFilterBefore(authenticationTokenFilterBean(),
UsernamePasswordAuthenticationFilter.class);
}
/**
* If we use AngularJS as a client application, it will send CSRF token using
* name X-XSRF token. We have to tell Spring to expect this name instead of
* X-CSRF-TOKEN (which is default one)
* #return
*/
private CsrfTokenRepository csrfTokenRepository() {
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
repository.setHeaderName("X-XSRF-TOKEN");
return repository;
}
}
I should mention that I am using Angularjs for frontend, but even so I can login and the correct authorities are displayed for that user. But for some reason I can not access the admin page, even if I login as admin.
Also I tried .hasAuthority("ROLE_ADMIN") and .hasRole("ROLE_ADMIN")(which displays an error for ROLE_) and so I changed it to .hasRole("ADMIN") but it is still not working.
In the database the role for admin is saved as ROLE_ADMIN.
Try like this :
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
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.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private static String REALM="MY_TEST_REALM";
#Autowired
public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("bill").password("abc123").roles("ADMIN");
auth.inMemoryAuthentication().withUser("tom").password("abc123").roles("USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/user/**").hasRole("ADMIN")
.and().httpBasic().realmName(REALM).authenticationEntryPoint(getBasicAuthEntryPoint())
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);//We don't need sessions to be created.
}
#Bean
public CustomBasicAuthenticationEntryPoint getBasicAuthEntryPoint(){
return new CustomBasicAuthenticationEntryPoint();
}
/* To allow Pre-flight [OPTIONS] request from browser */
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers(HttpMethod.OPTIONS, "/**");
}
}
For a complet configuration example : Secure Spring REST API using Basic Authentication
import org.springframework.context.annotation.Configuration;
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;
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();// We don't need sessions to be created.
}
}
This did it for me. Now I am able to submit my post requests successfully
Try this in SecurityConfig:
.antMatchers("/api/admin").access("hasRole('ADMIN')")
.antMatchers("/api/user").access("hasRole('ADMIN') or hasRole('USER')")

How can I add newly signed up user in the Spring boot security config?

I am working Spring-Boot, Spring Security with basic Authentication. I will send login url from my client application written in AngularJS via RESTful API call.
Everything works as expected. All the users in the DB configured in the SecurityConfiguration.java as below.
#Autowired
public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
List<User> users = userService.getUsers();
for (User user : users) {
auth.inMemoryAuthentication().withUser(user.getUserName()).password(user.getPassword())
.roles(user.getRole().getName());
}
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests().antMatchers("/server/rest/secure/**")
.hasRole("ADMIN").and()
.httpBasic().realmName(REALM).authenticationEntryPoint(getBasicAuthEntryPoint());
}
#Bean
public CustomBasicAuthenticationEntryPoint getBasicAuthEntryPoint() {
return new CustomBasicAuthenticationEntryPoint();
}
CustomBasicAuthenticationEntryPoint.java
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
public class CustomBasicAuthenticationEntryPoint extends BasicAuthenticationEntryPoint {
#Override
public void commence(final HttpServletRequest request,
final HttpServletResponse response,
final AuthenticationException authException) throws IOException, ServletException {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.addHeader("WWW-Authenticate", "Basic realm=" + getRealmName() + "");
PrintWriter writer = response.getWriter();
writer.println("HTTP Status 401 : " + authException.getMessage());
response.setHeader("WWW-Authenticate", "FormBased");
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
}
#Override
public void afterPropertiesSet() throws Exception {
setRealmName("MY_TEST_REALM");
super.afterPropertiesSet();
}
}
So If I signup a new user which will inserted in the DB but not added in the above implementation. So authentication fails.
How can refresh the above implementation whenever i'm and doing signup of a new user
When doing authentication with db, you should do the following:
#Service("userDetailsService")
#Transactional
public class MUserDetailService implements UserDetailsService {
#Autowired
AppUserDao appUserDao;
#Override
public UserDetails loadUserByUsername(final String appUserName) throws UsernameNotFoundException {
AppUser appUser = appUserDao.findByName(appUserName);
if (appUser == null) throw new UsernameNotFoundException(appUserName);
else{
return new User(appUser.getUsername(),appUser.getPassword(),appUser.getActive(),true,true,true,getGrantedAuthorities(appUser));
}
}
private List<GrantedAuthority> getGrantedAuthorities(AppUser appUser){
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
for (Authority authority : appUser.getAuthorities()){
authorities.add(new SimpleGrantedAuthority(authority.getAuthorityName()));
}
return authorities;
}
}
and then define SecurityConfiguration as follows:
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter{
#Autowired
#Qualifier("userDetailsService")
UserDetailsService userDetailsService;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
}

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

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?

Resources