Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Document how to publish an AuthenticationManager @Bean without WebSecurityConfigurerAdapter #11926

Closed
sjohnr opened this issue Sep 30, 2022 · 9 comments
Assignees
Labels
in: docs An issue in Documentation or samples type: enhancement A general enhancement
Milestone

Comments

@sjohnr
Copy link
Member

sjohnr commented Sep 30, 2022

We should adapt the recommendations and examples in the blog article Spring Security without the WebSecurityConfigurerAdapter into the reference documentation.

For example, we can configure an AuthenticationManager for use by the application that can perform user authentication (similar to formLogin()) like so:

@Configuration
@EnableWebSecurity
public class SecurityConfiguration {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests((authorize) -> authorize
                .anyRequest().authenticated()
            );
        return http.build();
    }

    @Bean
    public AuthenticationManager authenticationManager(
            UserDetailsService userDetailsService,
            PasswordEncoder passwordEncoder) {
        DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
        authenticationProvider.setUserDetailsService(userDetailsService);
        authenticationProvider.setPasswordEncoder(passwordEncoder);

        return new ProviderManager(authenticationProvider);
    }

    @Bean
    public UserDetailsService userDetailsService() {
        UserDetails userDetails = User.withDefaultPasswordEncoder()
            .username("user")
            .password("password")
            .roles("USER")
            .build();

        return new InMemoryUserDetailsManager(userDetails);
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

}

Context:

Many applications require the use of an AuthenticationManager outside the Spring Security filter chain (e.g. in a @RestController). The LDAP Authentication example recommends publishing an AuthenticationManager @Bean, and this example can be generalized and numerous examples given for various scenarios.

@sjohnr sjohnr added in: docs An issue in Documentation or samples type: enhancement A general enhancement labels Sep 30, 2022
@sjohnr sjohnr modified the milestones: 5.8.0, 5.6.8, 5.7.4 Sep 30, 2022
@sjohnr
Copy link
Member Author

sjohnr commented Oct 13, 2022

Note: We should also enhance the deprecation notice in 5.7/5.8 to include a hint of where to get the same information. Perhaps a link to this reference documentation, or a simplified example method signature for a SecurityFilterChain bean.

@marcusdacoregio marcusdacoregio modified the milestones: 5.7.4, 5.7.5 Oct 17, 2022
@marcusdacoregio marcusdacoregio modified the milestones: 5.7.5, 5.7.6 Oct 31, 2022
@marcusdacoregio
Copy link
Contributor

When this is completed we should update the link to the blog post in WebSecurityConfigurerAdapter to a link to the reference documentation

@marcusdacoregio marcusdacoregio modified the milestones: 5.7.6, 5.7.7 Dec 16, 2022
@marcusdacoregio
Copy link
Contributor

#12343 can provide more use cases to the documentation

@michael-simons
Copy link
Contributor

Additional scenarios:

  • Describe how to configure the global authentication manager (the parent of all others), i.e. how to disable credential erasure
  • Make the DSL easier to use (a DSL should guide the user and prevent double / tripple usage and weird things like in this screenshot which is me trying to figure out one of the many places one can either access the shared object or appereantly set the one to use…
    FlYIkcgXgAAJzjF )

@uniquejava
Copy link

uniquejava commented Apr 7, 2023

Here is my journey.

I want to use authenticationManager in my custom LoginController (for some simple JWT login),

Here is my user.

spring.security.user.name=user
spring.security.user.password={noop}password

Here is my curl test.

curl  -d "username=user&password=password" -vvv http://localhost:8080/api/v1/login
*   Trying 127.0.0.1:8080...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> POST /api/v1/login HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.87.0
> Accept: */*
> Content-Length: 31
> Content-Type: application/x-www-form-urlencoded
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 403
< Vary: Origin
< Vary: Access-Control-Request-Method
< Vary: Access-Control-Request-Headers
< X-Content-Type-Options: nosniff
< X-XSS-Protection: 0
< Cache-Control: no-cache, no-store, max-age=0, must-revalidate
< Pragma: no-cache
< Expires: 0
< X-Frame-Options: DENY
< Content-Length: 0
< Date: Fri, 07 Apr 2023 03:41:09 GMT
<
* Connection #0 to host localhost left intact

As you can see, it always gives me 403 forbidden error, but I already configured /login permitAll().

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.csrf().disable();

        http.authorizeHttpRequests(auth -> auth
                .requestMatchers("/login").permitAll()
                .anyRequest().authenticated()
        );

        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

        http.addFilterBefore(new JwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }
}

Why? I found a solution here: https://stackoverflow.com/questions/75768437/requestmatchers-permitall-does-not-work
It suggests not use spring boot 3, but spring boot 2 🤣

After a lot of try and fail, I found that the following simplified version also works!

    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
        return config.getAuthenticationManager();
    }

   @Bean
    public UserDetailsService userDetailsService() {
        UserDetails userDetails = User.withDefaultPasswordEncoder()
            .username("user")
            .password("password")
            .roles("USER")
            .build();

        return new InMemoryUserDetailsManager(userDetails);
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

But once I remove @Bean public UserDetailsService userDetailsService() , it will throw jakarta.servlet.ServletException: Handler dispatch failed: java.lang.StackOverflowError

and forward the request to some /errorurl but /error by default is not in the permitAll(..) path, so a 403 forbidden is given to my curl

Looks like spring security uses the default DaoAuthenticationProvider in this case but not the default InMemoryUserDetailsService.

I think put the /error path to permitAll by default will make spring security experience much much better, because then the curl will clearly tell me it's 500 internal server error, and then I will not be using this keyword to search requestMatchers permitAll not work 😄

@uniquejava
Copy link

The reason I didn't configure UserDetailsService at first because spring security gives me this impression: it always has something by default.

AuthenticationFilter -> UsernamePasswordAuthenticationFilter by default
AutenticationManager -> ProviderManager by default
AuthenticationProvider -> DaoAuthenticationProvider by default
UserDetailsService -> InMemoryUserDetailsService by default

In my previous journey, once we exposed return config.getAuthenticationManager(); like so, then InMemoryUserDetailsService is not the default UserDetailsService.

@sjohnr
Copy link
Member Author

sjohnr commented Apr 10, 2023

Sorry you had some trouble, @uniquejava.

Please see my initial comment on this issue for how I recommend publishing an AuthenticationManager @Bean. Additional ways to publish one are documented on the 5.8 migration guide under Publish an AuthenticationManager Bean.

Regarding the StackOverflowError, see this comment.

I think put the /error path to permitAll by default will make spring security experience much much better, because then the curl will clearly tell me it's 500 internal server error, and then I will not be using this keyword to search requestMatchers permitAll not work 😄

Thanks for the suggestion! However, we prefer to use a more secure posture by default. In Spring Security 6, for example, requests that are missing an authorization rule are actually denied by default. I would argue that it's better for you to learn about this than it would be to have a weaker security posture by default. Hopefully, you can see the point here. 😉

If you have any further questions, please use Stack Overflow and feel free to share a link to the posted question so others can find it.

@nandorholozsnyak
Copy link

Sorry you had some trouble, @uniquejava.

Please see my initial comment on this issue for how I recommend publishing an AuthenticationManager @Bean. Additional ways to publish one are documented on the 5.8 migration guide under Publish an AuthenticationManager Bean.

Regarding the StackOverflowError, see this comment.

I think put the /error path to permitAll by default will make spring security experience much much better, because then the curl will clearly tell me it's 500 internal server error, and then I will not be using this keyword to search requestMatchers permitAll not work smile

Thanks for the suggestion! However, we prefer to use a more secure posture by default. In Spring Security 6, for example, requests that are missing an authorization rule are actually denied by default. I would argue that it's better for you to learn about this than it would be to have a weaker security posture by default. Hopefully, you can see the point here. wink

If you have any further questions, please use Stack Overflow and feel free to share a link to the posted question so others can find it.

Hello there,

Just bumped into this "problem" after migrating from Spring Security 5.7.6 to Spring Security 6. The docs at first glance looked fine, but this stupid /error problem drove me crazy.

It started with a non-existing favicon.ico and ended with a double login in the background that set a lot of things back and fort for my users, but guess what, on Firefox it did not cause problems, on Chromium it DID!

I know the docs are clear but maybe for the error page there could be a simple example or an admonition that if you forgot to configure the paths properly, you can end up having HTTP 403 for or redirects (it depends on your config) for example for non existing static resources. I understand the logic behind the enforcement, but maybe a lot of developer will face it, and they might not be able to understand the reason behind that.

@sjohnr
Copy link
Member Author

sjohnr commented Oct 13, 2023

Thanks for your input on this everyone, and sorry for the delay in getting this task done. I've updated the Username/Password Authentication page. It now includes a few full examples (those discussed on this issue), and also links to other pages organized by use case similar to other recent updates to the docs.

You can preview the 5.8 version here (also forward ported up through 6.2/main).

Feedback welcome! If you do see additional items to add, please feel free to open a new issue.

sjohnr added a commit that referenced this issue Oct 25, 2023
sjohnr added a commit that referenced this issue Oct 25, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: docs An issue in Documentation or samples type: enhancement A general enhancement
Projects
Archived in project
Development

No branches or pull requests

6 participants