Search code examples
springsecurityuserdetailsservice

Springboot authentication with an additional custom parameter


I’m trying to use spring boot security in my application. I need to give access to both sales men and customers. Each are mapped to different entities and in turn use different repositories.

How can my UserDetailServive implementation use a different repository depending on a custom form parameter?

<form th:action="@{/login}" method="post">
    <div>
        <label>User Name: <input type="text" name="username"/></label>
        <label>Password: <input type="password" name="password"/></label>
        <label>User type: <input type="radio" name="userType" value="customer"/>
                          <input type="radio" name="userType" value="salesMen"/></label>
    </div>
    <div><input type="submit" value="Login"/></div>
</form>

@Service
public class UserDetailServiceImpl implements UserDetailsService {

    @Autowired
    SalesMenRepository salesMenRepository;

    @Autowired
    CustomersRepository customersRepository;    


    @Override
    public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {

        // How can I get userType parameter ????

        if ("salesMen".equals(userType)) {
            Optional<SalesMan> salesMan = salesMenRepository.findById(userName);
            if (!salesMan.isPresent()) {
                throw new UsernameNotFoundException(userName);
            }

            return new UserDetailsImp(salesMan.get());
        } else {
            Optional<Customer> customer = customersRepository.findById(userName);
            if (!customer.isPresent()) {
                throw new UsernameNotFoundException(userName);
            }

            return new UserDetailsImp(customer.get());      
        }
    }

}   

Solution

  • You can join userName with userType by any character, eg colon: userName:userType, and in loadUserByUsername method, you split and get it String[] parts = userName.split(":");
    But when you join custom parameter into userName, you must custom authentication filter. In my case, I add new custom param have name is dmBhxhId. I create CustomUser:

    public class CustomUser extends User {
        private Long dmBhxhId;
    
        public Long getDmBhxhId() {
            return dmBhxhId;
        }
    
        public void setDmBhxhId(Long dmBhxhId) {
            this.dmBhxhId = dmBhxhId;
        }
    
        public CustomUser(String username, String password, Collection<? extends GrantedAuthority> authorities,
                Long dmBhxhId) {
            super(username, password, authorities);
            this.dmBhxhId = dmBhxhId;
        }
    
        public CustomUser(String username, String password, Collection<? extends GrantedAuthority> authorities) {
            super(username, password, authorities);
        }
    
    }
    

    And I custom authentication filter

    public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
        private String extraParameter = "extra";
        private String delimiter = ":";
    
        /**
         * Given an {@link HttpServletRequest}, this method extracts the username
         * and the extra input values and returns a combined username string of
         * those values separated by the delimiter string.
         *
         * @param request
         *            The {@link HttpServletRequest} containing the HTTP request
         *            variables from which the username client domain values can be
         *            extracted
         */
        @Override
        protected String obtainUsername(HttpServletRequest request) {
            String username = request.getParameter(getUsernameParameter());
            String extraInput = request.getParameter(getExtraParameter());
            Map<String, String[]> map = request.getParameterMap();
            String combinedUsername = username + getDelimiter() + extraInput;
            return combinedUsername;
        }
    
        /**
         * @return The parameter name which will be used to obtain the extra input
         *         from the login request
         */
        public String getExtraParameter() {
            return this.extraParameter;
        }
    
        /**
         * @param extraParameter
         *            The parameter name which will be used to obtain the extra
         *            input from the login request
         */
        public void setExtraParameter(String extraParameter) {
            this.extraParameter = extraParameter;
        }
    
        /**
         * @return The delimiter string used to separate the username and extra
         *         input values in the string returned by
         *         <code>obtainUsername()</code>
         */
        public String getDelimiter() {
            return this.delimiter;
        }
    
        /**
         * @param delimiter
         *            The delimiter string used to separate the username and extra
         *            input values in the string returned by
         *            <code>obtainUsername()</code>
         */
        public void setDelimiter(String delimiter) {
            this.delimiter = delimiter;
        }
    }
    

    In SecurityConfiguration file, I init CustomAuthenticationFilter

     @Bean  
        public CustomAuthenticationFilter customAuthenticationFilter() throws Exception {
            CustomAuthenticationFilter bcsAuthFilter = new CustomAuthenticationFilter();
            bcsAuthFilter.setAuthenticationManager(authenticationManager());
            bcsAuthFilter.setAuthenticationFailureHandler(ajaxAuthenticationFailureHandler);
            bcsAuthFilter.setAuthenticationSuccessHandler(ajaxAuthenticationSuccessHandler);
            bcsAuthFilter.setFilterProcessesUrl("/api/authentication");
            bcsAuthFilter.setPostOnly(true);
            bcsAuthFilter.setExtraParameter("dm_bhxh_id");
            bcsAuthFilter.setUsernameParameter("j_username");
            bcsAuthFilter.setPasswordParameter("j_password");
            return bcsAuthFilter;
        } 
    

    And call it in configure method

    .addFilterBefore(bcsAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
    

    look like

        @Override
            protected void configure(HttpSecurity http) throws Exception {
                http
                    .csrf()
                    .ignoringAntMatchers("/websocket/**").ignoringAntMatchers("/api/public/odts/**")
                .and()
                    .addFilterBefore(bcsAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
                    .addFilterAfter(new CsrfCookieGeneratorFilter(), CsrfFilter.class)
                    .exceptionHandling()
                    .authenticationEntryPoint(authenticationEntryPoint)
    ....
    

    Done, hope to help you! Sorry, my english is not good.