65

我知道以前有人问过这个问题,但是我在这里遇到了一个特殊问题。

我使用弹簧安全 3.1.3。

我的 Web 应用程序中有 3 个可能的登录案例:

  1. 通过登录页面登录:好的。
  2. 通过受限页面登录:也可以。
  3. 通过非限制页面登录:不行……每个人都可以访问“产品”页面,如果用户已登录,则可以发表评论。因此,登录表单包含在同一页面中,以允许用户连接。

案例 3) 的问题是我无法将用户重定向到“产品”页面。无论如何,他们在成功登录后都会被重定向到主页。

请注意,在案例 2) 中,成功登录后重定向到受限页面的功能开箱即用。

这是我的 security.xml 文件的相关部分:

<!-- Authentication policy for the restricted page  -->
<http use-expressions="true" auto-config="true" pattern="/restrictedPage/**">
    <form-login login-page="/login/restrictedLogin" authentication-failure-handler-ref="authenticationFailureHandler" />
    <intercept-url pattern="/**" access="isAuthenticated()" />
</http>

<!-- Authentication policy for every page -->
<http use-expressions="true" auto-config="true">
    <form-login login-page="/login" authentication-failure-handler-ref="authenticationFailureHandler" />
    <logout logout-url="/logout" logout-success-url="/" />
</http>

我怀疑“每个页面的身份验证策略”是造成问题的原因。但是,如果我删除它,我将无法再登录... j_spring_security_check 发送 404 错误。


编辑:

感谢 Ralph,我能够找到解决方案。所以事情是这样的:我使用了这个属性

<property name="useReferer" value="true"/>

拉尔夫给我看的。在那之后,我的案例 1) 出现了问题:通过登录页面登录时,用户停留在同一页面中(并且没有像以前那样重定向到主页)。直到这个阶段的代码如下:

<!-- Authentication policy for login page -->
<http use-expressions="true" auto-config="true" pattern="/login/**">
    <form-login login-page="/login" authentication-success-handler-ref="authenticationSuccessHandlerWithoutReferer" />
</http>

<!-- Authentication policy for every page -->
<http use-expressions="true" auto-config="true">
    <form-login login-page="/login" authentication-failure-handler-ref="authenticationFailureHandler" />
    <logout logout-url="/logout" logout-success-url="/" authentication-success-handler-ref="authenticationSuccessHandler"/>
</http>

<beans:bean id="authenticationSuccessHandler" class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">
    <!-- After login, return to the last visited page -->
    <beans:property name="useReferer" value="true" />
</beans:bean>

<beans:bean id="authenticationSuccessHandlerWithoutReferer" class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">
    <!-- After login, stay to the same page -->
    <beans:property name="useReferer" value="false" />
</beans:bean>

至少在理论上,这应该有效,但事实并非如此。我仍然不知道为什么,所以如果有人对此有答案,我很乐意创建一个新主题来分配他分享他的解决方案。

与此同时,我找到了一个解决方法。不是最好的解决方案,但就像我说的,如果有人有更好的东西要展示,我会全神贯注。所以这是登录页面的新身份验证策略:

<http use-expressions="true" auto-config="true" pattern="/login/**" >
    <intercept-url pattern="/**" access="isAnonymous()" />
    <access-denied-handler error-page="/"/>
</http>

这里的解决方案很明显:登录页面只允许匿名用户使用。连接用户后,错误处理程序会将他重定向到主页。

我做了一些测试,一切似乎都运行良好。

4

9 回答 9

49

登录后发生的事情(用户被重定向到哪个 url)由AuthenticationSuccessHandler.

该接口(实现它的具体类)由方法中的 ()或其子类之一SavedRequestAwareAuthenticationSuccessHandler调用。AbstractAuthenticationProcessingFilterUsernamePasswordAuthenticationFiltersuccessfulAuthentication

因此,为了在案例 3 中进行其他重定向,您必须进行子类化SavedRequestAwareAuthenticationSuccessHandler并使其执行您想要的操作。


有时(取决于您的确切用例)启用由(超类)调用的useReferer标志就足够了。AbstractAuthenticationTargetUrlRequestHandlerSimpleUrlAuthenticationSuccessHandlerSavedRequestAwareAuthenticationSuccessHandler

<bean id="authenticationFilter"
      class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
    <property name="filterProcessesUrl" value="/login/j_spring_security_check" />
    <property name="authenticationManager" ref="authenticationManager" />
    <property name="authenticationSuccessHandler">
        <bean class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">
            <property name="useReferer" value="true"/>
        </bean>
    </property>
    <property name="authenticationFailureHandler">
        <bean class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
            <property name="defaultFailureUrl" value="/login?login_error=t" />
        </bean>
    </property>
</bean>
于 2013-01-29T06:51:46.173 回答
48

我想扩展Olcay的好答案。他的方法很好,您的登录页面控制器应该是这样的,以将引荐来源网址放入会话中:

@RequestMapping(value = "/login", method = RequestMethod.GET)
public String loginPage(HttpServletRequest request, Model model) {
    String referrer = request.getHeader("Referer");
    request.getSession().setAttribute("url_prior_login", referrer);
    // some other stuff
    return "login";
}

你应该扩展SavedRequestAwareAuthenticationSuccessHandler和覆盖它的onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)方法。像这样的东西:

public class MyCustomLoginSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
    public MyCustomLoginSuccessHandler(String defaultTargetUrl) {
        setDefaultTargetUrl(defaultTargetUrl);
    }

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException {
        HttpSession session = request.getSession();
        if (session != null) {
            String redirectUrl = (String) session.getAttribute("url_prior_login");
            if (redirectUrl != null) {
                // we do not forget to clean this attribute from session
                session.removeAttribute("url_prior_login");
                // then we redirect
                getRedirectStrategy().sendRedirect(request, response, redirectUrl);
            } else {
                super.onAuthenticationSuccess(request, response, authentication);
            }
        } else {
            super.onAuthenticationSuccess(request, response, authentication);
        }
    }
}

然后,在你的 spring 配置中,你应该将这个自定义类定义为一个 bean,并在你的安全配置中使用它。如果你使用注解配置,它应该看起来像这样(你扩展的类WebSecurityConfigurerAdapter):

@Bean
public AuthenticationSuccessHandler successHandler() {
    return new MyCustomLoginSuccessHandler("/yourdefaultsuccessurl");
}

configure方法:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
            // bla bla
            .formLogin()
                .loginPage("/login")
                .usernameParameter("username")
                .passwordParameter("password")
                .successHandler(successHandler())
                .permitAll()
            // etc etc
    ;
}
于 2014-05-22T01:24:10.753 回答
8

我有以下解决方案,它对我有用。

每当请求登录页面时,将引用值写入会话:

@RequestMapping(value="/login", method = RequestMethod.GET)
public String login(ModelMap model,HttpServletRequest request) {

    String referrer = request.getHeader("Referer");
    if(referrer!=null){
        request.getSession().setAttribute("url_prior_login", referrer);
    }
    return "user/login";
}

然后,成功登录后自定义实现SavedRequestAwareAuthenticationSuccessHandler会将用户重定向到上一页:

HttpSession session = request.getSession(false);
if (session != null) {
    url = (String) request.getSession().getAttribute("url_prior_login");
}

重定向用户:

if (url != null) {
    response.sendRedirect(url);
}
于 2013-07-03T12:47:18.143 回答
2

我已经自定义 OAuth2 授权,并且request.getHeader("Referer")在决定时不可用。但安全请求已保存在ExceptionTranslationFilter.sendStartAuthentication

protected void sendStartAuthentication(HttpServletRequest request,...
    ...
    requestCache.saveRequest(request, response);

所以,我们需要的只是requestCache作为 Spring bean 共享:

@Bean
public RequestCache requestCache() {
   return new HttpSessionRequestCache();
}

@Override
protected void configure(HttpSecurity http) throws Exception {
   http.authorizeRequests()
   ... 
   .requestCache().requestCache(requestCache()).and()
   ...
}     

并在授权完成后使用它:

@Autowired
private RequestCache requestCache;

public void authenticate(HttpServletRequest req, HttpServletResponse resp){
    ....
    SavedRequest savedRequest = requestCache.getRequest(req, resp);
    resp.sendRedirect(savedRequest != null && "GET".equals(savedRequest.getMethod()) ?  
    savedRequest.getRedirectUrl() : "defaultURL");
}
于 2019-06-03T20:50:51.010 回答
0

以下通用解决方案可与常规登录、Spring Social 登录或大多数其他 Spring Security 过滤器一起使用。

在您的 Spring MVC 控制器中,当加载产品页面时,如果用户尚未登录,则在会话中保存产品页面的路径。在 XML 配置中,设置默认目标 url。例如:

在您的 Spring MVC 控制器中,重定向方法应该从会话中读取路径并返回redirect:<my_saved_product_path>

因此,在用户登录后,他们将被发送到/redirect页面,该页面会立即将他们重定向回他们上次访问的产品页面。

于 2013-07-23T01:45:58.160 回答
0

成功登录后返回上一页,我们可以使用如下自定义认证管理器,如下:

<!-- enable use-expressions -->
    <http auto-config="true" use-expressions="true">
        <!-- src** matches: src/bar.c src/baz.c src/test/bartest.c-->
        <intercept-url pattern="/problemSolution/home/**" access="hasRole('ROLE_ADMIN')"/>
        <intercept-url pattern="favicon.ico" access="permitAll"/>
        <form-login
                authentication-success-handler-ref="authenticationSuccessHandler"
                always-use-default-target="true"
                login-processing-url="/checkUser"
                login-page="/problemSolution/index"

                default-target-url="/problemSolution/home"
                authentication-failure-url="/problemSolution/index?error"
                username-parameter="username"
                password-parameter="password"/>
        <logout logout-url="/problemSolution/logout"
                logout-success-url="/problemSolution/index?logout"/>
        <!-- enable csrf protection -->
        <csrf/>
    </http>

    <beans:bean id="authenticationSuccessHandler"
            class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">
        <beans:property name="defaultTargetUrl" value="/problemSolution/home"/>
    </beans:bean>

    <!-- Select users and user_roles from database -->
    <authentication-manager>
        <authentication-provider user-service-ref="customUserDetailsService">
            <password-encoder hash="plaintext">
            </password-encoder>
        </authentication-provider>
    </authentication-manager>

CustomUserDetailsS​​ervice 类

@Service
public class CustomUserDetailsService implements UserDetailsService {

        @Autowired
        private UserService userService;

        public UserDetails loadUserByUsername(String userName)
                        throws UsernameNotFoundException {
                com.codesenior.telif.local.model.User domainUser = userService.getUser(userName);

                boolean enabled = true;
                boolean accountNonExpired = true;
                boolean credentialsNonExpired = true;
                boolean accountNonLocked = true;

                return new User(
                                domainUser.getUsername(),
                                domainUser.getPassword(),
                                enabled,
                                accountNonExpired,
                                credentialsNonExpired,
                                accountNonLocked,
                                getAuthorities(domainUser.getUserRoleList())
                );
        }

        public Collection<? extends GrantedAuthority> getAuthorities(List<UserRole> userRoleList) {
                return getGrantedAuthorities(getRoles(userRoleList));
        }

        public List<String> getRoles(List<UserRole> userRoleList) {

                List<String> roles = new ArrayList<String>();

                for(UserRole userRole:userRoleList){
                        roles.add(userRole.getRole());
                }
                return roles;
        }

        public static List<GrantedAuthority> getGrantedAuthorities(List<String> roles) {
                List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();

                for (String role : roles) {
                        authorities.add(new SimpleGrantedAuthority(role));
                }
                return authorities;
        }

}

用户类

import com.codesenior.telif.local.model.UserRole;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
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 java.util.ArrayList;
import java.util.Collection;
import java.util.List;


@Service
public class CustomUserDetailsService implements UserDetailsService {

        @Autowired
        private UserService userService;

        public UserDetails loadUserByUsername(String userName)
                        throws UsernameNotFoundException {
                com.codesenior.telif.local.model.User domainUser = userService.getUser(userName);

                boolean enabled = true;
                boolean accountNonExpired = true;
                boolean credentialsNonExpired = true;
                boolean accountNonLocked = true;

                return new User(
                                domainUser.getUsername(),
                                domainUser.getPassword(),
                                enabled,
                                accountNonExpired,
                                credentialsNonExpired,
                                accountNonLocked,
                                getAuthorities(domainUser.getUserRoleList())
                );
        }

        public Collection<? extends GrantedAuthority> getAuthorities(List<UserRole> userRoleList) {
                return getGrantedAuthorities(getRoles(userRoleList));
        }

        public List<String> getRoles(List<UserRole> userRoleList) {

                List<String> roles = new ArrayList<String>();

                for(UserRole userRole:userRoleList){
                        roles.add(userRole.getRole());
                }
                return roles;
        }

        public static List<GrantedAuthority> getGrantedAuthorities(List<String> roles) {
                List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();

                for (String role : roles) {
                        authorities.add(new SimpleGrantedAuthority(role));
                }
                return authorities;
        }

}

用户角色类

@Entity
public class UserRole {
        @Id
        @GeneratedValue
        private Integer userRoleId;

        private String role;

        @ManyToMany(fetch = FetchType.LAZY, mappedBy = "userRoleList")
        @JsonIgnore
        private List<User> userList;

        public Integer getUserRoleId() {
                return userRoleId;
        }

        public void setUserRoleId(Integer userRoleId) {
                this.userRoleId= userRoleId;
        }

        public String getRole() {
                return role;
        }

        public void setRole(String role) {
                this.role= role;
        }

        @Override
        public String toString() {
                return String.valueOf(userRoleId);
        }

        public List<User> getUserList() {
                return userList;
        }

        public void setUserList(List<User> userList) {
                this.userList= userList;
        }
}
于 2015-05-07T12:03:43.497 回答
0

无论用户角色是什么,为了重定向到特定页面,只需在 Spring 的配置文件中使用 defaultSucessUrl 即可。

@Override
protected void configure(HttpSecurity http) throws Exception {
    
    
      http.authorizeRequests() 
      .antMatchers("/admin").hasRole("ADMIN") 
      .and()
      .formLogin() .loginPage("/login") 
                    .defaultSuccessUrl("/admin",true)
      .loginProcessingUrl("/authenticateTheUser")
      .permitAll();
     
    
于 2020-08-27T12:01:36.937 回答
0

我发现Utku Özdemir 的解决方案在某种程度上有效,但有点违背了保存请求的目的,因为会话属性将优先于它。这意味着重定向到安全页面将无法按预期工作 - 登录后,您将被发送到您所在的页面而不是重定向目标。因此,作为替代方案,您可以使用 SavedRequestAwareAuthenticationSuccessHandler 的修改版本,而不是对其进行扩展。这将使您能够更好地控制何时使用会话属性。

这是一个例子:

private static class MyCustomLoginSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {

    private RequestCache requestCache = new HttpSessionRequestCache();

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
            Authentication authentication) throws ServletException, IOException {
        SavedRequest savedRequest = requestCache.getRequest(request, response);

        if (savedRequest == null) {
            HttpSession session = request.getSession();
            if (session != null) {
                String redirectUrl = (String) session.getAttribute("url_prior_login");
                if (redirectUrl != null) {
                    session.removeAttribute("url_prior_login");
                    getRedirectStrategy().sendRedirect(request, response, redirectUrl);
                } else {
                    super.onAuthenticationSuccess(request, response, authentication);
                }
            } else {
                super.onAuthenticationSuccess(request, response, authentication);
            }

            return;
        }

        String targetUrlParameter = getTargetUrlParameter();
        if (isAlwaysUseDefaultTargetUrl()
                || (targetUrlParameter != null && StringUtils.hasText(request.getParameter(targetUrlParameter)))) {
            requestCache.removeRequest(request, response);
            super.onAuthenticationSuccess(request, response, authentication);

            return;
        }

        clearAuthenticationAttributes(request);

        // Use the DefaultSavedRequest URL
        String targetUrl = savedRequest.getRedirectUrl();
        logger.debug("Redirecting to DefaultSavedRequest Url: " + targetUrl);
        getRedirectStrategy().sendRedirect(request, response, targetUrl);
    }
}

此外,您不希望在身份验证失败时保存引荐来源网址,因为引荐来源网址将成为登录页面本身。因此,请手动检查错误参数或提供单独的 RequestMapping,如下所示。

@RequestMapping(value = "/login", params = "error")
public String loginError() {
    // Don't save referrer here!
}
于 2016-09-27T09:42:26.423 回答
0

您可以使用自定义 SuccessHandler 扩展 SimpleUrlAuthenticationSuccessHandler,以便在登录时根据用户分配的角色将用户重定向到不同的 URL。

CustomSuccessHandler 类提供自定义重定向功能:

package com.mycompany.uomrmsweb.configuration;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

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

import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

@Component
public class CustomSuccessHandler extends SimpleUrlAuthenticationSuccessHandler{

    private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

    @Override
    protected void handle(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
        String targetUrl = determineTargetUrl(authentication);

        if (response.isCommitted()) {
            System.out.println("Can't redirect");
            return;
        }

        redirectStrategy.sendRedirect(request, response, targetUrl);
    }

    protected String determineTargetUrl(Authentication authentication) {
        String url="";

        Collection<? extends GrantedAuthority> authorities =  authentication.getAuthorities();

        List<String> roles = new ArrayList<String>();

        for (GrantedAuthority a : authorities) {
            roles.add(a.getAuthority());
        }

        if (isStaff(roles)) {
            url = "/staff";
        } else if (isAdmin(roles)) {
            url = "/admin";
        } else if (isStudent(roles)) {
            url = "/student";
        }else if (isUser(roles)) {
            url = "/home";
        } else {
            url="/Access_Denied";
        }

        return url;
    }

    public void setRedirectStrategy(RedirectStrategy redirectStrategy) {
        this.redirectStrategy = redirectStrategy;
    }
    protected RedirectStrategy getRedirectStrategy() {
        return redirectStrategy;
    }

    private boolean isUser(List<String> roles) {
        if (roles.contains("ROLE_USER")) {
            return true;
        }
        return false;
    }

    private boolean isStudent(List<String> roles) {
        if (roles.contains("ROLE_Student")) {
            return true;
        }
        return false;
    }

    private boolean isAdmin(List<String> roles) {
        if (roles.contains("ROLE_SystemAdmin") || roles.contains("ROLE_ExaminationsStaff")) {
            return true;
        }
        return false;
    }

    private boolean isStaff(List<String> roles) {
        if (roles.contains("ROLE_AcademicStaff") || roles.contains("ROLE_UniversityAdmin")) {
            return true;
        }
        return false;
    }
}

扩展 Spring SimpleUrlAuthenticationSuccessHandler 类和覆盖 handle() 方法,该方法简单地使用配置的 RedirectStrategy [在这种情况下为默认值] 调用重定向,URL 由用户定义的 determineTargetUrl() 方法返回。此方法从 Authentication 对象中提取当前登录用户的角色,然后根据角色构造适当的 URL。最后 RedirectStrategy ,负责 Spring Security 框架内的所有重定向,将请求重定向到指定的 URL。

使用 SecurityConfiguration 类注册 CustomSuccessHandler:

package com.mycompany.uomrmsweb.configuration;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.userdetails.UserDetailsService;

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Autowired
    @Qualifier("customUserDetailsService")
    UserDetailsService userDetailsService;

    @Autowired
    CustomSuccessHandler customSuccessHandler;

    @Autowired
    public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
            auth.userDetailsService(userDetailsService);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
        .antMatchers("/", "/home").access("hasRole('USER')")
        .antMatchers("/admin/**").access("hasRole('SystemAdmin') or hasRole('ExaminationsStaff')")
        .antMatchers("/staff/**").access("hasRole('AcademicStaff') or hasRole('UniversityAdmin')")
        .antMatchers("/student/**").access("hasRole('Student')")  
                    .and().formLogin().loginPage("/login").successHandler(customSuccessHandler)
        .usernameParameter("username").passwordParameter("password")
        .and().csrf()
        .and().exceptionHandling().accessDeniedPage("/Access_Denied");
    }
}

successHandler 是负责基于任何自定义逻辑的最终重定向的类,在这种情况下,它将根据用户的角色 [USER/Student/SystemAdmin/UniversityAdmin/ExaminationsStaff/AcademicStaff] 将用户 [to student/admin/staff] 重定向。

于 2016-06-14T16:38:48.800 回答