Я пытаюсь управлять сеансами в Spring Security без использования файлов cookie. Причина в том, что наше приложение отображается в iframe из другого домена, нам нужно управлять сеансами в нашем приложении, а Safari ограничивает создание междоменных файлов cookie.. (контекст: domainA.com отображает domainB.com в iframe. domainB.com устанавливает cookie JSESSIONID для использования на domainB.com, но поскольку браузер пользователя показывает domainA.com - Safari запрещает domainB.com создавать cookie) .
Единственный способ добиться этого (вопреки рекомендациям по безопасности OWASP) - это включить JSESSIONID в URL-адрес в качестве параметра GET. Я не ХОЧУ этого делать, но не могу придумать альтернативы.
Итак, этот вопрос касается как:
Просмотр документации Spring по этому поводу, использование enableSessionUrlRewriting должно позволить это
Итак, я сделал это:
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.ALWAYS)
.enableSessionUrlRewriting(true)
Это не добавляло JSESSIONID к URL-адресу, но теперь это должно быть разрешено. Затем я использовал некоторый код, найденный в этом вопросе, чтобы установить "режим отслеживания" на URL
@SpringBootApplication
public class MyApplication extends SpringBootServletInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
servletContext
.setSessionTrackingModes(
Collections.singleton(SessionTrackingMode.URL)
);
Даже после этого приложение по-прежнему добавляет JSESSIONID в качестве файла cookie, а не в URL-адрес.
Может ли кто-нибудь помочь мне указать здесь правильное направление?
Попробуйте это и дайте мне знать. servletContext.setSessionTrackingModes (EnumSet.of (SessionTrackMode.URL));




Между сервером сайта DomainB.com и клиентским браузером может быть установлена связь на основе токенов. Токен может быть отправлен с сервера DomainB.com в заголовке ответа после аутентификации. Затем клиентский браузер может сохранить токен в локальном хранилище / хранилище сеансов (также имеет срок действия). Затем клиент может отправлять токен в заголовок каждого запроса. Надеюсь это поможет.
Вы смотрели на Весенняя сессия: HttpSession и RestfulAPI, который использует заголовки HTTP вместо файлов cookie. См. Примеры проектов REST в REST Образец.
Я опубликовал решение, которое в итоге выбрало (больше изменений на уровне инфраструктуры, а не на уровне приложений), но если бы мне понадобилось другое решение, оно было бы вторым. Спасибо!
Этот пример работает с аутентификацией, но мне нужно получить сеанс для каждого пользователя (с аутентификацией или без нее), как обычные сеансы. Это возможно?
@ DavidCanós, так и должно быть. Этот подход меняет только то, как информация о сеансе передается между клиентом и сервером, то есть в заголовке вместо файла cookie или части URL-адреса, но я не пробовал ваш вариант использования.
@JeanMarois, мне удалось это сделать. Он работает так, как ожидалось, вы должны отправить x-auth-token в заголовке, и он действительно работает как файл cookie (сеансы авторизации или не авторизации). Он создает сеанс на сервере и позволяет вам работать в обычном режиме. Спасибо
Логины на основе форм - это в основном сеансы с сохранением состояния. В вашем сценарии лучше всего использовать сеансы без сохранения состояния.
JWT обеспечивает реализацию для этого. В основном это ключ, который вам нужно передавать в качестве заголовка в каждом HTTP-запросе. Итак, пока у вас есть ключ. API доступен.
Мы можем интегрировать JWT с Spring.
В основном вам нужно написать эту логику.
Я могу дать вам фору
pom.xml
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
TokenHelper.java
Содержат полезные функции для проверки, проверки и анализа токена.
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
SpringSecurity Logic для добавления проверки 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
Проверяйте каждый вызов на отдых на предмет наличия действующего жетона
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);
}
}
Я ценю все приведенные выше ответы - в итоге я выбрал более простое решение, не внося никаких изменений на уровне приложения, потому что владелец domainA.com был готов работать с нами. Размещаю это здесь для других, так как изначально я даже не думал об этом ...
По сути :
Еще раз спасибо за ответы, извиняюсь за то, что не выбрал ответ здесь - напряженная неделя.
Вы можете решить это и другим способом. Если у вас есть SPA, вы можете использовать заголовки и память. Кроме того, если вы используете
iframe, вам должно быть хорошо управлять своими собственными файлами cookie, вам следует создать сервлет, который "разделяет" сеанс в двух доменах, или я думаю, что лучшим решением было бы иметь прокси на domainA.com, который указывает на domainB.com. Таким образом, вы можете настроить любое сопоставление файлов cookie, все, что захотите. Если вы можете это использовать, я бы использовал github.com/mitre/HTTP-Proxy-Servlet. Я отправлю это как подробный ответ, если вы сможете использовать любой из этих методов. :)