Understanding Spring Security #EnableOAuth2Client annotation - spring

I'm having a problem implementing OpenID connect built on Spring Security Oauth2 library. (Read more about the problem in a separate question.) While researching it, I read the documentation for the #EnableOauth2Client annotation, which says:
Enable configuration for an OAuth2 client in a web application that uses Spring Security and wants to use the Authorization Code Grant from one or more OAuth2 Authorization servers. To take advantage of this feature you need a global servlet filter in your application of the DelegatingFilterProxy that delegates to a bean named "oauth2ClientContextFilter". Once that filter is in place your client app can use another bean provided by this annotation (an AccessTokenRequest) to create an OAuth2RestTemplate, e.g.
#Configuration
#EnableOAuth2Client
public class RemoteResourceConfiguration {
#Bean
public OAuth2RestOperations restTemplate(OAuth2ClientContext oauth2ClientContext) {
return new OAuth2RestTemplate(remote(), oauth2ClientContext);
}
}
Client apps that use client credentials grants do not need the AccessTokenRequest or the scoped RestOperations (the state is global for the app), but they should still use the filter to trigger the OAuth2RestOperations to obtain a token when necessary. Apps that us [sic] password grants need to set the authentication properties in the OAuth2ProtectedResourceDetails before using the RestOperations, and this means the resource details themselves also have to be per session (assuming there are multiple users in the system).
A Note About Versions and Documentation: this documentation is the 2.0.4 release, which is all that is linked to from the Spring Security project page even for the newer 2.3.5 link, which my project is using. Our other Spring versions: Spring Boot 1.3.0, Spring Security 3.2.5, Spring Framework 4.2.3.
I don't understand quite what it means, particularly
a global servlet filter in your application of the DelegatingFilterProxy that delegates to a bean named "oauth2ClientContextFilter"
Here is how we are configuring our rest template.
#Configuration
#EnableOAuth2Client
public class OpenIdConnectConfig {
#Bean
public OAuth2ProtectedResourceDetails openIdResourceDetails() {
AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails();
details.setClientId(clientId);
details.setClientSecret(clientSecret);
details.setAccessTokenUri(accessTokenUri);
details.setUserAuthorizationUri(userAuthorizationUri);
details.setClientAuthenticationScheme(AuthenticationScheme.form);
details.setScope(oidcScopes);
details.setPreEstablishedRedirectUri(redirectUri);
details.setUseCurrentUri(false);
return details;
}
#Bean(name = "my.company.ui.security.OpenIdRestTemplate")
// ToDo: fix org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scopedTarget.oauth2ClientContext': Scope 'session' is not active for the current thread
public OAuth2RestTemplate OpenIdRestTemplate(OAuth2ClientContext clientContext) {
return new OAuth2RestTemplate(openIdResourceDetails(), clientContext);
}
#Bean
public RequestContextListener requestContextListener() {
return new RequestContextListener();
}
#Value("${oidc.clientId}")
private String clientId;
#Value("${oidc.clientSecret}")
private String clientSecret;
#Value("${oidc.accessTokenUrl}")
private String accessTokenUri;
#Value("${oidc.userAuthorizationUri}")
private String userAuthorizationUri;
#Value("${oidc.redirectUri}")
private String redirectUri;
#Value("#{'${oidc.scopes}'.split(',')}")
private List<String> oidcScopes;
}
The filter that performs the authentication (some exception handling and user processing code removed):
public class OpenIdConnectFilter extends AbstractAuthenticationProcessingFilter {
public OpenIdConnectFilter(
RequestMatcher requiresAuthenticationRequestMatcher,
AuthenticationService authenticationService
) {
super(requiresAuthenticationRequestMatcher);
setAuthenticationManager(new NoopAuthenticationManager());
}
#SuppressWarnings("RedundantThrows") // Matching overridden method
#Override
public Authentication attemptAuthentication(
HttpServletRequest request,
HttpServletResponse response
) throws AuthenticationException, IOException, ServletException {
// Required parameters (one-time access code, state) are retrieved from the context
OAuth2AccessToken oAuth2AccessToken = restTemplate.getAccessToken();
// Process the token, get the user details, return an Authentication object.
}
public void setRestTemplate(OAuth2RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
private static class NoopAuthenticationManager implements AuthenticationManager {
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
throw new UnsupportedOperationException("No authentication should be done with this AuthenticationManager");
}
}
private static final Logger LOGGER = LoggerFactory.getLogger(OpenIdConnectFilter.class);
#Value("${oidc.clientId}")
private String clientId;
#Value("${oidc.issuer}")
private String issuer;
#Value("${oidc.jwt.jwk.url}")
private String jwkUrl;
private final AuthenticationService authenticationService;
private OAuth2RestTemplate restTemplate;
}
And the Security Config that sets up the Spring Security FilterProxyChain:
#Configuration
#EnableWebSecurity
#EnableOAuth2Client
public class SecurityConfig extends WebSecurityConfigurerAdapter{
#Override
#SuppressWarnings("unchecked")
protected void configure(HttpSecurity http)
throws Exception {
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.and()
.csrf()
.disable()
.authorizeRequests()
.expressionHandler(securityExpressionHandler)
.antMatchers("/asset/**").access("permitAll")
.antMatchers("/ws/ssoEnabled").access("permitAll")
.antMatchers("/**").access("hasRole('ROLE_USER') or hasRole('ROLE_TOKEN_ACCESS')")
.and()
.httpBasic()
.authenticationEntryPoint(ajaxAwareLoginUrlAuthenticationEntryPoint)
.and()
// Handles unauthenticated requests, catching UserRedirectRequiredExceptions and redirecting to OAuth provider
.addFilterAfter(new OAuth2ClientContextFilter(), SecurityContextPersistenceFilter.class)
// Handles the oauth callback, exchanging the one-time code for a durable token
.addFilterAfter(openIdConnectFilter, OAuth2ClientContextFilter.class)
.formLogin()
.loginPage("/login")
.loginProcessingUrl("/logincheck")
.usernameParameter("username")
.passwordParameter("password")
.successHandler(ajaxAwareAuthenticationSuccessHandler)
.failureHandler(ajaxAwareAuthenticationFailureHandler)
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/login")
.and()
.rememberMe()
.rememberMeServices(rememberMeServices)
// Even though this key has been added directly to the rememberMeServices instance, the RememberMeConfigurer
// can instantiate a new RememberMeServices with a made-up key if the same key is not provided.
.key("the key value")
;
// We do not configure a bean for the SessionAuthenticationStrategy. We want to use the Spring default strategy,
// which is configured by the above builder chain. In order to share the correct, configured instance with our
// custom OpenIdConnectFilter, we first tell the builder to perform the configuration (normally this would be
// done long after this method returns)...
http.getConfigurer(SessionManagementConfigurer.class).init(http);
// ... then we get the shared object by interface (SessionAuthenticationStrategy) class name...
final SessionAuthenticationStrategy sessionAuthenticationStrategy = http.getSharedObject(SessionAuthenticationStrategy.class);
// ... then set it in our custom filter.
openIdConnectFilter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy);
}
}
You'll notice multiple authentication mechanisms in there. We're in the process of migrating from Form Login to Oauth login and there is a feature flag in place for the initial release. The flag is working quite well, except for the problem described in my linked question at the top that occurs for a while after flipping the flag, then seems to resolve itself.
Is the configuration I've shown above sufficient fulfillment of the documented instructions for the #EnableOauth2Client annotation?
Or is there something else I need to do with a DelegationFilterProxy? If so, how?

Related

Spring Boot OAuth2 password login via basic auth

I have a Spring Boot application with multiple http security configurations. Each of them is using external Keycloak.
API URLs are using Bearer token authentication
swagger URLs are using authentication code flow (user interaction needed)
URLs that authenticates via Basic Auth
First 2 works fine but I can't get basic auth configuration running. For that I would like to use OAuth2 grant type password.
My application.properties oauth2 configuration:
spring.security.oauth2.client.registration.keycloak2.client-id=${KEYCLOAK_RESOURCE}
spring.security.oauth2.client.registration.keycloak2.client-secret=${KEYCLOAK_RESOURCE_CLIENT_SECRET}
spring.security.oauth2.client.registration.keycloak2.authorization-grant-type=password
spring.security.oauth2.client.registration.keycloak2.scope=openid
spring.security.oauth2.client.provider.keycloak2.issuer-uri=${keycloak.auth-server-url}/realms/${keycloak.realm}
My configuration for Basic auth endpoints looks like this:
#Configuration
#Order(2)
public static class ProcessConfigurationAdapter extends WebSecurityConfigurerAdapter {
public static class OAuth2PasswordAuthenticationProvider implements AuthenticationProvider {
private final OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> accessTokenResponseClient;
private final OAuth2UserService<OAuth2UserRequest, OAuth2User> userService;
private final ClientRegistrationRepository clientRegistrationRepository;
private GrantedAuthoritiesMapper authoritiesMapper = ((authorities) -> authorities);
public OAuth2PasswordAuthenticationProvider(
OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> accessTokenResponseClient,
OAuth2UserService<OAuth2UserRequest, OAuth2User> userService,
ClientRegistrationRepository clientRegistrationRepository) {
super();
this.accessTokenResponseClient = accessTokenResponseClient;
this.userService = userService;
this.clientRegistrationRepository = clientRegistrationRepository;
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if (!(authentication instanceof UsernamePasswordAuthenticationToken)) {
return null;
}
final UsernamePasswordAuthenticationToken usernamePassword = (UsernamePasswordAuthenticationToken) authentication;
final String username = (String) usernamePassword.getPrincipal();
final String password = (String) usernamePassword.getCredentials();
final String registrationId = "keycloak2";
final ClientRegistration keycloak2 = clientRegistrationRepository.findByRegistrationId(registrationId);
final OAuth2PasswordGrantRequest request = new OAuth2PasswordGrantRequest(keycloak2, username, password);
final OAuth2AccessTokenResponse accessTokenResponse = accessTokenResponseClient.getTokenResponse(request);
final OAuth2User oauth2User = this.userService.loadUser(new OAuth2UserRequest(
keycloak2, accessTokenResponse.getAccessToken(), accessTokenResponse.getAdditionalParameters()));
final Collection<? extends GrantedAuthority> mappedAuthorities = this.authoritiesMapper
.mapAuthorities(oauth2User.getAuthorities());
final OAuth2AuthenticationToken authenticationResult = new OAuth2AuthenticationToken(oauth2User, mappedAuthorities, registrationId);
return authenticationResult;
}
#Override
public boolean supports(Class authentication) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/api/v1/process/**")
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.oauth2Client()
.and()
.httpBasic();
http.csrf().disable();
}
#Bean
public OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> accessTokenResponseClient() {
return new DefaultPasswordTokenResponseClient();
}
#Bean
public OAuth2PasswordAuthenticationProvider oAuth2PasswordAuthenticationProvider(
OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> accessTokenResponseClient,
OAuth2UserService<OAuth2UserRequest, OAuth2User> userService,
ClientRegistrationRepository clientRegistrationRepository) {
// Here I'm missing userService
return new OAuth2PasswordAuthenticationProvider(accessTokenResponseClient, userService, clientRegistrationRepository);
}
}
I've got Parameter 1 of method oAuth2PasswordAuthenticationProvider in com.example.config.SecurityConfig$ProcessConfigurationAdapter required a bean of type 'org.springframework.security.oauth2.client.userinfo.OAuth2UserService' that could not be found.
I thought it would autowire based on configuration in application.properties but no. How can I obtain it?
Password grant flow is deprecated. Don't try to use it.
Authorization-code flow is a protocol between client and authorization-server to authenticate users and acquire access-token for client to act on behalf of those users. It is to be used client side (Angular, React, Vue, Flutter, etc. or Spring modules with Thymeleaf or other sever-side rendering) and has nothing to do with REST API.
To authenticated trusted programs (server-side applications that you can trust to keep a secret actually secret), you should use client-credentials flow to acquire access-tokens (for the client itself, not on behalf of the user). If you write such Spring services, configure it as OAuth2 client with client credentials.
In both cases from the resource-server point of view (the Spring REST API documented with Swagger), this doesn't make a difference: requests come with an Authorization header containing a Bearer access-token, and this is what you should build security-context from. Sample there.

Evaluate Web Services Interceptor Before Spring Security Filter Chain

I have a SOAP-based web services application which is leveraging Spring Web Services (and Spring WS Security) as well as Spring Security. I am using a custom AbstractWsSecurityInterceptor to authenticate the incoming requests (using an injected AuthenticationManager) and to add the successful authentications to the SecurityContext. I then have a custom AcessDecisionManager which is using a custom WebSecurityExpressionHandler to validate a certain property from the principal added to the context by the interceptor.
Below is an idea of what my configuration files look like:
SecurityConfig.java:
#Getter
#Setter
#Configuration
#RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final AccessDecisionManager customAccessDecisionManager;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.
cors()
.and().csrf().disable()
.authorizeRequests()
.accessDecisionManager(customAccessDecisionManager)
.antMatchers(GET, "/actuator/**").permitAll()
.anyRequest().access("customAccessMethod()")
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
WebServiceConfig.java:
#EnableWs
#Configuration
#RequiredArgsConstructor
public class WebServiceConfig extends WsConfigurerAdapter {
private final AuthenticationManager authenticationManager;
#Bean
public ServletRegistrationBean<MessageDispatcherServlet> messageDispatcherServlet(ApplicationContext applicationContext) {
MessageDispatcherServlet servlet = new MessageDispatcherServlet();
servlet.setApplicationContext(applicationContext);
servlet.setTransformWsdlLocations(true);
return new ServletRegistrationBean<>(servlet, "/ws/*");
}
...
...
#Bean
AbstractWsSecurityInterceptor customAuthenticationInterceptor() {
return new CustomAuthenticationInterceptor(authenticationManager);
}
#Override
public void addInterceptors(List<EndpointInterceptor> interceptors) {
interceptors.add(customAuthenticationInterceptor());
}
}
The issue with this setup is that the Spring Security filter chain is evaluated first and fails the authentication because the AccessDecisionManager is evaluated before the request has a chance to enter the custom AbstractWsSecurityInterceptor and place the authentication in the SecurityContext.
Is there any way to evaluate the interceptor and handling of the request on the Web Services and WS Security side of things before it then hits the Spring Security filter chain? Is this a possibility?
Thank you in advance for the help!

How can I do to my spring boot resource server oauth 2 get user's extra data from api when user authenticate e keep it into security context?

I have a resource server done with Spring Boot. I'm using Spring Security 5.3 to authenticate and authorize the frontend exchange data. I've configured a authorization server "issuer-uri" in application.yml that provides and validates the access_token (jwt).
Until there ok. The problem that authorization server doesn't provide at once every user's information that I need in access_token or id_token. With the sub claim and access_token I need to do a request to endpoint to get more extra data about user.
I would like to know how can I do a request to get that information just when the user authenticates and put them into security context togheter the information that's already comes. So that way, I could get that information in some service when needed without make a request to endpoint each time:
SecurityContextHolder.getContext().getAuthentication().getDetails()
It's here my WebSecurityConfigurerAdapter
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private static final String CLAIM_ROLES = "role";
private static final String AUTHORITY_PREFIX = "ROLE_";
#Value("${sso.issuers_uri}")
private String issuers;
Map<String, AuthenticationManager> authenticationManagers = new HashMap<>();
JwtIssuerAuthenticationManagerResolver authenticationManagerResolver =
new JwtIssuerAuthenticationManagerResolver(authenticationManagers::get);
#Override
protected void configure(HttpSecurity http) throws Exception {
String[] result = issuers.split(",");
List<String> arrIssuers = Arrays.asList(result);
arrIssuers.stream().forEach(issuer -> addManager(authenticationManagers, issuer));
http
.httpBasic().disable()
.formLogin(AbstractHttpConfigurer::disable)
.csrf(AbstractHttpConfigurer::disable)
.authorizeRequests(auth -> auth
.antMatchers(
"/*",
"/signin-oidc",
"/uri-login_unico",
"/assets/**","/views/**",
"index.html",
"/api/segmentos/listar_publicados",
"/api/modelos",
"/api/modelos/*"
).permitAll()
.antMatchers(
"/api/admin/**"
).hasRole("role.PGR.Admin")
.antMatchers(
"/api/govbr/**"
).hasAnyAuthority("SCOPE_govbr_empresa")
.anyRequest().authenticated()
).oauth2ResourceServer(oauth2ResourceServer -> {
oauth2ResourceServer.authenticationManagerResolver(this.authenticationManagerResolver);
});
}
public void addManager(Map<String, AuthenticationManager> authenticationManagers, String issuer) {
JwtAuthenticationProvider authenticationProvider = new JwtAuthenticationProvider(JwtDecoders.fromOidcIssuerLocation(issuer));
authenticationProvider.setJwtAuthenticationConverter(getJwtAuthenticationConverter());
authenticationManagers.put(issuer, authenticationProvider::authenticate);
}
private Converter<Jwt, AbstractAuthenticationToken> getJwtAuthenticationConverter() {
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(getJwtGrantedAuthoritiesConverter());
return jwtAuthenticationConverter;
}
private Converter<Jwt, Collection<GrantedAuthority>> getJwtGrantedAuthoritiesConverter() {
JwtGrantedAuthoritiesConverter converter = new JwtGrantedAuthoritiesConverter();
converter.setAuthorityPrefix(AUTHORITY_PREFIX);
converter.setAuthoritiesClaimName(CLAIM_ROLES);
return converter;
}
}
I don't know if I need to do a custom AuthenticationManger or if I can do this with a security filter after authenticated. If someone could help me, I really apprecite it. Tks!!!

Required a bean of type 'org.springframework.security.authentication.AuthenticationManager' that could not be found. message from spring security

I am trying to implement one sample demo for Spring Security with Spring Boot for checking the authentication. I am trying to implement a basic workout for Spring Security and getting the following message,
Description:
Parameter 0 of constructor in com.spacestudy.service.CustomAuthenticationProvider required a bean of type 'org.springframework.security.authentication.AuthenticationManager' that could not be found.
Action:
Consider defining a bean of type 'org.springframework.security.web.AuthenticationEntryPoint' in your configuration.
My security config class SecurityConfig.java,
#EnableWebSecurity
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private AuthenticationEntryPoint authEntryPoint;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.addFilter(new ApplicationContextHeaderFilter((ApplicationContext) authenticationManager()));
}
}
And my BasicAuthenticationFilter implementation like the following,
#Component
public class CustomAuthenticationProvider extends BasicAuthenticationFilter {
public CustomAuthenticationProvider(AuthenticationManager authenticationManager) {
super(authenticationManager);
// TODO Auto-generated constructor stub
}
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException {
UsernamePasswordAuthenticationToken authentication = getAuthentication(request);
SecurityContextHolder.getContext().setAuthentication(authentication);
filterChain.doFilter(request, response);
}
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
String bearerToken = request.getHeader("accessToken");
String username = "test";
String password = "test";
if (username != null && !username.isEmpty()) {
return new UsernamePasswordAuthenticationToken(username, null, null);
}
return null;
}
}
How can I resolve this issue?
Lot of problems are there in your code.
(ApplicationContext) authenticationManager()
you can not cast AuthenticationManager to ApplicationContext
.addFilter(new ApplicationContextHeaderFilter(...))
I don't know Why you are using ApplicationContextHeaderFilter for simple demo application.
You should have preferred BasicAuthenticationFilter or even simple default configuration provided for HttpSecurity with .httpBasic()
You should have preferred UsernamePasswordAuthenticationFilter or even simple default configuration provided in HttpSecurity with .formLogin()
CustomAuthenticationProvider extends BasicAuthenticationFilter
An authentication provider is one which implements AuthenticationProvider interface. In your case naming should be xxxAuthFilter.
You have done nothing in below code.(got existing authentication object and set it back without creating an valid authentication object.)
UsernamePasswordAuthenticationToken authentication = getAuthentication(request);
SecurityContextHolder.getContext().setAuthentication(authentication);
Coming to the AuthenticationManager and AuthFilters implementation point of view,
For add filter you can add any implementation of Spring Security provided filters as given below
.addFilter(AnyFilterImplementationFromThisLink)
(But not all filters are auth filters. Where auth filters will attempt to authenticate with the authenticationManager configured)
For example If you consider UsernamePasswordAuthenticationFilter or BasicAuthenticationFilter
you should take care of setting AuthenticationManager where your auth manager should override authenticate() method and it should return Authentication object(where Authentication object will have auth principal, credentials and granted authorities list)
Or
If you don't want to implement authentication manager...
In simple way in your filters(implementation of OncePerRequestFilter) doFilterInternal() method set the `Authentication` object in `SecurityContext`
List<GrantedAuthority> authorityList = new ArrayList<>();
GrantedAuthority authority = new SimpleGrantedAuthority("ROLE_USER");
authorityList.add(authority);
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(username, password, authorityList);
//Note UsernamePasswordAuthenticationToken implements Authentication
SecurityContextHolder.getContext().setAuthentication(authToken);
How any auth filter works is if there is a valid Authentication object then filter chain will continue without attempting authentication otherwise it will attemptAuthentication by overrided attemptAuthentication() method.
But your ApplicationContextHeaderFilter is a implementation of OncePerRequestFilter where it has no attemptAuthentication() and i don't know the order of ApplicationContextHeaderFilter if it's order is after creating security context then you can set the authentication object to security context.
Your error seems to be that the AuthenticationManager is not present as a Spring Bean.
Option 1
Register an AuthenticationManager in Spring Bean. All is provided by Spring for do this directly in your SecurityConfig class by overriding the WebSecurityConfigurerAdapter#authenticationManagerBean method like explain in the documentation of it
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
Option 2
Avoid to register an AuthenticationManager in Spring, but directly your CustomAuthenticationProvider classs.
#EnableWebSecurity
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
public CustomAuthenticationProvider authenticationProvider() throws Exception {
return new CustomAuthenticationProvider(authenticationManager());
}
}
Don't forget to remove the #Component annotation on the CustomAuthenticationProvider class with this method.
I am not sure but shoudn't the CustomAuthenticationProvider implement AuthenticationProvider and AuthenticationManager is just a container for authentication providers and it seems that you dont have any.
Check this site for more info
https://www.baeldung.com/spring-security-authentication-provider
You can try on this, put it in config security file
#Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
return authConfig.getAuthenticationManager();
}
Create a new java class. And Configure like below:
#Configuration
#RequiredArgsConstructor
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception{
return super.authenticationManagerBean();
}
}

Spring Security Remember Me Does not work With Spring boot 1.5.2, Spring 4.3, Spring Security 4.2 [duplicate]

Right after registration (sign up) I'm logging in my user programmatically via Spring Security:
public register(HttpServletRequest request, String user, String password) {
...
request.login(user, password);
}
This works fine, but it doesn't create the remember-me cookie (although with interactive login the cookie is created fine).
Now I've read in this and this answer, that you have to wire in the implementation of RememberMeServices (I use PersistentTokenBasedRememberMeServices) and then call onLoginSuccess. I haven't been successful to autowire PersistentTokenBasedRememberMeServices.
How to make this work? Is this the right way? Why Spring Security doesn't offer a more convenient way?
P.S.: This is an excerpt from my configuration:
#Configuration
#EnableWebSecurity
public class WebSecConf extends WebSecurityConfigurerAdapter {
...
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.rememberMe()
.tokenRepository(new MyPersistentTokenRepository())
.rememberMeCookieName("rememberme")
.tokenValiditySeconds(60 * 60 * 24)
.alwaysRemember(true)
.useSecureCookie(true)
.and()
....
...
}
}
You didn't mention the Spring version. Below configuration will work with Spring 4 but you can modify it for other version. In your WebSecConf class autowire PersistentTokenRepository and UserDetailsService interfaces. Add Bean to get PersistentTokenBasedRememberMeServices instance.
#Configuration
#EnableWebSecurity
public class WebSecConf extends WebSecurityConfigurerAdapter {
#Autowired
PersistentTokenRepository persistenceTokenRepository;
#Autowired
UserDetailsService userDetailsService;
...
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.rememberMe()
.tokenRepository(persistenceTokenRepository)
.rememberMeCookieName("rememberme")
.tokenValiditySeconds(60 * 60 * 24)
.alwaysRemember(true)
.useSecureCookie(true)
.and()
....
...
}
#Bean
public PersistentTokenBasedRememberMeServices getPersistentTokenBasedRememberMeServices() {
PersistentTokenBasedRememberMeServices persistenceTokenBasedservice = new PersistentTokenBasedRememberMeServices("rememberme", userDetailsService, persistenceTokenRepository);
persistenceTokenBasedservice.setAlwaysRemember(true);
return persistenceTokenBasedservice;
}
}
Now in your Controller or class where you are doing programmatic login, autowire PersistentTokenBasedRememberMeServices and add below code inside the method to invoke loginSuccess method.
#Autowired
PersistentTokenBasedRememberMeServices persistentTokenBasedRememberMeServices;
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null){
persistentTokenBasedRememberMeServices.loginSuccess(request, response, auth);
}
I've stumbled on this issue and struggled a bit to get everything working correctly, for future reference this is how to set things up.
Define a RememberMeService bean configured to your needs.
Use TokenBasedRememberMeServices if you want a simple hash based token system or PersistentTokenBasedRememberMeServices if you'd rather persist the tokens to database. Both solutions are described in further details here : https://docs.spring.io/spring-security/site/docs/3.2.0.CI-SNAPSHOT/reference/html/remember-me.html
Please note that the constructor first argument is not the cookie name but the key used to validate remember-me tokens.
#Configuration
public class SecurityBeans {
#Autowire
PersistentTokenRepository persistenceTokenRepository;
#Autowired
UserDetailsService userDetailsService;
#Bean
public PersistentTokenBasedRememberMeServices getPersistentTokenBasedRememberMeServices() {
PersistentTokenBasedRememberMeServices persistenceTokenBasedservice = new TokenBasedRememberMeServices("remember-me-key", userDetailsService, persistenceTokenRepository);
persistenceTokenBasedservice.setCookieName("rememberme");
persistenceTokenBasedservice.setTokenValiditySeconds(60 * 60 * 24);
persistenceTokenBasedservice.setAlwaysRemember(true);
persistenceTokenBasedservice.setUseSecureCookie(true);
return persistenceTokenBasedservice;
}
}
You should inject the RememberMeService directly when configuring HttpSecurity. You also have to configure the exact same key as defined in your RememberMeService because the configurer also sets up the RememberMeAuthenticationProvider which checks that the remember-me token key generated by RememberMeService is correct.
#Configuration
#EnableWebSecurity
public class WebSecConf extends WebSecurityConfigurerAdapter {
#Autowired
RememberMeServices rememberMeServices;
...
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.rememberMe()
.rememberMeServices(rememberMeServices)
.key("remember-me-key")
.and()
....
...
}
}
And finally you should invoke RememberMeService's loginSuccess in your method doing the programmatic login as described in abaghel's answer.

Resources