I was wondering what I am doing wrong here to authenticate a user. I have an application where the user goes through several steps to activate their account, and upon doing so I would like to bypass the login form and take them directly to their dashboard.
Here is what my automated login function looks like:
protected void automatedLogin(String username, String password, HttpServletRequest request) {
try {
// Must be called from request filtered by Spring Security, otherwise SecurityContextHolder is not updated
CustomUserDetailsService udService = new CustomUserDetailsService(userDAO, request);
UserDetails uDetails = udService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(uDetails, password);
token.setDetails(new WebAuthenticationDetails(request));
DaoAuthenticationProvider authenticator = new DaoAuthenticationProvider();
Authentication authentication = authenticator.authenticate(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
} catch (Exception e) {
e.printStackTrace();
SecurityContextHolder.getContext().setAuthentication(null);
}
}
I must use the DaoAuthenticationProvider class as my authentication provider. I have verified that I am getting a UserDetails model containing the correct credentials, ID, authority roles, etc.
When it calls the authenticate method I run into a Null Pointer somewhere along the way in the DaoAuthenticationProvider class:
org.springframework.security.authentication.AuthenticationServiceException
at
org.springframework.security.authentication.dao.DaoAuthenticationProvider.retrieveUser(DaoAuthenticationProvider.java:109)
at
org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider.authenticate(AbstractUserDetailsAuthenticationProvider.java:132)
at
com.bosch.actions.BaseController.doAutoLogin(BaseController.java:659)
. . . Caused by: java.lang.NullPointerException at
org.springframework.security.authentication.dao.DaoAuthenticationProvider.retrieveUser(DaoAuthenticationProvider.java:101)
I'm really not sure what is null, as I don't have the source code available.
Edit
I was able to find the source code here - https://github.com/SpringSource/spring-security/blob/master/core/src/main/java/org/springframework/security/authentication/dao/DaoAuthenticationProvider.java
I was able to get around the Null Pointer by explicitly setting the UserDetailsService on the object:
authenticator.setUserDetailsService(udService);
But now I get bad credentials exception when I know the password provided is correct, because I've seen it in the debugger in the UserDetails object set earlier in the code.
org.springframework.security.authentication.BadCredentialsException:
Bad credentials at
org.springframework.security.authentication.dao.DaoAuthenticationProvider.additionalAuthenticationChecks(DaoAuthenticationProvider.java:87)
at
org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider.authenticate(AbstractUserDetailsAuthenticationProvider.java:149)
I was able to get the authentication working by piecing together all of the properties defined in the spring bean definition and setting them programmatically on the DaoAuthenticationProvider object. Looking back this seems like it may have been a silly question, but I hope it helps someone!
Corrected Code:
protected void automatedLogin(String username, String password, HttpServletRequest request) {
try {
// Must be called from request filtered by Spring Security, otherwise SecurityContextHolder is not updated
CustomUserDetailsService udService = new CustomUserDetailsService(userDAO, request);
CustomMd5PasswordEncoder passEncoder = new CustomMd5PasswordEncoder();
ReflectionSaltSource saltSource = new ReflectionSaltSource();
saltSource.setUserPropertyToUse("salt");
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, password);
token.setDetails(new WebAuthenticationDetails(request));
DaoAuthenticationProvider authenticator = new DaoAuthenticationProvider();
authenticator.setUserDetailsService(udService);
authenticator.setPasswordEncoder(passEncoder);
authenticator.setSaltSource(saltSource);
Authentication authentication = authenticator.authenticate(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
} catch (Exception e) {
e.printStackTrace();
SecurityContextHolder.getContext().setAuthentication(null);
}
}
Related
Spring Security 4.2.4. Java 8
I need to change the permissions of users without re-logging them. It's my service:
#Component
public class AuthoritiesUpdater {
private final UserRoleService userRoleService;
#Autowired
public AuthoritiesUpdater(UserRoleService userRoleService) {
this.userRoleService = userRoleService;
}
public void update(User user) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
List<UserRole> userRoles = userRoleService.findByUser(user);
List<GrantedAuthority> actualAuthorities = userRoles.stream().map(userRole -> new
SimpleGrantedAuthority(userRole.getRole())).collect(Collectors.toList());
Authentication newAuth = new
UsernamePasswordAuthenticationToken(auth.getPrincipal(), auth.getCredentials(), actualAuthorities);
SecurityContextHolder.getContext().setAuthentication(newAuth);
}
}
But there is a trouble. I need to change roles for any user. Let's say I'm manager and I want to change roles for test user. I need to reload the permissions after saving configuration and test user will have new roles when he press F5 button and reload the page without re-login.
But SecurityContextHolder.getContext().getAuthentication(); returns authentication object only for current user (manager). I need to get authentication object for changed user to call new
UsernamePasswordAuthenticationToken(auth.getPrincipal(), auth.getCredentials(), actualAuthorities);.
Can I get authentication object for any user or can I solve this problem in another way? Maybe I can get authentication object uses session registry?
P.S.: Doing an expired session for user isn't a good option.
Excellent answer, thanks #virkom!
For newer users, the steps are as follow:
Create a list of authorities you want the user to have (including the previous one(s) they had). In this case (below) I created a set so they would be unique.
Create an authentication object which matches the user you want to authenticate with their new authorities. In this case, I used a user who was in memory to simplify the example, but database authentication is preferred!
Update the security context with the new authentication information. This will have the effect of a refresh of the user's credentials.
A code snippet is shown below:
Set<GrantedAuthority> authorities = new HashSet<>();
authorities.add(new SimpleGrantedAuthority("USER"));
authorities.add(new SimpleGrantedAuthority("ADMIN"));
Authentication reAuth = new UsernamePasswordAuthenticationToken("user",new
BCryptPasswordEncoder().encode("password"),authorities);
SecurityContextHolder.getContext().setAuthentication(reAuth);
Sample code is available on github: https://github.com/aoa4eva/ContextDemo
The solution is a bit easier than I thought. I have a user.
And I can call UsernamePasswordAuthenticationToken(user.getUserName(), user.getPassword(), actualAuthorities); instead of UsernamePasswordAuthenticationToken(auth.getPrincipal(), auth.getCredentials(), actualAuthorities);.
I don't need to get authentication object. Final version of service:
#Component
public class AuthoritiesUpdater {
private final UserRoleService userRoleService;
#Autowired
public AuthoritiesUpdater(UserRoleService userRoleService) {
this.userRoleService = userRoleService;
}
public void update(User user) {
List<UserRole> userRoles = userRoleService.findByUser(user);
List<GrantedAuthority> actualAuthorities = userRoles.stream().map(userRole -> new SimpleGrantedAuthority(userRole.getRole())).collect(Collectors.toList());
Authentication newAuth = new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword(), actualAuthorities);
SecurityContextHolder.getContext().setAuthentication(newAuth);
}
}
I have a web application with spring-boot 2.0.1 protected by spring-security. I use a PersistentTokenRepository for Remember-Me and store the tokens in a MySQL database.
In the server logfiles I see quite a lot of stacktraces with CookieTheftExceptions. It's so many that I find it hard to believe that actual Cookies are stolen but assume some kind of misconfiguration. From adding some analyzing code, it seems only mobile browsers are affected.
Servlet.service() for servlet [dispatcherServlet] in context with path [/r] threw exception
org.springframework.security.web.authentication.rememberme.CookieTheftException: Invalid remember-me token (Series/token) mismatch. Implies previous cookie theft attack.
at org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices.processAutoLoginCookie(PersistentTokenBasedRememberMeServices.java:119) ~[spring-security-web-5.0.4.RELEASE.jar!/:5.0.4.RELEASE]
Manual testing was not able to reproduce this. Deleting the session cookie, but keeping the remember-me Cookie and making a request to a restricted URL leads to a normal authenticated session.
Here are the relevant parts of my security configuration:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig {
#Configuration
public static class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private RememberMeServices rememberMeServices;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.rememberMe()
.key(rememberMeKey)
.rememberMeServices(rememberMeServices);
;
}
}
/**
* Key for RememberMeServices and RememberMeAuthenticationProvider.
*/
private static final String rememberMeKey = "...";
#Bean
public RememberMeServices rememberMeServices(UserDetailsService userDetailsService, PersistentTokenRepository persistentTokenRepository) {
PersistentTokenBasedRememberMeServices rememberMeServices = new AnalyzingPersistentTokenBasedRememberMeServices(
rememberMeKey, userDetailsService, persistentTokenRepository);
rememberMeServices.setTokenValiditySeconds((int) Duration.of(366L, ChronoUnit.DAYS).toSeconds());
return rememberMeServices;
}
#Bean
public PersistentTokenRepository persistentTokenRepository(JdbcTemplate jdbcTemplate) {
JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
tokenRepository.setJdbcTemplate(jdbcTemplate);
return tokenRepository;
}
}
That AnalyzingPersistentTokenBasedRememberMeServices is a PersistentTokenBasedRememberMeServices with some additional logging in processAutoLoginCookie.
Another speciality is, that I use a custom AuthenticationProvider, and provide a UserDetailsService only for RememberMe. But as said above, manual testing works just fine. Still, users report they get logged out too often (session timeout is 24 hours).
Did anybody experience something like this and has a solution? Do I miss some crucial configuration?
PersistentTokenBasedRememberMeServices is not suitable for applications with concurrent requests, which might send the same token series.
See these almost five year old and unresolved bug reports:
https://github.com/spring-projects/spring-security/issues/2648
https://github.com/spring-projects/spring-security/issues/3079
Using TokenBasedRememberMeServices does not have those problems.
I ended up using Redisson to implement distributed locking and a distributed map to hold a fast-expiring cache, enabling the first-arriving request of a batch of concurrent requests to renew the token and allowing the soon-to-follow requests to be aware that the token had recently refreshed and use that new value.
In spring security web 4.2.13 implementation problem in mechanism of token prolongation
protected UserDetails processAutoLoginCookie(String[] cookieTokens,...
.....
PersistentRememberMeToken newToken = new PersistentRememberMeToken(
token.getUsername(), token.getSeries(), generateTokenData(), new Date());
try {
tokenRepository.updateToken(newToken.getSeries(), newToken.getTokenValue(),
newToken.getDate());
addCookie(newToken, request, response);
}
catch (Exception e) {
logger.error("Failed to update token: ", e);
throw new RememberMeAuthenticationException(
"Autologin failed due to data access problem");
}
During refreshing in case of concurrent request token value for same series can be rewritten in DB few times, but in cookie can be saved another value.
The token creation during login works well, in worst case u can have many persisted tokens in DB, but one of them will work =)
In my project I have fix bug by modification of refresh algorithm:
if (token.date + 1 < new Date()){
try {
PersistentRememberMeToken newToken = new PersistentRememberMeToken( token.getUsername(), generateSeriesData(), generateTokenData(), new Date())
tokenRepository.createNewToken(newToken)
addCookie(newToken, request, response)
}
catch (Exception e) {
logger.error("Failed to update token: ", e);
throw new RememberMeAuthenticationException(
"Autologin failed due to data access problem");
}
}
This code produce many DB records at result, but works
I am trying to configure a Spring Boot OAuth2.0 server on my existing schema. To authenticate a user with my schema, I need to retrieve an encrypted password from my users table and pass it to an external service for validation.
I was able to define a custom UserDetailsService as below, but the default behavior authenticates based on a comparison of the password in the returned UserDetails object and the password from the request.
How can I customize this behavior so that I can use my custom UserDetailsService, but offload the password validation to the external service?
#Component("userDetailsService")
public class UserDetailsService implements org.springframework.security.core.userdetails.UserDetailsService {
#Autowired
private DataSource dataSource;
#Override
public UserDetails loadUserByUsername(final String login) {
String sql = "select encrypted_password from users where login_name = ?";
String encryptedPassword = null;
try {
Connection con = dataSource.getConnection();
PreparedStatement statement = con.prepareStatement(sql));
statement.setString(1, login);
try (ResultSet results = statement.executeQuery()) {
if (results.next()) {
encryptedPassword = results.getLong("encrypted_password");
}
}
} catch (SQLException e) {
e.printStackTrace();
}
return new User(login, encryptedPassword, Arrays.asList(new SimpleGrantedAuthority("simpleAuth")));
}
}
I probably don't have enough details to go off from your question, but I will make some assumptions and give you the best answer I can.
Assumption: User has to login using some sort of credential that you are then doing a loadUserByUsername(loginCreds).
If you don't want to use that specific UserDetails object, you can create an CustomAuthenticationFilter that is configured in your WebSecurity class by using http.addFilterBefore(new CustomAuthenticationFilter(), UsernamePasswordAuthenticationFilter)
From there, you can customize your entire authentication flow. You can choose to create a CustomAuthenticationToken that in turn gets authenticated via CustomAuthenticationProvider. Within this provider, you can do your password check with your 3rd party validation. If it passes, you can load your user object as you have there.
I've started to use JHipster weeks ago and everything went find since now. I want to have a LDAP authentification with at the same time the default authentification of JHipster.
I followed this https://jhipster.github.io/tips/016_tip_ldap_authentication.html and it doesn't work as planned.
Actually my configuration is connecting well to my LDAP server and i know by viewing logs that the login search into the LDAP server and compare the password.
The problem is the login fail with the error :
UT005023: Exception handling request to /api/authentication
org.springframework.security.core.userdetails.UsernameNotFoundException: User nseys was not found in the database
at com.mycompany.myapp.security.PersistentTokenRememberMeServices.lambda$onLoginSuccess$1(PersistentTokenRememberMeServices.java:116)
at java.util.Optional.orElseThrow(Optional.java:290)
at com.mycompany.myapp.security.PersistentTokenRememberMeServices.onLoginSuccess(PersistentTokenRememberMeServices.java:116)
at org.springframework.security.web.authentication.rememberme.AbstractRememberMeServices.loginSuccess(AbstractRememberMeServices.java:294)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
...
The thing is I want JHipster to automatically create the user in database when it doesn't exist in there with a mapping of parameters (but only when it's a LDAP user) and just connect if it's already done.
I've searched Spring-security solution aswell but the implementations are too far away from the initial files created by JHipster and I don't want to destroy all this.
Well I tried something that work, I don't know if this is how I should have done, but since I've found nothing about that, and it's not documented alot, I'll stick with that solution unless I find a better solution.
// PersistentTokenRememberMeServices.java
protected void onLoginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication
successfulAuthentication) {
String login = successfulAuthentication.getName();
log.debug("Creating new persistent login for user {}", login);
PersistentToken t = new PersistentToken();
t.setSeries(RandomUtil.generateSeriesData());
t.setTokenValue(RandomUtil.generateTokenData());
t.setTokenDate(LocalDate.now());
t.setIpAddress(request.getRemoteAddr());
t.setUserAgent(request.getHeader("User-Agent"));
PersistentToken token = userRepository.findOneByLogin(login).map(u -> {
t.setUser(u);
return t;
}).orElse(null);
if (token == null) {
if (successfulAuthentication.getPrincipal() instanceof LdapUserDetails) {
User ldapUser = new User();
ldapUser.setLogin(login);
ldapUser.setPassword(RandomStringUtils.random(60)); // We use LDAP password, but the password need to be set
ldapUser.setActivated(true);
CustomLdapUserDetails customLdapUserDetails = (CustomLdapUserDetails) successfulAuthentication.getPrincipal();
ldapUser.setEmail(customLdapUserDetails.getEmail());
ldapUser.setFirstName(customLdapUserDetails.getFirstName());
ldapUser.setLastName(customLdapUserDetails.getLastName());
Set<Authority> authorities = new HashSet<>();
authorities.add(this.authorityRepository.findOneByName("ROLE_USER"));
ldapUser.setAuthorities(authorities);
ldapUser.setLangKey("fr");
userRepository.save(ldapUser);
t.setUser(ldapUser);
token = t;
} else {
throw new UsernameNotFoundException("User " + login + " was not found in the database");
}
}
...
}
And I added a contextMapper to get the attributes in the LDAP server
// SecurityConfiguration.java
#Bean
public UserDetailsContextMapper userDetailsContextMapper() {
return new LdapUserDetailsMapper() {
#Override
public UserDetails mapUserFromContext(DirContextOperations ctx, String username, Collection<? extends GrantedAuthority> authorities) {
UserDetails details = super.mapUserFromContext(ctx, username, authorities);
return new CustomLdapUserDetails((LdapUserDetails) details, ctx);
}
};
}
#Inject
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
LdapContextSource contextSource = new LdapContextSource();
contextSource.setUrl(applicationProperties.getLdap().getUrl());
contextSource.setBase(applicationProperties.getLdap().getBase());
contextSource.setUserDn(applicationProperties.getLdap().getUserDn());
contextSource.setPassword(applicationProperties.getLdap().getPassword());
contextSource.afterPropertiesSet(); //needed otherwise you will have a NullPointerException in spring
auth.ldapAuthentication()
.userDetailsContextMapper(userDetailsContextMapper())
.userSearchBase(applicationProperties.getLdap().getSearchBase()) //don't add the base
.userSearchFilter(applicationProperties.getLdap().getSearchFilter())
.contextSource(contextSource)
;
}
I'm using the following method in a Spring Controller to allow authentication via Ajax. It works, but it doesn't seem to create a cookie or anything that makes the authentication persistent.
#RequestMapping(method = RequestMethod.POST)
#ResponseBody
public LoginStatus login(#RequestParam("j_username") String username,
#RequestParam("j_password") String password) {
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, password);
try {
Authentication auth = authenticationManager.authenticate(token);
SecurityContextHolder.getContext().setAuthentication(auth);
return new LoginStatus(auth.isAuthenticated(), auth.getName());
} catch (BadCredentialsException e) {
return new LoginStatus(false, null);
}
}
What do I need to do to make the authentication persistent?
Make sure
you have SecurityContextPersistenceFilter configured
you are not setting create-session attribute of <security:http> tag to none.