日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

Spring Security框架

發布時間:2024/3/24 javascript 64 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Spring Security框架 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

Spring Security框架

關于用戶身份認證與授權

Spring Security是用于解決認證與授權的框架。

添加依賴

<!-- Spring Boot Security:處理認證與授權 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency>

啟動項目,在啟動的日志中,可以看到類似以下內容:

Using generated security password: 2abb9119-b5bb-4de9-8584-9f893e4a5a92

Spring Security有默認登錄的賬號和密碼(以上提示的值),密碼是隨機的,每次啟動項目都會不同。

Spring Security默認要求所有的請求都是必須先登錄才允許的訪問,可以使用默認的用戶名user和自動生成的隨機密碼來登錄。在測試登錄時,在瀏覽器訪問當前主機的任意網址都可以(包括不存在的資源),會自動跳轉到登錄頁(是由Spring Security提供的,默認的URL是:http://localhost:8080/login),當登錄成功后,會自動跳轉到此前訪問的URL(跳轉登錄頁之前的URL),另外,還可以通過 http://localhost:8080/logout 退出登錄。

Spring Security的依賴項中包括了Bcrypt算法的工具類,Bcrypt是一款非常優秀的密碼加密工具,適用于對需要存儲下來的密碼進行加密處理。

測試例 :

public class BcryptPasswordEncoderTests {private BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();@Testpublic void testEncode() {// 原文相同的情況,每次加密得到的密文都不同for (int i = 0; i < 10; i++) {String rawPassword = "123456";String encodedPassword = passwordEncoder.encode(rawPassword);System.out.println("rawPassword = " + rawPassword);System.out.println("encodedPassword = " + encodedPassword);}// rawPassword = 123456// encodedPassword = $2a$10$HWuJ9WgPazrwg9.isaae4u7XdP7ohH7LetDwdlTWuPC4ZAvG.Uc7W}@Testpublic void testMatches() {String rawPassword = "123456";String encodedPassword = "$2a$10$hI4wweFOGJ7FMduSmCjNBexbKFOjYMWl8hkug0n0k1LNR5vEyhhMW";boolean matchResult = passwordEncoder.matches(rawPassword, encodedPassword);System.out.println("match result : " + matchResult);}}

Spring Security的認證機制中包含:當客戶端提交登錄后,會自動調用UserDetailsService接口(Spring Security定義的)的實現類對象中的UserDetails loadUserByUsername(String username)方法(根據用戶名加載用戶數據),將得到UserDetails類型的對象,此對象中應該至少包括此用戶名對應的密碼、權限等信息,接下來,Spring Security會自動完成密碼的對比,并確定此次客戶端提交的信息是否允許登錄!類似于:

// Spring Security的行為 UserDetails userDetails = userDetailsService.loadUserByUsername("chengheng"); // Spring Security將從userDetails中獲取密碼,用于驗證客戶端提交的密碼,判斷是否匹配

所以,要實現Spring Security通過數據庫的數據來驗證用戶名與密碼(而不是采用默認的user用戶名和隨機的密碼),則在passport包下創建security.UserDetailsServiceImpl類,實現UserDetailsService接口,并重寫接口中的抽象方法:

@Service public class UserDetailsServiceImpl implements UserDetailsService {@Autowiredprivate AdminMapper adminMapper;@Overridepublic UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {System.out.println("根據用戶名查詢嘗試登錄的管理員信息,用戶名=" + s);/*通過用戶名從緩存中獲取用戶信息*/AdminLoginVO admin = adminMapper.getLoginInfoByUsername(s);System.out.println("通過持久層進行查詢,結果=" + admin);if (admin == null) {System.out.println("根據用戶名沒有查詢到有效的管理員數據,將拋出異常");throw new BadCredentialsException("登錄失敗,用戶名不存在!");}System.out.println("查詢到匹配的管理員數據,需要將此數據轉換為UserDetails并返回");UserDetails userDetails = User.builder().username(admin.getUsername()).password(admin.getPassword())/* accountExpired 賬號是否過期 */.accountExpired(false)/* accountLocked 賬號是否鎖定 */.accountLocked(false)/* disabled 賬號是否禁用 */.disabled(admin.getIsEnable() != 1)/* credentialsExpired 證書是否過期 */.credentialsExpired(false)/* authorities 用戶權限 */.authorities(admin.getPermissions().toArray(new String[] {})).build();System.out.println("轉換得到UserDetails=" + userDetails);return userDetails;}}

完成后,再配置密碼加密器即可:

@Configuration public class SecurityConfiguration {@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}}

重啟項目,可以發現在啟動過程中不再生成隨機的密碼值,在瀏覽器上訪問此項目的任何URL,進入登錄頁,即可使用數據庫中的管理員數據進行登錄。

在Spring Security,默認使用Session機制存儲成功登錄的用戶信息(因為HTTP協議是無狀態協議,并不保存客戶端的任何信息,所以,同一個客戶端的多次訪問,對于服務器而言,等效于多個不同的客戶端各訪問一次,為了保存用戶信息,使得服務器端能夠識別客戶端的身份,必須采取某種機制),當下,更推薦使用Token或相關技術(例如JWT)來解決識別用戶身份的問題。

JWT

JWT = JSON Web Token,它是通過JSON格式組織必要的數據,將數據記錄在票據(Token)上,并且,結合一定的算法,使得這些數據會被加密,然后在網絡上傳輸,服務器端收到此數據后,會先對此數據進行解密,從而得到票據上記錄的數據(JSON數據),從而識別用戶的身份,或者處理相關的數據。

其實,在客戶端第1次訪問服務器端時,是“空著手”訪問的,不會攜帶任何票據數據,當服務器進行響應時,會將JWT響應到客戶端,客戶端從第2次訪問開始,每次都應該攜帶JWT發起請求,則服務器都會收到請求中的JWT并進行處理。

要使用JWT,需要添加相關的依賴項,可以實現生成JWT、解析JWT的框架較多,目前,主流的JWT框架可以是jjwt:

JWT的組成部分:Header(頭),Payload(載荷),Signature(簽名)

<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt --> <dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version> </dependency>

則在根項目中管理以上依賴,并在csmall-passport中添加以上依賴。

測試使用JWT:

public class JwtTests {// 密鑰String secretKey = "fgfdsfadsfadsafdsafdsfadsfadsfdsafdasfdsafdsafdsafds4rttrefds";@Testpublic void testGenerateJwt() {// ClaimsMap<String, Object> claims = new HashMap<>();claims.put("id", 9527);claims.put("name", "家丁");// JWT的組成部分:Header(頭),Payload(載荷),Signature(簽名)String jwt = Jwts.builder()// Header:指定算法與當前數據類型// 格式為: { "alg": 算法, "typ": "jwt" }.setHeaderParam(Header.CONTENT_TYPE, "HS256").setHeaderParam(Header.TYPE, Header.JWT_TYPE)// Payload:通常包含Claims(自定義數據)和過期時間.setClaims(claims).setExpiration(new Date(System.currentTimeMillis() + 5 * 60 * 1000))// Signature:由算法和密鑰(secret key)這2部分組成.signWith(SignatureAlgorithm.HS256, secretKey)// 打包生成.compact();// eyJjdHkiOiJIUzI1NiIsInR5cCI6IkpXVCIsImFsZyI6IkhTMjU2In0.eyJuYW1lIjoi5pif5pifIiwiaWQiOjk1MjcsImV4cCI6MTY1NTM2NzMwMn0.qBBHearv8iHPNjtDGtO2ci_-KAL4CALHnwzaG_ljsQgSystem.out.println(jwt);}@Testpublic void testParseJwt() {String jwt = "eyJjdHkiOiJIUzI1NiIsInR5cCI6IkpXVCIsImFsZyI6IkhTMjU2In0.eyJuYW1lIjoi5pif5pifIiwiaWQiOjk1MjcsImV4cCI6MTY1NTM2NzMwMn0.qBBHearv8iHPNjtDGtO2ci_-KAL4CALHnwzaG_ljsQg";Claims claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwt).getBody();Object id = claims.get("id");Object name = claims.get("name");System.out.println("id=" + id);System.out.println("name=" + name);}}

當JWT數據過期時,異常信息例如:

io.jsonwebtoken.ExpiredJwtException: JWT expired at 2022-06-16T15:47:57Z. Current time: 2022-06-16T16:08:32Z, a difference of 1235869 milliseconds. Allowed clock skew: 0 milliseconds.

當JWT解析失敗(數據有誤)時,異常信息例如:

io.jsonwebtoken.MalformedJwtException: Unable to read JSON value: {"cty""HS256","typ":"JWT","alg":"HS256"}

當生成JWT和解析JWT的密鑰不一致時,異常信息例如:

io.jsonwebtoken.SignatureException: JWT signature does not match locally computed signature. JWT validity cannot be asserted and should not be trusted.

要在Spring Security中使用JWT,至少需要:

  • 不能讓Spring Security按照原有模式來處理登錄(原有模式中,登錄成功后,自動裝用戶信息存儲到Session中,且跳轉頁面),需要

    • 需要自動裝配AuthenticationManager對象

      • 使得SecurityConfiguration配置類繼承自WebSecurityConfigurerAdapter類,重寫其中的xx方法,在此方法中直接調用父級方法即可,并在此方法上添加@Bean注解
    • 創建AdminLoginDTO類,此類中應該包含用戶登錄時需要提交的用戶名、密碼

    • 創建IAdminService接口

    • 在IAdminService接口中添加登錄的抽象方法

      String login(AdminLoginDTO adminLoginDTO);
    • 創建AdminServiceImpl類,實現以上接口

      • 在實現過程中,調用AuthenticationManager實現認證,當認證成功后,生成JWT并返回
    • 創建AdminController類,在類中處理登錄請求

    • 在SecurityConfiguration中配置Spring Security,對特定的請求進行放行(默認所有請求都必須先登錄)

配置代碼

@Configuration public class SecurityConfiguration extends WebSecurityConfigurerAdapter {@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}@Bean@Overridepublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}@Overrideprotected void configure(HttpSecurity http) throws Exception {// 禁用防跨域攻擊http.csrf().disable();// URL白名單String[] urls = {"/admins/login"};// 配置各請求路徑的認證與授權http.authorizeRequests() // 請求需要授權才可以訪問.antMatchers(urls) // 匹配一些路徑.permitAll() // 允許直接訪問(不需要經過認證和授權).anyRequest() // 匹配除了以上配置的其它請求.authenticated(); // 都需要認證} } @Data public class AdminLoginDTO implements Serializable {private String username;private String password; } public interface IAdminService {String login(AdminLoginDTO adminLoginDTO);} @Service public class AdminServiceImpl implements IAdminService {@Autowiredprivate AuthenticationManager authenticationManager;@Overridepublic String login(AdminLoginDTO adminLoginDTO) {// 準備被認證數據Authentication authentication= new UsernamePasswordAuthenticationToken(adminLoginDTO.getUsername(), adminLoginDTO.getPassword());// 調用AuthenticationManager驗證用戶名與密碼// 執行認證,如果此過程沒有拋出異常,則表示認證通過,如果認證信息有誤,將拋出異常authenticationManager.authenticate(authentication);// 如果程序可以執行到此處,則表示登錄成功// 生成此用戶數據的JWTString jwt = "This is a JWT."; // 臨時return jwt;} } @RestController @RequestMapping(value = "/admins", produces = "application/json; charset=utf-8") public class AdminController {@Autowiredprivate IAdminService adminService;// http://localhost:8080/admins/login?username=root&password=123456@RequestMapping("/login")public String login(AdminLoginDTO adminLoginDTO) {String jwt = adminService.login(adminLoginDTO);return jwt;} }

測試

完成以上配置后,可以通過 http://localhost:8080/admins/login?username=root&password=123456 這類URL測試登錄,使用數據庫中的用戶名和密碼進行嘗試。

當通過以上URL進行訪問時,其內部過程大概是:

  • Spring Security的相關配置會進行URL的檢查,來判斷是否允許訪問此路徑
    • 所以,需要在SecurityConfiguration中將以上路徑設置為白名單
    • 如果沒有將以上路徑配置到白名單,將直接跳轉到登錄頁,因為默認所有請求都必須先登錄
  • 由AdminController接收到請求后,調用了IAdminService接口的實現類對象來處理登錄
    • IAdminService接口的實現是AdminServiceImpl
  • 在AdminServiceImpl中,調用了AuthenticationManager處理登錄的認證
    • AuthenticationManager對象調用authenticate()方法進行登錄處理
      • 內部實現中,會自動調用UserDetailsService實現對象的loadUserByUsername()方法以獲取用戶信息,并自動完成后續的認證處理(例如驗證密碼是否正確),所以,在步驟中,具體執行的是UserDetailsServiceImpl類中重寫的方法,此方法返回了用戶信息,Spring Security自動驗證,如果失敗(例如賬號已禁用、密碼錯誤等),會拋出異常
    • 以上調用的authenticate()方法如果未拋出異常,可視為認證成功,即登錄成功
    • 當登錄成功時,應該返回此用戶的JWT數據

Spring Security + JWT

此前,在處理登錄的業務中,當視為登錄成功時,返回的字符串并不是JWT數據,則應該將此數據改為必要的JWT數據。

@Service public class AdminServiceImpl implements IAdminService {// ===== 原有其它代碼 =====/*** JWT數據的密鑰*/private String secretKey = "fgfdsfadsfadsafdsafdsfadsfadsfdsafdasfdsafdsafdsafds4rttrefds";@Overridepublic String login(AdminLoginDTO adminLoginDTO) {// ===== 原有其它代碼 =====// 如果程序可以執行到此處,則表示登錄成功// 生成此用戶數據的JWT// ClaimsUser user = (User) authenticate.getPrincipal();System.out.println("從認證結果中獲取Principal=" + user.getClass().getName());Map<String, Object> claims = new HashMap<>();claims.put("username", user.getUsername());claims.put("permissions", user.getAuthorities());System.out.println("即將向JWT中寫入數據=" + claims);// JWT的組成部分:Header(頭),Payload(載荷),Signature(簽名)String jwt = Jwts.builder()// Header:指定算法與當前數據類型// 格式為: { "alg": 算法, "typ": "jwt" }.setHeaderParam(Header.CONTENT_TYPE, "HS256").setHeaderParam(Header.TYPE, Header.JWT_TYPE)// Payload:通常包含Claims(自定義數據)和過期時間.setClaims(claims).setExpiration(new Date(System.currentTimeMillis() + 5 * 60 * 1000))// Signature:由算法和密鑰(secret key)這2部分組成.signWith(SignatureAlgorithm.HS256, secretKey)// 打包生成.compact();// 返回JWT數據return jwt;}}

在控制器中,應該響應JSON格式的數據,所以,需要添加依賴包含JsonResult類的依賴。將控制器中處理請求的方法的返回值類型改為JsonResult<String>,并調整返回值:

// http://localhost:8080/admins/login?username=root&password=123456 @RequestMapping("/login") public JsonResult<String> login(AdminLoginDTO adminLoginDTO) {String jwt = adminService.login(adminLoginDTO);return JsonResult.ok(jwt); }

此時,重啟項目,在瀏覽器中,使用正確的用戶名和密碼訪問,響應的結果例如:

{"state":20000,"message":null,"data":"eyJjdHkiOiJIUzI1NiIsInR5cCI6IkpXVCIsImFsZyI6IkhTMjU2In0.eyJwZXJtaXNzaW9ucyI6W3siYXV0aG9yaXR5IjoiL2Ftcy9hZG1pbi9kZWxldGUifSx7ImF1dGhvcml0eSI6Ii9hbXMvYWRtaW4vcmVhZCJ9LHsiYXV0aG9yaXR5IjoiL2Ftcy9hZG1pbi91cGRhdGUifSx7ImF1dGhvcml0eSI6Ii9wbXMvcHJvZHVjdC9kZWxldGUifSx7ImF1dGhvcml0eSI6Ii9wbXMvcHJvZHVjdC9yZWFkIn0seyJhdXRob3JpdHkiOiIvcG1zL3Byb2R1Y3QvdXBkYXRlIn1dLCJleHAiOjE2NTU0MzQwMzcsInVzZXJuYW1lIjoicm9vdCJ9.8ZIfpxxjJlwNo-E3JhXwH4sZR0J5-FU-HAOMu1Tg-44" }

注意:以上只是訪問/admins/login時會執行所編寫的流程(發送用戶名和密碼,得到含JWT的結果),并不代表真正意義的實現了“登錄”!

登錄的流程應該是

客戶端提交用戶名和密碼到服務器端 >>> 服務器端認證成功后響應JWT >>> 客戶端在后續的請求中都攜帶JWT >>> 服務器端驗證JWT來決定是否允許訪問。

為了便于體現“客戶端在后續的請求中都攜帶JWT”的操作,可以在項目中添加使用Knife4j。

當使用Knife4j時,需要在白名單中添加相關的放行資源路徑,否則,Knife4j的頁面將無法使用:

@Configuration public class SecurityConfiguration extends WebSecurityConfigurerAdapter {// ===== 原有其它代碼 =====@Overrideprotected void configure(HttpSecurity http) throws Exception {// ===== 原有其它代碼 =====// URL白名單String[] urls = {"/admins/login","/doc.html", // 從本行開始,以下是新增"/**/*.js","/**/*.css","/swagger-resources","/v2/api-docs","/favicon.ico"};// ===== 原有其它代碼 =====} }

在后續的訪問中,必須在請求中攜帶JWT數據, 服務器端才可以嘗試解析此JWT數據,從而判斷用戶是否已登錄或允許訪問。

為了便于測試,在控制器中添加一個測試訪問的請求配置:

// 以下是測試訪問的請求 @GetMapping("/hello") public String sayHello() {return "hello~~~"; }

由于以上 /admins/hello 路徑并不在白名單中,如果直接訪問,會出現403錯誤。

在規范的使用方式中,JWT數據必須攜帶在請求頭(Request Header)的Authorization屬性中。

按照以上規范,則服務器端在每次接收到請求后,首先,就應該判斷請求頭中是否存在Authorization、Authorization的值是否有效等操作,通常,是通過過濾器來實現以上檢查的。

在passport的根包下的security包下創建JwtAuthenticationFilter過濾器類,需要繼承自OncePerRequestFilter類:

@Component public class JwtAuthenticationFilter extends OncePerRequestFilter {@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {System.out.println("JwtAuthenticationFilter.doFilterInternal()");} }

所有的過濾器都必須注冊后才可以使用,且同一個項目中允許存在多個過濾器,形成過濾器鏈,以上用于驗證JWT的過濾器應該運行在Spring Security處理登錄的過濾器之前,需要在自定義的SecurityConfiguration中的configure()方法中將以上自定義的過濾器注冊在Spring Security的相關過濾器之前:

@Configuration public class SecurityConfiguration extends WebSecurityConfigurerAdapter {// 新增@Autowiredprivate JwtAuthenticationFilter jwtAuthenticationFilter;// ===== 原有其它代碼 =====@Overrideprotected void configure(HttpSecurity http) throws Exception {// ===== 原有其它代碼 =====// 注冊處理JWT的過濾器// 此過濾器必須在Spring Security處理登錄的過濾器之前http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);} }

完成后,重啟項目,無論對哪個路徑發出請求,在控制臺都可以看出輸出了過濾器中的輸出語句內容,并且,在瀏覽器將顯示一片空白。

關于JwtAuthenticationFilter,它需要實現:

  • 嘗試從請求頭中獲取JWT數據
    • 如果無JWT數據,應該直接放行,Spring Security還會進行后續的處理,例如白名單的請求將允許訪問,其它請求將禁止訪問
  • 如果存在JWT數據,應該嘗試解析
    • 如果解析失敗,應該視為錯誤,可以要求客戶端重新登錄,客戶端就可以得到新的、正確的JWT,客戶端在下一次提交請求時,使用新的JWT即可正確訪問
  • 將解析得到的數據封裝到Authentication對象中
    • Spring Security的上下文中存儲的數據類型是Authentication類型
  • 為避免存入1次后,Spring Security的上下文中始終存在Authentication,在此過濾器執行的第一時間,應該清除上下文中的數據
/*** JWT過濾器:從請求頭的Authorization中獲取JWT中存入的用戶信息* 并添加到Spring Security的上下文中* 以致于Spring Security后續的組件(包括過濾器等)能從上下文中獲取此用戶的信息* 從而驗證是否已經登錄、是否具有權限等*/ @Component public class JwtAuthenticationFilter extends OncePerRequestFilter {/*** JWT數據的密鑰*/private String secretKey = "fgfdsfadsfadsafdsafdsfadsfadsfdsafdasfdsafdsafdsafds4rttrefds";@Overrideprotected void doFilterInternal(HttpServletRequest request,HttpServletResponse response,FilterChain filterChain) throws ServletException, IOException {System.out.println("JwtAuthenticationFilter.doFilterInternal()");// 清除Spring Security上下文中的數據// 避免此前曾經存入過用戶信息,后續即使沒有攜帶JWT,在Spring Security仍保存有上下文數據(包括用戶信息)System.out.println("清除Spring Security上下文中的數據");SecurityContextHolder.clearContext();// 客戶端提交請求時,必須在請求頭的Authorization中添加JWT數據,這是當前服務器程序的規定,客戶端必須遵守// 嘗試獲取JWT數據String jwt = request.getHeader("Authorization");System.out.println("從請求頭中獲取到的JWT=" + jwt);// 判斷是否不存在jwt數據// StringUtils.hasText 判斷是否屬于jwt數據if (!StringUtils.hasText(jwt)) {// 不存在jwt數據,則放行,后續還有其它過濾器及相關組件進行其它的處理,例如未登錄則要求登錄等// 此處不宜直接阻止運行,因為“登錄”、“注冊”等請求本應該沒有jwt數據System.out.println("請求頭中無JWT數據,當前過濾器將放行");filterChain.doFilter(request, response); // 繼續執行過濾器鏈中后續的過濾器return; // 必須}// 注意:此時執行時,如果請求頭中攜帶了Authentication,日志中將輸出,且不會有任何響應,因為當前過濾器尚未放行// 以下代碼有可能拋出異常的// 密鑰和各個Key應該統一定義String username = null;String permissionsString = null;try {System.out.println("請求頭中包含JWT,準備解析此數據……");Claims claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwt).getBody();username = claims.get("username").toString();permissionsString = claims.get("permissions").toString();System.out.println("username=" + username);System.out.println("permissionsString=" + permissionsString);} catch (ExpiredJwtException e) {System.out.println("解析JWT失敗,此JWT已過期:" + e.getMessage());JsonResult<Void> jsonResult = JsonResult.fail(State.ERR_JWT_EXPIRED, "您的登錄已過期,請重新登錄!");String jsonString = JSON.toJSONString(jsonResult);System.out.println("響應結果:" + jsonString);response.setContentType("application/json; charset=utf-8");response.getWriter().println(jsonString);return;} catch (MalformedJwtException e) {System.out.println("解析JWT失敗,此JWT數據錯誤,無法解析:" + e.getMessage());JsonResult<Void> jsonResult = JsonResult.fail(State.ERR_JWT_MALFORMED, "獲取登錄信息失敗,請重新登錄!");String jsonString = JSON.toJSONString(jsonResult);System.out.println("響應結果:" + jsonString);response.setContentType("application/json; charset=utf-8");response.getWriter().println(jsonString);return;} catch (SignatureException e) {System.out.println("解析JWT失敗,此JWT簽名錯誤:" + e.getMessage());JsonResult<Void> jsonResult = JsonResult.fail(State.ERR_JWT_SIGNATURE, "獲取登錄信息失敗,請重新登錄!");String jsonString = JSON.toJSONString(jsonResult);System.out.println("響應結果:" + jsonString);response.setContentType("application/json; charset=utf-8");response.getWriter().println(jsonString);return;} catch (Throwable e) {System.out.println("解析JWT失敗,異常類型:" + e.getClass().getName());e.printStackTrace();JsonResult<Void> jsonResult = JsonResult.fail(State.ERR_INTERNAL_SERVER_ERROR, "獲取登錄信息失敗,請重新登錄!");String jsonString = JSON.toJSONString(jsonResult);System.out.println("響應結果:" + jsonString);response.setContentType("application/json; charset=utf-8");response.getWriter().println(jsonString);return;}// 將此前從JWT中讀取到的permissionsString(JSON字符串)轉換成Collection<? extends GrantedAuthority>List<SimpleGrantedAuthority> permissions= JSON.parseArray(permissionsString, SimpleGrantedAuthority.class);System.out.println("從JWT中獲取到的權限轉換成Spring Security要求的類型:" + permissions);// 將解析得到的用戶信息傳遞給Spring Security// 獲取Spring Security的上下文,并將Authentication放到上下文中// 在Authentication中封裝:用戶名、null(密碼)、權限列表// 因為接下來并不會處理認證,所以Authentication中不需要密碼// 后續,Spring Security發現上下文中有Authentication時,就會視為已登錄,甚至可以獲取相關信息Authentication authentication= new UsernamePasswordAuthenticationToken(username, null, permissions);SecurityContextHolder.getContext().setAuthentication(authentication);System.out.println("將解析得到的用戶信息傳遞給Spring Security");// 放行System.out.println("JwtAuthenticationFilter 放行");filterChain.doFilter(request, response);}}

要使用Spring Security實現授權訪問,首先,必須保證用戶登錄后,在Spring Security上下文中存在權限相關信息(目前,此項已完成,在JwtAuthenticationFilter的最后,已經存入權限信息)。

然后,需要在配置類上使用@EnableGlobalMethodSecurity注解開啟“通過注解配置權限”的功能,所以,在SecrutiyConfiguration類上添加:

@Configuration @EnableGlobalMethodSecurity(prePostEnabled = true) // 新增 public class SecurityConfiguration extends WebSecurityConfigurerAdapter {// ===== 類中原有代碼 ===== }

最后,在任何你需要設置權限的處理請求的方法上,通過@PreAuthorize注解來配置要求某種權限,例如:

@GetMapping("/hello") @PreAuthorize("hasAuthority('/ams/admin/read')") // 新增 public String sayHello() {return "hello~~~"; }

完成后,重啟項目,使用具有/ams/admin/read權限的用戶可以直接訪問,不具有此權限的用戶則不能訪問

總結

以上是生活随笔為你收集整理的Spring Security框架的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。