การรับรองความถูกต้อง RESTful ผ่าน Spring


262

ปัญหา:
เรามี RESTful API แบบสปริง MVC ซึ่งมีข้อมูลที่ละเอียดอ่อน API ควรมีความปลอดภัย แต่การส่งข้อมูลรับรองของผู้ใช้ (คำสั่งผสมของผู้ใช้ / รหัสผ่าน) พร้อมกับคำขอแต่ละรายการไม่เป็นที่ต้องการ ตามแนวทาง REST (และข้อกำหนดทางธุรกิจภายใน) เซิร์ฟเวอร์จะต้องอยู่ในสถานะไร้สัญชาติ API จะใช้งานโดยเซิร์ฟเวอร์อื่นในรูปแบบการตอบโต้กับผู้ใช้ได้

ที่ต้องการ:

  • ไคลเอนต์ทำการร้องขอ.../authenticate(URL ที่ไม่มีการป้องกัน) ด้วยหนังสือรับรอง เซิร์ฟเวอร์ส่งคืนโทเค็นที่ปลอดภัยซึ่งมีข้อมูลเพียงพอสำหรับเซิร์ฟเวอร์เพื่อตรวจสอบคำขอในอนาคตและยังคงไร้สัญชาติ นี้อาจจะประกอบด้วยข้อมูลเช่นเดียวกับการรักษาความปลอดภัยของฤดูใบไม้ผลิจำ-Me Token

  • ไคลเอนต์ทำการร้องขอที่ตามมาไปยัง URL ต่างๆ (ที่มีการป้องกัน) ซึ่งจะเพิ่มโทเค็นที่ได้รับก่อนหน้านี้เป็นพารามิเตอร์การสืบค้น (หรือน้อยกว่านั้นคือส่วนหัวคำขอ HTTP)

  • ไม่สามารถคาดว่าลูกค้าจะจัดเก็บคุกกี้ได้

  • เนื่องจากเราใช้ Spring อยู่แล้วโซลูชันควรใช้ประโยชน์จาก Spring Security

เราต่อสู้กับกำแพงที่พยายามทำให้งานนี้สำเร็จดังนั้นหวังว่าจะมีใครบางคนแก้ไขปัญหานี้ได้แล้ว

จากสถานการณ์ข้างต้นคุณจะแก้ไขความต้องการนี้ได้อย่างไร


49
สวัสดีคริสฉันไม่แน่ใจว่าการส่งโทเค็นนั้นในพารามิเตอร์การสืบค้นเป็นความคิดที่ดีที่สุด ที่จะปรากฏในบันทึกโดยไม่คำนึงถึง HTTPS หรือ HTTP ส่วนหัวอาจปลอดภัยกว่า เพียงแค่ FYI คำถามที่ดีแม้ว่า +1
jmort253

1
คุณมีความเข้าใจเรื่องไร้สัญชาติอย่างไร? ข้อกำหนดโทเค็นของคุณขัดแย้งกับความเข้าใจของฉันเกี่ยวกับไร้สัญชาติ คำตอบการตรวจสอบความถูกต้อง Http สำหรับฉันนั้นเป็นเพียงการติดตั้งแบบไร้รัฐเท่านั้น
Markus Malkusch

9
@MarkusMalkusch stateless อ้างถึงความรู้ของเซิร์ฟเวอร์เกี่ยวกับการสื่อสารก่อนหน้านี้กับไคลเอนต์ที่กำหนด HTTP ไม่มีสถานะตามคำจำกัดความและคุกกี้เซสชันทำให้เป็นสถานะ อายุการใช้งาน (และแหล่งที่มาสำหรับเรื่องนั้น) ของโทเค็นนั้นไม่เกี่ยวข้อง เซิร์ฟเวอร์ใส่ใจว่าถูกต้องและสามารถเชื่อมโยงกลับไปยังผู้ใช้ (ไม่ใช่เซสชัน) ดังนั้นการส่งผ่านโทเค็นที่ระบุจึงไม่รบกวนความเป็นรัฐ
Chris Cashwell

1
@ChrisCashwell คุณมั่นใจได้อย่างไรว่าโทเค็นไม่ได้ถูกปลอมแปลง / สร้างโดยลูกค้า คุณใช้ไพรเวตคีย์บนฝั่งเซิร์ฟเวอร์เพื่อเข้ารหัสโทเค็นมอบให้กับลูกค้าแล้วใช้คีย์เดียวกันเพื่อถอดรหัสระหว่างการร้องขอในอนาคตหรือไม่ เห็นได้ชัดว่า Base64 หรือการทำให้งงอื่น ๆ อาจไม่เพียงพอ คุณสามารถอธิบายรายละเอียดเกี่ยวกับเทคนิคสำหรับ "การตรวจสอบความถูกต้อง" ของโทเค็นเหล่านี้ได้หรือไม่?
Craig Otis

6
แม้ว่านี่จะเป็นวันที่และฉันยังไม่ได้แตะต้องหรืออัปเดตรหัสในช่วง 2 ปีที่ผ่านมา แต่ฉันได้สร้างส่วนสำคัญในการขยายแนวคิดเหล่านี้เพิ่มเติม gist.github.com/ccashwell/dfc05dd8bd1a75d189d1
Chris Cashwell

คำตอบ:


190

เราจัดการเพื่อให้การทำงานตรงตามที่อธิบายไว้ใน OP และหวังว่าคนอื่นจะสามารถใช้ประโยชน์จากโซลูชันได้ นี่คือสิ่งที่เราทำ:

ตั้งค่าบริบทความปลอดภัยดังนี้:

<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>

ในขณะที่คุณสามารถดูเราได้สร้างที่กำหนดเองAuthenticationEntryPointซึ่งโดยทั่วไปเพียงแค่ส่งกลับถ้าขอไม่ได้รับรองความถูกต้องในห่วงโซ่กรองตามเรา401 UnauthorizedAuthenticationTokenProcessingFilter

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

เห็นได้ชัดว่าTokenUtilsมีรหัสองคมนตรี (และเฉพาะกรณี) และไม่สามารถแบ่งปันได้อย่างง่ายดาย นี่คืออินเตอร์เฟส:

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

นั่นควรจะพาคุณไปสู่จุดเริ่มต้นที่ดี การเข้ารหัสที่มีความสุข :)


จำเป็นหรือไม่ในการตรวจสอบความถูกต้องของโทเค็นเมื่อโทเค็นส่งพร้อมคำขอ รับข้อมูลชื่อผู้ใช้โดยตรงและตั้งค่าในบริบท / คำขอปัจจุบันหรือไม่
ฟิชเชอร์

1
@Spring ผมไม่ได้เก็บไว้ที่ใดก็ได้ ... ความคิดทั้งหมดของโทเค็นก็คือว่ามันจะต้องมีการส่งผ่านไปกับทุกการร้องขอและสามารถแยกชิ้นส่วน (บางส่วน) เพื่อตรวจสอบความถูกต้อง (เพราะฉะนั้นvalidate(...)วิธีการ) นี่เป็นสิ่งสำคัญเพราะฉันต้องการให้เซิร์ฟเวอร์ยังคงไร้สัญชาติ ฉันคิดว่าคุณสามารถใช้วิธีนี้ได้โดยไม่ต้องใช้สปริง
Chris Cashwell

1
หากลูกค้าเป็นเบราว์เซอร์จะสามารถจัดเก็บโทเค็นได้อย่างไร หรือคุณต้องทำซ้ำการตรวจสอบสำหรับแต่ละคำขอ?
เริ่มต้น

2
เคล็ดลับที่ดี @ChrisCashwell - ส่วนที่ฉันไม่สามารถหาได้คือคุณตรวจสอบข้อมูลประจำตัวผู้ใช้จากที่ใดและส่งโทเค็นกลับมาหรือไม่ ฉันเดาว่ามันควรจะอยู่ที่ไหนสักแห่งใน / implicate จุดสิ้นสุด ฉันถูกไหม ? หากไม่ใช่เป้าหมาย / รับรองความถูกต้องคืออะไร?
Yonatan Maman

3
AuthenticationManager คืออะไร
MoienGK

25

คุณอาจพิจารณาDigest การเข้าถึงการตรวจสอบสิทธิ์ โดยพื้นฐานแล้วโปรโตคอลมีดังนี้:

  1. ทำคำขอจากลูกค้า
  2. เซิร์ฟเวอร์ตอบกลับด้วยสตริง nonce ที่ไม่ซ้ำกัน
  3. ไคลเอ็นต์ระบุชื่อผู้ใช้และรหัสผ่าน (และค่าอื่น ๆ ) md5 แฮชด้วย nonce; แฮชนี้เรียกว่า HA1
  4. เซิร์ฟเวอร์นั้นสามารถตรวจสอบตัวตนของลูกค้าและให้บริการวัสดุที่ร้องขอ
  5. การสื่อสารกับ nonce สามารถดำเนินต่อไปได้จนกว่าเซิร์ฟเวอร์จะจัดหา nonce ใหม่ (ตัวนับใช้เพื่อกำจัดการโจมตีซ้ำ)

การสื่อสารทั้งหมดนี้ทำผ่านส่วนหัวซึ่งตามที่ jmort253 ชี้ให้เห็นโดยทั่วไปแล้วจะมีความปลอดภัยมากกว่าการสื่อสารวัสดุที่มีความละเอียดอ่อนในพารามิเตอร์ url

Digest เข้าถึงรับรองความถูกต้องได้รับการสนับสนุนโดยฤดูใบไม้ผลิการรักษาความปลอดภัย โปรดสังเกตว่าแม้ว่าเอกสารจะบอกว่าคุณต้องมีสิทธิ์เข้าถึงรหัสผ่านข้อความธรรมดาของลูกค้าคุณก็สามารถทำได้ตรวจสอบสิทธิ์ได้สำเร็จหากคุณมีแฮช HA1สำหรับลูกค้าของคุณ


1
ขณะนี้เป็นวิธีที่เป็นไปได้การเดินทางไปกลับหลายครั้งที่ต้องทำเพื่อดึงโทเค็นทำให้ไม่พึงประสงค์เล็กน้อย
Chris Cashwell

หากลูกค้าของคุณทำตามข้อกำหนดการตรวจสอบสิทธิ์ HTTP นั้นการเดินทางไปกลับจะเกิดขึ้นเฉพาะเมื่อมีการโทรครั้งแรกและเมื่อ 5 เกิดขึ้น
Markus Malkusch

5

เกี่ยวกับโทเค็นที่ถือข้อมูล JSON Web Tokens http://jwt.io ) เป็นเทคโนโลยีที่ยอดเยี่ยม แนวคิดหลักคือการฝังองค์ประกอบข้อมูล (การอ้างสิทธิ์) ลงในโทเค็นแล้วเซ็นโทเค็นทั้งหมดเพื่อให้การตรวจสอบปลายสามารถตรวจสอบว่าการเรียกร้องนั้นน่าเชื่อถืออย่างแท้จริง

ฉันใช้การติดตั้ง Java นี้: https://bitbucket.org/b_c/jose4j/wiki/Home

นอกจากนี้ยังมีโมดูล Spring (spring-security-jwt) แต่ฉันไม่ได้ตรวจสอบสิ่งที่สนับสนุน


2

ทำไมคุณไม่เริ่มใช้ OAuth กับ JSON WebTokens

http://projects.spring.io/spring-security-oauth/

OAuth2 เป็นโปรโตคอล / กรอบการอนุญาตที่เป็นมาตรฐาน ตามข้อกำหนด OAuth2 อย่างเป็นทางการ :

คุณสามารถหาข้อมูลเพิ่มเติมได้ที่นี่

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.