使用 Spring Security 默认登录将用户放入 HttpSession 并进行身份验证

Posted

技术标签:

【中文标题】使用 Spring Security 默认登录将用户放入 HttpSession 并进行身份验证【英文标题】:Put user in HttpSession with Spring Security default login and authenticate 【发布时间】:2019-04-19 19:33:42 【问题描述】:

我确切地说我是 Java 开发者一年级的法国学生。

我正在开发一个小的多模块应用程序,使用:Spring Boot、Spring security、Hibernate、Spring Data、Spring MVC 和 Thymeleaf。

我想在登录时设置会话中的用户,或者至少设置 userId。这样我就不必每次需要时手动将其放入会话或模型中。

但是由于我使用默认的 Spring Security 登录和身份验证配置,我真的不知道如何或在哪里调用这样的方法:

void putUserInHttpSession( HttpSession httpSession ) 
        httpSession.setAttribute( "user" , getManagerFactory().getUserManager().findByUserName( SecurityContextHolder.getContext().getAuthentication().getName()) );
    

我可以在每次需要它的时候做到这一点,但我发现不只是在登录时这样做非常难看!

以下是我认为您可能需要帮助我的内容(那太棒了!!!:)

我的 WebSecurityConfig 类:

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter 

    @Autowired
    private UserDetailsServiceImpl userDetailsService;

    @Autowired
    private DataSource dataSource;

    @Bean
    public BCryptPasswordEncoder passwordEncoder() 
        return new BCryptPasswordEncoder();
    

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception 

        // Setting Service to find User in the database.
        // And Setting PassswordEncoder
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());

    


    @Override
    protected void configure( HttpSecurity http ) throws Exception 

        http.csrf().disable();


        // /userInfo page requires login as ROLE_USER or ROLE_ADMIN.
        // If no login, it will redirect to /login page.
        http.authorizeRequests().antMatchers(
                "/user/**")
                .access("hasAnyRole('ROLE_USER', 'ROLE_ADMIN')");

        // For ADMIN only.
        http.authorizeRequests().antMatchers(
                "/admin/**")
                .access("hasRole('ROLE_ADMIN')");

        // When the user has logged in as XX.
        // But access a page that requires role YY,
        // AccessDeniedException will be thrown.
        http.authorizeRequests().and().exceptionHandling().accessDeniedPage("/public/403");

        // Config for Login Form
        http.authorizeRequests().and().formLogin()//
                // Submit URL of login page.
                .loginProcessingUrl("/j_spring_security_check") // Submit URL
                .loginPage("/public/login").defaultSuccessUrl("/public/showAtlas")//
                .failureUrl("/public/login?error=true")//
                .usernameParameter("username")//
                .passwordParameter("password")
                //Config for Logout Page
                .and()
                .logout().logoutUrl("/public/logout").logoutSuccessUrl("/public/logoutSuccessful");

        http.authorizeRequests().antMatchers(
                "/public/**").permitAll();
        // The pages does not require login
    


我的 UserDetailsS​​erviceImpl 类:

@Service
public class UserDetailsServiceImpl implements UserDetailsService

    @Autowired
    private ManagerFactory managerFactory;

//  private HttpSession httpSession;

    /**
     * The authentication method uses the user email, since it is easier to remember for most users
     * @param input
     * @return a UserDetails object
     * @throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername( String input) throws UsernameNotFoundException 

        User user = new User();

        if( input.contains( "@" ))
            user =  this.managerFactory.getUserManager().findByEmail( input );
        
        else 
            user =  this.managerFactory.getUserManager().findByUserName( input );
        


        if (user == null) 
            throw new UsernameNotFoundException( "User with email " + input + " was not found in the database" );
        

        // [ROLE_USER, ROLE_ADMIN,..]
        List<String> roleNames = this.managerFactory.getRoleManager().findRoleByUserName(user.getUserName());

        List<GrantedAuthority> grantList = new ArrayList<GrantedAuthority>();
        if (roleNames != null) 
            for (String role : roleNames) 
                // ROLE_USER, ROLE_ADMIN,..
                GrantedAuthority authority = new SimpleGrantedAuthority(role);
                grantList.add(authority);
            
        

        return (UserDetails) new org.springframework.security.core.userdetails.User(user.getUserName(),
                user.getPassword(), grantList);
    

我的简单 LoginController:

@Controller
public class LoginController

    @GetMapping("/public/login")
    public String login(Model model )


        return "view/login";
    

    @GetMapping("/public/logoutSuccessful")
    public String logout(Model model) 

        return "view/logoutSuccessful";

    

那么,有没有一种简单的方法可以在登录时将用户或 userId 放入 httpSession 中?

非常感谢你们!!!

解决方案

创建一个 CustomAuthenticationSuccessHandler

@Component
public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler 

    @Autowired
    private ManagerFactory managerFactory;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request,
                                        HttpServletResponse response,
                                        Authentication authentication)
            throws IOException, ServletException 

        String userName = "";
        HttpSession session = request.getSession();
        Collection< GrantedAuthority > authorities = null;
        if(authentication.getPrincipal() instanceof Principal ) 
            userName = ((Principal)authentication.getPrincipal()).getName();
            session.setAttribute("role", "none");
        else 
            User userSpringSecu = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
            session.setAttribute("role", String.valueOf( userSpringSecu.getAuthorities()));
            session.setAttribute( "connectedUser" , managerFactory.getUserManager().findByUserName( userSpringSecu.getUsername() ) );
        
        response.sendRedirect("/public/showAtlas" );
    

然后Autowired这个类并添加到WebSecurityConfigurerAdapter中

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter 

    @Autowired
    private UserDetailsServiceImpl userDetailsService;

    @Autowired
    private CustomAuthenticationSuccessHandler customAuthenticationSuccessHandler;

    @Autowired
    private DataSource dataSource;


    @Bean
    public BCryptPasswordEncoder passwordEncoder() 
        return new BCryptPasswordEncoder();
    

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception 

        // Setting Service to find User in the database.
        // And Setting PassswordEncoder
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());

    


    @Override
    protected void configure( HttpSecurity http ) throws Exception 

        http.csrf().disable();


        // /userInfo page requires login as ROLE_USER or ROLE_ADMIN.
        // If no login, it will redirect to /login page.
        http.authorizeRequests().antMatchers(
                "/user/**")
                .access("hasAnyRole('ROLE_USER', 'ROLE_ADMIN')");

        // For ADMIN only.
        http.authorizeRequests().antMatchers(
                "/admin/**")
                .access("hasRole('ROLE_ADMIN')");
//      http.exceptionHandling().accessDeniedPage( "/error/403" );

        // When the user has logged in as XX.
        // But access a page that requires role YY,
        // AccessDeniedException will be thrown.
        http.authorizeRequests().and().exceptionHandling().accessDeniedPage("/public/403");

        // Config for Login Form
        http.authorizeRequests().and().formLogin()//
                // Submit URL of login page.
                .loginProcessingUrl("/j_spring_security_check") // Submit URL
                .loginPage("/public/login")
                .defaultSuccessUrl("/public/showAtlas")//
                .successHandler( customAuthenticationSuccessHandler )
                .failureUrl("/public/login?error=true")//
                .usernameParameter("username")//
                .passwordParameter("password")
                //Config for Logout Page
                .and()
                .logout().logoutUrl("/public/logout").logoutSuccessUrl("/public/logoutSuccessful");

        http.authorizeRequests().antMatchers(
                "/public/**").permitAll();
        // The pages does not require login
    


【问题讨论】:

您可以在控制器类中自动连接HttpSession。你的问题是关于如何获得user 我尝试在“登录(模型模型)”方法中调用我的 LoginController 中的“putUserInHttpSession”方法。但是这个方法是由 Spring Mvc 在 Spring Security 身份验证过程之前调用的。所以那个时候用户还没有在 SecurityContextHolder 中。那就不行了。我想要的是每次连接用户时都会将用户或 user.id 置于会话中(我的意思是其 ID 和 PWD)。我希望我足够清楚以被理解...... ^^ 你可以在你的WebSecurityConfig类中注册successHandler,你会得到Authentication对象,你可以从中获取userId。 听起来很有趣。我不想滥用,但你能告诉我如何“注册successHandler”吗?我知道我必须创建 AuthenticationSuccessHandler 的自定义实现:@Component public class CustomizeAuthenticationSuccessHandler implements AuthenticationSuccessHandler 但是我应该输入什么? @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException 让我的登录功能像现在一样工作? 【参考方案1】:

假设您想在成功登录时将用户添加到会话,您可以像下面一样创建AuthenticationSuccessHandler 并使用successHandler(new AuthenticationSuccessHandlerImpl()) 注册

更新: 如果我们创建对象AuthenticationSuccessHandlerImpl,它将不会被弹簧管理,因此autowire 进入您的Securityconfig 并如下所示使用它。

这里在你的WebSecurityConfig 中自动连接AuthenticationSuccessHandler

@Autowired
AuthenticationSuccessHandler authenticationSuccessHandler;

并使用它 WebSecurityConfig.java

@Override
protected void configure(HttpSecurity http) throws Exception 
    http
            .authorizeRequests()
                .antMatchers("/resources/**", "/registration").permitAll()
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login")
                .permitAll().successHandler(authenticationSuccessHandler) // See here
                .and()
            .logout()
                .permitAll();

AuthenticationSuccessHandlerImpl.java

import java.io.IOException;
import java.security.Principal;

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

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

import com.techdisqus.auth.repository.UserRepository;

@Component
public class AuthenticationSuccessHandlerImpl implements AuthenticationSuccessHandler

    @Autowired HttpSession session; //autowiring session

    @Autowired UserRepository repository; //autowire the user repo


    private static final Logger logger = LoggerFactory.getLogger(AuthenticationSuccessHandlerImpl.class);
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
            Authentication authentication) throws IOException, ServletException 
        // TODO Auto-generated method stub
        String userName = "";
        if(authentication.getPrincipal() instanceof Principal) 
             userName = ((Principal)authentication.getPrincipal()).getName();

        else 
            userName = ((User)authentication.getPrincipal()).getUsername();
        
        logger.info("userName: " + userName);
        //HttpSession session = request.getSession();
        session.setAttribute("userId", userName);

    


希望这会有所帮助。

【讨论】:

非常感谢!我想我明白了机制!我将在今晚尝试实施。希望我能设法让它工作......如果没有,那么我想我会再次打扰你;) 这帮助很大!我现在可以在会话中使用 userId! :) 但我有点贪心,我想把整个用户放在会话中,仍然在登录。新的 pb 是我无法访问 AuthenticationSuccessHandlerImpl 中的 JpaRepositories。我可以在我的 Constollers 中访问它们,但在这里没有。你知道我如何修复它吗? @Autowired 不起作用... 您是否需要与您获取 userId 的对象不同的 User 对象? 您已经手动创建了该对象,因此它不是 Spring 托管 bean,而是在您的配置中使用 autowire 并使用它。更新代码以说明相同。 是的,我确实需要来自我的域模块的用户对象。为了在我的模板中访问它的属性。不过,我不确定这是否是一个好习惯..?我确实尝试@Autowired UserRepository,但它保持为空......【参考方案2】:

让我补充以上两个解决方案。我的经验显示以下异常启动了以下语句:

session.setAttribute("userId", userName);

例外:

java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread?

在学习https://html.developreference.com/article/22570246/ 之后,我能够将其删除。也就是说,我在继承 AbstractAnnotationConfigDispatcherServletInitializer 类的类中重写了 onStartup 方法。

@Override
public void onStartup(ServletContext servletContext) 
        throws ServletException 
    super.onStartup(servletContext);
    servletContext.addListener(new RequestContextListener());

【讨论】:

以上是关于使用 Spring Security 默认登录将用户放入 HttpSession 并进行身份验证的主要内容,如果未能解决你的问题,请参考以下文章

Spring Security 默认登录表单不会被自定义登录页面替换

如何禁用 spring-security 登录屏幕?

Spring Security - 无法将默认登录页面更改为自定义

如何使用 Spring Security / Spring MVC 处理表单登录

Spring Security 默认登录页面既不呈现也不能够创建自定义登录页面

Spring Security 登录页面