With the folowing code a new WebSession is created each time someone accesses my site. The state of the WebSession is set to NEW containing no attributes. This session is never deleted for some reason.
#Bean
public SecurityWebFilterChain securityWebFilterChainCatchAll(ServerHttpSecurity http) {
return http
.csrf().disable()
.authorizeExchange()
.pathMatchers("/", "/static/**")
.permitAll()
.anyExchange()
.denyAll()
.and()
.exceptionHandling()
.authenticationEntryPoint(this::returnPage)
.accessDeniedHandler(this::returnPage)
.and()
.formLogin().disable()
.httpBasic().disable()
.build();
}
private Mono<Void> returnPage(ServerWebExchange exchange, RuntimeException denied) {
Resource indexHtml = new ClassPathResource("/static/index.html");
return ok().contentType(MediaType.TEXT_HTML).syncBody(indexHtml).flatMap(d -> d.writeTo(exchange, new HandlerStrategiesResponseContext(HandlerStrategies.withDefaults())));
}
class HandlerStrategiesResponseContext implements ServerResponse.Context
{
private final HandlerStrategies strategies;
HandlerStrategiesResponseContext(HandlerStrategies strategies) {
this.strategies = strategies;
}
#Override
public List<HttpMessageWriter<?>> messageWriters() {
return this.strategies.messageWriters();
}
#Override
public List<ViewResolver> viewResolvers() {
return this.strategies.viewResolvers();
}
}
This is a catchall ServerHttpSecurity I have more specific configuration for my /api endpoint but they are working as expected when the WebSession is set in STARTED state and the attribute of SPRING_SECURITY_CONTEXT.
I have two questions regarding this.
What does it mean for WebSession to be in NEW state and why is that
not deleted, only STARTED state is deleted.
What do I need to change to either not creating any session or make sure the sessions created are deleted when expired.
Related
I'm implementing a server using Spring Boot. After the user do an oauth login, I want the user to go redirect to a specific uri so I can let the user register or login. The Google OAuth login seems like it is working fine but it keeps going to "/" uri. I want to user to be redirected to "/api/v1/member/oauth"
This is my Spring Security setup.
...
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.cors()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/swagger-ui/**", "/swagger-resources/**", "/v2/api-docs")
.permitAll()
.anyRequest()
.permitAll()
.and()
.oauth2Login()
.defaultSuccessUrl("/api/v1/member/oauth")
.userInfoEndpoint()
.userService(customOAuth2MemberService);
}
...
This is the OAuth service that a user is directed to. (This works fine)
#Service
#RequiredArgsConstructor
public class CustomOAuth2MemberService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
#Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) {
OAuth2UserService<OAuth2UserRequest, OAuth2User> delegate = new DefaultOAuth2UserService();
OAuth2User oAuth2User;
try {
oAuth2User = delegate.loadUser(userRequest);
} catch (OAuth2AuthenticationException e) {
throw new CustomException(OAUTH_FAIL);
}
return new DefaultOAuth2User(oAuth2User.getAuthorities(), oAuth2User.getAttributes(), "sub");
}
}
I want to get the DefaultOAuth2User which is returned from the above to this uri.
#PostMapping("/api/v1/member/oauth")
public Object registerOrLogin(DefaultOAuth2User defaultOAuth2user) {
return ResponseEntity.status(200)
.body(DefaultResponseDto.builder()
.responseCode("MEMBER_LOGIN")
.build());
}
It currently is not going to this uri and is redirected to "/".
NEW: I redirected it by having .defaultSuccessUrl() but now the DefaultOAuth2User is not sent with the redirection, causing the parameter of redirected api to be null. How do I fix this problem?
Try to use
.oauth2Login()
.defaultSuccessUrl("/api/v1/member/oauth")
this should override post-authentication behavior and redirect to the desired page after successful login. Also, there is a similar method for setting redirection URL for failed authentication .failureUrl("url").
Spring-Security AbstractAuthenticationProcessingFilter class has successfulAuthentication() methos, which defines what happens when a User is successfully authenticated. You can register your success handler and put your redirect logic there.
But here is a catch, when using OAuth2.0, we need to specify redirect-uri to which user will be landed after client receives an access-token.
If you are okay with this Oauth's redirect-uri, do not alter the redirect in success handler or if you need to redirect irrespective of that, use response.sendRedirect("/social-login-sample/some-page");
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.cors()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/swagger-ui/**", "/swagger-resources/**", "/v2/api-docs")
.permitAll()
.anyRequest()
.permitAll()
.and()
.oauth2Login()
.userInfoEndpoint()
.userService(customOAuth2MemberService)
.and()
.successHandler(
new AuthenticationSuccessHandler() {
#Override
public void onAuthenticationSuccess(
HttpServletRequest request,
HttpServletResponse response,
Authentication authentication)
throws IOException, ServletException {
// authentication.getName() : Principal Name
CustomOAuth2User oauthUser = (CustomOAuth2User) authentication.getPrincipal();
// Check if user is registered in your Database, if not, register new user
//userService.processAuthenticatedUser(oauthUser.getEmail());
// Get actual redirect-uri set in OAuth-Provider(Google, Facebook)
String redirectUri =
UriComponentsBuilder.fromHttpUrl(UrlUtils.buildFullRequestUrl(request))
.replaceQuery(null)
.build()
.toUriString();
log.info("redirectUri: {}", redirectUri);
// Ignore redirect-uri, and send user to a different page instead...
// response.sendRedirect("/social-login-sample/some-ther-page");
}
})
}
HttpSecurity object configed like this:
http.authorizeRequests().antMatchers("/login","/loginPage","/static/login.html","/","/index","/static/authenticationErr.html","/static/duplicatedUserErr.html").permitAll()
.and()
.httpBasic()
.authenticationEntryPoint(customAuthenticationEntryPoint)
.and()
.addFilterAt(customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.csrf().disable()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginProcessingUrl("/login")
.loginPage("/loginPage")
.permitAll()
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessHandler(customLogoutHandler)
.permitAll()
.and()
.sessionManagement() // not working??
.maximumSessions(1)
.maxSessionsPreventsLogin(true)
.expiredUrl("/static/duplicatedUserErr.html")
;
Here is what I tried: by following the spring security reference at https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#concurrent-sessions and source code trace, I found out the key to determine if this is a duplicate login session is this part of code written in method onAuthentication of class ConcurrentSessionControlAuthenticationStrategy:
final List<SessionInformation> sessions = sessionRegistry.getAllSessions(
authentication.getPrincipal(), false);
then base on the size of this list, compare with the maximumSessions limit deined in HttpSecurity config to check if this is an duplicated user. After debugging, I know every time a user try to login, this line of code will be called, however no matter how many times try to login in my browsers, sessions object always be null, it turns out the principals field defined in SessionRegistryImpl has been a empty map since it is created and never be filled with new elements.
Here is other detail of my config:
AuthenticationProvider: org.springframework.security.authentication.dao.DaoAuthenticationProvider
UserDetailService:
CustomUserDetailsService implements UserDetailsService
UserDetails:
org.springframework.security.core.userdetails.User
AuthenticationProcessingFilter:
CustomAuthenticationFilter extends AbstractAuthenticationProcessingFilter
Updated:
#Bean
public CustomAuthenticationFilter customAuthenticationFilter() throws Exception {
CustomAuthenticationFilter filter = new CustomAuthenticationFilter("/login");
filter.setAuthenticationManager(this.authenticationManagerBean());
filter.setAuthenticationFailureHandler(failureHandler);
filter.setAuthenticationSuccessHandler(successHandler);
filter.setSessionAuthenticationStrategy(sessionControlAuthenticationStrategy());
return filter;
}
Can someone give me a light of this?
Finally I have figured it out, you have to create beans by following this reference https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#concurrent-sessions, and you have to set CompositeSessionAuthenticationStrategy bean manually to CustomAuthenticationFilter , and then in your ConcurrentSessionControlAuthenticationStrategy bean, set ExceptionIfMaximumExceeded as true so it will throw a SessionAuthenticationException when a duplicate seesion of the same user created.
My code of above description is like this:
#Bean
public CustomAuthenticationFilter customAuthenticationFilter() throws Exception {
CustomAuthenticationFilter filter = new CustomAuthenticationFilter("/login");
filter.setAuthenticationManager(this.authenticationManagerBean());
filter.setAuthenticationFailureHandler(failureHandler);
filter.setAuthenticationSuccessHandler(successHandler);
//If don't set it here, spring will inject a compositeSessionAuthenticationStrategy bean automatically, but looks like it didn't work as expected for me
filter.setSessionAuthenticationStrategy(compositeSessionAuthenticationStrategy());
return filter;
}
#Bean
public ConcurrentSessionControlAuthenticationStrategy sessionControlAuthenticationStrategy() {
ConcurrentSessionControlAuthenticationStrategy csas = new ConcurrentSessionControlAuthenticationStrategy(sessionRegistry());
csas.setExceptionIfMaximumExceeded(true);
return csas;
}
I have a spring mvc web application that i want protect with springs security 5 Oauth2, the application successfully redirect to google for authentication, but the problem is that it keeps redirection back to the consent page just immediately after the user selects account from that consent page.
bellow is my relevant configuration.
#Override
protected void configure(HttpSecurity http) throws Exception {
http.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint())
.and()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/")
.and()
.addFilterAfter(oauth2ClientContextFilter, ExceptionTranslationFilter.class)
.addFilterBefore(oauth2ClientAuthenticationProcessingFilter(), FilterSecurityInterceptor.class)
.anonymous()
.disable();
}
#Bean
public OAuth2ProtectedResourceDetails authorizationCodeResource() {
logger.info("authorizationCodeResource");
AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails();
details.setId("google");
details.setClientId(environment.getProperty("google.clientId"));
details.setClientSecret(environment.getProperty("google.clientSecret"));
details.setUserAuthorizationUri(environment.getProperty("google.userAuthorizationUri"));
details.setAccessTokenUri(environment.getProperty("google.accessTokenUri"));
details.setAuthenticationScheme(AuthenticationScheme.query);
details.setClientAuthenticationScheme(AuthenticationScheme.form);
details.setPreEstablishedRedirectUri(environment.getProperty("google.reDirectURI"));
details.setUseCurrentUri(false);
details.setScope(new ArrayList<>(Arrays.asList("openid")));
return details;
}
#Bean
public OAuth2ClientAuthenticationProcessingFilter oauth2ClientAuthenticationProcessingFilter() {
logger.info("oauth2ClientAuthenticationProcessingFilter");
OAuth2RestOperations restTemplate = new OAuth2RestTemplate(authorizationCodeResource(), oauth2ClientContext);
OAuth2ClientAuthenticationProcessingFilter filter = new OAuth2ClientAuthenticationProcessingFilter(
environment.getProperty("google.callbackURI"));
filter.setRestTemplate(restTemplate);
filter.setTokenServices(googleUserInfoTokenServices());
return filter;
}
#Bean
#Description("Google API UserInfo resource server")
public GoogleUserInfoTokenServices googleUserInfoTokenServices() {
logger.info("googleUserInfoTokenServices");
GoogleUserInfoTokenServices userInfoTokenServices = new GoogleUserInfoTokenServices(
environment.getProperty("google.userInfoUri"),
environment.getProperty("google.clientId"));
return userInfoTokenServices;
}
#Bean
public AuthenticationEntryPoint authenticationEntryPoint() {
logger.info("authenticationEntryPoint");
return new LoginUrlAuthenticationEntryPoint(environment.getProperty("google.callbackURI"));
}
Someone please help me. i can provide any addition details that you may require
I am attempting to setup a very basic spring boot authenticated application. I am setting the Authorization header in the client and sending it to the backend. I can verify that the client is sending the correct header.
The backend receives the header correctly on the first attempt to login. However if the login credentials are incorrect subsequent requests retain whatever the header for the intial request was (caching it or something).
I am using Redis to Cache the session. My config is as follows:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired AuthenticationEntryPoint authenticationEntryPoint;
#Autowired CsrfTokenRepository csrfTokenRepository;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.and()
.csrf()
.disable()
.authorizeRequests().antMatchers("**")
.permitAll()
.anyRequest()
.authenticated()
;
}
}
AuthenticationEntryPoint
public class AuthenticationEntryPointBean {
#Bean
AuthenticationEntryPoint authenticationEntryPoint() {
return new RestAuthenticationEntryPoint();
}
}
Any direction would be appreciated.
** Edit **
Adding cache settings
#Configuration
#EnableRedisHttpSession
public class HttpSessionConfig {
#Bean
public JedisConnectionFactory connectionFactory() {
return new JedisConnectionFactory(); // <2>
}
}
Also I am trying to invalidate cache but that doesn't seem to work
#CrossOrigin
#RequestMapping(value="/auth/login", method = RequestMethod.GET, produces="application/json")
public #ResponseBody String login(#RequestHeader(name = "authorization") String authorization, HttpSession session, HttpServletRequest request)
{
try
{
authorization = authorization.substring("Basic ".length());
String decoded = new String(Base64.getDecoder().decode(authorization),"UTF-8");
Gson gson = new Gson();
LoginRequest login = gson.fromJson(decoded,LoginRequest.class);
UserAuthenticationEntity entity = service.getSecurityContext(login).orElseThrow(() ->
new BadCredentialsException("Authentication Failed.")
);
session.setMaxInactiveInterval((int)TimeUnit.MINUTES.toSeconds(expiresInMinutes));
SecurityContextHolder.getContext().setAuthentication(new EntityContext(entity,expiresInMinutes));
String response = gson.toJson(BasicResponse.SUCCESS);
return response;
}
catch (Exception e)
{
session.invalidate();
e.printStackTrace();
throw new AuthenticationCredentialsNotFoundException("Authentication Error");
}
}
Adding the following to my web security config seemed to do the trick.
.requestCache()
.requestCache(new NullRequestCache())
I am not sure what side effects are of doing this. I picked it up off of a blog https://drissamri.be/blog/2015/05/21/spring-security-and-spring-session/
If there is any more insight into if this is good practice or bad practice I would appreciate any comments.
My final web security config looks like the following:
http
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.and()
.csrf()
.disable()
.authorizeRequests().antMatchers("**")
.permitAll()
.anyRequest()
.authenticated()
.and()
.requestCache()
.requestCache(new NullRequestCache())
.and()
.sessionManagement()
.sessionFixation()
.newSession()
;
I need change the redirect url when my user is succefull logged in using some of Spring Social Providers, like Twitter in this case.
I'm getting in every set***Url("") a null pointer exception
Some times setting this don't work too
I tried so far setting:
public ProviderSignInController signInController(ConnectionFactoryLocator connectionFactoryLocator,
UsersConnectionRepository usersConnectionRepository) {
ProviderSignInController providerSignInController = new ProviderSignInController(connectionFactoryLocator,
usersConnectionRepository,
new CSignInAdapter(requestCache()));
providerSignInController.setPostSignInUrl("/home");
providerSignInController.setApplicationUrl("localhost:8080/home");
return providerSignInController;
}
I tried each one of setPostSignInUrl and setApplicationUrl, separately.
Also tried:
#Bean
public ConnectController connectController(ConnectionFactoryLocator connectionFactoryLocator,
ConnectionRepository connectionRepository) {
ConnectController connectController = new ConnectController(connectionFactoryLocator, connectionRepository);
connectController.addInterceptor(new TweetAfterConnectInterceptor());
connectController.setApplicationUrl("/home");
return connectController;
}
I'm using Spring Social showcase with Security as base to do this.
In case of need I'm posting the HttpSecurity configuration:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()
.loginPage("/signin")
.loginProcessingUrl("/signin/authenticate")
.failureUrl("/signin?param.error=bad_credentials")
.defaultSuccessUrl("/home")
.and()
.csrf()
.and()
.logout()
.logoutUrl("/signout")
.deleteCookies("JSESSIONID")
.and()
.authorizeRequests()
.antMatchers("/admin/**", "/favicon.ico", "/resources/**", "/auth/**", "/signin/**", "/signup/**",
"/disconnect/facebook").permitAll()
.antMatchers("/**").authenticated()
.and()
.rememberMe()
.and()
.apply(new SpringSocialConfigurer());
}
Try this:
private SpringSocialConfigurer getSpringSocialConfigurer() {
SpringSocialConfigurer config = new SpringSocialConfigurer();
config.alwaysUsePostLoginUrl(true);
config.postLoginUrl("/home");
return config;
}
Then change your configure method:
.apply(getSpringSocialConfigurer());
For Spring Social, you can configure the post login URL to a default URL, such as "/home".
But under certain circumstances, you would like to direct the user to a different URL. In order to dynamically change the redirect URL after successful login, you can simply return a String representing any URL you desire in the signIn method of your SignInAdapter implementation class:
import org.springframework.social.connect.web.SignInAdapter;
public class SocialSignInAdapter implements SignInAdapter {
public String signIn(String localUserId, Connection<?> connection, NativeWebRequest request) {
boolean flag = true;
if (flag) {
return "/a_different_url";
}
return null; // Default, which means using the default post login URL
}
}
I verified this using Spring Social version 1.1.0.RELEASE