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.
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";
}
}