spring security oauth2 java config - Handler dispatch failed; nested exception is java.lang.StackOverflowError - spring

Configured Spring Security with OAuth2 in java config, And client_credentails flow is working fine, but password flow is throwing Handler dispatch failed; nested exception is java.lang.StackOverflowError below is the log information
2016-10-10 23:19:08 DEBUG o.s.w.s.m.m.a.ExceptionHandlerExceptionResolver - Resolving exception from handler [public org.springframework.http.ResponseEntity<org.springframework.security.oauth2.common.OAuth2AccessToken> org.springframework.security.oauth2.provider.endpoint.TokenEndpoint.postAccessToken(java.security.Principal,java.util.Map<java.lang.String, java.lang.String>) throws org.springframework.web.HttpRequestMethodNotSupportedException]: org.springframework.web.util.NestedServletException: Handler dispatch failed; nested exception is java.lang.StackOverflowError
here is my configuration files:
AuthorizationServerConfiguration.java
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
#Autowired
DataSource dataSource;
#Autowired
private UserDetailsService userDetailsService;
#Autowired
#Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
#Override
public void configure(
AuthorizationServerSecurityConfigurer oauthServer)
throws Exception {
oauthServer
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
}
#Override
public void configure(ClientDetailsServiceConfigurer clients)
throws Exception {
clients.jdbc(dataSource);
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
endpoints
.tokenStore(tokenStore())
.authenticationManager(authenticationManager);
}
#Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
}
ResourceServerConfiguration.java
#Configuration
#EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
private String resourceId = "rest-api";
#Override
public void configure(ResourceServerSecurityConfigurer resources) {
// #formatter:off
resources.resourceId(resourceId);
// #formatter:on
}
#Override
public void configure(HttpSecurity http) throws Exception {
// #formatter:off
http.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS).permitAll()
.anyRequest().authenticated();
// #formatter:on
}
}
WebApplicationInitializer.java
public class WebAppInitializer implements WebApplicationInitializer {
private Logger logger = LoggerFactory.getLogger(getClass());
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
AnnotationConfigWebApplicationContext rootContext=new AnnotationConfigWebApplicationContext();
rootContext.register(ApplicationRootConfig.class, HibernateConfig.class, AnnotationBasedSecurityConfig.class,
GlobalAuthenticationConfig.class, SecurityConfiguration.class, AuthorizationServerConfiguration.class, ResourceServerConfiguration.class);
servletContext.addListener(new ContextLoaderListener(rootContext));
servletContext.setInitParameter("defaultHtmlEscape", "true");
FilterRegistration.Dynamic encodingFilter=servletContext.addFilter("encoding-filter", new CharacterEncodingFilter());
encodingFilter.setInitParameter("encoding", "UTF-8");
encodingFilter.setInitParameter("forceEncoding", "true");
encodingFilter.addMappingForServletNames(null, true, "/*");
FilterRegistration.Dynamic securityFilter = servletContext.addFilter("securityFilter",new DelegatingFilterProxy("springSecurityFilterChain"));
securityFilter.addMappingForUrlPatterns(null, false,"/*");
servletContext.addListener(new HttpSessionEventPublisher());
ServletRegistration.Dynamic dispatcher = servletContext.addServlet("dispatcher", new DispatcherServlet(rootContext));
dispatcher.setLoadOnStartup(1);
Set<String> mappingConflicts = dispatcher.addMapping("/");
if(!mappingConflicts.isEmpty()){
for (String map : mappingConflicts) {
logger.error("Mapping Conflict "+map);
}
throw new IllegalStateException("Dispatcher : cannot be mapped to '/' under Tomcat vesions <= 7.0.4");
}
rootContext.setServletContext(servletContext);
rootContext.refresh();
}
}
UserDetailsServiceImpl.java
#Service
#Transactional(readOnly=true)
public class UserDetailsServiceImpl implements UserDetailsService{
Logger logger = LoggerFactory.getLogger(UserDetailsServiceImpl.class);
#Autowired
UserDAO userDAO;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
logger.info("loadUserByUsername "+username);
Collection<User> user = userDAO.findByEmail(username);
return user.iterator().next();
}
}
SecurityConfiguration.java
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
UserDetailsService userDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.userDetailsService(userDetailsService);
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean()
throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/login").permitAll()
.anyRequest().authenticated()
.and()
.formLogin().permitAll();
}
}
oauth_client_details table
Full Stack Trace
2016-10-14 18:05:43 DEBUG o.s.o.h.HibernateTransactionManager - Creating new transaction with name [org.springframework.security.oauth2.provider.token.DefaultTokenServices.createAccessToken]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''
2016-10-14 18:05:43 DEBUG o.s.o.h.HibernateTransactionManager - Opened new Session [SessionImpl(PersistenceContext[entityKeys=[],collectionKeys=[]];ActionQueue[insertions=org.hibernate.engine.spi.ExecutableList#1e09a738 updates=org.hibernate.engine.spi.ExecutableList#67d0f9f8 deletions=org.hibernate.engine.spi.ExecutableList#c60ee2d orphanRemovals=org.hibernate.engine.spi.ExecutableList#3bdd7e0f collectionCreations=org.hibernate.engine.spi.ExecutableList#683e2e2b collectionRemovals=org.hibernate.engine.spi.ExecutableList#84115ed collectionUpdates=org.hibernate.engine.spi.ExecutableList#3db934e collectionQueuedOps=org.hibernate.engine.spi.ExecutableList#5326b83c unresolvedInsertDependencies=UnresolvedEntityInsertActions[]])] for Hibernate transaction
2016-10-14 18:05:43 DEBUG o.s.o.h.HibernateTransactionManager - Preparing JDBC Connection of Hibernate Session [SessionImpl(PersistenceContext[entityKeys=[],collectionKeys=[]];ActionQueue[insertions=org.hibernate.engine.spi.ExecutableList#1e09a738 updates=org.hibernate.engine.spi.ExecutableList#67d0f9f8 deletions=org.hibernate.engine.spi.ExecutableList#c60ee2d orphanRemovals=org.hibernate.engine.spi.ExecutableList#3bdd7e0f collectionCreations=org.hibernate.engine.spi.ExecutableList#683e2e2b collectionRemovals=org.hibernate.engine.spi.ExecutableList#84115ed collectionUpdates=org.hibernate.engine.spi.ExecutableList#3db934e collectionQueuedOps=org.hibernate.engine.spi.ExecutableList#5326b83c unresolvedInsertDependencies=UnresolvedEntityInsertActions[]])]
2016-10-14 18:05:43 DEBUG o.s.o.h.HibernateTransactionManager - Exposing Hibernate transaction as JDBC transaction [com.mchange.v2.c3p0.impl.NewProxyConnection#4a1e11db]
2016-10-14 18:05:44 DEBUG o.s.o.h.HibernateTransactionManager - Initiating transaction rollback
2016-10-14 18:05:44 DEBUG o.s.o.h.HibernateTransactionManager - Rolling back Hibernate transaction on Session [SessionImpl(PersistenceContext[entityKeys=[],collectionKeys=[]];ActionQueue[insertions=org.hibernate.engine.spi.ExecutableList#1e09a738 updates=org.hibernate.engine.spi.ExecutableList#67d0f9f8 deletions=org.hibernate.engine.spi.ExecutableList#c60ee2d orphanRemovals=org.hibernate.engine.spi.ExecutableList#3bdd7e0f collectionCreations=org.hibernate.engine.spi.ExecutableList#683e2e2b collectionRemovals=org.hibernate.engine.spi.ExecutableList#84115ed collectionUpdates=org.hibernate.engine.spi.ExecutableList#3db934e collectionQueuedOps=org.hibernate.engine.spi.ExecutableList#5326b83c unresolvedInsertDependencies=UnresolvedEntityInsertActions[]])]
2016-10-14 18:05:44 DEBUG o.s.o.h.HibernateTransactionManager - Closing Hibernate Session [SessionImpl(PersistenceContext[entityKeys=[],collectionKeys=[]];ActionQueue[insertions=org.hibernate.engine.spi.ExecutableList#1e09a738 updates=org.hibernate.engine.spi.ExecutableList#67d0f9f8 deletions=org.hibernate.engine.spi.ExecutableList#c60ee2d orphanRemovals=org.hibernate.engine.spi.ExecutableList#3bdd7e0f collectionCreations=org.hibernate.engine.spi.ExecutableList#683e2e2b collectionRemovals=org.hibernate.engine.spi.ExecutableList#84115ed collectionUpdates=org.hibernate.engine.spi.ExecutableList#3db934e collectionQueuedOps=org.hibernate.engine.spi.ExecutableList#5326b83c unresolvedInsertDependencies=UnresolvedEntityInsertActions[]])] after transaction
2016-10-14 18:05:44 DEBUG o.s.w.s.m.m.a.ExceptionHandlerExceptionResolver - Resolving exception from handler [public org.springframework.http.ResponseEntity<org.springframework.security.oauth2.common.OAuth2AccessToken> org.springframework.security.oauth2.provider.endpoint.TokenEndpoint.postAccessToken(java.security.Principal,java.util.Map<java.lang.String, java.lang.String>) throws org.springframework.web.HttpRequestMethodNotSupportedException]: org.springframework.web.util.NestedServletException: Handler dispatch failed; nested exception is java.lang.StackOverflowError
2016-10-14 18:05:44 DEBUG o.s.w.s.m.m.a.ExceptionHandlerExceptionResolver - Invoking #ExceptionHandler method: public org.springframework.http.ResponseEntity<org.springframework.security.oauth2.common.exceptions.OAuth2Exception> org.springframework.security.oauth2.provider.endpoint.TokenEndpoint.handleException(java.lang.Exception) throws java.lang.Exception
2016-10-14 18:05:44 INFO o.s.s.o.p.endpoint.TokenEndpoint - Handling error: NestedServletException, Handler dispatch failed; nested exception is java.lang.StackOverflowError
2016-10-14 18:05:44 DEBUG o.s.w.s.m.m.a.HttpEntityMethodProcessor - Written [error="server_error", error_description="Handler dispatch failed; nested exception is java.lang.StackOverflowError"] as "application/json" using [org.springframework.http.converter.json.MappingJackson2HttpMessageConverter#120db352]
2016-10-14 18:05:44 DEBUG o.s.web.servlet.DispatcherServlet - Null ModelAndView returned to DispatcherServlet with name 'dispatcher': assuming HandlerAdapter completed request handling
2016-10-14 18:05:44 DEBUG o.s.web.servlet.DispatcherServlet - Successfully completed request
2016-10-14 18:05:44 DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'delegatingApplicationListener'
2016-10-14 18:05:44 DEBUG o.s.s.w.a.ExceptionTranslationFilter - Chain processed normally
2016-10-14 18:05:44 DEBUG o.s.s.w.c.SecurityContextPersistenceFilter - SecurityContextHolder now cleared, as request processing completed
Am testing from postman by passing grant_type = "password", username="user", password="pass" in body with form-urlencoded and Authorization Basic Auth(client details).
User.java
#Entity
#Table(name="xt_user_profile")
public class User extends XtremandTimeStamp implements UserDetails{
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator="user_id_seq")
#SequenceGenerator(
name="user_id_seq",
sequenceName="user_id_seq",
allocationSize=1
)
#Column(name="user_id")
private Integer userId;
#Column(name="user_name")
private String userName;
#Column(name="email_id")
private String emailId;
#Column(name="password")
private String password;
#Column(name="firstname")
private String firstName;
#Column(name="lastname")
private String lastName;
#Column(name="status")
#org.hibernate.annotations.Type(type="com.xtremand.user.bom.UserStatusType")
private UserStatus userStatus;
private String country;
private String city;
private String zip;
#Column(name="headline")
private String headLine;
private String description;
#Column(name="googlelogin")
private String googleLogin;
#Column(name="linkedinlogin")
private String linkedinLogin;
#Column(name="twitterlogin")
private String twitterLogin;
#Column(name="facebooklogin")
private String facebookLogin;
#Column(name="datereg", columnDefinition="DATETIME")
#Temporal(TemporalType.TIMESTAMP)
private Date dateReg;
#Column(name="datelastedit", columnDefinition="DATETIME")
#Temporal(TemporalType.TIMESTAMP)
private Date dateLastEdit;
#Column(name="datelastlogin", columnDefinition="DATETIME")
#Temporal(TemporalType.TIMESTAMP)
private Date dateLastLogin;
#Column(name="datelastnav", columnDefinition="DATETIME")
#Temporal(TemporalType.TIMESTAMP)
private Date dateLastNav;
#Column(name="alias")
private String alias;
public enum UserStatus{
// UNAPPROVED,APPROVE,DECLINE,BLOCK,SUSPEND,DELETE
UAPPROVED("UnApproved"),
APPROVED("APPROVED"),
DECLINE("DECLINE"),
BLOCK("BLOCK"), SUSPEND("SUSPEND"), DELETE("DELETE");
protected String status;
private UserStatus(String status) {
this.status = status;
}
public String getStatus() {
return status;
}
}
public void initialiseCommonFields(boolean isCreate, int updatedBy){
super.initialiseCommonFields(isCreate, updatedBy);
this.setDateLastEdit(new Date());
this.setDateLastLogin(new Date());
this.setDateLastNav(new Date());
this.setDateReg(new Date());
}
#ManyToMany(fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#Fetch(FetchMode.SUBSELECT)
#JoinTable(name = "xt_user_role", joinColumns = {
#JoinColumn(name = "user_id", nullable = false, updatable = false) },
inverseJoinColumns = { #JoinColumn(name = "role_id",
nullable = false, updatable = false) })
private Set<Role> roles = new HashSet<Role>(0);
#Fetch(FetchMode.SUBSELECT)
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user", cascade = {CascadeType.PERSIST, CascadeType.MERGE})
private Set<UserSubscription> userSubscriptions = new HashSet<UserSubscription>(0);
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public String getEmailId() {
return emailId;
}
public void setEmailId(String emailId) {
this.emailId = emailId;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public UserStatus getUserStatus() {
return userStatus;
}
public void setUserStatus(UserStatus userStatus) {
this.userStatus = userStatus;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getZip() {
return zip;
}
public void setZip(String zip) {
this.zip = zip;
}
public String getHeadLine() {
return headLine;
}
public void setHeadLine(String headLine) {
this.headLine = headLine;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getGoogleLogin() {
return googleLogin;
}
public void setGoogleLogin(String googleLogin) {
this.googleLogin = googleLogin;
}
public String getLinkedinLogin() {
return linkedinLogin;
}
public void setLinkedinLogin(String linkedinLogin) {
this.linkedinLogin = linkedinLogin;
}
public String getTwitterLogin() {
return twitterLogin;
}
public void setTwitterLogin(String twitterLogin) {
this.twitterLogin = twitterLogin;
}
public String getFacebookLogin() {
return facebookLogin;
}
public void setFacebookLogin(String facebookLogin) {
this.facebookLogin = facebookLogin;
}
public Date getDateReg() {
return dateReg;
}
public void setDateReg(Date dateReg) {
this.dateReg = dateReg;
}
public Date getDateLastEdit() {
return dateLastEdit;
}
public void setDateLastEdit(Date dateLastEdit) {
this.dateLastEdit = dateLastEdit;
}
public Date getDateLastLogin() {
return dateLastLogin;
}
public void setDateLastLogin(Date dateLastLogin) {
this.dateLastLogin = dateLastLogin;
}
public Date getDateLastNav() {
return dateLastNav;
}
public void setDateLastNav(Date dateLastNav) {
this.dateLastNav = dateLastNav;
}
public Set<Role> getRoles() {
return roles;
}
public void setRoles(Set<Role> roles) {
this.roles = roles;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getAlias() {
return alias;
}
public void setAlias(String alias) {
this.alias = alias;
}
public Set<UserSubscription> getUserSubscriptions() {
return this.userSubscriptions;
}
public void setUserSubscriptions(Set<UserSubscription> userSubscriptions) {
this.userSubscriptions = userSubscriptions;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
// TODO Auto-generated method stub
return getRoles();
}
#Override
public boolean isAccountNonExpired() {
// TODO Auto-generated method stub
return true;
}
#Override
public boolean isAccountNonLocked() {
// TODO Auto-generated method stub
return true;
}
#Override
public boolean isCredentialsNonExpired() {
// TODO Auto-generated method stub
return true;
}
#Override
public boolean isEnabled() {
// TODO Auto-generated method stub
return true;
}
#Override
public String getUsername() {
return getUsername();
}
}

Please try to add next configuration, might help you:
endpoints.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
This will allow both methods for authorization.
And add:
oauthServer.allowFormAuthenticationForClients() // it should allow authorize from form submitting.
Also check that you granted authorizedGrantTypes "password" for your clients in db.
In your User.class check method:
#Override
public String getUsername() {
return getUsername();
}
it's references on himself and when spring tries get username raise StackOverflowException. Please, change implementation of this method, it will fix your StackOverflowException
.

Please pay attention to the method name is authenticationManagerBean, rather than the authenticationManager
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}

Related

Error creating bean with name 'registrationListener': Unsatisfied dependency expressed through field 'service' (Showing this after building war)

These codes runs fine on my STS but not running after making jar/war.
also tried with deploying on external tomcat. what is making the differace after making the war?? i am getting lost.
This is the registration listener class
#Component
public class RegistrationListener implements ApplicationListener<OnRegistrationCompleteEvent> {
#Autowired
private IUserService service;
#Autowired
private MessageSource messages;
#Autowired
private JavaMailSender mailSender;
#Autowired
private Environment env;
// API
#Override
public void onApplicationEvent(final OnRegistrationCompleteEvent event) {
this.confirmRegistration(event);
}
private void confirmRegistration(final OnRegistrationCompleteEvent event) {
final User user = event.getUser();
final String token = UUID.randomUUID().toString();
service.createVerificationTokenForUser(user, token);
final SimpleMailMessage email = constructEmailMessage(event, user, token);
mailSender.send(email);
}
//
private SimpleMailMessage constructEmailMessage(final OnRegistrationCompleteEvent event, final User user, final String token) {
final String recipientAddress = user.getEmail();
final String subject = "Registration Confirmation";
final String confirmationUrl = event.getAppUrl() + "/registrationConfirm?token=" + token;
String message= "Your SECRET for Google Authenticator: "+user.getSecret()+"\n";
message+= messages.getMessage("message.regSuccLink", null, "You registered successfully. To confirm your registration, please click on the below link.", event.getLocale());
final SimpleMailMessage email = new SimpleMailMessage();
email.setTo(recipientAddress);
email.setSubject(subject);
email.setText(message + " \r\n" + confirmationUrl);
email.setFrom(env.getProperty("support.email"));
return email;
}
}
I have tried with #Service and without #Service tag
#Service
public interface IUserService {
User registerNewUserAccount(UserDto accountDto);
User getUser(String verificationToken);
void saveRegisteredUser(User user);
void deleteUser(User user);
void createVerificationTokenForUser(User user, String token);
VerificationToken getVerificationToken(String VerificationToken);
VerificationToken generateNewVerificationToken(String token);
void createPasswordResetTokenForUser(User user, String token);
User findUserByEmail(String email);
PasswordResetToken getPasswordResetToken(String token);
Optional<User> getUserByPasswordResetToken(String token);
Optional<User> getUserByID(long id);
void changeUserPassword(User user, String password);
boolean checkIfValidOldPassword(User user, String password);
String validateVerificationToken(String token);
String generateQRUrl(User user) throws UnsupportedEncodingException;
User updateUser2FA(boolean use2FA);
List<String> getUsersFromSessionRegistry();
NewLocationToken isNewLoginLocation(String username, String ip);
String isValidNewLocationToken(String token);
void addUserLocation(User user, String ip);
}
is this where the problem is happening?
#Service
#Transactional
public class UserService implements IUserService {
#Autowired
private UserRepository userRepository;
#Autowired
private VerificationTokenRepository tokenRepository;
#Autowired
private PasswordResetTokenRepository passwordTokenRepository;
#Autowired
private PasswordEncoder passwordEncoder;
#Autowired
private RoleRepository roleRepository;
#Autowired
private SessionRegistry sessionRegistry;
#Autowired
#Qualifier("GeoIPCountry")
private DatabaseReader databaseReader;
#Autowired
private UserLocationRepository userLocationRepository;
#Autowired
private NewLocationTokenRepository newLocationTokenRepository;
#Autowired
private Environment env;
public static final String TOKEN_INVALID = "invalidToken";
public static final String TOKEN_EXPIRED = "expired";
public static final String TOKEN_VALID = "valid";
public static String QR_PREFIX = "https://chart.googleapis.com/chart?chs=200x200&chld=M%%7C0&cht=qr&chl=";
public static String APP_NAME = "SpringRegistration";
// API
#Override
public User registerNewUserAccount(final UserDto accountDto) {
if (emailExists(accountDto.getEmail())) {
throw new UserAlreadyExistException("There is an account with that email address: " + accountDto.getEmail());
}
final User user = new User();
user.setFirstName(accountDto.getFirstName());
user.setLastName(accountDto.getLastName());
user.setPassword(passwordEncoder.encode(accountDto.getPassword()));
user.setEmail(accountDto.getEmail());
user.setUsing2FA(accountDto.isUsing2FA());
user.setRoles(Arrays.asList(roleRepository.findByName("ROLE_USER")));
return userRepository.save(user);
}
#Override
public User getUser(final String verificationToken) {
final VerificationToken token = tokenRepository.findByToken(verificationToken);
if (token != null) {
return token.getUser();
}
return null;
}
#Override
public VerificationToken getVerificationToken(final String VerificationToken) {
return tokenRepository.findByToken(VerificationToken);
}
#Override
public void saveRegisteredUser(final User user) {
userRepository.save(user);
}
#Override
public void deleteUser(final User user) {
final VerificationToken verificationToken = tokenRepository.findByUser(user);
if (verificationToken != null) {
tokenRepository.delete(verificationToken);
}
final PasswordResetToken passwordToken = passwordTokenRepository.findByUser(user);
if (passwordToken != null) {
passwordTokenRepository.delete(passwordToken);
}
userRepository.delete(user);
}
#Override
public void createVerificationTokenForUser(final User user, final String token) {
final VerificationToken myToken = new VerificationToken(token, user);
tokenRepository.save(myToken);
}
#Override
public VerificationToken generateNewVerificationToken(final String existingVerificationToken) {
VerificationToken vToken = tokenRepository.findByToken(existingVerificationToken);
vToken.updateToken(UUID.randomUUID()
.toString());
vToken = tokenRepository.save(vToken);
return vToken;
}
#Override
public void createPasswordResetTokenForUser(final User user, final String token) {
final PasswordResetToken myToken = new PasswordResetToken(token, user);
passwordTokenRepository.save(myToken);
}
#Override
public User findUserByEmail(final String email) {
return userRepository.findByEmail(email);
}
#Override
public PasswordResetToken getPasswordResetToken(final String token) {
return passwordTokenRepository.findByToken(token);
}
#Override
public Optional<User> getUserByPasswordResetToken(final String token) {
return Optional.ofNullable(passwordTokenRepository.findByToken(token) .getUser());
}
#Override
public Optional<User> getUserByID(final long id) {
return userRepository.findById(id);
}
#Override
public void changeUserPassword(final User user, final String password) {
user.setPassword(passwordEncoder.encode(password));
userRepository.save(user);
}
#Override
public boolean checkIfValidOldPassword(final User user, final String oldPassword) {
return passwordEncoder.matches(oldPassword, user.getPassword());
}
#Override
public String validateVerificationToken(String token) {
final VerificationToken verificationToken = tokenRepository.findByToken(token);
if (verificationToken == null) {
return TOKEN_INVALID;
}
final User user = verificationToken.getUser();
final Calendar cal = Calendar.getInstance();
if ((verificationToken.getExpiryDate()
.getTime() - cal.getTime()
.getTime()) <= 0) {
tokenRepository.delete(verificationToken);
return TOKEN_EXPIRED;
}
user.setEnabled(true);
// tokenRepository.delete(verificationToken);
userRepository.save(user);
return TOKEN_VALID;
}
#Override
public String generateQRUrl(User user) throws UnsupportedEncodingException {
return QR_PREFIX + URLEncoder.encode(String.format("otpauth://totp/%s:%s?secret=%s&issuer=%s", APP_NAME, user.getEmail(), user.getSecret(), APP_NAME), "UTF-8");
}
#Override
public User updateUser2FA(boolean use2FA) {
final Authentication curAuth = SecurityContextHolder.getContext()
.getAuthentication();
User currentUser = (User) curAuth.getPrincipal();
currentUser.setUsing2FA(use2FA);
currentUser = userRepository.save(currentUser);
final Authentication auth = new UsernamePasswordAuthenticationToken(currentUser, currentUser.getPassword(), curAuth.getAuthorities());
SecurityContextHolder.getContext()
.setAuthentication(auth);
return currentUser;
}
private boolean emailExists(final String email) {
return userRepository.findByEmail(email) != null;
}
#Override
public List<String> getUsersFromSessionRegistry() {
return sessionRegistry.getAllPrincipals()
.stream()
.filter((u) -> !sessionRegistry.getAllSessions(u, false)
.isEmpty())
.map(o -> {
if (o instanceof User) {
return ((User) o).getEmail();
} else {
return o.toString()
;
}
}).collect(Collectors.toList());
}
#Override
public NewLocationToken isNewLoginLocation(String username, String ip) {
if(!isGeoIpLibEnabled()) {
return null;
}
try {
final InetAddress ipAddress = InetAddress.getByName(ip);
final String country = databaseReader.country(ipAddress)
.getCountry()
.getName();
System.out.println(country + "====****");
final User user = userRepository.findByEmail(username);
final UserLocation loc = userLocationRepository.findByCountryAndUser(country, user);
if ((loc == null) || !loc.isEnabled()) {
return createNewLocationToken(country, user);
}
} catch (final Exception e) {
return null;
}
return null;
}
#Override
public String isValidNewLocationToken(String token) {
final NewLocationToken locToken = newLocationTokenRepository.findByToken(token);
if (locToken == null) {
return null;
}
UserLocation userLoc = locToken.getUserLocation();
userLoc.setEnabled(true);
userLoc = userLocationRepository.save(userLoc);
newLocationTokenRepository.delete(locToken);
return userLoc.getCountry();
}
#Override
public void addUserLocation(User user, String ip) {
if(!isGeoIpLibEnabled()) {
return;
}
try {
final InetAddress ipAddress = InetAddress.getByName(ip);
final String country = databaseReader.country(ipAddress)
.getCountry()
.getName();
UserLocation loc = new UserLocation(country, user);
loc.setEnabled(true);
userLocationRepository.save(loc);
} catch (final Exception e) {
throw new RuntimeException(e);
}
}
private boolean isGeoIpLibEnabled() {
return Boolean.parseBoolean(env.getProperty("geo.ip.lib.enabled"));
}
private NewLocationToken createNewLocationToken(String country, User user) {
UserLocation loc = new UserLocation(country, user);
loc = userLocationRepository.save(loc);
final NewLocationToken token = new NewLocationToken(UUID.randomUUID()
.toString(), loc);
return newLocationTokenRepository.save(token);
}
}
This is my error from jar,war,extarnal tomcat
o.s.b.f.UnsatisfiedDependencyException: Error creating bean with name 'registrationListener': Unsatisfied dependency expressed through field 'service'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'userService': Unsatisfied dependency expressed through field 'databaseReader'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'GeoIPCountry' defined in class path resource [com/birol/spring/SecSecurityConfig.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.maxmind.geoip2.DatabaseReader]: Factory method 'databaseReader' threw exception; nested exception is java.io.FileNotFoundException: src\main\resources\maxmind\GeoLite2-Country.mmdb (The system cannot find the path specified)
at o.s.b.f.a.GbHi8bXhDyMP2m1NDUSiYmznAJdJzX36DDor$AutowiredFieldElement.resolveFieldValue(GbHi8bXhDyMP2m1NDUSiYmznAJdJzX36DDor.java:659)
at o.s.b.f.a.GbHi8bXhDyMP2m1NDUSiYmznAJdJzX36DDor$AutowiredFieldElement.inject(GbHi8bXhDyMP2m1NDUSiYmznAJdJzX36DDor.java:639)
at o.s.b.f.a.InjectionMetadata.inject(InjectionMetadata.java:119)
at o.s.b.f.a.GbHi8bXhDyMP2m1NDUSiYmznAJdJzX36DDor.postProcessProperties(GbHi8bXhDyMP2m1NDUSiYmznAJdJzX36DDor.java:399)
at o.s.b.f.s.GbHi8bXhDyMP2m1NDUSiYmznAJdJzX36DD.populateBean(GbHi8bXhDyMP2m1NDUSiYmznAJdJzX36DD.java:1431)
... 46 frames truncated

Jwt in Springboot v2.4.5

I have this RestController:
#RestController
#Slf4j
public class AuthenticationRestController {
#Value("${jwt.header}")
private String tokenHeader;
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private JwtTokenUtil jwtTokenUtil;
#Autowired
private UserSecurityService userSecurityService;
#Autowired
private EmailService emailService;
#PostMapping(path = "/api/v1/auth", consumes = "application/json", produces = "application/json")
public ResponseEntity<JwtAuthenticationResponse>
createAuthenticationToken( #RequestBody JwtAuthenticationRequest authenticationRequest,
HttpServletRequest request) throws AuthenticationException {
LOG.info("authenticating {} " , authenticationRequest.getUsername());
authenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword());
...
/**
* Authenticates the user. If something is wrong, an {#link AuthenticationException} will be thrown
*/
private void authenticate(String username, String password) {
Objects.requireNonNull(username);
Objects.requireNonNull(password);
try {
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
} catch (DisabledException e) {
e.printStackTrace();
throw new AuthenticationException("User is disabled!", e);
} catch (BadCredentialsException e) {
throw new AuthenticationException("Bad credentials!", e);
} catch (Exception e) {
e.printStackTrace();
}
}
}
but I have this error when logging:
org.springframework.security.authentication.DisabledException: User is disabled
at org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider$DefaultPreAuthenticationChecks.check(AbstractUserDetailsAuthenticationProvider.java:331)
at org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider.authenticate(AbstractUserDetailsAuthenticationProvider.java:146)
at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:182)
at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:201)
at org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter$AuthenticationManagerDelegator.authenticate(WebSecurityConfigurerAdapter.java:518)
at com.kispackp.security.controllers.AuthenticationRestController.authenticate(AuthenticationRestController.java:138)
and
#Entity
#Table(name="t_user")
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonIgnoreProperties(ignoreUnknown = true)
#Data
#Builder
#AllArgsConstructor
#NoArgsConstructor
public class User implements Serializable, UserDetails {
/** The Serial Version UID for Serializable classes. */
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(unique = true)
#JsonIgnore
private String username;
#JsonIgnore
private String password;
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
}
This error happens when the user is not enabled. The interface UserDetails has a method called isEnabled, and it's checked when authenticating the user.
AbstractUserDetailsAuthenticationProvider.java
...
if (!user.isEnabled()) {
AbstractUserDetailsAuthenticationProvider.this.logger
.debug("Failed to authenticate since user account is disabled");
throw new DisabledException(AbstractUserDetailsAuthenticationProvider.this.messages
.getMessage("AbstractUserDetailsAuthenticationProvider.disabled", "User is disabled"));
}
...
You should implement it and return true in the case the user is enabled, like so:
public class User implements Serializable, UserDetails {
... your current fields and methods
#Override
public boolean isEnabled() {
return true;
}
}

Property Source not getting autowired

I am using external property sources for some reason one of the external property source is not getting autowired, receiving null pointer while creating the authenticaion bean
Error Message
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.filechecker.check.Authenticator]: Constructor threw exception; nested exception is java.lang.NullPointerException
Caused by: java.lang.NullPointerException: null
at com.filechecker.check.Authenticator.(Authenticator.java:30) ~[classes!/:0.0.1-SNAPSHOT]
Line no 30:
String username = emailPropertyConfig.getEmailConfig().getUsername();
not working one
#Component
#PropertySource(value="${email.app.properties}",ignoreResourceNotFound = false)
#ConfigurationProperties
public class PropertyEmailConfiguration {
private EmailConfig emailConfig = new EmailConfig();
public EmailConfig getEmailConfig() {
return emailConfig;
}
public void setEmailConfig(EmailConfig emailConfig) {
this.emailConfig = emailConfig;
}
}
#Component
public class Authenticator extends javax.mail.Authenticator {
#Autowired
PropertyEmailConfiguration emailPropertyConfig;
#Autowired
CipherCrypt cipherCrypt;
private PasswordAuthentication authentication;
public Authenticator() throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException, UnsupportedEncodingException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeySpecException {
String username = emailPropertyConfig.getEmailConfig().getUsername();
String password = cipherCrypt.decrypt(emailPropertyConfig.getEmailConfig().getEncryptPassword());
authentication = new PasswordAuthentication(username, password);
}
#Override
protected PasswordAuthentication getPasswordAuthentication() {
return authentication;
}
}
working one
#Component
#PropertySource(value="${external.app.properties}", ignoreResourceNotFound = true)
#ConfigurationProperties
public class PropertyConfiguration {
private List<FileStructureConfig> fileStructureConfig = new ArrayList();
private List<EmailSendingProperties> emailSendingProperties = new ArrayList();
public List<FileStructureConfig> getFileStructureConfig() {
return fileStructureConfig;
}
public void setFileStructureConfig(List<FileStructureConfig> fileStructureConfig) {
this.fileStructureConfig = fileStructureConfig;
}
public List<EmailSendingProperties> getEmailSendingProperties() {
return emailSendingProperties;
}
public void setEmailSendingProperties(List<EmailSendingProperties> emailSendingProperties) {
this.emailSendingProperties = emailSendingProperties;
}
}
You are trying to access an #Autowired property within the constructor. The property cannot be autowired at this stage.
In order for Spring to "bake the bean", Spring has to create your object (using your constructor), and only afterwards it applys the autowiring mechanism to inject emailPropertyConfig and cipherCrypt. Therefore you cannot access the two #Autowired properties within the constructor.
If you need to extract some values from the emailPropertyConfig or cipherCrypt you can do it in #PostConstruct
#Component
public class Authenticator {
#Autowired
PropertyEmailConfiguration emailPropertyConfig;
#Autowired
CipherCrypt cipherCrypt;
private PasswordAuthentication authentication;
#PostConstruct
void init() throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException, UnsupportedEncodingException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeySpecException {
String username = emailPropertyConfig.getEmailConfig().getUsername();
String password = cipherCrypt.decrypt(emailPropertyConfig.getEmailConfig().getEncryptPassword());
authentication = new PasswordAuthentication(username, password);
}
#Override
protected PasswordAuthentication getPasswordAuthentication() {
return authentication;
}
}
or use constructor injection:
#Component
public class Authenticator {
PropertyEmailConfiguration emailPropertyConfig;
CipherCrypt cipherCrypt;
private PasswordAuthentication authentication;
public Authenticator(PropertyEmailConfiguration emailPropertyConfig, CipherCrypt cipherCrypt) throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException, UnsupportedEncodingException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeySpecException {
String username = emailPropertyConfig.getEmailConfig().getUsername();
String password = cipherCrypt.decrypt(emailPropertyConfig.getEmailConfig().getEncryptPassword());
authentication = new PasswordAuthentication(username, password);
}
#Override
protected PasswordAuthentication getPasswordAuthentication() {
return authentication;
}
}

Spring Boot - Test - Validator: Invalid target for Validator

I'm getting the following error when I'm trying to run a test:
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.IllegalStateException: Invalid target for Validator [userCreateFormValidator bean]: com.ar.empresa.forms.UserCreateForm#15c3585
Caused by: java.lang.IllegalStateException: Invalid target for Validator [userCreateFormValidator bean]: com.ar.empresa.forms.UserCreateForm#15c3585
at org.springframework.validation.DataBinder.assertValidators(DataBinder.java:567)
at org.springframework.validation.DataBinder.addValidators(DataBinder.java:578)
at com.ar.empresa.controllers.UserController.initBinder(UserController.java:36)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
The code is:
Controller:
#Controller
public class UserController {
private UserService userService;
private UserCreateFormValidator userCreateFormValidator;
#Autowired
public UserController(UserService userService, UserCreateFormValidator userCreateFormValidator) {
this.userService = userService;
this.userCreateFormValidator = userCreateFormValidator;
}
#InitBinder("form")
public void initBinder(WebDataBinder binder) {
binder.addValidators(userCreateFormValidator);
}
#PreAuthorize("hasAuthority('ADMIN')")
#RequestMapping(value = "/user/create", method = RequestMethod.GET)
public ModelAndView getUserCreatePage() {
return new ModelAndView("user_create", "form", new UserCreateForm());
}
#PreAuthorize("hasAuthority('ADMIN')")
#RequestMapping(value = "/user/create", method = RequestMethod.POST)
public String handleUserCreateForm(#Valid #ModelAttribute("form") UserCreateForm form, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return "user_create";
}
try {
userService.create(form);
} catch (DataIntegrityViolationException e) {
bindingResult.reject("email.exists", "Email already exists");
return "user_create";
}
return "redirect:/users";
}
}
Validator:
#Component
public class UserCreateFormValidator implements Validator {
private final UserService userService;
#Autowired
public UserCreateFormValidator(UserService userService) {
this.userService = userService;
}
#Override
public boolean supports(Class<?> clazz) {
return clazz.equals(UserCreateForm.class);
}
#Override
public void validate(Object target, Errors errors) {
UserCreateForm form = (UserCreateForm) target;
validatePasswords(errors, form);
validateEmail(errors, form);
}
private void validatePasswords(Errors errors, UserCreateForm form) {
if (!form.getPassword().equals(form.getPasswordRepeated())) {
errors.reject("password.no_match", "Passwords do not match");
}
}
private void validateEmail(Errors errors, UserCreateForm form) {
if (userService.getUserByEmail(form.getEmail()).isPresent()) {
errors.reject("email.exists", "User with this email already exists");
}
}
}
UserCreateForm:
public class UserCreateForm {
#NotEmpty
private String email = "";
#NotEmpty
private String password = "";
#NotEmpty
private String passwordRepeated = "";
#NotNull
private Role role = Role.USER;
public String getEmail() {
return email;
}
public String getPassword() {
return password;
}
public String getPasswordRepeated() {
return passwordRepeated;
}
public Role getRole() {
return role;
}
public void setEmail(String email) {
this.email = email;
}
public void setPassword(String password) {
this.password = password;
}
public void setPasswordRepeated(String passwordRepeated) {
this.passwordRepeated = passwordRepeated;
}
public void setRole(Role role) {
this.role = role;
}
}
Test:
#RunWith(SpringRunner.class)
#SpringBootTest
public class UserControllerTest {
private MockMvc mockMvc;
private MediaType contentType = new MediaType(APPLICATION_JSON.getType(),
APPLICATION_JSON.getSubtype(),
Charset.forName("utf8"));
#MockBean
private UserService userService;
#MockBean
private UserCreateFormValidator userCreateFormValidator;
#Autowired
FilterChainProxy springSecurityFilterChain;
#Before
public void setup() {
this.mockMvc = MockMvcBuilders.standaloneSetup(new UserController(userService,userCreateFormValidator)).apply(SecurityMockMvcConfigurers.springSecurity(springSecurityFilterChain)).build();
}
#Test
#WithMockUser(username="user",
password="password",
roles="ADMIN")
public void homePage_authenticatedUser() throws Exception {
mockMvc.perform(get("/user/create"))
.andExpect(status().isOk())
.andExpect(view().name("user_create"));
}
}
I don't know why, because it is a GET method, so it don't have to validate it.
Thanks! :)
You got this exception because you didn't mock the behaviour of public boolean supports(Class<?> clazz) method on your userCreateFormValidator #Mockbean.
If you take a look at the code of org.springframework.validation.DataBinder.assertValidators(DataBinder.java) from the log you posted, you can find there how the validators are processed and how java.lang.IllegalStateException is thrown. In Spring 4.3.8, it looks like this
if(validator != null && this.getTarget() != null && !validator.supports(this.getTarget().getClass())) {
throw new IllegalStateException("Invalid target for Validator [" + validator + "]: " + this.getTarget());
}
You didn't mock supports method of the validator and returns false by default, causing Spring code above throw the IllegalStateException.
TLDR, just give me solution:
You have to mock supports method on your validator. Add following to #Before or #BeforeClass method.
when(requestValidatorMock.supports(any())).thenReturn(true);
I cant comment on the correct answer but his solution worked:
Here is what I had to do for this exact error.
//Imports
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
#MockBean
ApiValidationRouter apiValidationRouter;
#Before
public void beforeClass() throws Exception {
when(apiValidationRouter.supports(any())).thenReturn(true);
}

Spring Boot - Custom Filter/Stateless auth and #Secured annotation

I have been struggling with this for over 2 hours with no luck after reading around 10 different articles.
I want to use my custom filter to perform stateless authorization based on roles from DB and #Secured annotation.
Let's start with my example account identified in database by api-key: '6c1bb23e-e24c-41a5-8f12-72d3db0a6979'.
He has following String role fetched from DB: 'FREE_USER_ROLE'.
My filter:
public class ApiKeyAuthFilter extends OncePerRequestFilter {
private final AccountService accountService;
private final GlobalExceptionsAdvice exceptionAdvice;
private static final String API_KEY_HEADER_FIELD = "X-AUTH-KEY";
public static final List<String> NON_AUTH_END_POINTS
= Collections.unmodifiableList(Arrays.asList("/Accounts", "/Accounts/Login"));
AntPathMatcher pathMatcher = new AntPathMatcher();
public ApiKeyAuthFilter(AccountService accountService, GlobalExceptionsAdvice exceptionAdvice) {
this.accountService = accountService;
this.exceptionAdvice = exceptionAdvice;
}
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain fc) throws ServletException, IOException {
Optional authKey = Optional.ofNullable(request.getHeader(API_KEY_HEADER_FIELD));
if (!authKey.isPresent()) {
sendForbiddenErrorMessage(response);
} else {
try {
AccountDTO account = accountService.findByApiKey(authKey.get().toString());
Set<GrantedAuthority> roles = new HashSet();
account.getRoles().forEach((singleRole) -> roles.add(new SimpleGrantedAuthority(singleRole.getName())));
Authentication accountAuth = new UsernamePasswordAuthenticationToken(account.getEmail(), account.getApiKey(),
roles);
SecurityContextHolder.getContext().setAuthentication(accountAuth);
SecurityContextHolder.getContext().getAuthentication().getAuthorities().forEach((role) -> {
System.out.println(role.getAuthority());
});
fc.doFilter(request, response);
} catch (ElementDoesNotExistException ex) {
//TODO: Add logging that user tried to falsy authenticate
sendForbiddenErrorMessage(response);
}
}
}
#Override
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
return NON_AUTH_END_POINTS.stream().anyMatch(p -> {
return pathMatcher.match(p, request.getServletPath())
&& request.getMethod().equals("POST");
});
}
private void sendForbiddenErrorMessage(HttpServletResponse resp) throws IOException {
ObjectMapper mapper = new ObjectMapper();
ErrorDetail error = exceptionAdvice.handleAccessDeniedException();
resp.setStatus(HttpServletResponse.SC_FORBIDDEN);
resp.setContentType("application/json");
resp.setCharacterEncoding("UTF-8");
resp.getWriter().write(mapper.writeValueAsString(error));
}
As You can see I am using X-AUTH-KEY header to retrieve provided apiKey, then I fetch info from Database based on that key and assign appropiate roles into SecurityContextHolder. Until that point everything works. I am sending poper apiKey, DB returns 'FREE_USER_ROLE'.
My #Configuration annotation class. (I bet something is wrong here but I can not tell what):
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true)
public class ApiKeySecurityConfiguration extends WebSecurityConfigurerAdapter {
AccountService accountService;
GlobalExceptionsAdvice exceptionAdvice;
#Autowired
public ApiKeySecurityConfiguration(AccountService accountService, GlobalExceptionsAdvice exceptionAdvice) {
this.accountService = accountService;
this.exceptionAdvice = exceptionAdvice;
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
httpSecurity.csrf().disable();
httpSecurity.authorizeRequests().anyRequest().authenticated();
httpSecurity.addFilterBefore(new ApiKeyAuthFilter(accountService, exceptionAdvice), UsernamePasswordAuthenticationFilter.class);
}
}
And final piece of puzzle - Controller that uses #Secured:
#RestController
#RequestMapping("/Accounts")
public class AccountsResource {
#Secured({"FREE_USER_ROLE"})
#PutMapping()
public boolean testMethod() {
return true;
}
}
I have tried with both 'FREE_USER_ROLE' and 'ROLE_FREE_USER_ROLE'. Everytime I get 403 Forbidden.
So I have spent some more time yesterday on that and I have managed to get it working with #PreAuthorize annotation. Posting code below because it may be useful to someone in future.
Filter:
#Component
public class ApiKeyAuthFilter extends OncePerRequestFilter {
private final AccountService accountService;
private final GlobalExceptionsAdvice exceptionAdvice;
private static final String API_KEY_HEADER_FIELD = "X-AUTH-KEY";
public static final List<String> NON_AUTH_END_POINTS
= Collections.unmodifiableList(Arrays.asList("/Accounts", "/Accounts/Login"));
AntPathMatcher pathMatcher = new AntPathMatcher();
#Autowired
public ApiKeyAuthFilter(AccountService accountService, GlobalExceptionsAdvice exceptionAdvice) {
this.accountService = accountService;
this.exceptionAdvice = exceptionAdvice;
}
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain fc) throws ServletException, IOException {
Optional authKey = Optional.ofNullable(request.getHeader(API_KEY_HEADER_FIELD));
if (!authKey.isPresent()) {
sendForbiddenErrorMessage(response);
} else {
try {
AccountDTO account = accountService.findByApiKey(authKey.get().toString());
Set<GrantedAuthority> roles = new HashSet();
account.getRoles().forEach((singleRole) -> roles.add(new SimpleGrantedAuthority(singleRole.getName())));
Authentication accountAuth = new UsernamePasswordAuthenticationToken(account.getEmail(), account.getApiKey(),
roles);
SecurityContextHolder.getContext().setAuthentication(accountAuth);
SecurityContextHolder.getContext().getAuthentication().getAuthorities().forEach((role) -> {
System.out.println(role.getAuthority());
});
fc.doFilter(request, response);
} catch (ElementDoesNotExistException ex) {
//TODO: Add logging that user tried to falsy authenticate
sendForbiddenErrorMessage(response);
}
}
}
#Override
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
return NON_AUTH_END_POINTS.stream().anyMatch(p -> {
return pathMatcher.match(p, request.getServletPath())
&& request.getMethod().equals("POST");
});
}
private void sendForbiddenErrorMessage(HttpServletResponse resp) throws IOException {
ObjectMapper mapper = new ObjectMapper();
ErrorDetail error = exceptionAdvice.handleAccessDeniedException();
resp.setStatus(HttpServletResponse.SC_FORBIDDEN);
resp.setContentType("application/json");
resp.setCharacterEncoding("UTF-8");
resp.getWriter().write(mapper.writeValueAsString(error));
}
}
Configuration file:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class ApiKeySecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.
csrf().disable().
sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
Secured methods and methods allowed for anybody to use:
#RestController
#RequestMapping("/Accounts")
public class AccountsResource {
#PostMapping
#PreAuthorize("permitAll()")
public boolean forAll() {
return true;
}
#PutMapping()
#PreAuthorize("hasAuthority('FREE_USER_ROLE')")
public boolean testMethod() {
return true;
}
}

Resources