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
Related
I'm trying to get OIDC working from behind a corporate firewall, and I'm running into some issues.
I am trying to use the Google as my OpenID provider. I have configured an OAuth 2.0 Client ID in Google with type "Web Application".
I have updated my application.yaml and added the following properties:
security:
oauth2:
client:
registration:
google:
client-id: <client-id from google>
client-secret: <client-secret from google>
I have a WebSecurityConfigurerAdapter class with the following configure() method (shamelessly copied from Baeldung):
#Override
protected void configure(HttpSecurity http) throws Exception{
Set<String> googleScopes = new HashSet<>();
googleScopes.add(
"https://www.googleapis.com/auth/userinfo.email");
googleScopes.add(
"https://www.googleapis.com/auth/userinfo.profile");
OidcUserService googleUserService = new OidcUserService();
googleUserService.setAccessibleScopes(googleScopes);
http
.authorizeRequests(authorizeRequests -> authorizeRequests
.anyRequest().authenticated())
.oauth2Login(oauthLogin -> oauthLogin
.userInfoEndpoint()
.oidcUserService(googleUserService)
);
}
When I attempt view the application in a browser, everything initially looks good. I get redirected to google to authenticate, and then google redirects back to my application. However it all goes south when the application attempts to exchange the authorization code for a access token.
I get the following error:
[invalid_token_response] An error occurred while attempting to retrieve the OAuth 2.0 Access Token
Response: I/O error on POST request for "https://www.googleapis.com/oauth2/v4/token": Software caused
connection abort: recv failed; nested exception is java.net.SocketException: Software caused connection
abort: recv failed
Ok, looks like an issue with the corporate firewall. Cool, no problem I'll just use a RestTemplateCustomizer. So I declare the following beans:
#Bean
HttpHost proxyHost(#Value("${http.proxyHost}") String proxyHost) {
log.debug("Setting proxy host to: '{}'", proxyHost);
return HttpHost.create(proxyHost);
}
#Bean
DefaultProxyRoutePlanner proxyRoutePlanner(HttpHost proxyHost) {
return new DefaultProxyRoutePlanner(proxyHost);
}
#Bean
HttpClient httpClient(DefaultProxyRoutePlanner proxyRoutePlanner) {
return HttpClientBuilder.create().setRoutePlanner(proxyRoutePlanner).build();
}
#Bean
HttpComponentsClientHttpRequestFactory httpRequestFactory(HttpClient httpClient) {
return new HttpComponentsClientHttpRequestFactory(httpClient);
}
#Bean
RestTemplateCustomizer restTemplateCustomizer(HttpComponentsClientHttpRequestFactory httpRequestFactory) {
RestTemplateCustomizer restTemplateCustomizer = (RestTemplate template)->{
log.debug("Returning a customized rest template.");
template.setRequestFactory(httpRequestFactory);
};
return restTemplateCustomizer;
}
This failed to fix the problem. After some debugging, I realized that the DefaultAuthorizationCodeTokenResponseClient is creating its restOperations by calling new RestTemplate() rather than using RestTemplateBuilder, so all of my work to create a RestTemplateCustomizer is for nothing.
So I updated my WebSecurityConfigurerAdapter:
public class OidcSecurityConfigurer extends WebSecurityConfigurerAdapter {
private RestTemplateBuilder restTemplateBuilder;
public OidcSecurityConfigurer(#Autowired RestTemplateBuilder restTemplateBuilder) {
this.restTemplateBuilder = restTemplateBuilder;
}
#Override
protected void configure(HttpSecurity http) throws Exception{
Set<String> googleScopes = new HashSet<>();
googleScopes.add(
"https://www.googleapis.com/auth/userinfo.email");
googleScopes.add(
"https://www.googleapis.com/auth/userinfo.profile");
OidcUserService googleUserService = new OidcUserService();
googleUserService.setAccessibleScopes(googleScopes);
http
.authorizeRequests(
authorizeRequests -> authorizeRequests.anyRequest().authenticated()
)
.oauth2Login(oauthLogin -> oauthLogin
.tokenEndpoint(teCustomizer->{
DefaultAuthorizationCodeTokenResponseClient client = new DefaultAuthorizationCodeTokenResponseClient();
client.setRestOperations(this.restTemplateBuilder.build());
teCustomizer.accessTokenResponseClient(client);
})
.userInfoEndpoint()
.oidcUserService(googleUserService)
);
}
}
This got a little further. Now it was getting connected to google, but it wasn't able to read the response due to a NullPointerException on line 80 in DefaultAuthorizationCodeTokenResponseClient.java.
java.lang.NullPointerException: null
at org.springframework.security.oauth2.client.endpoint.DefaultAuthorizationCodeTokenResponseClient.getTokenResponse(DefaultAuthorizationCodeTokenResponseClient.java:80) ~[spring-security-oauth2-client-5.6.3.jar:5.6.3]
at org.springframework.security.oauth2.client.endpoint.DefaultAuthorizationCodeTokenResponseClient.getTokenResponse(DefaultAuthorizationCodeTokenResponseClient.java:57) ~[spring-security-oauth2-client-5.6.3.jar:5.6.3]
at org.springframework.security.oauth2.client.oidc.authentication.OidcAuthorizationCodeAuthenticationProvider.getResponse(OidcAuthorizationCodeAuthenticationProvider.java:170) ~[spring-security-oauth2-client-5.6.3.jar:5.6.3]
at org.springframework.security.oauth2.client.oidc.authentication.OidcAuthorizationCodeAuthenticationProvider.authenticate(OidcAuthorizationCodeAuthenticationProvider.java:144) ~[spring-security-oauth2-client-5.6.3.jar:5.6.3]
at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:182) ~[spring-security-core-5.6.3.jar:5.6.3]
at org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter.attemptAuthentication(OAuth2LoginAuthenticationFilter.java:195) ~[spring-security-oauth2-client-5.6.3.jar:5.6.3]
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:223) ~[spring-security-web-5.6.3.jar:5.6.3]
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:213) ~[spring-security-web-5.6.3.jar:5.6.3]
Looking at the code, it seems that the token isn't being decoded properly because I didn't add the right messageConverters to the RestTemplate. So updated the RestTemplateCustomizer declaration as follows:
#Bean
RestTemplateCustomizer restTemplateCustomizer(HttpComponentsClientHttpRequestFactory httpRequestFactory) {
RestTemplateCustomizer restTemplateCustomizer = (RestTemplate template)->{
log.debug("Returning a customized rest template.");
template.setRequestFactory(httpRequestFactory);
template.setMessageConverters(
Arrays.asList(
new FormHttpMessageConverter(),
new OAuth2AccessTokenResponseHttpMessageConverter()
)
);
template.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
};
return restTemplateCustomizer;
}
Now the access token is being decoded correctly, but the JWT user token is failing. WTH? So I looked at the class NimbusJwtDecoder and it also is calling new RestTemplate(). So I apparently need to figure out how to change the RestTemplate being used by the NimbusJwtDecoder similar to what I did for DefaultAuthorizationCodeTokenResponseClient.
At this point I'm wondering if I'm barking up the wrong tree. This seems like so much work just get the built-in OAuth2 support working. It almost seems like it would be easier to just handle the all the OAuth2 handshake stuff manually rather than using what's built into spring-security. The kicker is that this is primarily an issue that will only occur during development. For a production deployment of the app they can easily open the firewall to allow the application to make outgoing connections to the OpenID provider.
I would appreciate any guidance anyone could give.
Thanks,
Dave
What I wanna achieve
So I have a client application in java (JavaFX + Spring-boot hybrid-application). You can have a look at it here https://github.com/FAForever/downlords-faf-client . So till now we stored username/ password if the user wished to be kept logged in which is obviously a pretty bad idea. So now I wanna store the refreshtoken and then log the user in with that.
What it looks like now
See here
ResourceOwnerPasswordResourceDetails details = new ResourceOwnerPasswordResourceDetails();
details.setClientId(apiProperties.getClientId());
details.setClientSecret(apiProperties.getClientSecret());
details.setClientAuthenticationScheme(AuthenticationScheme.header);
details.setAccessTokenUri(apiProperties.getBaseUrl() + OAUTH_TOKEN_PATH);
details.setUsername(username);
details.setPassword(password);
OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(details);
restOperations = templateBuilder
// Base URL can be changed in login window
.rootUri(apiProperties.getBaseUrl())
.configure(restTemplate);
What I found so far
I found out that restTemplate.getAccessToken().getRefreshToken() will give me the refreshtoken I want to save and later so to keep the user logged in.
What I can not figure out
I can not find a way to create a OAuth2RestTemplate with an refresh token only. Is that even possible? Can someone point me in the right direction? Maybe link me some articles to read? Is this the right place to read?
I do not think this is possible with an OAuth2RestTemplate, but you can reimplement the desired parts yourself. I'd like to share an example with your for OAuth password login to Microsofts flavour of OAuth2 (Azure Active Directory). It does miss the piece of fetching a new token from an existing refresh token yet, but I added a comment where you need to add it.
A simple way to mimic OAuthRestTemplates behavior is a custom ClientHttpRequestInterceptor which delegates the token fetching to a dedicated Spring service component, that you append to your RestTemplate:
#RequiredArgsConstructor
#Slf4j
public class OAuthTokenInterceptor implements ClientHttpRequestInterceptor {
private final TokenService tokenService;
#NotNull
#Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body,
ClientHttpRequestExecution execution) throws IOException {
request.getHeaders().add("Authorization", "Bearer " + tokenService.getRefreshedToken().getValue());
return execution.execute(request, body);
}
}
This interceptor can be added to your primary RestTemplate:
List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
interceptors.add(globalOAuthTokenInterceptor);
restTemplate.setInterceptors(interceptors);
The token service used in the interceptor holds the token in a cache and on request checks for the expiry of the token and if required queries a new one.
#Service
#Slf4j
public class TokenService {
private final TokenServiceProperties tokenServiceProperties;
private final RestTemplate simpleRestTemplate;
private OAuth2AccessToken tokenCache;
public TokenService(TokenServiceProperties tokenServiceProperties) {
this.tokenServiceProperties = tokenServiceProperties;
simpleRestTemplate = new RestTemplateBuilder().
build();
}
public OAuth2AccessToken getRefreshedToken() {
if (tokenCache == null || tokenCache.isExpired()) {
log.debug("Token expired, fetching new token");
tokenCache = refreshOAuthToken();
} else {
log.debug("Token still valid for {} seconds", tokenCache.getExpiresIn());
}
return tokenCache;
}
public OAuth2AccessToken loginWithCredentials(String username, String password) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON_UTF8));
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("grant_type", "password");
map.add("resource", tokenServiceProperties.getAadB2bResource());
map.add("client_id", tokenServiceProperties.getAadB2bClientId());
map.add("username", username);
map.add("password", password);
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);
return simpleRestTemplate.postForObject(
tokenServiceProperties.getAadB2bUrl(),
request,
OAuth2AccessToken.class
);
}
private OAuth2AccessToken refreshOAuthToken() {
return loginWithRefreshToken(tokenCache.getRefreshToken().getValue());
}
public OAuth2AccessToken loginWithRefreshToken(String refreshToken) {
// add code for fetching OAuth2 token from refresh token here
return null;
}
}
In this code example you would once login using username and password and afterwards all further logins would be using the refresh token. If you want to use the refresh token directly, you use the public method, otherwise it will be done internally.
Since the login code is specifically written for login to Microsoft AAD, you should recheck the MultiValueMap parameters.
TokenServiceProperties are straightforward:
#Data
public class TokenServiceProperties {
private String aadB2bUrl;
private String aadB2bClientId;
private String aadB2bResource;
}
Adapt them if needed.
The whole solution has one minor drawback: Instead of one RestTemplate that you usually fetch via depency injection, you now need a second one (a "simple" one) to fetch the OAuth token. In this example we create it in the constructor of the TokenService. However this is in general bad style as it makes it harder for unit testing etc. You could also think about using qualified beans or using a more basic http client in the TokenService.
Another important thing to note: I am using the spring-security-oauth2 package here. If you did not configure Spring Security in your project, this will trigger Spring Security auto-configuration which might not be desired - you can solve this by excluding undesired packages, e.g. in gradle:
implementation("org.springframework.security.oauth:spring-security-oauth2") {
because "We only want the OAuth2AccessToken interface + implementations without activating Spring Security"
exclude group: "org.springframework.security", module: "spring-security-web"
exclude group: "org.springframework.security", module: "spring-security-config"
exclude group: "org.springframework.security", module: "spring-security-core"
}
I am trying to request an object via oauth2 authentification with spring security and then try to use those credentials in my service, which is supposed to request data in fixed intervals.
The SleepController's purpose is to initially get data and as a side effect cache the important data for the service below.
#RestController
public class SleepController {
#Autowired
OAuth2RestTemplate oAuth2RestTemplate;
public static OAuth2RestTemplate currentOAuth2RestTemplate;
#RequestMapping("/sleep")
public OverallSleep getSleep(String date) {
if(date != null) {
OverallSleep overallSleep = null;
try {
overallSleep = oAuthRestTemplate.getForObject("https://api.fitbit.com/1.2/user/-/sleep/date/" + date + ".json", OverallSleep.class);
currentOAuth2RestTemplate = oAuth2RestTemplate;
} catch (Exception e) {
e.printStackTrace();
}
OverallSleep tmp = overallSleepDao.findOverallSleepByToday();
return overallSleep;
}
return null;
}
}
The BeatService has a scheduled function that tries to request data from the server without user interaction.
#Service
public class BeatService {
private static final long INTERVAL = 60_000L;
private static final long INITIAL_DELAY = 3_000L;
public BeatService() { }
#Scheduled(fixedRate = INTERVAL, initialDelay = INITIAL_DELAY)
public void beat() {
if(SleepController.currentOAuth2RestTemplate != null) {
OverallSleep overallSleep = null;
try {
overallSleep = SleepController.currentOAuth2RestTemplate.getForObject("https://api.fitbit.com/1.2/user/-/sleep/date/" + "2018-02-11" + ".json", OverallSleep.class);
overallSleepDao.save(overallSleep);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
Sadly, my approach of statically storing the oauthRestTemplate and trying to use that in my scheduled service does not work.
This Exception is thrown
org.springframework.beans.factory.BeanCreationException:
Error creating bean with name 'scopedTarget.oauth2ClientContext':
Scope 'session' is not active for the current thread; consider defining a
scoped proxy for this bean if you intend to refer to it from a singleton;
nested exception is java.lang.IllegalStateException: No thread-bound request
found: Are you referring to request attributes outside of an actual web
request, or processing a request outside of the originally receiving thread?
If you are actually operating within a web request and still receive this
message, your code is probably running outside of
DispatcherServlet/DispatcherPortlet: In this case, use
RequestContextListener or RequestContextFilter to expose the current
request.
at [...]
Caused by: java.lang.IllegalStateException:
No thread-bound request found: Are you referring to request attributes
outside of an actual web request, or processing a request outside of the
originally receiving thread? If you are actually operating within a web
request and still receive this message, your code is probably running
outside of DispatcherServlet/DispatcherPortlet: In this case, use
RequestContextListener or RequestContextFilter to expose the current
request.
Is there an appriopriate or right way to reuse the credentials from a prior authentification in oauth2 in Spring? I found a post sadly it doesn't work and actually make the controller crash so I can't test the service at all.
If needed, I can add other classes or what #Configuration I use.
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 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);
}
}