web-dev-qa-db-ja.com

Cookieを使用しないSpring Securityセッション

Cookieを利用せずにSpring Securityでセッションを管理しようとしています。その理由は、アプリケーションが別のドメインのiframe内に表示されるため、アプリケーションでセッションを管理する必要があるため 、SafariはクロスドメインCookieの作成を制限するためです 。 (コンテキスト:domainA.comはiframeにdomainB.comを表示します。domainB.comはdomainB.comで利用するためにJSESSIONID Cookieを設定しますが、ユーザーのブラウザにdomainA.comが表示されるため、SafariはdomainB.comによるCookieの作成を制限します) 。

これを達成するために私が考えることができる唯一の方法(OWASPセキュリティの推奨事項に対して)-GETパラメーターとしてURLにJSESSIONIDを含めることです。私はこれをしたくありませんが、私は代替案を考えることができません。

したがって、この質問は両方についてです:

  • この問題に取り組むためのより良い代替手段はありますか?
  • そうでない場合-Spring Securityでこれをどのように達成できますか

これに関する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)
      );

この後でも-アプリケーションは、URLではなくCookieとしてJSESSIONIDを追加します。

誰かが私をここで正しい方向に向けるのを手伝ってもらえますか?

21
Phas1c

Spring Session:HttpSession&RestfulAPI を見て、Cookieの代わりにHTTPヘッダーを使用しています。 REST Sample のRESTサンプルプロジェクトを参照してください。

5
Jean Marois

フォームベースのログインは、主にステートフルセッションです。シナリオでは、ステートレスセッションを使用するのが最適です。

[〜#〜] 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);
    }


}

WebSecurity

JWTチェックを追加するSpringSecurityロジック

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


}
3
MyTwoCents

サイトDomainB.comサーバーとクライアントブラウザーの間でトークンベースの通信を行うことができます。トークンは、認証後にDomainB.comサーバーから応答のヘッダーで送信できます。クライアントブラウザは、トークンをローカルストレージ/セッションストレージに保存できます(有効期限もあります)。その後、クライアントはすべてのリクエストのヘッダーでトークンを送信できます。お役に立てれば。

3
Amit Parashar

上記のすべての回答に感謝します。domainA.comの所有者が喜んで協力してくれたため、アプリケーションレベルの変更を行わずに簡単なソリューションを選択することになりました。もともとこれも考えていなかったので、他の人のためにここに投稿します...

基本的に :

  • DomainA.comの所有者がdomainB.domainA.comのDNSレコードを作成しました-> domainB.com
  • DomainB.comの所有者(私)は、「電子メール検証」を介してdomainB.domainA.comの公開SSL証明書を要求しました(AWSを介してこれを行いましたが、他のプロバイダーを介した他のメカニズムがあると確信しています)
  • 上記のリクエストは、domainA.comのウェブマスターに送信されました->彼らは承認し、公開証明書を発行しました
  • 発行後、この新しい証明書を使用するようにアプリケーション(またはロードバランサー)を構成することができ、「domainB.domainA.com」(その後DNSでdomainB.comにルーティングされる)を指すようにアプリケーションを構成しました。
  • 現在、ブラウザはdomainB.domainA.comのCookieを発行します。これらは同じプライマリドメインであるため、回避策を必要とせずにCookieが作成されます。

回答をありがとう、ここで回答を選択しなかったことをおaびします-忙しい週。

0
Phas1c