J'essaie de gérer des sessions dans Spring Security sans exploiter les cookies. Le raisonnement est le suivant: notre application est affichée dans une iframe d'un autre domaine, nous devons gérer les sessions dans notre application, et Safari limite la création de cookies entre domaines . (contexte: domainA.com affiche domainB.com dans une iframe. domainB.com définit un cookie JSESSIONID à exploiter sur domainB.com, mais comme le navigateur de l'utilisateur affiche domainA.com - Safari empêche domainB.com de créer le cookie) .
La seule façon pour moi d'y parvenir (par rapport aux recommandations de sécurité OWASP) est d'inclure le JSESSIONID dans l'URL en tant que paramètre GET. Je ne veux pas faire cela, mais je ne peux pas penser à une alternative.
Donc, cette question concerne à la fois:
Passer en revue la documentation de Spring à ce sujet en utilisant enableSessionUrlRewriting devrait permettre cela
Alors j'ai fait ça:
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.ALWAYS)
.enableSessionUrlRewriting(true)
Cela n'a pas ajouté le JSESSIONID à l'URL, mais cela devrait être autorisé maintenant. J'ai ensuite exploité le code trouvé dans cette question pour définir le "mode de suivi" sur URL
@SpringBootApplication
public class MyApplication extends SpringBootServletInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
servletContext
.setSessionTrackingModes(
Collections.singleton(SessionTrackingMode.URL)
);
Même après cela, l'application ajoute toujours le JSESSIONID en tant que cookie et non dans l'URL.
Quelqu'un peut m'aider à me diriger dans la bonne direction ici?
Avez-vous regardé Spring Session: HttpSession & RestfulAPI qui utilise des en-têtes HTTP au lieu de cookies. Voir les REST exemples de projets dans exemples REST .
Les connexions basées sur des formulaires sont principalement des sessions avec état. Dans votre scénario, utiliser des sessions sans état serait préférable.
JWT fournit une implémentation pour cela. C'est essentiellement une clé que vous devez passer comme en-tête dans chaque requête HTTP. Donc, tant que vous avez la clé. L'API est disponible.
Nous pouvons intégrer JWT avec Spring.
Fondamentalement, vous devez écrire cette logique.
Je peux vous donner une longueur d'avance
pom.xml
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
TokenHelper.Java
Contient des fonctions utiles pour la validation, la vérification et l'analyse de 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);
}
}
WebSecurity
Logique SpringSecurity à ajouter à la vérification 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
Vérifiez chaque appel restant pour un jeton valide
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);
}
}
Vous pouvez établir une communication à base de jeton entre le serveur DomainB.com du site et le navigateur client. Le jeton peut être envoyé à partir du serveur DomainB.com dans l'en-tête de la réponse, après authentification. Le navigateur client peut alors enregistrer le jeton dans le stockage localstorage/session (également avec un délai d'expiration). Le client peut ensuite envoyer le jeton dans l'en-tête de chaque demande. J'espère que cela t'aides.
J'apprécie toutes les réponses ci-dessus - j'ai finalement opté pour une solution plus simple sans apporter de modifications au niveau de l'application, car le propriétaire de domainA.com était disposé à travailler avec nous. Le poster ici pour les autres, car je n'y avais même pas pensé à l'origine ...
Fondamentalement :
Merci encore pour les réponses, excuses pour ne pas avoir choisi de réponse ici - semaine bien remplie.