mvc - spring security tutorial
Spring Security redirige a la página anterior después de iniciar sesión correctamente (7)
Sé que esta pregunta se ha hecho antes, sin embargo, estoy enfrentando un problema particular aquí.
Uso seguridad de primavera 3.1.3.
Tengo 3 posibles casos de inicio de sesión en mi aplicación web:
- Inicie sesión a través de la página de inicio de sesión: OK.
- Ingresar a través de una página restringida: OK también.
- Inicie sesión a través de una página no restringida: no OK ... todos pueden acceder a una página de "producto" y un usuario puede publicar un comentario si está conectado. Entonces, un formulario de inicio de sesión está contenido en la misma página para permitir que los usuarios se conecten.
El problema con el caso 3) es que no puedo redirigir a los usuarios a la página "producto". Ellos son redirigidos a la página de inicio después de un inicio de sesión exitoso, pase lo que pase.
Tenga en cuenta que con el caso 2) la redirección a la página restringida funciona de manera predeterminada después de iniciar sesión correctamente.
Aquí está la parte relevante de mi archivo 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>
Sospecho que la "política de autenticación para cada página" es responsable del problema. Sin embargo, si lo elimino, ya no puedo iniciar sesión ... j_spring_security_check envía un error 404.
EDITAR:
Gracias a Ralph, pude encontrar una solución. Así que aquí está la cosa: utilicé la propiedad
<property name="useReferer" value="true"/>
que Ralph me mostró. Después de eso tuve un problema con mi caso 1): al iniciar sesión a través de la página de inicio de sesión, el usuario permaneció en la misma página (y no se redirigió a la página de inicio, como solía ser). El código hasta esta etapa fue el siguiente:
<!-- 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>
Esto debería funcionar, al menos en teoría, pero no fue así. Todavía no sé por qué, así que si alguien tiene una respuesta al respecto, con mucho gusto crearé un nuevo tema para que él comparta su solución.
Mientras tanto, llegué a una solución. No es la mejor solución, pero como dije, si alguien tiene algo mejor que mostrar, soy todo oídos. Esta es la nueva política de autenticación para la página de inicio de sesión:
<http use-expressions="true" auto-config="true" pattern="/login/**" >
<intercept-url pattern="/**" access="isAnonymous()" />
<access-denied-handler error-page="/"/>
</http>
La solución aquí es bastante obvia: la página de inicio de sesión está permitida solo para usuarios anónimos. Una vez que un usuario está conectado, el controlador de errores lo redirige a la página de inicio.
Hice algunas pruebas, y todo parece estar funcionando bien.
La siguiente solución genérica puede usarse con un inicio de sesión normal, un inicio de sesión de Spring Social o la mayoría de los demás filtros de Spring Security.
En su controlador Spring MVC, al cargar la página del producto, guarde la ruta a la página del producto en la sesión si el usuario no ha iniciado sesión. En la configuración XML, configure la url objetivo predeterminada. Por ejemplo:
En su controlador Spring MVC, el método de redirección debe leer la ruta desde la sesión y devolver la redirect:<my_saved_product_path>
.
Por lo tanto, después de que el usuario inicie sesión, se enviarán a la página /redirect
, lo que los redireccionará de inmediato a la página del producto que visitaron por última vez.
Lo que sucede después del inicio de sesión (al que se redirige el URL) es gestionado por AuthenticationSuccessHandler
.
Esta interfaz (una clase concreta que la implementa es SavedRequestAwareAuthenticationSuccessHandler
) es invocada por AbstractAuthenticationProcessingFilter
o una de sus subclases como ( UsernamePasswordAuthenticationFilter
) en el método successfulAuthentication
.
Por lo tanto, para tener otra redirección en el caso 3, debe SavedRequestAwareAuthenticationSuccessHandler
subclase SavedRequestAwareAuthenticationSuccessHandler
y hacer que haga lo que desee.
A veces (dependiendo de su caso de uso exacto) es suficiente para habilitar el indicador useReferer
de AbstractAuthenticationTargetUrlRequestHandler
invocado por SimpleUrlAuthenticationSuccessHandler
( SavedRequestAwareAuthenticationSuccessHandler
de SavedRequestAwareAuthenticationSuccessHandler
).
<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>
Puede usar un CustomHandler que extiende SimpleUrlAuthenticationSuccessHandler para redirigir a los usuarios a diferentes URL cuando inicien sesión de acuerdo con sus roles asignados.
La clase CustomSuccessHandler proporciona una funcionalidad de redirección personalizada:
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;
}
}
Extender Spring SimpleUrlAuthenticationSuccessHandler class y reemplazar el método handle () que simplemente invoca un redireccionamiento utilizando RedirectStrategy configurado [predeterminado en este caso] con la URL devuelta por el método definido por el usuario determinTargetUrl (). Este método extrae los Roles del usuario actualmente conectado del objeto Autenticación y luego construye la URL adecuada en función de sus roles. Finalmente RedirectStrategy, que es responsable de todas las redirecciones en el marco de Spring Security, redirige la solicitud a la URL especificada.
Registrando CustomSuccessHandler usando la clase SecurityConfiguration:
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 es la clase responsable de la eventual redirección basada en cualquier lógica personalizada, que en este caso será redirigir al usuario [a estudiante / administrador / personal] en función de su rol [USER / Student / SystemAdmin / UniversityAdmin / ExaminationsStaff / AcademicStaff].
Quiero extender la buena respuesta de Olcay . Su enfoque es bueno, su controlador de página de inicio de sesión debería ser así para poner la url de referencia en sesión:
@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";
}
Y debe ampliar SavedRequestAwareAuthenticationSuccessHandler
y anular su onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
. Algo como esto:
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);
}
}
}
Luego, en su configuración de primavera, debe definir esta clase personalizada como un bean y usarla en su configuración de seguridad. Si está utilizando la configuración de anotación , debería verse así (la clase que extiende desde WebSecurityConfigurerAdapter
):
@Bean
public AuthenticationSuccessHandler successHandler() {
return new MyCustomLoginSuccessHandler("/yourdefaultsuccessurl");
}
En el método de configure
:
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// bla bla
.formLogin()
.loginPage("/login")
.usernameParameter("username")
.passwordParameter("password")
.successHandler(successHandler())
.permitAll()
// etc etc
;
}
Tengo la siguiente solución y funcionó para mí.
Siempre que se solicite la página de inicio de sesión, escriba el valor del referer en la sesión:
@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";
}
Luego, después del inicio de sesión exitoso, la implementación personalizada de SavedRequestAwareAuthenticationSuccessHandler
redirigirá al usuario a la página anterior:
HttpSession session = request.getSession(false);
if (session != null) {
url = (String) request.getSession().getAttribute("url_prior_login");
}
Redirigir al usuario:
if (url != null) {
response.sendRedirect(url);
}
Volviendo a la página anterior después de un inicio de sesión exitoso, podemos usar el siguiente administrador de autenticación personalizado de la siguiente manera:
<!-- 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>
CustomUserDetailsService class
@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;
}
}
Clase de usuario
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;
}
}
Clase UserRole
@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;
}
}
Encontré que la solución de Utku Özdemir funciona hasta cierto punto, pero de algún modo frustra el propósito de la solicitud guardada ya que el atributo de la sesión tendrá prioridad sobre ella. Esto significa que los redireccionamientos a páginas seguras no funcionarán según lo previsto: después de iniciar sesión, se le enviará a la página en la que se encontraba en lugar del objetivo de redireccionamiento. Entonces, como alternativa, podría usar una versión modificada de SavedRequestAwareAuthenticationSuccessHandler en lugar de extenderla. Esto le permitirá tener un mejor control sobre cuándo usar el atributo de sesión.
Aquí hay un ejemplo:
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);
}
}
Además, no desea guardar la referencia cuando la autenticación ha fallado, ya que la referencia será entonces la página de inicio de sesión. Por lo tanto, verifique el parámetro de error manualmente o proporcione un RequestMapping por separado como se muestra a continuación.
@RequestMapping(value = "/login", params = "error")
public String loginError() {
// Don''t save referrer here!
}