Spring ile RESTful Kimlik Doğrulaması


262

Sorun:
Hassas bilgiler içeren bir Spring MVC tabanlı RESTful API'miz var. API güvenli olmalıdır, ancak her istekle birlikte kullanıcının kimlik bilgilerini (kullanıcı / şifre birleşik) göndermek istenmez. REST yönergelerine (ve dahili işletme gereksinimlerine) göre, sunucu durumsuz kalmalıdır. API, bir karma sunucu yaklaşımıyla başka bir sunucu tarafından tüketilecektir.

Gereksinimler:

  • Müşteri .../authenticatekimlik bilgileriyle (korumasız URL) istekte bulunur ; sunucu, gelecekteki istekleri doğrulamak ve vatansız kalmak için yeterli bilgi içeren güvenli bir simge döndürür. Bu muhtemelen Spring Security'nin Beni Hatırla Jetonu ile aynı bilgilerden oluşacaktır .

  • İstemci, daha önce elde edilen belirteci bir sorgu parametresi (veya daha az istenirse bir HTTP istek başlığı) ekleyerek çeşitli (korumalı) URL'lere sonraki isteklerde bulunur.

  • İstemciden çerezleri saklaması beklenemez.

  • Spring'i zaten kullandığımız için, çözüm Spring Security'yi kullanmalıdır.

Bu işi yapmaya çalışırken başımızı duvara çarpıyoruz, umarım dışarıdan biri bu sorunu zaten çözmüştür.

Yukarıdaki senaryo göz önüne alındığında, bu özel ihtiyacı nasıl çözebilirsiniz?


49
Merhaba Chris, bu token sorgu parametresinde geçen emin değilim en iyi fikir. Bu, HTTPS veya HTTP'den bağımsız olarak günlüklerde görünecektir. Başlıklar muhtemelen daha güvenlidir. Sadece FYI. Büyük soru olsa. +1
jmort253

1
Vatansız anlayışınız nedir? Jeton gereksiniminiz vatansız anlayışımla çarpışıyor. Http kimlik doğrulama cevabı bana tek durumsuz uygulama gibi geliyor.
Markus Malkusch

9
@MarkusMalkusch vatansız, sunucunun belirli bir istemciyle önceden iletişim kurma bilgisini ifade eder. HTTP tanım olarak vatansızdır ve oturum çerezleri durumu bildirir. Simgenin ömrü (ve bu konu için kaynak) önemsizdir; sunucu yalnızca geçerli olduğuna ve bir kullanıcıya geri bağlanabileceğine önem verir (oturum değil). Bu nedenle, belirleyici bir belirteç iletmek durumsallığa müdahale etmez.
Chris Cashwell

1
@ChrisCashwell Simgenin istemci tarafından sahte / oluşturulmadığından nasıl emin olabilirsiniz? Simgeyi şifrelemek, istemciye sağlamak için sunucu tarafında özel bir anahtar mı kullanıyorsunuz ve daha sonra aynı anahtarları gelecekteki istekler sırasında şifresini çözmek için mi kullanıyorsunuz? Tabii ki Base64 veya başka bir şaşkınlık yeterli olmaz. Bu belirteçlerin "onaylanması" için teknikler üzerinde durulabilir misiniz?
Craig Otis

6
Bu tarihli ve 2 yıldan fazla bir süredir koda dokunmamış veya güncellememiş olmama rağmen, bu kavramları daha da genişletmek için bir Gist oluşturdum. gist.github.com/ccashwell/dfc05dd8bd1a75d189d1
Chris Cashwell

Yanıtlar:


190

Bu çalışmayı tam olarak OP'de açıklandığı şekilde almayı başardık ve umarım bir başkası çözümü kullanabilir. İşte yaptıklarımız:

Güvenlik bağlamını şu şekilde ayarlayın:

<security:http realm="Protected API" use-expressions="true" auto-config="false" create-session="stateless" entry-point-ref="CustomAuthenticationEntryPoint">
    <security:custom-filter ref="authenticationTokenProcessingFilter" position="FORM_LOGIN_FILTER" />
    <security:intercept-url pattern="/authenticate" access="permitAll"/>
    <security:intercept-url pattern="/**" access="isAuthenticated()" />
</security:http>

<bean id="CustomAuthenticationEntryPoint"
    class="com.demo.api.support.spring.CustomAuthenticationEntryPoint" />

<bean id="authenticationTokenProcessingFilter"
    class="com.demo.api.support.spring.AuthenticationTokenProcessingFilter" >
    <constructor-arg ref="authenticationManager" />
</bean>

Gördüğünüz gibi, AuthenticationEntryPointbir 401 Unauthorizedistek oluşturduk ; bu , istek, filtre zincirinde bizim tarafımızdan doğrulanmadıysa yalnızca bir döndürür AuthenticationTokenProcessingFilter.

CustomAuthenticationEntryPoint :

public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response,
            AuthenticationException authException) throws IOException, ServletException {
        response.sendError( HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized: Authentication token was either missing or invalid." );
    }
}

AuthenticationTokenProcessingFilter :

public class AuthenticationTokenProcessingFilter extends GenericFilterBean {

    @Autowired UserService userService;
    @Autowired TokenUtils tokenUtils;
    AuthenticationManager authManager;

    public AuthenticationTokenProcessingFilter(AuthenticationManager authManager) {
        this.authManager = authManager;
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
        @SuppressWarnings("unchecked")
        Map<String, String[]> parms = request.getParameterMap();

        if(parms.containsKey("token")) {
            String token = parms.get("token")[0]; // grab the first "token" parameter

            // validate the token
            if (tokenUtils.validate(token)) {
                // determine the user based on the (already validated) token
                UserDetails userDetails = tokenUtils.getUserFromToken(token);
                // build an Authentication object with the user's info
                UsernamePasswordAuthenticationToken authentication = 
                        new UsernamePasswordAuthenticationToken(userDetails.getUsername(), userDetails.getPassword());
                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails((HttpServletRequest) request));
                // set the authentication into the SecurityContext
                SecurityContextHolder.getContext().setAuthentication(authManager.authenticate(authentication));         
            }
        }
        // continue thru the filter chain
        chain.doFilter(request, response);
    }
}

Açıkçası, TokenUtilsbazı özel (ve çok büyük-küçük harf) kodlar içerir ve kolayca paylaşılamaz. İşte arayüzü:

public interface TokenUtils {
    String getToken(UserDetails userDetails);
    String getToken(UserDetails userDetails, Long expiration);
    boolean validate(String token);
    UserDetails getUserFromToken(String token);
}

Bu seni iyi bir başlangıç ​​yapmalı. Mutlu kodlama. :)


Belirteç istekle gönderilirken belirtecin kimliğinin doğrulanması gerekli mi? Kullanıcı adı bilgilerini doğrudan almaya ve mevcut bağlamda / isteğe göre ayarlamaya ne dersiniz?
Fisher

1
@Spring Onları hiçbir yerde saklamıyorum ... jetonun tüm fikri, her taleple birlikte geçirilmesi gerektiğidir ve geçerliliğini (dolayısıyla validate(...)yöntemi) belirlemek için (kısmen) yeniden yapılandırılabilir . Sunucunun vatansız kalmasını istediğim için bu önemlidir. Spring kullanmaya gerek olmadan bu yaklaşımı kullanabileceğinizi düşünürüm.
Chris Cashwell

1
İstemci bir tarayıcıysa, belirteç nasıl saklanabilir? veya her istek için kimlik doğrulamasını yeniden yapmak zorunda mısınız?
beginner_

2
harika ipuçları. @ChrisCashwell - bulamadığım kısım, kullanıcı kimlik bilgilerini nerede doğrular ve bir jeton geri gönderirsiniz? / Kimlik doğrulaması bitiş noktasının impl bir yerde olması gerektiğini tahmin ediyorum. haklı mıyım Değilse / kimlik doğrulamasının amacı nedir?
Yonatan Maman

3
AuthenticationManager'ın içinde ne var?
MoienGK

25

Özet Erişim Kimlik Doğrulaması'nı düşünebilirsiniz . Esasen protokol aşağıdaki gibidir:

  1. İstek istemciden yapılır
  2. Sunucu benzersiz bir nonce dizesiyle yanıt veriyor
  3. İstemci md5 nonce ile bir kullanıcı adı ve şifre (ve diğer bazı değerler) sağlar; bu karma HA1 olarak bilinir
  4. Sunucu daha sonra müşterinin kimliğini doğrulayabilir ve istenen malzemeleri sunabilir
  5. Nonce ile iletişim, sunucu yeni bir nonce tedarik edene kadar devam edebilir (tekrar saldırılarını ortadan kaldırmak için bir sayaç kullanılır)

Tüm bu iletişim, jmort253'ün işaret ettiği gibi, genellikle hassas malzemeyi url parametrelerinde iletmekten daha güvenli olan başlıklar yoluyla yapılır.

Özet Erişim Kimlik Doğrulaması Spring Security tarafından desteklenmektedir . Belgeler, müşterinizin düz metin şifresine erişiminizin olması gerektiğini söylese de, istemciniz için HA1 karma özelliğine sahipseniz başarılı bir şekilde kimlik doğrulaması yapabileceğinizi unutmayın.


1
Bu olası bir yaklaşım olsa da, bir token almak için yapılması gereken birkaç gidiş-dönüş yolculuğu biraz istenmeyen bir hale getirir.
Chris Cashwell

İstemciniz HTTP Kimlik Doğrulaması belirtimine uyuyorsa, bu gidiş-dönüş yolculukları yalnızca ilk aramada ve 5. gerçekleştiğinde gerçekleşir.
Markus Malkusch

5

Bilgi taşıyan jetonlarla ilgili olarak, JSON Web Jetonları ( http://jwt.io ) mükemmel bir teknolojidir. Ana konsept, bilgi öğelerini (talepleri) jetona gömmek ve daha sonra doğrulama jetonunun iddiaların gerçekten güvenilir olduğunu doğrulayabilmesi için tüm jetonu imzalamaktır.

Bu Java uygulamasını kullanıyorum: https://bitbucket.org/b_c/jose4j/wiki/Home

Ayrıca bir Spring modülü (spring-security-jwt) var, ama desteklediğine bakmadım.


Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.