ภาพรวม
อย่างที่เราทราบกันดีว่าการใช้ JWT สำหรับการตรวจสอบการอนุญาต เมื่อเทียบกับเซสชันข้อได้เปรียบของเซสชันคือเซสชันต้องใช้หน่วยความจำเซิร์ฟเวอร์จำนวนมากและเมื่อใช้เซิร์ฟเวอร์หลายตัวมันจะเกี่ยวข้องกับปัญหาเซสชันที่ใช้ร่วมกันซึ่งมีปัญหามากขึ้นเมื่อเข้าถึงเทอร์มินัลมือถือเช่นโทรศัพท์มือถือ
JWT ไม่จำเป็นต้องเก็บไว้บนเซิร์ฟเวอร์และไม่ได้ครอบครองทรัพยากรเซิร์ฟเวอร์ (นั่นคือไร้สัญชาติ) หลังจากผู้ใช้เข้าสู่ระบบเขาจะแนบโทเค็นเมื่อเข้าถึงคำขอที่ต้องได้รับอนุญาต (โดยปกติจะตั้งไว้ในส่วนหัวคำขอ HTTP) JWT ไม่มีปัญหาในการแบ่งปันเซิร์ฟเวอร์หลายเครื่องและไม่มีปัญหาการเข้าถึงมือถือบนโทรศัพท์มือถือ หากเพื่อปรับปรุงความปลอดภัยโทเค็นสามารถผูกพันกับที่อยู่ IP ของผู้ใช้
กระบวนการส่วนหน้า
ผู้ใช้เข้าสู่ระบบผ่าน Ajax เพื่อรับโทเค็น
หลังจากนั้นเมื่อเข้าถึงต้องได้รับอนุญาตให้แนบโทเค็นเพื่อเข้าถึง
<! doctype html> <html lang = "en"> <head> <meta charset = "utf-8"> <title> title </title> <script src = "http://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.min.min.min.js ฟังก์ชั่นล็อกอิน () {$ .post ("http: // localhost: 8080/auth/login", {ชื่อผู้ใช้: $ ("#username"). val (), รหัสผ่าน: $ ("#รหัสผ่าน") val ()}, ฟังก์ชั่น (data) {console.log (data); "Get", URL: "http: // localhost: 8080/userpage", beforesend: ฟังก์ชั่น (คำขอ) {request.setRequestheader ("การอนุญาต", ส่วนหัว); } </script> </head> <body> <fieldSet> <legend> โปรดเข้าสู่ระบบ </legend> <darble> ชื่อผู้ใช้ </label> <อินพุต type = "text" id = "ชื่อผู้ใช้"> <label> รหัสผ่าน </label> <อินพุต type = "text" id = "รหัสผ่าน"> onclick = "touserPageBTN ()"> เข้าถึง USERPAGE </button> </body> </html>กระบวนการแบ็กเอนด์ (Spring Boot + Spring Security + JJWT)
แนวคิด:
เขียนคลาสเอนทิตีของผู้ใช้และแทรกชิ้นส่วนข้อมูล
ผู้ใช้ (ผู้ใช้) คลาสเอนทิตี
@data @entitypublic class user {@id @GeneratedValue ID int ส่วนตัว; ชื่อสตริงส่วนตัว; รหัสผ่านสตริงส่วนตัว @Manytomany (cascade = {cascadetype.refresh}, fetch = fetchType.eager) @jointable (name = "user_role", Joincolumns = {@joincolumn (name = "uid", referencedColumnname = "id" "id")}) รายการส่วนตัว <role> บทบาท;} บทบาท (อนุญาต) ระดับเอนทิตี
@data @entitypublic class บทบาท {@id @GeneratedValue ID int ส่วนตัว; ชื่อสตริงส่วนตัว; @Manytomany (mappedby = "บทบาท") รายการส่วนตัว <ผู้ใช้> ผู้ใช้;} แทรกข้อมูล
ตารางผู้ใช้
| รหัสประจำตัว | ชื่อ | รหัสผ่าน |
|---|---|---|
| 1 | Linyuan | 123 |
ตารางบทบาท
| รหัสประจำตัว | ชื่อ |
|---|---|
| 1 | ผู้ใช้ |
ตาราง user_role
| uid | กำจัด |
|---|---|
| 1 | 1 |
อินเทอร์เฟซเลเยอร์ DAO รับข้อมูลผ่านชื่อผู้ใช้และส่งคืนวัตถุเสริมด้วยค่า Java8
อินเทอร์เฟซสาธารณะ userRepository ขยายที่เก็บ <ผู้ใช้จำนวนเต็ม> {ตัวเลือก <user> findByName (ชื่อสตริง);} เขียน logindto สำหรับการถ่ายโอนข้อมูลด้วยส่วนหน้า
@Datapublic คลาส LogIndto ใช้ serializable {@notblank (message = "ชื่อผู้ใช้ไม่สามารถว่างเปล่า") ชื่อผู้ใช้ส่วนตัว @NotBlank (message = "รหัสผ่านไม่สามารถว่าง") รหัสผ่านสตริงส่วนตัว;} เขียนเครื่องมือสร้างโทเค็นและสร้างโดยใช้ไลบรารี JJWT มีสามวิธีทั้งหมด: สร้างโทเค็น (ส่งคืนสตริง) แยกวิเคราะห์โทเค็น (ส่งคืนวัตถุการตรวจสอบสิทธิ์การตรวจสอบสิทธิ์) และตรวจสอบโทเค็น (ส่งคืนค่าบูลีน)
@ComponentPublic คลาส Jwttokenutils {logger สุดท้าย logger สุดท้าย = loggerFactory.getLogger (jwttokenuts.class); ผู้ให้บริการสตริงสุดท้ายคงที่ส่วนตัว _key = "auth"; สตริงส่วนตัว SecretKey; // การลงนามคีย์ส่วนตัวยาว tokenvalidityinmilliseconds; // วันหมดอายุวันหมดเวลา TOKENVALITYININMILLISECONDSFORREMEBMBERME; // (จำฉัน) วันหมดอายุ @PostConstruct โมฆะสาธารณะ init () {this.secretkey = "linyuanmima"; int secondIn1day = 1,000 * 60 * 60 * 24; this.TokenValidityInMilliseconds = SecondIn1day * 2l; this.tokenvalidityinmillisecondsforrememberme = SecondIn1day * 7l; } ช่วงเวลาคงที่ครั้งสุดท้ายคงที่ครั้งสุดท้าย = 432_000_000; // สร้างโทเค็นสาธารณะสตริง createToken (การรับรองความถูกต้องของการรับรองความถูกต้อง, บูลีนจำได้ว่า) {String chartion = Authentication.getAuthorities (). สตรีม () // รับสตริงการอนุญาตของผู้ใช้เช่นผู้ใช้, ผู้ดูแลระบบ. Long Now = (วันที่ใหม่ ()). getTime (); // รับความถูกต้องของวันที่ Timestamp ปัจจุบัน; // เวลาหมดอายุการจัดเก็บข้อมูลถ้า (จำ me) {ความถูกต้อง = วันที่ใหม่ (ตอนนี้ + this.tokenvalidityinmilliseconds); } else {ความถูกต้อง = วันที่ใหม่ (ตอนนี้ + this.tokenValidityInmillisecondSforRememberMe); } return jwts.builder () // สร้างโทเค็นโทเค็น Token.setSubject (Authentication.getName ()) // ตั้งค่าผู้ใช้ uriented.claim (challe chey, เจ้าหน้าที่) // เพิ่มแอตทริบิวต์การอนุญาต } // รับสิทธิ์ของผู้ใช้การตรวจสอบความถูกต้องสาธารณะ getAuthentication (โทเค็นสตริง) {system.out.println ("โทเค็น:"+โทเค็น); การเรียกร้องการเรียกร้อง = jwts.parser () // parse token payload. setsigningkey (secretkey) .parseclaimsjws (โทเค็น) .getbody (); คอลเลกชัน <? ขยาย GrantedAuthority> เจ้าหน้าที่ = arrays.stream (cames.get (chaleities_key) .tostring (). แยก (",")) // รับสตริงการอนุญาตผู้ใช้ (SimpleGrantedauthority :: ใหม่) .Collect (collector.tolist () // แปลงองค์ประกอบเป็นเงินต้นผู้ใช้ InfertedAuthority Collection ผู้ใช้ = ผู้ใช้ใหม่ (cames.getSubject (), "", เจ้าหน้าที่); ส่งคืนผู้ใช้งานใหม่คำว่าคำสั่งการเทน (อาจารย์ใหญ่, "", เจ้าหน้าที่); } // ตรวจสอบว่าโทเค็นเป็นบูลีนสาธารณะที่ถูกต้อง ValidateToken (โทเค็นสตริง) {ลอง {jwts.parser (). setSigningKey (SecretKey) .parseclaimsjws (โทเค็น); // ตรวจสอบโทเค็นโดยการส่งคืนคีย์จริง; } catch (signatureexception e) {// signature exception log.info ("Signature JWT ไม่ถูกต้อง"); log.trace ("การติดตามลายเซ็น JWT ไม่ถูกต้อง: {}", e); } catch (malformedjwtexception e) {// jwt ข้อผิดพลาดรูปแบบ log.info ("โทเค็น JWT ไม่ถูกต้อง"); log.trace ("token token token ไม่ถูกต้อง: {}", e); } catch (expiredjwtexception e) {// jwt หมดอายุ log.info ("Token JWT หมดอายุ"); log.trace ("การติดตามโทเค็น JWT หมดอายุ: {}", e); } catch (unsupportedjwtexception e) {// jwt log.info ("โทเค็น JWT ที่ไม่ได้รับการสนับสนุน"); log.trace ("การติดตามโทเค็น JWT ที่ไม่ได้รับการสนับสนุน: {}", e); } catch (unlegalArgumentException e) {// พารามิเตอร์ข้อผิดพลาดข้อยกเว้น log.info ("JWT โทเค็นขนาดกะทัดรัดของตัวจัดการไม่ถูกต้อง"); log.trace ("JWT โทเค็นขนาดกะทัดรัดของตัวจัดการเป็นร่องรอยที่ไม่ถูกต้อง: {}", e); } return false; - ใช้อินเทอร์เฟซ UserDetails ซึ่งเป็นตัวแทนของคลาสเอนทิตีของผู้ใช้ถูกห่อไว้ในวัตถุผู้ใช้ของเรามีสิทธิ์และคุณสมบัติอื่น ๆ และสามารถใช้งานได้โดย Spring Security
MyUserDetails ระดับสาธารณะดำเนินการใช้งานผู้ใช้ {ผู้ใช้ส่วนตัว public myuserDetails (ผู้ใช้ผู้ใช้) {this.user = ผู้ใช้; } @Override Collection Public <? ขยาย GrantedAuthority> getAuthorities () {รายการ <role> roles = user.getRoles (); รายการ <RantedAuthority> เจ้าหน้าที่ = new ArrayList <> (); StringBuilder sb = new StringBuilder (); if (roles.size ()> = 1) {สำหรับ (บทบาท: บทบาท: บทบาท) {change.add (ใหม่ simplegrantedauthority (role.getName ())); } เจ้าหน้าที่ส่งคืน; } เจ้าหน้าที่ส่งคืน; } return AuthorityTils.CommaseparatedStringToAuthorityList (""); } @Override สตริงสาธารณะ getPassword () {return user.getPassword (); } @Override สตริงสาธารณะ getUserName () {return user.getName (); } @Override บูลีนสาธารณะ isaccountNonexpired () {return true; } @Override บูลีนสาธารณะ isaccountnonlocked () {return true; } @Override บูลีนสาธารณะ isCredentialsentsNonexpired () {return true; } @Override บูลีนสาธารณะ isenabled () {return true; - ใช้อินเทอร์เฟซ UserDetailsService ซึ่งมีเพียงวิธีเดียวเท่านั้นที่จะได้รับผู้ใช้ เราสามารถรับวัตถุผู้ใช้จากฐานข้อมูลจากนั้นห่อลงในผู้ใช้และส่งคืน
@ServicePublic Class MyUserDetailsService ใช้ UserDetailsService {@autowired UserRepository UserRepository; @Override Public UserDetails LoadUserByUserName (String S) พ่น USERNAMENOTFoundException {// โหลดวัตถุผู้ใช้จากฐานข้อมูลตัวเลือก <user> user = userRepository.findByName (S); // สำหรับการดีบักหากมีค่าอยู่ชื่อผู้ใช้และรหัสผ่านจะถูกส่งออก ifpresent ((ค่า)-> system.out.println ("ชื่อผู้ใช้:"+value.getName ()+"รหัสผ่านผู้ใช้:"+value.getPassword ())); // หากไม่มีค่าอีกต่อไปให้ส่งคืน null return ใหม่ myuserDetails (user.orelse (null)); - เขียนตัวกรอง หากผู้ใช้ดำเนินการโทเค็นเขาจะได้รับโทเค็นและสร้างวัตถุการตรวจสอบความถูกต้องของการรับรองความถูกต้องตามโทเค็นและเก็บไว้ใน SecurityContext เพื่อควบคุมการอนุญาตโดย Spring Security
ระดับสาธารณะ JWTAUTHENTICATIONTOKENFILTER ขยาย GENINICFILTERBEAN {LOGGER สุดท้าย logger สุดท้าย = loggerFactory.getLogger (jwtauthenticationTokenFilter.class); @autowired ส่วนตัว jwttokenutils tokenprovider; @Override โมฆะสาธารณะ Dofilter (ServletRequest ServletRequest, ServletResponse ServletResponse, FilterChain FilterChain) พ่น IOException, Servletexception {System.out.println ("JWTauthenticationTokenFilter"); ลอง {httpservletRequest httpreq = (httpservletrequest) servletrequest; String jwt = ResolVetoken (httpreq); if (stringutils.hastext (jwt) && this.tokenprovider.validatetoken (jwt)) {// ตรวจสอบว่า JWT นั้นถูกต้องการรับรองความถูกต้องที่ถูกต้อง = this.tokenprovider.getauthentication (JWT); // รับข้อมูลการรับรองความถูกต้องของผู้ใช้ SecurityContextholder.getContext (). setauthentication (การตรวจสอบ); // บันทึกผู้ใช้ไปยัง SecurityContext} filterChain.dofilter (ServletRequest, ServletResponse); } catch (expiredjwtexception e) {// jwt ไม่ถูกต้อง log.info ("ข้อยกเว้นความปลอดภัยสำหรับผู้ใช้ {} - {}", e.getClaims (). getSubject (), e.getMessage ()); log.trace ("ร่องรอยข้อยกเว้นความปลอดภัย: {}", e); ((httpservletResponse) servletResponse) .SetStatus (httpservletResponse.sc_unauthorized); }} สตริงส่วนตัว RELVETOKEN (คำขอ HTTPSERVLETREQUEST) {String BearerToken = request.getheader (websecurityConfig.authorization_header); // รับโทเค็นจากส่วนหัว http ถ้า (stringutils.hastext (bearertoken) && bearertoken.startswith ("ผู้ถือ")) {return bearertoken.substring (7, Bearertoken.length ()); // ส่งคืนสตริงโทเค็นและลบ String} String jwt = request.getParameter (websecurityConfig.authorization_token); // รับโทเค็นจากพารามิเตอร์คำขอถ้า (stringUtils.hastext (jwt)) {return jwt; } return null; - เขียน LoginController ผู้ใช้เข้าถึง /รับรองความถูกต้อง /เข้าสู่ระบบผ่านชื่อผู้ใช้และรหัสผ่านรับผ่านวัตถุ LogIndto และสร้างวัตถุการตรวจสอบสิทธิ์ รหัสนี้เป็น UserNamePasswordaThenticationToken เพื่อพิจารณาว่าวัตถุนั้นมีอยู่หรือไม่ ตรวจสอบวัตถุการรับรองความถูกต้องผ่านวิธีการรับรองความถูกต้องของ AuthenticationManager AuthenticationManager คลาสการใช้งานผู้ให้บริการจะตรวจสอบผ่าน AuthenticationProvider (การประมวลผลการรับรองความถูกต้อง) ผู้ให้บริการเริ่มต้นเรียก daoauthenticationprovider สำหรับการประมวลผลการตรวจสอบความถูกต้อง DaoauthenticationProvider จะได้รับผู้ใช้งานผ่าน USERDETAILSSERVICE (แหล่งข้อมูลการรับรองความถูกต้อง) หากการรับรองความถูกต้องประสบความสำเร็จ
@RestControllerPublic คลาส LoginController {@AutoWired UserRepository UserRepository; @autowired AuthenticationManager AuthenticationManager; @autowired ส่วนตัว jwttokenutils jwttokenutils; @RequestMapping (value = "/auth/login", method = requestMethod.post) การเข้าสู่ระบบสตริงสาธารณะ (@valid logindto logindto, httpservletResponse httpresponse) โยนข้อยกเว้น {// สร้างวัตถุการรับรองความถูกต้องของการตรวจสอบความถูกต้อง ใหม่ UserNamePasswordAtHenticationToken (logindto.getUserName (), logindto.getPassword ()); // ถ้าวัตถุการรับรองความถูกต้องไม่ว่างถ้า (objects.nonnull (AuthenticationToken)) {userrepository.findbyName (AuthenticationToken.getPrincipal (). toString ()) .orelsethrow (()-> ข้อยกเว้นใหม่ ("ผู้ใช้ไม่มี")); } ลอง {// ตรวจสอบวัตถุการรับรองความถูกต้องผ่าน AuthenticationManager (เริ่มต้นใช้งานเป็น ProviderManager) Authentication = AuthenticationManager.authenticate (AuthenticationToken); // bind การรับรองความถูกต้องกับ SecurityContext SecurityContextholder.getContext (). setauthentication (การตรวจสอบ); // สร้างโทเค็นโทเค็นโทเค็น = jwttokenUtils.createToken (การรับรองความถูกต้อง, เท็จ); // เขียนโทเค็นลงในส่วนหัว http httpresponse.addheader (websecurityconfig.authorization_header, "ผู้ถือ"+โทเค็น); กลับ "ผู้ถือ"+โทเค็น; } catch (badcredentialsexception uthentication) {โยนข้อยกเว้นใหม่ ("ข้อผิดพลาดรหัสผ่าน"); - เขียนคลาสการกำหนดค่าความปลอดภัยสืบทอด WebSecurityConfigurerAdapter และแทนที่วิธีการกำหนดค่า
@configuration@enableWebsecurity@enableGlobalMethodSecurity (prepOntenabled = TRUE) คลาสสาธารณะ WebSecurityConfig ขยาย WebSecurityConfigurerAdapter {Public String Final Authorization_header = "การอนุญาต"; การอนุญาตสตริงสุดท้ายของสาธารณะคงที่ _token = "access_token"; @AutoWired Private UserDetailsService UserDetailsService; @Override Void Proftection Configure (AuthenticationManagerBuilder Auth) พ่นข้อยกเว้น {auth // ปรับแต่งเพื่อรับข้อมูลผู้ใช้ USERDETAILSSERVICE (USERDETAILSSERVICE) // ตั้งรหัสผ่านการเข้ารหัสรหัสผ่าน } @Override Void Protected Configure (httpsecurity http) โยนข้อยกเว้น {// กำหนดค่านโยบายการเข้าถึงนโยบาย http // ปิด csrf และ cors .cors (). disable () .csrf () disable () // secession ไม่จำเป็น .and () // ตรวจสอบ http request.authorizeRequests () // อนุญาตให้ผู้ใช้ทุกคนเข้าถึงหน้าแรกและเข้าสู่ระบบ IntMatchers ("/", "/auth/login"). permitall () // คำขออื่น ๆ ทั้งหมด) .and () // set logout (). permitall (); // เพิ่มตัวกรอง JWT ที่ http .addfilterbefore (genericfilterbean (), usernamepasswordauthenticationfilter.class); } @Bean รหัสผ่านสาธารณะรหัสผ่านรหัสผ่าน () {ส่งคืน bcryptPasswordEncoder ใหม่ (); } @Bean Public GenericFilterBean GenericFilterBean GenericFilterBean () {ส่งคืน JWTAUthenticationTokenFilter ใหม่ (); - เขียนคอนโทรลเลอร์สำหรับการทดสอบ
@RestControllerPublic คลาส userController {@postmapping ("/login") การเข้าสู่ระบบสตริงสาธารณะ () {return "เข้าสู่ระบบ"; } @getMapping ("/") ดัชนีสตริงสาธารณะ () {return "hello"; } @getMapping ("/userpage") สตริงสาธารณะ httpapi () {system.out.println (SecurityContextholder.getContext (). getAuthentication (). getPrincipal ()); กลับ "UserPage"; } @getMapping ("/adminPage") สตริงสาธารณะ httpsuite () {return "userpage"; -ดาวน์โหลดซอร์สโค้ดกรณี (ดาวน์โหลดในพื้นที่)
สรุป
ข้างต้นเป็นเนื้อหาทั้งหมดของบทความนี้ ฉันหวังว่าเนื้อหาของบทความนี้จะมีค่าอ้างอิงบางอย่างสำหรับการศึกษาหรือที่ทำงานของทุกคน หากคุณมีคำถามใด ๆ คุณสามารถฝากข้อความไว้เพื่อสื่อสาร ขอบคุณสำหรับการสนับสนุน Wulin.com