使用Spring Security重定向登录用户

2023/05/17

1. 概述

网站通常会阻止用户在登录后访问登录页面。一种常见的方法是将用户重定向到另一个页面,通常是登录后应用程序的首页。

在本教程中,我们将探索使用Spring Security实现此解决方案的多种方法。

另外,要了解更多关于如何快速实现登录的信息,我们可以从这篇文章开始。

2. 身份验证

首先,我们需要一种方法来验证authentication。

换句话说,我们需要从SecurityContext获取Authentication详细信息并验证用户是否已登录

@Controller
class UsersController {

    private boolean isAuthenticated() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication == null || AnonymousAuthenticationToken.class.isAssignableFrom(authentication.getClass()))
            return false;
        return authentication.isAuthenticated();
    }
}

我们将在以下所有负责重定向的组件中使用它

3. 从登录控制器重定向

实现我们目标的最简单方法是在控制器中为登录页面定义一个端点。

如果用户通过身份验证,我们需要返回一个特定页面,否则返回登录页面:

@GetMapping("/userMainPage")
public String getUserPage() {
    return "userMainPage";
}

@GetMapping("/loginUser")
public String getUserLoginPage() {
    if (isAuthenticated())
        return "redirect:userMainPage";
    return "loginUser";
}

4. 使用拦截器

重定向用户的另一种方法是通过登录页面URI上的拦截器

拦截器将在请求到达控制器之前将其拦截。因此,我们可以根据authentication来决定是让它继续进行还是阻止它并返回重定向响应。

如果用户已通过身份验证,则需要在响应中修改两件事:

  • 将状态码设置为HttpStatus.SC_TEMPORARY_REDIRECT
  • 添加带有重定向URL的Location标头

最后,我们将通过返回false来中断执行链:

class LoginPageInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {

        UrlPathHelper urlPathHelper = new UrlPathHelper();
        if ("/loginUser".equals(urlPathHelper.getLookupPathForRequest(request)) && isAuthenticated()) {

            String encodedRedirectURL = response.encodeRedirectURL(request.getContextPath() + "/userMainPage");
            response.setStatus(HttpStatus.SC_TEMPORARY_REDIRECT);
            response.setHeader("Location", encodedRedirectURL);

            return false;
        } else {
            return true;
        }
    }
    
    // isAuthenticated method 
}

我们还需要将拦截器添加到Spring MVC生命周期中

@Configuration
public class LoginRedirectMvcConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginPageInterceptor());
    }
}

我们可以使用Spring的基于XML Schema的配置来实现相同的目的:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:beans="http://www.springframework.org/schema/beans"
       xmlns:security="http://www.springframework.org/schema/security"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd
       http://www.springframework.org/schema/security
        http://www.springframework.org/schema/security/spring-security-5.2.xsd
		http://www.springframework.org/schema/beans
		http://www.springframework.org/schema/beans/spring-beans.xsd">

    <mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/loginUser"/>
            <bean class="cn.tuyucheng.taketoday.loginredirect.LoginPageInterceptor"/>
        </mvc:interceptor>
    </mvc:interceptors>
</beans>

5. 使用过滤器

类似地,我们可以实现一个Spring过滤器

过滤器可以使用Spring Security的过滤器链直接应用于SecurityContext。因此,它可以在创建authentication后立即拦截请求。

让我们扩展GenericFilterBean,重写doFilter方法,并验证authentication:

class LoginPageFilter extends GenericFilterBean {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest servletRequest = (HttpServletRequest) request;
        HttpServletResponse servletResponse = (HttpServletResponse) response;

        if (isAuthenticated() && "/loginUser".equals(servletRequest.getRequestURI())) {

            String encodedRedirectURL = ((HttpServletResponse) response).encodeRedirectURL(servletRequest.getContextPath() + "/userMainPage");

            servletResponse.setStatus(HttpStatus.SC_TEMPORARY_REDIRECT);
            servletResponse.setHeader("Location", encodedRedirectURL);
        }

        chain.doFilter(servletRequest, servletResponse);
    }

    // isAuthenticated method 
}

我们需要在过滤器链中的UsernamePasswordAuthenticationFilter之后添加该过滤器

此外,我们需要授权对登录页面URI的请求,以便为其启用过滤器链:

@Configuration
@EnableWebSecurity
class LoginRedirectSecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.addFilterAfter(new LoginPageFilter(), UsernamePasswordAuthenticationFilter.class)
              .authorizeRequests()
              .antMatchers("/loginUser")
              .permitAll()
              .antMatchers("/user*")
              .hasRole("USER")
              .and()
              .formLogin()
              .loginPage("/loginUser")
              .loginProcessingUrl("/user_login")
              .failureUrl("/loginUser?error=loginError")
              .defaultSuccessUrl("/userMainPage")
              .permitAll()
              .and()
              .logout()
              .logoutUrl("/user_logout")
              .logoutSuccessUrl("/loginUser")
              .deleteCookies("JSESSIONID")
              .and()
              .csrf()
              .disable();
        return http.build();
    }
}

最后,如果我们选择使用XML配置,我们可以为过滤器定义bean,并将其添加到HTTP标签中的过滤器链中:

<security:http pattern="/**" use-expressions="true" auto-config="true">
    <security:intercept-url pattern="/loginUser" access="permitAll"/>
    <security:intercept-url pattern="/user*" access="hasRole('ROLE_USER')"/>

    <security:form-login login-page="/loginUser"
                         login-processing-url="/user_login"
                         authentication-failure-url="/loginUser?error=loginError"
                         default-target-url="/userMainPage"/>
    <security:csrf disabled="true"/>
    <security:logout logout-url="/user_logout" delete-cookies="JSESSIONID" logout-success-url="/loginUser"/>
    <security:custom-filter after="BASIC_AUTH_FILTER" ref="loginPageFilter"/>
</security:http>

<beans:bean id="loginPageFilter" class="LoginPageFilter"/>

6. 总结

在本教程中,我们探讨了如何使用Spring Security将已登录用户从登录页面重定向到其他页面的多种方法。

另一个可能相关的教程是使用Spring Security登录后重定向到不同的页面,其中我们学习如何将不同类型的用户重定向到特定页面。

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章