Spring Security Sessions没有cookie

f0brbegy  于 2023-10-20  发布在  Spring
关注(0)|答案(4)|浏览(114)

我正在尝试在不使用cookie的情况下管理Spring Security中的会话。原因是-我们的应用程序显示在另一个域的iframe中,我们需要管理应用程序中的会话,and Safari restricts cross-domain cookie creation。(上下文:domainA.comdomainB.com在iframe中显示www.example.com。domainB.com正在设置一个JSESSIONID cookie来利用domainB.com,但由于用户的浏览器显示domainA.com- Safari限制domainB.com创建cookie)。
我能想到的实现这一点的唯一方法(反对OWASP安全建议)是在URL中包含JSESSIONID作为GET参数。我不想这样做,但我想不出一个替代方案。
所以这个问题是关于:

  • 是否有更好的替代方案来解决这个问题?
  • 如果没有-我如何使用Spring Security实现这一点

查看Spring的文档,使用enableSessionUrlRewriting应该可以实现这一点
所以我这样做了:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        .sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.ALWAYS)
            .enableSessionUrlRewriting(true)

这并没有将JSESSIONID添加到URL中,但现在应该允许了。然后,我利用in this question找到的一些代码将“跟踪模式”设置为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中。
有人能帮我指个方向吗?

ffdz8vbo

ffdz8vbo1#

你看过《Spring》吗?HttpSession & RestfulAPI,使用HTTP头而不是cookie。请参阅REST示例中的REST示例项目。

vddsk6oq

vddsk6oq2#

基于表单的登录主要是有状态会话。在您的场景中,最好使用无状态会话。
JWT为此提供了实现。它基本上是一个键,你需要在每个HTTP请求中作为头传递。只要你有钥匙。API可用。
我们可以将JWT与Spring集成。
基本上你需要写这些逻辑。

  • 生成密钥逻辑
  • 在Spring Security中使用JWT
  • 每次呼叫时按下按键

我可以给予你个好机会

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);
    }

}

网络安全

  • 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

  • 检查每个Rest Call的有效令牌 *
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);
    }

}
wa7juj8i

wa7juj8i3#

您可以在网站DomainB.com服务器和客户端浏览器之间进行基于令牌的通信。令牌可以在身份验证后从DomainB.com服务器在响应的标头中发送。然后客户端浏览器可以将令牌保存在本地存储/会话存储中(也有到期时间)。然后客户端可以在每个请求的报头中发送令牌。希望这对你有帮助。

jmo0nnb3

jmo0nnb34#

我很感激上面的所有答案-我最终选择了一个更简单的解决方案,而没有做任何应用程序级别的更改,因为www.example.com的所有者domainA.com愿意与我们合作。在这里贴给别人,因为我甚至没有想到这一点最初.
基本上:

  • domainA.com的所有者为domainB.domainA.com-> domainB.com创建了DNS记录
  • domainB.com的所有者(我)通过“电子邮件验证”为domainB.domainA.com请求了一个公共SSL证书(我通过AWS这样做,但我相信还有其他机制通过其他提供商)
  • 上述请求已发送给domainA.com的网站管理员->他们批准并颁发了公共证书
  • 一旦颁发-我能够配置我的应用程序(或负载均衡器)使用这个新证书,他们配置他们的应用程序指向“domainB.domainA.com“(随后在DNS中路由到domainB.com)
  • 现在,浏览器为domainB.domainA.com发出cookie,由于它们是相同的主域,因此无需任何变通方法即可创建cookie。

再次感谢您的回答,抱歉没有在这里选择答案-忙碌周。

相关问题