未调用 Spring Security Authentication UserDetailsS​​ervice 实现类

Posted

技术标签:

【中文标题】未调用 Spring Security Authentication UserDetailsS​​ervice 实现类【英文标题】:Spring Security Authentication UserDetailsService Implementation class not being invoked 【发布时间】:2020-01-03 09:31:14 【问题描述】:

我正在尝试从基于 Spring XML 的配置迁移到纯 Java 配置,我已经配置了所有配置文件,但是当我尝试在登录页面中输入用户名和密码时,它再次将我重定向回登录页面.我相信 userServiceImpl 方法没有被调用,因为控件没有去那里。 在这里,我有自动装配的 userServiceImpl 方法,它实现了 Spring Security 核心凭据的 UserDetailsS​​ervice。

package com.lw.sms.config;

import java.util.Properties;

import javax.sql.DataSource;

import org.apache.commons.dbcp.BasicDataSource;
import org.hibernate.SessionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;
import org.springframework.orm.hibernate4.HibernateTransactionManager;
import org.springframework.orm.hibernate4.LocalSessionFactoryBean;
import org.springframework.orm.hibernate4.LocalSessionFactoryBuilder;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import com.lw.sms.CustomAuthenticationSuccessHandler;
import com.lw.sms.UserServiceImpl;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
@EnableTransactionManagement
@Order(1000)
public class SecurityConfig extends WebSecurityConfigurerAdapter 

    private static final Logger logger = LoggerFactory.getLogger(SecurityConfig.class);

    @Value("$jdbc.dialect")
    String jdbcDialect;

    @Value("$jdbc.driverClassName")
    String jdbcDriverClassName;

    @Value("$jdbc.databaseurl")
    String jdbcDatabaseurl;

    @Value("$jdbc.username")
    String jdbcusername;

    @Value("$jdbc.password")
    String jdbcPassword;

    @Autowired
    private CustomAuthenticationSuccessHandler customAuthenticationSuccessHandler;

    @Autowired
    private UserServiceImpl userServiceImpl;


    @Override
    public void configure(final AuthenticationManagerBuilder auth) throws Exception 
        logger.info("configure auth");
        auth.userDetailsService(userServiceImpl);
    


    @Bean
    public PasswordEncoder passwordEncoder() 
        PasswordEncoder encoder = new BCryptPasswordEncoder();
        return encoder;
    

    @Override
    public void configure(HttpSecurity http) throws Exception 
        logger.info("configure http");
        http.httpBasic().disable();

        http.authorizeRequests().and().formLogin().loginPage("/login").usernameParameter("employeeId")
                .passwordParameter("password").successHandler(customAuthenticationSuccessHandler)
                .failureUrl("/login?error").defaultSuccessUrl("/dashboard", true)

                .loginProcessingUrl("j_spring_security_check")

                .and().logout().logoutUrl("/j_spring_security_logout").logoutSuccessUrl("/logout")
                .invalidateHttpSession(true).deleteCookies("JSESSIONID")

                .and().sessionManagement().invalidSessionUrl("/logout").maximumSessions(1)
                .maxSessionsPreventsLogin(true).expiredUrl("/logout");
        http.csrf().disable();

        http.authorizeRequests().anyRequest().authenticated();

    

    @Bean
    public SessionRegistry sessionRegistry() 
        logger.info("sessionRegistry");
        return new SessionRegistryImpl();
    

    @Bean
    public SessionFactory sessionFactory() 
        LocalSessionFactoryBuilder builder = new LocalSessionFactoryBuilder(dataSource());
        builder.scanPackages("com.lw.sms").addProperties(hibernateProperties());
        return builder.buildSessionFactory();
    

    @Bean
    public LocalSessionFactoryBean hibernateSessionFactory() 
        logger.info("sessionFactory");
        org.hibernate.cfg.Configuration configuration = new org.hibernate.cfg.Configuration();
        configuration.configure("hibernate.cfg.xml");

        LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
        sessionFactory.setDataSource(dataSource());
        sessionFactory.setPackagesToScan("com.lw.sms");
        sessionFactory.setHibernateProperties(hibernateProperties());
        return sessionFactory;
    

    @Bean
    public DataSource dataSource() 
        logger.info("dataSource");
        BasicDataSource dataSource = new BasicDataSource();
        dataSource.setDriverClassName(jdbcDriverClassName);
        dataSource.setUrl(jdbcDatabaseurl);
        dataSource.setUsername(jdbcusername);
        dataSource.setPassword(jdbcPassword);
        return dataSource;
    

    @Bean
    @Autowired
    public HibernateTransactionManager transactionManager(SessionFactory sessionFactory) 
        logger.info("transactionManager");
        HibernateTransactionManager htm = new HibernateTransactionManager();
        htm.setSessionFactory(sessionFactory);
        return htm;
    

    @Bean
    public Properties hibernateProperties() 
        logger.info("hibernateProperties");
        Properties hibernateProperties = new Properties();
        hibernateProperties.setProperty("hibernate.show_sql", "true");
        hibernateProperties.setProperty("hibernate.dialect", jdbcDialect);
        hibernateProperties.setProperty("hibernate.default_schema", "sms");
        return hibernateProperties;
    


CustomAuthenticationSuccessHandler 代码附在下面

package com.lw.sms;

import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.lang.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.WebAttributes;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.WebAuthenticationDetails;
import org.springframework.stereotype.Component;

import com.lw.sms.constants.Constants;
import com.lw.sms.user.entities.EmployeeEntity;
import com.lw.sms.user.entities.EmployeePermissionsEntity;
import com.lw.sms.user.service.UserManagementService;
import com.lw.sms.util.QueryBuilder;


@Component("customAuthenticationSuccessHandler")
public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler 

    private static final Logger logger = LoggerFactory.getLogger(CustomAuthenticationSuccessHandler.class);

    private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

    @Autowired
    @Qualifier("sessionInfo")
    private SessionInfo sessionInfo;

    @Autowired
    @Qualifier("userManagementServiceImpl")
    private UserManagementService userManagementService;

    @Autowired
    private HttpServletRequest httpServletRequest;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
            Authentication authentication) throws IOException 
        response.setStatus(HttpServletResponse.SC_OK);
        clearAuthenticationAttributes(request);
        handle(request, response);
    

    protected void handle(HttpServletRequest request, HttpServletResponse response)
            throws IOException 
        Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();

            if (principal instanceof UserDetails) 
                EmployeeEntity employeeEntity = (EmployeeEntity) principal;
                setSessionInformationForEmployee(employeeEntity);
            

        if (response.isCommitted()) 
            return;
        
        startServices();
        redirectStrategy.sendRedirect(request, response, "/dashboard");
    

    /**
     * @param employeeEntity 
     * 
     */
    private void setSessionInformationForEmployee(EmployeeEntity employeeEntity) 
        try 
            WebAuthenticationDetails details = (WebAuthenticationDetails) SecurityContextHolder.getContext()
                    .getAuthentication().getDetails();
            userManagementService.updateLoginInfo(employeeEntity.getUsername(), details.getRemoteAddress());
            // setting session information
         catch (Exception e) 
            logger.info("Exception while set Session Information For Employee :" + ExceptionUtils.getFullStackTrace(e));
        
    

    private void startServices() 
        logger.info("Starting Services..");
        try 
            String domainName = httpServletRequest.getScheme() + "://" + httpServletRequest.getServerName();
            if (httpServletRequest.getServerPort() != 0) 
                domainName += ":" + httpServletRequest.getServerPort();
            
            domainName += httpServletRequest.getContextPath();
            Constants.domainName = domainName + "/resources";
         catch (Exception e) 
            logger.error("Error in start services :"+ ExceptionUtils.getFullStackTrace(e));
        

    

    protected void clearAuthenticationAttributes(HttpServletRequest request) 
        HttpSession session = request.getSession(false);
        if (session == null) 
            return;
        
        session.removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
    

    public void setRedirectStrategy(RedirectStrategy redirectStrategy) 
        this.redirectStrategy = redirectStrategy;
    

    protected RedirectStrategy getRedirectStrategy() 
        return redirectStrategy;
    

UserServiceImpl 代码附在下面

package com.lw.sms;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.lw.sms.user.dao.UserManagementDAO;
import com.lw.sms.user.entities.EmployeeEntity;

@Service("userServiceImpl")
public class UserServiceImpl implements UserDetailsService 

    @Autowired
    @Qualifier("userManagementDAOImpl")
    private UserManagementDAO userManagementDAO;

    @Override
    @Transactional
    public UserDetails loadUserByUsername(final String username) throws UsernameNotFoundException 
        EmployeeEntity employeeEntity = userManagementDAO.loadUserByUsername(username);
        if (employeeEntity == null) 
            ContractorEntity contractorEntity = userManagementDAO.loadUserByUsernameContractor(username);
            if(contractorEntity == null)
                throw new AuthenticationCredentialsNotFoundException("Invalid Username or Password");
        
        if(!employeeEntity.isEnabled())
            throw new AuthenticationCredentialsNotFoundException("Employee Disabled");
        return employeeEntity;
    


全局异常处理代码:

package com.lw.sms;

import java.io.IOException;
import java.nio.charset.Charset;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.exception.ExceptionUtils;
import org.json.simple.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.ConversionNotSupportedException;
import org.springframework.beans.TypeMismatchException;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
import org.springframework.stereotype.Controller;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.HttpSessionRequiredException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException;

@SuppressWarnings("deprecation")
@Controller
@ControllerAdvice
public class CustomExceptionHandler extends RuntimeException 

    /**
     * 
     */
    private static final long serialVersionUID = 3699167829164697594L;


    private static final Logger logger = LoggerFactory
            .getLogger(CustomExceptionHandler.class);

    @ExceptionHandler(AuthenticationCredentialsNotFoundException.class)
    @ResponseStatus(HttpStatus.UNAUTHORIZED)
    public ModelAndView handleLoginExceptions(HttpServletRequest request,
            HttpServletResponse response,Exception exception) 
        logger.info("Handling exception time error"+ exception.getMessage());
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("login");
        return modelAndView;
    

    @ExceptionHandler(HttpSessionRequiredException.class)
    public String handleSessionExpired() 
        logger.info("session-expired");
        return "logout";
    

    @SuppressWarnings("unchecked")
    @ExceptionHandler(value =  MissingServletRequestParameterException.class,
            ServletRequestBindingException.class, TypeMismatchException.class,
            HttpMessageNotReadableException.class,
            MethodArgumentNotValidException.class,
            MissingServletRequestPartException.class )
    @ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = "Bad Request")
    @ResponseBody
    public JSONObject handle400Exception(Exception ex,HttpServletResponse response) 
        logger.error("400 Exception" + ex.getMessage());
        JSONObject jsonObject=new JSONObject();
        jsonObject.put("status", HttpStatus.BAD_REQUEST);
        jsonObject.put("success", false);
        jsonObject.put("message", ex.getMessage());
        try 
            response.getOutputStream().write(jsonObject.toString().getBytes(Charset.defaultCharset()));
         catch (IOException e) 
            logger.error(ExceptionUtils.getFullStackTrace(e));
        
        response.setContentType("application/json; charset=UTF-8");
        return jsonObject;
    

    @ExceptionHandler(value =  NoSuchRequestHandlingMethodException.class, )
    @ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "Not Found")
    @ResponseBody
    public Boolean handle404Exception(Exception ex) 
        logger.error("404 Exception" + ExceptionUtils.getFullStackTrace(ex));
        return false;
    

    @ExceptionHandler(value =  HttpRequestMethodNotSupportedException.class,
            HttpMediaTypeNotSupportedException.class )
    @ResponseStatus(value = HttpStatus.NOT_ACCEPTABLE, reason = "Not Supported")
    @ResponseBody
    public Boolean handle405Exception(Exception ex) 
        logger.error("405 Exception" + ExceptionUtils.getFullStackTrace(ex));
        return false;
    

    @ExceptionHandler(value =  HttpMessageNotWritableException.class,
            ConversionNotSupportedException.class )
    @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR, reason = "Internal Server Error")
    @ResponseBody
    public Boolean handle500Exception(Exception ex) 
        logger.error("500 Exception" + ExceptionUtils.getFullStackTrace(ex));
        return false;
    

    @ExceptionHandler( Exception.class )
    public ModelAndView handleException(Exception ex, HttpServletRequest request,
            HttpServletResponse response) 
        logger.info("*****************************************************");
        logger.error("Exception: " + ExceptionUtils.getFullStackTrace(ex));
        ex.printStackTrace();
        logger.info("*****************************************************");
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("logout");
        return modelAndView;
    

这是我的 HomeController。

package com.lw.sms;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.servlet.ModelAndView;

import com.lw.sms.master.service.MasterDataService;
import com.lw.sms.user.entities.EmployeeEntity;

/**
 * Handles requests for the application home page.
 */
@Controller("homeController")
public class HomeController 

    @Autowired
    @Qualifier("masterDataServiceImpl")
    private MasterDataService masterDataService;

    @Autowired
    @Qualifier("sessionInfo")
    private SessionInfo sessionInfo;

    @GetMapping(value =  "/home", "/", "login" )
    public ModelAndView home() 
        return new ModelAndView("login");
    

    @GetMapping(value = "dashboard")
    public ModelAndView dashboard() 

        EmployeeEntity user = sessionInfo.getEmployeeEntity();
        return new ModelAndView("dashboard", "userDetails", user);
    

    // Logout page
    @ResponseStatus(code = HttpStatus.UNAUTHORIZED)
    @GetMapping(value = "logout")
    public String logout() 
        return "logout";
    


【问题讨论】:

【参考方案1】:

我能够复制错误。 我必须在 SecurityConfig 文件中配置一个新的 bean。

import org.springframework.security.authentication.dao.DaoAuthenticationProvider;

        @Bean
        public DaoAuthenticationProvider authenticationProvider() 
            DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
            authenticationProvider.setUserDetailsService(userServiceImpl);
            authenticationProvider.setPasswordEncoder(passwordEncoder());
            return authenticationProvider;
        

【讨论】:

【参考方案2】:

您能否尝试添加successForwardUrl,如下所示。

 http.authorizeRequests().antMatchers("/login").permitAll()
        .antMatchers("/dashboard").access("IS_AUTHENTICATED_FULLY")
        .antMatchers("/j_spring_security_check").access("IS_AUTHENTICATED_ANONYMOUSLY")
        .and().formLogin().loginPage("/login").usernameParameter("employeeId").passwordParameter("password")
        .successForwardUrl("/dashboard").defaultSuccessUrl("/dashboard", true).failureForwardUrl("/loginfailed")
        .loginProcessingUrl("/j_spring_security_check")
        .and().logout().logoutSuccessUrl("/logout").invalidateHttpSession(true)
        .and().sessionManagement().sessionFixation().none()
        .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
        .invalidSessionUrl("/login")
        .and().exceptionHandling().accessDeniedPage("/Access_Denied").and().csrf().disable();

【讨论】:

嘿 Sujay,现在控件将转到 UserServiceImpl 方法并成功返回,但返回后,控件不会转到 customAuthenticationSuccessHandler,它被困在带有 406 的 @ControllerAdvice 类中。任何想法为什么? 项目中是否写了ControllerAdvice类? 这就像整个项目的全局异常处理程序,如果我们错过了在任何地方处理异常,它就会在这里处理。我在这里更新了代码。控件落在 405 Exception 方法中。 由于您获得的方法不受支持,请检查具有请求映射 /dashboard 的控制器方法,这是成功的 url。它应该是一个获取请求。 嗨 Sujay,我已经使用 AbstractAnnotationConfigDispatcherServletInitializer 更改了 AppInitializer 代码,并使用 AbstractSecurityWebApplicationInitializer 更改了 Spring 安全性,并参考了 this 文章。现在它工作正常。

以上是关于未调用 Spring Security Authentication UserDetailsS​​ervice 实现类的主要内容,如果未能解决你的问题,请参考以下文章

未调用 Spring Security 自定义登录表单

Spring security - 未调用自定义 userDetailsS​​ervice 实现

Spring Security:DB 和 applicationContext 中的密码编码

未调用 Spring Security Authentication UserDetailsS​​ervice 实现类

Spring安全性j_spring_security_check调用给出404未找到错误[关闭]

Spring Security:未调用自定义 UserDetailsS​​ervice(使用 Auth0 身份验证)