เมื่อเร็ว ๆ นี้ฉันพบปัญหาในโครงการ: ส่วนหน้าและส่วนหลังถูกแยกออกจากกันส่วนหน้าจะทำด้วย Vue คำขอข้อมูลทั้งหมดใช้ vue-resource และไม่มีการใช้แบบฟอร์มดังนั้นจึงใช้การโต้ตอบข้อมูลโดย JSON พื้นหลังใช้การบูตฤดูใบไม้ผลิและการตรวจสอบการอนุญาต เนื่องจากใช้ความปลอดภัยในฤดูใบไม้ผลิมาก่อนพวกเขาประมวลผลหน้าและครั้งนี้พวกเขาเพียงแค่ประมวลผลคำขอ AJAX ดังนั้นพวกเขาจึงบันทึกปัญหาบางอย่างที่พบ วิธีแก้ปัญหาที่นี่ไม่เพียง แต่เหมาะสำหรับคำขอ AJAX เท่านั้น แต่ยังแก้ไขการตรวจสอบคำขอมือถือ
สร้างโครงการ
ก่อนอื่นเราต้องสร้างโครงการสปริงบูต เมื่อสร้างเราต้องแนะนำเว็บ, ความปลอดภัยในฤดูใบไม้ผลิ, MySQL และ MyBatis (กรอบฐานข้อมูลนั้นเป็นจริงโดยพลการฉันใช้ mybatis ที่นี่) หลังจากการสร้างไฟล์การพึ่งพามีดังนี้:
<การพึ่งพา> <roupId> org.mybatis.spring.boot </groupid> <ratifactid> mybatis-spring-boot-starter </artifactid> <cersion> 1.3.1 </เวอร์ชัน> </การพึ่งพา> <ArtIfactId> Spring-Boot-Starter-Security </ArtifactId> </การพึ่งพาอาศัย> <การพึ่งพาอาศัย> <GroupId> org.springframework.boot </groupId> <ArtIfactId> mysql-connector-java </artifactid> <scope> รันไทม์ </cope> </การพึ่งพาอาศัย> <การพึ่งพา> <sdeperency> <sdependency> <sdependency>
โปรดทราบว่าฉันมีการเพิ่มการพึ่งพา คอมมอนส์-โค้ด ครั้งสุดท้ายด้วยตนเอง นี่คือโครงการโอเพนซอร์สของ Apache ที่สามารถใช้ในการสร้าง MD5 Message Digests ฉันจะประมวลผลรหัสผ่านในข้อความต่อไปนี้
สร้างฐานข้อมูลและกำหนดค่า
เพื่อลดความซับซ้อนของตรรกะฉันได้สร้างสองตารางที่นี่คือตารางผู้ใช้ตารางบทบาทและตารางการเชื่อมโยงบทบาทผู้ใช้ดังนี้:
ต่อไปเราต้องสร้างการกำหนดค่าฐานข้อมูลของเราอย่างง่ายในแอปพลิเคชัน properties ที่นี่เราถูกกำหนดโดยสถานการณ์เฉพาะของคุณ
Spring.datasource.url = jdbc: mysql: ///vueblogspring.datasource.username=rootspring.datasource.password=123
สร้างคลาสเอนทิตี
ที่นี่ส่วนใหญ่หมายถึงการสร้างคลาสผู้ใช้ คลาสผู้ใช้ที่นี่ค่อนข้างพิเศษและต้องใช้อินเทอร์เฟซ UserDetails ดังนี้:
ผู้ใช้ระดับสาธารณะใช้ผู้ใช้งาน {ID ส่วนตัวส่วนตัว; ชื่อผู้ใช้สตริงส่วนตัว; รหัสผ่านสตริงส่วนตัว ชื่อเล่นสตริงส่วนตัว; เปิดใช้งานบูลีนส่วนตัว รายการส่วนตัว <loes> บทบาท; @Override บูลีนสาธารณะ isaccountnonexpired () {return true; } @Override บูลีนสาธารณะ isaccountnonlocked () {return true; } @Override บูลีนสาธารณะ isCredentialsentsNonexpired () {return true; } @Override บูลีนสาธารณะ isenabled () {เปิดใช้งาน return; } @Override รายการสาธารณะ <RentedAuthority> getAuthorities () {รายการ <RentEdAuthority> เจ้าหน้าที่ = arrayList ใหม่ <> (); สำหรับ (บทบาทบทบาท: บทบาท) {change.add (ใหม่ SimpleGrantedAuthority ("role_" + role.getName ())); } เจ้าหน้าที่ส่งคืน; } // getter/setter omit ... } หลังจากใช้อินเทอร์เฟซ UserDetails มีหลายวิธีในอินเทอร์เฟซนี้ที่เราต้องใช้ สี่วิธีที่ส่งคืนบูลีนล้วนเป็นที่รู้จักและเป็นที่รู้จัก เปิดใช้งานระบุว่าบัญชีประจำเดือนเปิดใช้งานหรือไม่ ฟิลด์นี้มีอยู่ในฐานข้อมูลของฉัน ดังนั้นตามผลลัพธ์การสืบค้นอื่น ๆ จะกลับมาโดยตรงสำหรับช่วงเวลาง่ายๆ วิธี getAuthorities ส่งคืนข้อมูลบทบาทของผู้ใช้ปัจจุบัน บทบาทของผู้ใช้คือข้อมูลในบทบาท ข้อมูลในบทบาทจะถูกแปลงเป็นรายการ <rentedauthority> จากนั้นส่งคืน มีจุดที่ควรทราบที่นี่ เนื่องจากชื่อบทบาทที่ฉันเก็บไว้ในฐานข้อมูลทั้งหมดเช่น 'Super Administrator', 'ผู้ใช้ทั่วไป' ฯลฯ และไม่เริ่มต้นด้วยตัวละครเช่น ROLE_ ดังนั้นคุณต้องเพิ่ม ROLE_ ที่นี่ด้วยตนเอง
นอกจากนี้ยังมีคลาสเอนทิตีบทบาทซึ่งค่อนข้างง่ายและสามารถสร้างได้ตามฟิลด์ของฐานข้อมูล ฉันจะไม่ทำซ้ำที่นี่
สร้าง Userservice
ผู้ใช้ที่นี่ก็ค่อนข้างพิเศษและจำเป็นต้องใช้อินเทอร์เฟซ UserDetailsService ดังต่อไปนี้:
@ServicePublic คลาส Userservice ใช้ UserDetailSservice {@AutoWired USERMAPPER USERMAPPER; @autowired Rolesmapper Rolesmapper; @Override Public UserDetails LoadUserByUserName (String S) พ่น USERNAMENOTFoundException {ผู้ใช้ผู้ใช้ = USERMAPPER.LoadUserByEnSerName (S); ถ้า (user == null) {// หลีกเลี่ยงการส่งคืนค่า null ที่นี่จะส่งคืนวัตถุผู้ใช้ที่ไม่มีค่าใด ๆ และการตรวจสอบจะล้มเหลวในกระบวนการเปรียบเทียบรหัสผ่านในภายหลังส่งคืนผู้ใช้ใหม่ (); } // สอบถามข้อมูลบทบาทของผู้ใช้และกลับไปที่ผู้ใช้ รายการ <role> roles = rolesmapper.getRolesByUid (user.getId ()); user.setRoles (บทบาท); ผู้ใช้ส่งคืน; -หลังจากใช้อินเทอร์เฟซ UserDetailsService แล้วเราจำเป็นต้องใช้วิธีการ loaduserByUserName ในอินเทอร์เฟซนั่นคือการสืบค้นผู้ใช้ตามชื่อผู้ใช้ Mappers สองตัวใน MyBatis ถูกฉีดที่นี่ USERMAPPER ใช้สำหรับการสืบค้นผู้ใช้และ RolesMapper ใช้ในการสืบค้นบทบาท ในเมธอด LoadUserByERSERNAME การค้นหาครั้งแรกผู้ใช้ตามพารามิเตอร์ที่ผ่าน (พารามิเตอร์คือชื่อผู้ใช้ที่ป้อนเมื่อผู้ใช้เข้าสู่ระบบ) หากผู้ใช้พบว่าเป็นโมฆะคุณสามารถโยนข้อยกเว้น UsernamenotFoundException โดยตรง อย่างไรก็ตามเพื่อความสะดวกในการประมวลผลฉันส่งคืนวัตถุผู้ใช้โดยไม่มีค่าใด ๆ ด้วยวิธีนี้ในระหว่างกระบวนการเปรียบเทียบรหัสผ่านที่ตามมาคุณจะพบว่าการเข้าสู่ระบบล้มเหลว (ที่นี่คุณสามารถปรับได้ตามความต้องการทางธุรกิจของคุณ) หากผู้ใช้พบว่าไม่เป็นโมฆะเราจะสอบถามบทบาทของผู้ใช้ตามรหัสผู้ใช้และใส่ผลลัพธ์การสืบค้นลงในวัตถุผู้ใช้ ผลลัพธ์การสืบค้นนี้จะถูกใช้ในวิธี getAuthorities ของวัตถุผู้ใช้
การกำหนดค่าความปลอดภัย
มาดูการกำหนดค่าความปลอดภัยของฉันก่อนแล้วฉันจะอธิบายทีละคน:
@ConfigurationPublic คลาส WebSecurityConfig ขยาย WebSecurityConfigurerAdapter {@AutoWired UserserVice Userservice; @Override Void Protected Configure (AuthenticationManagerBuilder Auth) พ่นข้อยกเว้น {auth.userDetailsService (ผู้ใช้งาน) .PasswordEncoder (รหัสผ่านใหม่ () {@Override String encode @Param Charsequence Plaintext* @param S Ciphertext* @return*/ @Override การแข่งขันบูลีนสาธารณะ (Charsequence Charsequence, String S) {return s.equals (Digestutils.md5digestashex (Charsequence.toString () } @Override Void Protected Configure (httpsecurity http) โยนข้อยกเว้น {http.authorizeRequests () .AntMatchers ("/admin/**"). hasrole ("super admin"). in.and (). formlogin (). loginPage ("/login_page"). SuccessHandler (การตรวจสอบใหม่ใหม่ ; }) .failureHandler (AuthenticationFailureHandler () {@Override โมฆะสาธารณะ onauthenticationFailure (httpservletRequest httpservletrequest, httpservletResponse httpservletResponse, AuthenticationException E) httpservletresponse.setContentType ("แอปพลิเคชัน/json; charset = utf-8"); }). LoginProcessingUrl ("/เข้าสู่ระบบ") .USERNAMEPARAMETER ("ชื่อผู้ใช้"). รหัสผ่านพารามิเตอร์ ("รหัสผ่าน"). permitAll () .and (). logout (). permitall () และ (). csrf (). ปิดการใช้งาน (); } @Override โมฆะสาธารณะกำหนดค่า (WebSecurity Web) โยนข้อยกเว้น {web.ignoring (). antmatchers ("/reg"); -นี่คือแกนหลักของการกำหนดค่าของเรา โปรดฟังฉัน:
1. ก่อนอื่นนี่คือคลาสการกำหนดค่าดังนั้นอย่าลืมเพิ่มคำอธิบายประกอบ @Configuration เนื่องจากนี่คือการกำหนดค่าของความปลอดภัยในฤดูใบไม้ผลิคุณต้องสืบทอด WebSecurityConfigurerAdapter
2. ฉีด Userservice ที่เพิ่งสร้างขึ้นและเราจะใช้มันในภายหลัง
3. การกำหนดค่า (AuthenticationManagerBuilder Auth) ใช้เพื่อกำหนดค่าวิธีการตรวจสอบความถูกต้องของเราและส่งผ่านผู้ใช้ในวิธี Auth.userDetailsService () เพื่อให้วิธีการโหลด LoadUserByUserName ใน Userservice จะถูกเรียกโดยอัตโนมัติเมื่อผู้ใช้เข้าสู่ระบบรหัสผ่าน จำเป็นต้องใช้รหัสผ่านแบบธรรมดาในการประมวลผลเมื่อเข้าสู่ระบบดังนั้นฉันจึงเพิ่มรหัสผ่านรหัสผ่านและหลังจากเพิ่มรหัสผ่านรหัสผ่านฉันสามารถใหม่ได้โดยตรงคลาสภายในที่ไม่ระบุชื่อของรหัสผ่านรหัสผ่าน มีสองวิธีในการใช้งานที่นี่และคุณสามารถรู้ความหมายของวิธีการโดยดูที่ชื่อ วิธีแรกเข้ารหัสเข้ารหัสอย่างเห็นได้ชัดเข้ารหัสข้อความธรรมดา ที่นี่ฉันใช้ MD5 Message Digest วิธีการดำเนินการเฉพาะนั้นจัดทำโดยการพึ่งพาคอมมอนส์-โด็ก วิธีที่สองที่ตรงกันคือการเปรียบเทียบรหัสผ่านพารามิเตอร์สองพารามิเตอร์พารามิเตอร์แรกคือรหัสผ่านแบบธรรมดาและที่สองคือ ciphertext ที่นี่คุณจะต้องเข้ารหัสข้อความธรรมดาและเปรียบเทียบกับ ciphertext (หากคุณสนใจสิ่งนี้คุณสามารถพิจารณาเพิ่มเกลือลงในรหัสผ่านได้)
4. การกำหนดค่า (httpsecurity http) ใช้ในการกำหนดค่ากฎการตรวจสอบความถูกต้องของเรา ฯลฯ วิธีการ AuthorizeRequests หมายความว่าการกำหนดค่ากฎการตรวจสอบสิทธิ์ถูกเปิดใช้งาน, antmatchers ("/admin/**"). hasrole ("ผู้ดูแลระบบซุปเปอร์") หมายถึงเส้นทางของ /admin/** ฉันเห็นบนอินเทอร์เน็ตว่าเพื่อนของฉันมีคำถามเกี่ยวกับว่าจะเพิ่ม ROLE_ ในวิธี Hasrole หรือไม่ อย่าเพิ่มที่นี่ หากคุณใช้วิธี Hasauthority คุณต้องเพิ่ม AnyRequest (). การรับรองความถูกต้อง () หมายความว่าเส้นทางอื่น ๆ ทั้งหมดจะต้องได้รับการรับรองความถูกต้อง/เข้าสู่ระบบก่อนเข้าถึง ต่อไปเรากำหนดค่าหน้าเข้าสู่ระบบเป็น LOGIN_PAGE เส้นทางการประมวลผลเข้าสู่ระบบคือ /เข้าสู่ระบบชื่อผู้ใช้เข้าสู่ระบบคือชื่อผู้ใช้และรหัสผ่านคือรหัสผ่าน เรากำหนดค่าพา ธ เหล่านี้เพื่อเข้าถึงโดยตรงและออกจากระบบเข้าสู่ระบบสามารถเข้าถึงได้โดยตรงและในที่สุดก็ปิด CSRF ใน SuccessHandler ให้ใช้การตอบกลับเพื่อส่งคืน JSON ที่เข้าสู่ระบบได้สำเร็จอย่าลืมใช้ค่าเริ่มต้น DEFAINTSuccessurl เป็นหน้าเว็บที่ถูกเปลี่ยนเส้นทางหลังจากเข้าสู่ระบบได้สำเร็จเท่านั้น เหตุผลเดียวกันนี้ยังใช้สำหรับ FailureHandler
5. ฉันได้กำหนดค่ากฎการกรองบางอย่างในวิธีการกำหนดค่า (Websecurity Web) ดังนั้นฉันจะไม่เข้าไปดูรายละเอียด
6. นอกจากนี้สำหรับไฟล์คงที่เช่น /images/** , /css/** และ /js/** ค่าเริ่มต้นจะไม่ถูกดักจับ
ผู้ควบคุม
สุดท้ายมาดูคอนโทรลเลอร์ของเราดังนี้:
@RestControllerPublic คลาส LoginRegController {/** * ถ้าคุณข้ามไปที่หน้านี้โดยอัตโนมัติหมายความว่าผู้ใช้ไม่ได้เข้าสู่ระบบและคุณสามารถส่งคืนพรอมต์ที่สอดคล้องกัน * <p> * หากคุณต้องการสนับสนุนการเข้าสู่ระบบแบบฟอร์ม Respbean LoginPage () {ส่งคืน Respbean ใหม่ ("ข้อผิดพลาด", "ยังไม่ได้เข้าสู่ระบบโปรดเข้าสู่ระบบ!"); - โดยรวมแล้วคอนโทรลเลอร์นี้ค่อนข้างง่าย Respbean ส่งคืน JSON ง่าย ๆ ซึ่งไม่ได้มีรายละเอียด สิ่งที่คุณต้องใส่ใจที่นี่คือ login_page หน้าเข้าสู่ระบบที่เรากำหนดค่าคือ login_page แต่ในความเป็นจริง login_page ไม่ใช่หน้า แต่เป็น JSON นี่เป็นเพราะเมื่อฉันเยี่ยมชมหน้าอื่น ๆ โดยไม่ต้องเข้าสู่ระบบความปลอดภัยของสปริงจะข้ามไปที่หน้า login_page โดยอัตโนมัติ อย่างไรก็ตามในคำขอ AJAX ไม่จำเป็นต้องกระโดดแบบนี้ ทั้งหมดที่ฉันต้องการคือพรอมต์ในการเข้าสู่ระบบหรือไม่ดังนั้นฉันสามารถคืน JSON ได้ที่นี่
ทดสอบ
ในที่สุดเพื่อน ๆ สามารถใช้เครื่องมือเช่นบุรุษไปรษณีย์หรือที่พักอาศัยเพื่อทดสอบการเข้าสู่ระบบและปัญหาการอนุญาตดังนั้นฉันจะไม่แสดงให้เห็น
ตกลงหลังจากการแนะนำข้างต้นฉันเชื่อว่าเพื่อน ๆ ได้เข้าใจการจัดการของ Spring Boot+Spring Security ของคำขอล็อกอิน AJAX แล้ว โอเคนั่นคือทั้งหมดสำหรับบทความนี้ หากคุณมีคำถามใด ๆ โปรดฝากข้อความเพื่อพูดคุย