ห่วงโซ่ตัวกรองความปลอดภัยของสปริงเป็นเครื่องยนต์ที่ซับซ้อนและยืดหยุ่นมาก
ตัวกรองหลักในห่วงโซ่คือ (ตามลำดับ)
- SecurityContextPersistenceFilter (กู้คืนการพิสูจน์ตัวตนจาก JSESSIONID)
- UsernamePasswordAuthenticationFilter (ดำเนินการตรวจสอบสิทธิ์)
- ExceptionTranslationFilter (จับข้อยกเว้นด้านความปลอดภัยจาก FilterSecurityInterceptor)
- FilterSecurityInterceptor (อาจมีข้อยกเว้นในการพิสูจน์ตัวตนและการอนุญาต)
เมื่อดูเอกสารรุ่นที่เสถียร 4.2.1 ในปัจจุบันส่วนที่13.3 การสั่งกรองคุณจะเห็นองค์กรตัวกรองทั้งหมดของห่วงโซ่ตัวกรอง:
13.3 ลำดับตัวกรอง
ลำดับที่ตัวกรองกำหนดไว้ในห่วงโซ่มีความสำคัญมาก ไม่ว่าคุณจะใช้ตัวกรองใดจริงลำดับควรเป็นดังนี้:
ChannelProcessingFilterเนื่องจากอาจต้องเปลี่ยนเส้นทางไปยังโปรโตคอลอื่น
SecurityContextPersistenceFilterดังนั้นจึงสามารถตั้งค่า SecurityContext ใน SecurityContextHolder ที่จุดเริ่มต้นของคำขอเว็บและการเปลี่ยนแปลงใด ๆ ใน SecurityContext สามารถคัดลอกไปยัง HttpSession เมื่อคำขอเว็บสิ้นสุดลง (พร้อมสำหรับการใช้งานกับคำขอเว็บถัดไป)
ConcurrentSessionFilterเนื่องจากใช้ฟังก์ชัน SecurityContextHolder และจำเป็นต้องอัปเดต SessionRegistry เพื่อสะท้อนการร้องขอต่อเนื่องจากหลัก
กลไกการประมวลผลการพิสูจน์ตัวตน -
UsernamePasswordAuthenticationFilter , CasAuthenticationFilter ,
BasicAuthenticationFilterฯลฯ - เพื่อให้ SecurityContextHolder สามารถแก้ไขเพื่อให้มีโทเค็นการร้องขอการพิสูจน์ตัวตนที่ถูกต้อง
SecurityContextHolderAwareRequestFilterถ้าคุณจะใช้มันในการติดตั้งระบบรักษาความปลอดภัยในฤดูใบไม้ผลิตระหนัก HttpServletRequestWrapper ลงในภาชนะ servlet ของคุณ
JaasApiIntegrationFilterถ้าJaasAuthenticationTokenอยู่ใน SecurityContextHolder นี้จะดำเนินการ FilterChain เป็นเรื่องใน JaasAuthenticationToken
RememberMeAuthenticationFilterดังนั้นหากไม่มีกลไกการประมวลผลการพิสูจน์ตัวตนก่อนหน้านี้ได้อัปเดต SecurityContextHolder และคำขอแสดงคุกกี้ที่ช่วยให้สามารถใช้บริการ remember-me ได้จะมีการใส่อ็อบเจ็กต์ Authentication ที่จำได้อย่างเหมาะสมไว้ที่นั่น
AnonymousAuthenticationFilterดังนั้นหากไม่มีกลไกการประมวลผลการพิสูจน์ตัวตนก่อนหน้านี้อัพเดต SecurityContextHolder อ็อบเจ็กต์การพิสูจน์ตัวตนแบบไม่ระบุชื่อจะถูกวางไว้ที่นั่น
ExceptionTranslationFilterเพื่อตรวจจับข้อยกเว้น Spring Security เพื่อให้สามารถส่งคืนการตอบสนองข้อผิดพลาด HTTP หรือสามารถเรียกใช้ AuthenticationEntryPoint ที่เหมาะสมได้
FilterSecurityInterceptorเพื่อป้องกัน URI ของเว็บและเพิ่มข้อยกเว้นเมื่อการเข้าถึงถูกปฏิเสธ
ตอนนี้ฉันจะพยายามตอบคำถามของคุณทีละคำถาม:
ฉันสับสนว่าตัวกรองเหล่านี้ใช้อย่างไร สำหรับการเข้าสู่ระบบแบบฟอร์มที่มีให้ในฤดูใบไม้ผลิ UsernamePasswordAuthenticationFilter จะใช้สำหรับ / login เท่านั้นและตัวกรองหลังไม่ได้ใช่หรือไม่? องค์ประกอบเนมสเปซแบบฟอร์มล็อกอินกำหนดค่าตัวกรองเหล่านี้โดยอัตโนมัติหรือไม่ ทุกคำขอ (รับรองความถูกต้องหรือไม่) เข้าถึง FilterSecurityInterceptor สำหรับ URL ที่ไม่เข้าสู่ระบบหรือไม่
เมื่อคุณกำหนดค่า<security-http>
ส่วนสำหรับแต่ละส่วนคุณต้องระบุกลไกการพิสูจน์ตัวตนอย่างน้อยหนึ่งรายการ ต้องเป็นหนึ่งในตัวกรองที่ตรงกับกลุ่ม 4 ในส่วน 13.3 Filter Ordering จากเอกสาร Spring Security ที่ฉันเพิ่งอ้างถึง
นี่คือองค์ประกอบความปลอดภัยขั้นต่ำที่ถูกต้อง: http ซึ่งสามารถกำหนดค่าได้:
<security:http authentication-manager-ref="mainAuthenticationManager"
entry-point-ref="serviceAccessDeniedHandler">
<security:intercept-url pattern="/sectest/zone1/**" access="hasRole('ROLE_ADMIN')"/>
</security:http>
เพียงแค่ทำมันตัวกรองเหล่านี้ได้รับการกำหนดค่าในพร็อกซีตัวกรอง:
{
"1": "org.springframework.security.web.context.SecurityContextPersistenceFilter",
"2": "org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter",
"3": "org.springframework.security.web.header.HeaderWriterFilter",
"4": "org.springframework.security.web.csrf.CsrfFilter",
"5": "org.springframework.security.web.savedrequest.RequestCacheAwareFilter",
"6": "org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter",
"7": "org.springframework.security.web.authentication.AnonymousAuthenticationFilter",
"8": "org.springframework.security.web.session.SessionManagementFilter",
"9": "org.springframework.security.web.access.ExceptionTranslationFilter",
"10": "org.springframework.security.web.access.intercept.FilterSecurityInterceptor"
}
หมายเหตุ: ฉันได้รับพวกเขาโดยการสร้าง RestController แบบง่ายซึ่ง @Autowires FilterChainProxy และส่งคืนเนื้อหา:
@Autowired
private FilterChainProxy filterChainProxy;
@Override
@RequestMapping("/filterChain")
public @ResponseBody Map<Integer, Map<Integer, String>> getSecurityFilterChainProxy(){
return this.getSecurityFilterChainProxy();
}
public Map<Integer, Map<Integer, String>> getSecurityFilterChainProxy(){
Map<Integer, Map<Integer, String>> filterChains= new HashMap<Integer, Map<Integer, String>>();
int i = 1;
for(SecurityFilterChain secfc : this.filterChainProxy.getFilterChains()){
//filters.put(i++, secfc.getClass().getName());
Map<Integer, String> filters = new HashMap<Integer, String>();
int j = 1;
for(Filter filter : secfc.getFilters()){
filters.put(j++, filter.getClass().getName());
}
filterChains.put(i++, filters);
}
return filterChains;
}
ที่นี่เราจะเห็นว่าเพียงแค่ประกาศ<security:http>
องค์ประกอบด้วยการกำหนดค่าขั้นต่ำเพียงรายการเดียวตัวกรองเริ่มต้นทั้งหมดจะรวมอยู่ด้วย แต่ไม่มีตัวกรองใดที่เป็นประเภทการตรวจสอบความถูกต้อง (กลุ่มที่ 4 ในส่วนลำดับตัวกรอง 13.3) ดังนั้นจึงหมายความว่าเพียงแค่ประกาศsecurity:http
องค์ประกอบเท่านั้น SecurityContextPersistenceFilter, ExceptionTranslationFilter และ FilterSecurityInterceptor จะได้รับการกำหนดค่าโดยอัตโนมัติ
ในความเป็นจริงควรกำหนดค่ากลไกการประมวลผลการพิสูจน์ตัวตนอย่างใดอย่างหนึ่งและแม้แต่การประมวลผลเนมสเปซการรักษาความปลอดภัยก็อ้างว่าเกิดข้อผิดพลาดระหว่างการเริ่มต้น แต่สามารถข้ามการเพิ่มแอตทริบิวต์จุดเริ่มต้น - จุดอ้างอิงใน <http:security>
หากฉันเพิ่มพื้นฐาน<form-login>
ในการกำหนดค่าด้วยวิธีนี้:
<security:http authentication-manager-ref="mainAuthenticationManager">
<security:intercept-url pattern="/sectest/zone1/**" access="hasRole('ROLE_ADMIN')"/>
<security:form-login />
</security:http>
ตอนนี้ filterChain จะเป็นดังนี้:
{
"1": "org.springframework.security.web.context.SecurityContextPersistenceFilter",
"2": "org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter",
"3": "org.springframework.security.web.header.HeaderWriterFilter",
"4": "org.springframework.security.web.csrf.CsrfFilter",
"5": "org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter",
"6": "org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter",
"7": "org.springframework.security.web.savedrequest.RequestCacheAwareFilter",
"8": "org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter",
"9": "org.springframework.security.web.authentication.AnonymousAuthenticationFilter",
"10": "org.springframework.security.web.session.SessionManagementFilter",
"11": "org.springframework.security.web.access.ExceptionTranslationFilter",
"12": "org.springframework.security.web.access.intercept.FilterSecurityInterceptor"
}
ตอนนี้สองตัวกรองorg.springframework.security.web.authentication.UsernamePasswordAuthenticationFilterและ org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter ถูกสร้างและกำหนดค่าใน FilterChainProxy
ตอนนี้คำถาม:
สำหรับการเข้าสู่ระบบแบบฟอร์มที่มีให้ในฤดูใบไม้ผลิ UsernamePasswordAuthenticationFilter จะใช้สำหรับ / login เท่านั้นและตัวกรองหลังไม่ได้ใช่หรือไม่?
ใช่มันถูกใช้เพื่อพยายามดำเนินการตามกลไกการประมวลผลการเข้าสู่ระบบในกรณีที่คำขอตรงกับ URL ของ UsernamePasswordAuthenticationFilter URL นี้สามารถกำหนดค่าหรือปรับเปลี่ยนพฤติกรรมให้ตรงกับทุกคำขอ
คุณอาจมีกลไกการประมวลผลการพิสูจน์ตัวตนมากกว่าหนึ่งตัวที่กำหนดค่าไว้ใน FilterchainProxy เดียวกัน (เช่น HttpBasic, CAS ฯลฯ )
องค์ประกอบเนมสเปซแบบฟอร์มล็อกอินกำหนดค่าตัวกรองเหล่านี้โดยอัตโนมัติหรือไม่
ไม่องค์ประกอบการเข้าสู่ระบบแบบฟอร์มจะกำหนดค่า UsernamePasswordAUthenticationFilter และในกรณีที่คุณไม่ได้ระบุ url หน้าการเข้าสู่ระบบมันยังกำหนดค่า org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter ซึ่งจะสิ้นสุดในการเข้าสู่ระบบที่สร้างขึ้นโดยอัตโนมัติ หน้า.
ตัวกรองอื่น ๆ ได้รับการกำหนดค่าโดยอัตโนมัติตามค่าเริ่มต้นเพียงแค่สร้าง<security:http>
องค์ประกอบที่ไม่มีsecurity:"none"
แอตทริบิวต์
ทุกคำขอ (รับรองความถูกต้องหรือไม่) เข้าถึง FilterSecurityInterceptor สำหรับ URL ที่ไม่เข้าสู่ระบบหรือไม่
ทุกคำขอควรไปถึงเนื่องจากเป็นองค์ประกอบที่ดูแลว่าคำขอมีสิทธิ์เข้าถึง URL ที่ร้องขอหรือไม่ FilterChain.doFilter(request, response);
แต่บางส่วนของฟิลเตอร์ประมวลผลก่อนอาจหยุดการประมวลผลห่วงโซ่ตัวกรองก็ไม่เรียก ตัวอย่างเช่นตัวกรอง CSRF อาจหยุดการประมวลผลโซ่ตัวกรองหากคำขอไม่มีพารามิเตอร์ csrf
จะเกิดอะไรขึ้นถ้าฉันต้องการรักษาความปลอดภัย REST API ด้วย JWT-token ซึ่งดึงมาจากการเข้าสู่ระบบ? ฉันต้องกำหนดค่าแท็ก http การกำหนดค่าเนมสเปซสองรายการสิทธิ์? อีกคนหนึ่งสำหรับ / เข้าสู่ระบบด้วยUsernamePasswordAuthenticationFilter
และอีกคนหนึ่งสำหรับส่วนที่เหลือของ URL JwtAuthenticationFilter
กับที่กำหนดเอง
ไม่คุณไม่ได้บังคับให้ทำแบบนี้ คุณสามารถประกาศทั้งสองUsernamePasswordAuthenticationFilter
และJwtAuthenticationFilter
ในองค์ประกอบ http เดียวกันได้ แต่ขึ้นอยู่กับลักษณะการทำงานที่เป็นรูปธรรมของแต่ละตัวกรองนี้ ทั้งสองแนวทางเป็นไปได้และวิธีใดที่จะเลือกได้ในที่สุดก็ขึ้นอยู่กับความชอบของตัวเอง
การกำหนดค่าองค์ประกอบ http สองรายการจะสร้าง springSecurityFitlerChains สองรายการหรือไม่
ใช่นั่นเป็นความจริง
UsernamePasswordAuthenticationFilter ถูกปิดโดยค่าเริ่มต้นหรือไม่จนกว่าฉันจะประกาศเข้าสู่ระบบแบบฟอร์ม?
ใช่คุณสามารถเห็นได้ในตัวกรองที่เพิ่มขึ้นในการกำหนดค่าแต่ละรายการที่ฉันโพสต์
ฉันจะแทนที่ SecurityContextPersistenceFilter ได้อย่างไรซึ่งจะได้รับการรับรองความถูกต้องจากโทเค็น JWT ที่มีอยู่แทนที่จะเป็น JSESSIONID
คุณสามารถหลีกเลี่ยง SecurityContextPersistenceFilter ได้เพียงกำหนดค่ากลยุทธ์เซสชันใน<http:element>
. เพียงกำหนดค่าดังนี้:
<security:http create-session="stateless" >
หรือในกรณีนี้คุณสามารถเขียนทับด้วยตัวกรองอื่นด้วยวิธีนี้ภายใน<security:http>
องค์ประกอบ:
<security:http ...>
<security:custom-filter ref="myCustomFilter" position="SECURITY_CONTEXT_FILTER"/>
</security:http>
<beans:bean id="myCustomFilter" class="com.xyz.myFilter" />
แก้ไข:
คำถามหนึ่งเกี่ยวกับ "คุณสามารถกำหนดกลไกการประมวลผลการพิสูจน์ตัวตนได้มากกว่าหนึ่งรายการใน FilterchainProxy เดียวกัน" รายการหลังจะเขียนทับการตรวจสอบสิทธิ์ที่ดำเนินการโดยรายการแรกหรือไม่หากประกาศตัวกรองการตรวจสอบสิทธิ์หลายรายการ (การใช้งานแบบสปริง) สิ่งนี้เกี่ยวข้องกับการมีผู้ให้บริการตรวจสอบสิทธิ์หลายรายอย่างไร
สุดท้ายนี้ขึ้นอยู่กับการใช้งานของตัวกรองแต่ละตัว แต่เป็นความจริงที่ว่าตัวกรองการตรวจสอบสิทธิ์หลังอย่างน้อยก็สามารถเขียนทับการรับรองความถูกต้องก่อนหน้านี้ได้ในที่สุดโดยตัวกรองก่อนหน้านี้
แต่สิ่งนี้จะไม่เกิดขึ้นอย่างแน่นอน ฉันมีกรณีการผลิตบางอย่างในบริการ REST ที่มีการรักษาความปลอดภัยซึ่งฉันใช้โทเค็นการอนุญาตชนิดหนึ่งซึ่งสามารถจัดให้เป็นส่วนหัว Http หรือภายในเนื้อหาคำขอ ดังนั้นฉันจึงกำหนดค่าตัวกรองสองตัวที่กู้คืนโทเค็นนั้นในกรณีหนึ่งจากส่วนหัว Http และอีกตัวหนึ่งจากเนื้อหาคำขอของคำขอส่วนที่เหลือของตัวเอง เป็นความจริงที่ว่าหากคำขอ http หนึ่งรายการให้โทเค็นการตรวจสอบความถูกต้องทั้งเป็นส่วนหัว Http และภายในเนื้อหาคำขอตัวกรองทั้งสองจะพยายามเรียกใช้กลไกการตรวจสอบสิทธิ์ที่มอบหมายให้ผู้จัดการ แต่สามารถหลีกเลี่ยงได้ง่ายเพียงตรวจสอบว่าคำขอนั้น ได้รับการรับรองความถูกต้องแล้วตั้งแต่เริ่มต้นของdoFilter()
วิธีการของแต่ละตัวกรอง
การมีตัวกรองการตรวจสอบความถูกต้องมากกว่าหนึ่งตัวเกี่ยวข้องกับการมีผู้ให้บริการการตรวจสอบสิทธิ์มากกว่าหนึ่งราย แต่อย่าบังคับ ในกรณีที่ฉันเปิดเผยก่อนหน้านี้ฉันมีตัวกรองการพิสูจน์ตัวตนสองตัว แต่ฉันมีผู้ให้บริการการพิสูจน์ตัวตนเพียงรายเดียวเนื่องจากตัวกรองทั้งสองสร้างอ็อบเจ็กต์การพิสูจน์ตัวตนประเภทเดียวกันดังนั้นในทั้งสองกรณีตัวจัดการการพิสูจน์ตัวตนจะมอบหมายให้กับผู้ให้บริการรายเดียวกัน
และตรงข้ามกับสิ่งนี้ฉันก็มีสถานการณ์ที่ฉันเผยแพร่ UsernamePasswordAuthenticationFilter เพียงตัวเดียว แต่ข้อมูลรับรองของผู้ใช้ทั้งสองสามารถอยู่ใน DB หรือ LDAP ดังนั้นฉันจึงมีผู้ให้บริการสนับสนุน UsernamePasswordAuthenticationToken สองตัวและ AuthenticationManager จะมอบหมายความพยายามในการตรวจสอบสิทธิ์ใด ๆ จากตัวกรองไปยังผู้ให้บริการ อย่างเป็นความลับเพื่อตรวจสอบข้อมูลรับรอง
ดังนั้นฉันคิดว่าเป็นที่ชัดเจนว่าทั้งจำนวนตัวกรองการตรวจสอบสิทธิ์จะไม่กำหนดจำนวนผู้ให้บริการการตรวจสอบสิทธิ์หรือจำนวนผู้ให้บริการเป็นตัวกำหนดจำนวนตัวกรอง
นอกจากนี้เอกสารประกอบยังระบุว่า SecurityContextPersistenceFilter มีหน้าที่ทำความสะอาด SecurityContext ซึ่งมีความสำคัญเนื่องจากการรวมเธรด หากฉันละเว้นหรือให้การใช้งานแบบกำหนดเองฉันต้องดำเนินการทำความสะอาดด้วยตนเองใช่ไหม มี gotcha ที่คล้ายกันมากขึ้นเมื่อปรับแต่งโซ่หรือไม่?
ฉันไม่ได้ตรวจสอบตัวกรองนี้อย่างละเอียดมาก่อน แต่หลังจากคำถามสุดท้ายของคุณฉันได้ตรวจสอบการใช้งานและตามปกติในฤดูใบไม้ผลิเกือบทุกอย่างสามารถกำหนดค่าขยายหรือเขียนทับได้
SecurityContextPersistenceFilterผู้ได้รับมอบหมายในSecurityContextRepositoryการดำเนินการค้นหา SecurityContext โดยค่าเริ่มต้นจะใช้HttpSessionSecurityContextRepositoryแต่สามารถเปลี่ยนแปลงได้โดยใช้หนึ่งในตัวสร้างของตัวกรอง ดังนั้นจึงอาจเป็นการดีกว่าที่จะเขียน SecurityContextRepository ที่เหมาะกับความต้องการของคุณและกำหนดค่าใน SecurityContextPersistenceFilter โดยเชื่อมั่นในพฤติกรรมที่พิสูจน์แล้วแทนที่จะเริ่มสร้างทั้งหมดตั้งแต่ต้น