java - servlet - Sesiones de seguridad de primavera sin cookies.
session java servlet (4)
¿Has visto Spring Session: HttpSession & RestfulAPI que usa encabezados HTTP en lugar de cookies? Vea los proyectos de ejemplo REST en Ejemplo REST .
Estoy tratando de administrar sesiones en Spring Security sin aprovechar las cookies. El razonamiento es: nuestra aplicación se muestra dentro de un iframe de otro dominio, necesitamos administrar las sesiones en nuestra aplicación y Safari restringe la creación de cookies entre dominios . (contexto: domainA.com muestra domainB.com en un iframe. domainB.com está configurando una cookie JSESSIONID para aprovechar el dominio en domainB.com, pero dado que el navegador del usuario muestra domainA.com, Safari restringe que domainB.com cree la cookie) .
La única forma en que puedo pensar para lograr esto (en contra de las recomendaciones de seguridad de OWASP) es incluir el JSESSIONID en la URL como un parámetro GET. No QUIERO hacer esto, pero no puedo pensar en una alternativa.
Entonces esta pregunta es acerca de:
- ¿Hay mejores alternativas para abordar este problema?
- Si no, ¿cómo puedo lograr esto con Spring Security?
Al revisar la documentación de Spring sobre esto, el uso de enableSessionUrlRewriting debería permitir esto
Así que he hecho esto:
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.ALWAYS)
.enableSessionUrlRewriting(true)
Esto no agregó el JSESSIONID a la URL, pero debería estar permitido ahora. Luego aproveché el código encontrado en esta pregunta para establecer el "modo de seguimiento" en la URL
@SpringBootApplication
public class MyApplication extends SpringBootServletInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
servletContext
.setSessionTrackingModes(
Collections.singleton(SessionTrackingMode.URL)
);
Incluso después de esto, la aplicación aún agrega el JSESSIONID como una cookie y no en la URL.
¿Puede alguien ayudarme a orientarme en la dirección correcta aquí?
Aprecio todas las respuestas anteriores. Terminé optando por una solución más fácil sin hacer ningún cambio en el nivel de la aplicación porque el propietario de domainA.com estaba dispuesto a trabajar con nosotros. Lo publico aquí para otros, ya que ni siquiera pensé en esto originalmente ...
Básicamente :
- El propietario de domainA.com creó un registro DNS para domainB.domainA.com -> domainB.com
- El propietario de domainB.com (yo) solicitó un certificado SSL público para domainB.domainA.com a través de "validación de correo electrónico" (lo hice a través de AWS, pero estoy seguro de que hay otros mecanismos a través de otros proveedores)
- La solicitud anterior se envió a los webmasters de domainA.com -> aprobaron y emitieron el certificado público
- Una vez emitido, pude configurar mi aplicación (o equilibrador de carga) para usar este nuevo certificado, y ellos configuraron su aplicación para que apunte a "dominioB.dominio.com" (que posteriormente se enrutó a dominioB.com en DNS)
- Ahora, los navegadores emiten cookies para domainB.domainA.com y, dado que son el mismo dominio principal, las cookies se crean sin necesidad de soluciones.
Gracias de nuevo por las respuestas, disculpas por no seleccionar una respuesta aquí - semana ocupada.
Los inicios de sesión basados en formularios son principalmente sesiones con estado. En su escenario sería mejor usar sesiones sin estado.
JWT proporciona implementación para esto. Es básicamente una clave que debe pasar como encabezado en cada solicitud HTTP. Así que mientras tengas la llave. API está disponible.
Podemos integrar JWT con Spring.
Básicamente necesitas escribir estas lógicas.
- Generar Lógica Clave
- Usa JWT en Spring Security
- Validar clave en cada llamada.
Te puedo dar una ventaja
pom.xml
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
TokenHelper.java
Contiene funciones útiles para validar, verificar y analizar token.
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import com.test.dfx.common.TimeProvider;
import com.test.dfx.model.LicenseDetail;
import com.test.dfx.model.User;
@Component
public class TokenHelper {
protected final Log LOGGER = LogFactory.getLog(getClass());
@Value("${app.name}")
private String APP_NAME;
@Value("${jwt.secret}")
public String SECRET; // Secret key used to generate Key. Am getting it from propertyfile
@Value("${jwt.expires_in}")
private int EXPIRES_IN; // can specify time for token to expire.
@Value("${jwt.header}")
private String AUTH_HEADER;
@Autowired
TimeProvider timeProvider;
private SignatureAlgorithm SIGNATURE_ALGORITHM = SignatureAlgorithm.HS512; // JWT Algorithm for encryption
public Date getIssuedAtDateFromToken(String token) {
Date issueAt;
try {
final Claims claims = this.getAllClaimsFromToken(token);
issueAt = claims.getIssuedAt();
} catch (Exception e) {
LOGGER.error("Could not get IssuedDate from passed token");
issueAt = null;
}
return issueAt;
}
public String getAudienceFromToken(String token) {
String audience;
try {
final Claims claims = this.getAllClaimsFromToken(token);
audience = claims.getAudience();
} catch (Exception e) {
LOGGER.error("Could not get Audience from passed token");
audience = null;
}
return audience;
}
public String refreshToken(String token) {
String refreshedToken;
Date a = timeProvider.now();
try {
final Claims claims = this.getAllClaimsFromToken(token);
claims.setIssuedAt(a);
refreshedToken = Jwts.builder()
.setClaims(claims)
.setExpiration(generateExpirationDate())
.signWith( SIGNATURE_ALGORITHM, SECRET )
.compact();
} catch (Exception e) {
LOGGER.error("Could not generate Refresh Token from passed token");
refreshedToken = null;
}
return refreshedToken;
}
public String generateToken(String username) {
String audience = generateAudience();
return Jwts.builder()
.setIssuer( APP_NAME )
.setSubject(username)
.setAudience(audience)
.setIssuedAt(timeProvider.now())
.setExpiration(generateExpirationDate())
.signWith( SIGNATURE_ALGORITHM, SECRET )
.compact();
}
private Claims getAllClaimsFromToken(String token) {
Claims claims;
try {
claims = Jwts.parser()
.setSigningKey(SECRET)
.parseClaimsJws(token)
.getBody();
} catch (Exception e) {
LOGGER.error("Could not get all claims Token from passed token");
claims = null;
}
return claims;
}
private Date generateExpirationDate() {
long expiresIn = EXPIRES_IN;
return new Date(timeProvider.now().getTime() + expiresIn * 1000);
}
public int getExpiredIn() {
return EXPIRES_IN;
}
public Boolean validateToken(String token, UserDetails userDetails) {
User user = (User) userDetails;
final String username = getUsernameFromToken(token);
final Date created = getIssuedAtDateFromToken(token);
return (
username != null &&
username.equals(userDetails.getUsername()) &&
!isCreatedBeforeLastPasswordReset(created, user.getLastPasswordResetDate())
);
}
private Boolean isCreatedBeforeLastPasswordReset(Date created, Date lastPasswordReset) {
return (lastPasswordReset != null && created.before(lastPasswordReset));
}
public String getToken( HttpServletRequest request ) {
/**
* Getting the token from Authentication header
* e.g Bearer your_token
*/
String authHeader = getAuthHeaderFromHeader( request );
if ( authHeader != null && authHeader.startsWith("Bearer ")) {
return authHeader.substring(7);
}
return null;
}
public String getAuthHeaderFromHeader( HttpServletRequest request ) {
return request.getHeader(AUTH_HEADER);
}
}
Seguridad web
SpringSecurity Logic para agregar cheque JWT
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement().sessionCreationPolicy( SessionCreationPolicy.STATELESS ).and()
.exceptionHandling().authenticationEntryPoint( restAuthenticationEntryPoint ).and()
.authorizeRequests()
.antMatchers("/auth/**").permitAll()
.antMatchers("/login").permitAll()
.antMatchers("/home").permitAll()
.antMatchers("/actuator/**").permitAll()
.anyRequest().authenticated().and()
.addFilterBefore(new TokenAuthenticationFilter(tokenHelper, jwtUserDetailsService), BasicAuthenticationFilter.class);
http.csrf().disable();
}
TokenAuthenticationFilter.java
Compruebe cada Llamada de Restricción para ver si hay un Token válido
package com.test.dfx.security;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.web.filter.OncePerRequestFilter;
public class TokenAuthenticationFilter extends OncePerRequestFilter {
protected final Log logger = LogFactory.getLog(getClass());
private TokenHelper tokenHelper;
private UserDetailsService userDetailsService;
public TokenAuthenticationFilter(TokenHelper tokenHelper, UserDetailsService userDetailsService) {
this.tokenHelper = tokenHelper;
this.userDetailsService = userDetailsService;
}
@Override
public void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain chain
) throws IOException, ServletException {
String username;
String authToken = tokenHelper.getToken(request);
logger.info("AuthToken: "+authToken);
if (authToken != null) {
// get username from token
username = tokenHelper.getUsernameFromToken(authToken);
logger.info("UserName: "+username);
if (username != null) {
// get user
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (tokenHelper.validateToken(authToken, userDetails)) {
// create authentication
TokenBasedAuthentication authentication = new TokenBasedAuthentication(userDetails);
authentication.setToken(authToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}else{
logger.error("Something is wrong with Token.");
}
}
chain.doFilter(request, response);
}
}
Puede tener una comunicación basada en token entre el servidor DomainB.com del sitio y el navegador del cliente. El token se puede enviar desde el servidor DomainB.com en el encabezado de la respuesta, después de la autenticación. El navegador del cliente puede guardar el token en el almacenamiento local / almacenamiento de sesión (también tiene un tiempo de caducidad). El cliente puede enviar el token en cada encabezado de solicitud. Espero que esto ayude.