使用 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
我的 UserDetailsServiceImpl 类:
@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 MVC 处理表单登录