Search code examples
javaspringmaven

Ambiguous mapping. Cannot map ***; there is already *** bean method mapped


So after many years we are finally updating our Spring framework to the latest version, and that includes some hickups that i've been able to solve. But this one I can't seem to figure out.

We have a LoginController that contains the different mappings such as LoginRedirect.do, ChangePassword.Do etc. But for some reason I keep getting this error message when it tries to deploy the war file to tomcat:

Caused by: java.lang.IllegalStateException: Ambiguous mapping. Cannot map 'customUserDetailsService2' method com.test.controller.LoginController#loginRedirect(ModelMap) to {GET [/login/LoginRedirect.do]}: There is already 'loginController' bean method com.test.controller.LoginController#loginRedirect(ModelMap) mapped.

I have no other controllers that have these mappings, but it seems to be caused by our customUserDetailsService2 bean. Our ApplicationContext-security.xml has this:

<beans:bean id="customAuthenticationProvider" class="com.test.security.CustomDaoAuthenticationProvider">
    <beans:property name="userDetailsService" ref="customUserDetailsService2"/>
</beans:bean>

<beans:bean id="customUserDetailsService2" class="com.test.controller.LoginController"/>

And the loginControllers looks like this:

package com.test.controller;

import com.test.dao.datasnap.ILoginDAO;
import com.test.dao.datasnap.IPublicMethodsDAO;
import com.test.dao.datasnap.IUtilDAO;
import com.test.exception.DataSnapException;
import com.test.model.BasicResult;
import com.test.model.LoginResponse;
import com.test.model.PDCUser;
import com.test.security.CustomUser;
import com.test.security.CustomUserDetailsService;
import com.test.security.Module;
import com.test.translations.UITranslationCache;
import com.test.util.Util;
import jakarta.servlet.http.HttpServletRequest;
import org.json.JSONException;
import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.dao.DataAccessException;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

@Controller
@Scope("request")
@RequestMapping("login")
public class LoginController implements CustomUserDetailsService {
   
    /*
    * Autowired DAO for Login
    */
    @Autowired
    private ILoginDAO dataDAO;

    @Autowired
    private IUtilDAO utilDAO;
      
    @Autowired
    private IPublicMethodsDAO methodsDAO; 
    
    /*
     * Gets the current logged in user. 
     */
    @GetMapping(value="/GetUserName.do")
    public String printUser(ModelMap modelMap) {
           
        String name = Util.getUserName(); //get logged in username
        modelMap.put("data", name);
        return "ajax/json/dataresponse";   
    }

    //from the CustomUserDetailsService interface
    public UserDetails loadUserByUsernamePasswordAuthenticationToken(UsernamePasswordAuthenticationToken authentication)
               throws UsernameNotFoundException, DataAccessException {
    
        /*
        * Use the DAO to get the data we want. This is something we really want to delegate so that we can keep our controller code as small
        * as possible.
        */
            
        try {

            String username = authentication.getName();
            if (((username == null) || "".equals(username))) {
                throw new BadCredentialsException(UITranslationCache.GetUITranslation(16877, Util.GetCompanyLanguage(), "Username and/or password are not correct."));
            }

            LoginResponse returnObject = dataDAO.loginToPDC(authentication);
            PDCUser pdcUser = (PDCUser) returnObject.getDataObject();

            List<GrantedAuthority> authList = new ArrayList<GrantedAuthority>(2);

            /*
             * based on the settings retrieved from DataSnap, we set the appropriate authorities for the user
             */
            if (pdcUser != null && pdcUser.getModules() != null) {
                Iterator<Module> itr = pdcUser.getModules().iterator();
                while(itr.hasNext()) {
                    Module module = itr.next();
                    if (module.getGrantedauthority() != null) {
                        authList.add(new SimpleGrantedAuthority(module.getGrantedauthority()));
                    }
                }
            }
                    
            UserDetails user = null;            
            boolean enabled = true;         
            user = new CustomUser(
                    pdcUser.getUsername(),
                    pdcUser.getManNr(),
                    pdcUser.getPassword(),
                    enabled,
                    authList,
                    returnObject.getSessionId(),
                    pdcUser.getModules(),
                    pdcUser.getLanguageCode(),
                    pdcUser.getRememberMe(),
                    pdcUser.getPasswordExpired());

            return user;
        } catch (DataSnapException e) {
            throw new AuthenticationServiceException(e.getMessage());
        } catch (AuthenticationException e) {
            throw e;
        } catch (Exception e)   {
            e.printStackTrace();
        }
        return null;             
    }
    
    @ResponseStatus(value=HttpStatus.FORBIDDEN)
    @GetMapping(value = "/accessdenied.do")
    public ModelAndView accessDenied(ModelMap modelmap) {

        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        List<Module> modules = Util.getModules();
        
        modelmap.put("modules", modules);
        modelmap.put("username", auth.getName());
        
        return new ModelAndView("accessdenied", modelmap); // logical view name -> accessdenied.jsp
        
    }
    
        
    @ResponseStatus(value=HttpStatus.UNAUTHORIZED)
    @GetMapping(value ="/LoginRedirect.do")
    public String loginRedirect(ModelMap modelmap) throws JSONException{

        JSONObject jsonObject = new JSONObject();
        jsonObject.put("success", false);
        jsonObject.put("message", UITranslationCache.GetUITranslation(16878, Util.GetCompanyLanguage(), "User not authorized to view this page"));
        
        modelmap.put("data", jsonObject);
        
        return "ajax/json/dataresponse";
    }
    
    @GetMapping(value ="/LogoutAjax.do")
    public String logoutAjax(ModelMap modelmap) throws JSONException{

        JSONObject jsonObject = new JSONObject();
        jsonObject.put("success", true);
        jsonObject.put("message", UITranslationCache.GetUITranslation(16879, Util.GetCompanyLanguage(), "User was succesfully logged out"));
        
        modelmap.put("data", jsonObject);
        
        return "ajax/json/dataresponse";
    }

    @GetMapping(value ="/ChangePassword.do")
    public String changePassword(@RequestParam(required = true, value = "oldpw") String oldpw,
                                 @RequestParam(required = true, value = "newpw") String newpw,
                                 ModelMap modelMap) throws DataSnapException {

        BasicResult result = utilDAO.ChangePassword(oldpw, newpw);

        modelMap.put("data", result.getResult());

        return "ajax/json/dataresponse";
    }
    
}

It seems as if the LoginController by itselfs maps the different methods before the customUserDetailsService2 has a change to do this. But this used to work before so I'm a bit lost why it wont work now.


Solution

  • Your controller is strange and it mixes responsibilities. Your controller is both a controller and a UserDetailsService don't. Move the UserDetailsService into its own component, as it should be.

    It doesn't make sense for your controller to be request scoped either.

    Finally due to the @Controller and the declaration in XML you end up with 2 instances (which is also what the error tells you).

    @Service
    public class MyCustomUserDetailsService implements CustomUserDetailsService {
    
        @Autowired
        private ILoginDAO dataDAO;
    
        //from the CustomUserDetailsService interface
        public UserDetails loadUserByUsernamePasswordAuthenticationToken(UsernamePasswordAuthenticationToken authentication)
                   throws UsernameNotFoundException, DataAccessException {
        
            /*
            * Use the DAO to get the data we want. This is something we really want to delegate so that we can keep our controller code as small
            * as possible.
            */
                
            try {
    
                String username = authentication.getName();
                if (((username == null) || "".equals(username))) {
                    throw new BadCredentialsException(UITranslationCache.GetUITranslation(16877, Util.GetCompanyLanguage(), "Username and/or password are not correct."));
                }
    
                LoginResponse returnObject = dataDAO.loginToPDC(authentication);
                PDCUser pdcUser = (PDCUser) returnObject.getDataObject();
    
                List<GrantedAuthority> authList = new ArrayList<GrantedAuthority>(2);
    
                /*
                 * based on the settings retrieved from DataSnap, we set the appropriate authorities for the user
                 */
                if (pdcUser != null && pdcUser.getModules() != null) {
                    Iterator<Module> itr = pdcUser.getModules().iterator();
                    while(itr.hasNext()) {
                        Module module = itr.next();
                        if (module.getGrantedauthority() != null) {
                            authList.add(new SimpleGrantedAuthority(module.getGrantedauthority()));
                        }
                    }
                }
                        
                UserDetails user = null;            
                boolean enabled = true;         
                user = new CustomUser(
                        pdcUser.getUsername(),
                        pdcUser.getManNr(),
                        pdcUser.getPassword(),
                        enabled,
                        authList,
                        returnObject.getSessionId(),
                        pdcUser.getModules(),
                        pdcUser.getLanguageCode(),
                        pdcUser.getRememberMe(),
                        pdcUser.getPasswordExpired());
    
                return user;
            } catch (DataSnapException e) {
                throw new AuthenticationServiceException(e.getMessage());
            } catch (AuthenticationException e) {
                throw e;
            } catch (Exception e)   {
                e.printStackTrace();
            }
            return null;             
        }
    
    }
    

    Your modified XML configuration

    <beans:bean id="customAuthenticationProvider" class="com.test.security.CustomDaoAuthenticationProvider">
        <beans:property name="userDetailsService" ref="customUserDetailsService2"/>
    </beans:bean>
    
    <beans:bean id="customUserDetailsService2" class="com.test.controller.MyCustomUserDetailsService"/>
    
    

    Your slimmed down controller

    @Controller
    @RequestMapping("login")
    public class LoginController {
       
        @Autowired
        private IUtilDAO utilDAO;
             
        /*
         * Gets the current logged in user. 
         */
        @GetMapping(value="/GetUserName.do")
        public String printUser(ModelMap modelMap) {
               
            String name = Util.getUserName(); //get logged in username
            modelMap.put("data", name);
            return "ajax/json/dataresponse";   
        }
    
        @ResponseStatus(value=HttpStatus.FORBIDDEN)
        @GetMapping(value = "/accessdenied.do")
        public ModelAndView accessDenied(ModelMap modelmap) {
    
            Authentication auth = SecurityContextHolder.getContext().getAuthentication();
            List<Module> modules = Util.getModules();
            
            modelmap.put("modules", modules);
            modelmap.put("username", auth.getName());
            
            return new ModelAndView("accessdenied", modelmap); // logical view name -> accessdenied.jsp
            
        }
        
            
        @ResponseStatus(value=HttpStatus.UNAUTHORIZED)
        @GetMapping(value ="/LoginRedirect.do")
        public String loginRedirect(ModelMap modelmap) throws JSONException{
    
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("success", false);
            jsonObject.put("message", UITranslationCache.GetUITranslation(16878, Util.GetCompanyLanguage(), "User not authorized to view this page"));
            
            modelmap.put("data", jsonObject);
            
            return "ajax/json/dataresponse";
        }
        
        @GetMapping(value ="/LogoutAjax.do")
        public String logoutAjax(ModelMap modelmap) throws JSONException{
    
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("success", true);
            jsonObject.put("message", UITranslationCache.GetUITranslation(16879, Util.GetCompanyLanguage(), "User was succesfully logged out"));
            
            modelmap.put("data", jsonObject);
            
            return "ajax/json/dataresponse";
        }
    
        @GetMapping(value ="/ChangePassword.do")
        public String changePassword(@RequestParam(required = true, value = "oldpw") String oldpw,
                                     @RequestParam(required = true, value = "newpw") String newpw,
                                     ModelMap modelMap) throws DataSnapException {
    
            BasicResult result = utilDAO.ChangePassword(oldpw, newpw);
    
            modelMap.put("data", result.getResult());
    
            return "ajax/json/dataresponse";
        }
        
    }