Custom WebSecurityConfigurerAdapter

后端 未结 3 2135
时光说笑
时光说笑 2021-02-06 05:38

I have this problem implementing a custom login authentication using SpringBoot and SpringBoot-Security. I made a Bitbucket repository as reference for this thread (within Custo

相关标签:
3条回答
  • 2021-02-06 05:40

    Firstly I would encourage you to read about String Security Core Services.

    A key one in this situation is AuthenticationManager that is responsible for deciding if the user is authenticated or not. This is what you configure with AuthenticationManagerBuilder. It's primary implementation in Spring is ProviderManager that allows to define multiple authentication mechanisms in a single applications. The most common use case is that there is one, but it is still handled by this class. Each of those multiple authentication mechanisms is represented by a different AuthenticationProvider. ProviderManager takes a list of AunthenticationProviders an iterates through them to see if any of them can authenticate the user.

    What you are interested in is DaoAuthenticationProvider. As the name suggests, it allows to use a Data Access Object to authenticate the user. It uses a standard interface for such DAO - a UserDetailsService. There is a default implementation available in Spring Security, but usually this is the bit you will want to implement yourself. All the rest is provided.

    Also, the configuration bit you need is totally independent from Spring Boot. This is how you'd do it in XML:

    <sec:authentication-manager >
        <sec:authentication-provider user-service-ref="myUserDetailsService" />
    </sec:authentication-manager>
    

    And in Java it will be:

    @Configuration
    @EnableWebSecurity
    public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
    
        @Autowired
        private UserDetailsService myUserDetailsService;
    
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.userDetailsService(myUserDetailsService);
        }
    }
    

    As per UserDetails implementation, usually the one provided by Spring Security is enough. But you can also implement your own if need be.

    Usually you will also want a PasswordEncoder. A good one, like BCryptPasswordEncoder:

    @Configuration
    @EnableWebSecurity
    public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
    
        @Autowired
        private UserDetailsService userDetailsService;
    
        @Autowired
        private PasswordEncoder passwordEncoder;
    
        @Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.userDetailsService(userDetailsService)
                .passwordEncoder(passwordEncoder);
        }
    }
    

    Notice that it's a @Bean, so that you can @Autowire it in your UserRepository to encode user passwords as you save them in the database.

    0 讨论(0)
  • 2021-02-06 05:50

    First of all the two approaches are used for different purpose and not interchangeable.

    Case 1:

    UserDetailsService is used purely as DAO to locate user information by your authentication and based on that info authenticate user, no authentication should be done within UserDetailsService, just data access. Specifications clearly mention that. This is what you are looking for.

    Case2:

    AuthentictionProvider on the other hand is used for providing custom method of authentication, for example if you want to custom authenticate on fields other than login and password you may do that by implementing AuthentictionProvider and supplying this object to your AuthenticationManagerBuilder. I do not think this is what you want to do in you project. You are just looking to implement your authentication based on users stored in database using login and password which is default way. In above Case 1 where you implemented just UserDetailsService, instance of AuthentictionProvider was created for you in AuthenticationManager by the container and it was DaoAuthenticationProvider since you supplied UserDetailsService which is nothing else but DAO in your system that is used to retrive user for authentication.

    Now to your implementation, in your configuration instead of :

      @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    //        auth.userDetailsService(new AdminSecurityService());
            auth.authenticationProvider(new AdminSecurityAuthenticationProvider());
        }
    

    do something like this

    @Autowired
    private CustomUserDetailsService userDetailsService;
    
     @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService);
    
    }
    

    and your CustomUserDetailsService has to implement org.springframework.security.core.userdetails.UserDetailsService

    @Service
    public class CustomUserDetailsService implements UserDetailsService {
    
        private final AdminRepository userRepository;
    
        @Autowired
        public CustomUserDetailsService(AdminRepository userRepository) {
            this.userRepository = userRepository;
        }
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            Admin user = userRepository.findByLogin(username);
            if (user == null) {
                throw new UsernameNotFoundException(String.format("User %s does not exist!", username));
            }
            return new UserRepositoryUserDetails(user);
        }
    
        private final static class UserRepositoryUserDetails extends Admin implements UserDetails {
    
            private static final long serialVersionUID = 1L;
    
            private UserRepositoryUserDetails(User user) {
                super(user);
            }
    
            @Override
            public Collection<? extends GrantedAuthority> getAuthorities() {
                return AuthorityUtils.createAuthorityList("ROLE_USER");
            }
    
            @Override
            public String getUsername() {
                return getLogin();//inherited from user
            }
    
            @Override
            public boolean isAccountNonExpired() {
                return true;//not for production just to show concept
            }
    
            @Override
            public boolean isAccountNonLocked() {
                return true;//not for production just to show concept
            }
    
            @Override
            public boolean isCredentialsNonExpired() {
                return true;//not for production just to show concept
            }
    
            @Override
            public boolean isEnabled() {
                return true;//not for production just to show concept
            }
    //getPassword() is already implemented in User.class
        }
    
    }
    

    of course implementation is up to you but you have to be able to provide user password, and rest of the methods in that interface based on the retrieved user (Admin.class in your case). Hope it helps. I did not run this example so if I made some typos go ahead and ask if something does not work. I would also get rid of that 'AuthentictionProvider' from your project if you don't need it.

    Here you got documentation:http://docs.spring.io/spring-security/site/docs/4.0.0.RC1/reference/htmlsingle/#tech-userdetailsservice

    After comments: You can set PasswordEncoder in your configure method without too much hassle just do:

     @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
    
        }
    @Bean
        public PasswordEncoder passwordEncoder(){
            PasswordEncoder encoder = new BCryptPasswordEncoder();
            return encoder;
        }
    

    You can do that because you get access to AbstractDaoAuthenticationConfigurer returned from auth.userDetailsService(userDetailsService) and it allows you to configure DaoAuthenticationProvider, which is your provider of choice when you choose to use UserDetailsService. You are right PasswordEncoder is set in AuthenticationProvider but you do not have to implement AuthenticationProvider just use convineince object that is returned from auth.userDetailsService(userDetailsService) and set your encoder on that object which will pass it to AuthenticationPriovider in your case DaoAuthenticationProvider that was already created for you. Just like roadrunner mentioned in the comment you very rarely need to implement your own AuthenticationProvider usually most of authentication configuration adjustments can be done with the use of AbstractDaoAuthenticationConfigurer which as mentioned above is returned from auth.userDetailsService(userDetailsService).

    "And if I ever wanted to add a password encryption. And if I ever wanted to do other authentication (like checking if the user is locked, active, user is still logged-in, etc. [excluding password hashing]) will use the AuthenticationProvider."

    No this is done for you as part of standard authentication mechanism http://docs.spring.io/autorepo/docs/spring-security/3.2.0.RELEASE/apidocs/org/springframework/security/core/userdetails/UserDetails.html If you look at the interface UserDetails you will see that if any of the above methods returns false authentication will fail. Implementing AuthenticationProvider is really needed in very nonstandard cases. All standard stuff is pretty much covered by the framework .

    0 讨论(0)
  • 2021-02-06 05:54

    In a JDBC way http://www.mkyong.com/spring-security/spring-security-form-login-using-database/ basically, you have to specify the query to retrieve users.

    0 讨论(0)
提交回复
热议问题