(五)Spring Boot學習——spring security +jwt使用(前后端分離模式)

一定要熟悉spring security原理和jwt無狀態原理,理解了才知道代碼作用。

Spring Security + JWT 認證流程中,通常的做法是:

  1. 用戶提交用戶名和密碼
  2. Spring Security 認證管理器 (AuthenticationManager) 進行認證
  3. 如果認證成功,生成 JWT Token 并返回給用戶

更詳細一點

  1. 用戶首次登錄

    • 發送 POST /login 請求,攜帶 用戶名 + 密碼
    • authenticationManager.authenticate() 認證成功后,返回 JWT
    • 前端存儲 JWT(通常是 localStoragesessionStorage
  2. 用戶訪問受保護接口

    • 前端在 Authorization 頭中附帶 Bearer Token
    • 過濾器 JWTFilter 解析 JWT,從 數據庫 加載 UserDetails
    • SecurityContextHolder.setAuthentication() 認證成功,繼續訪問資源。

參考鏈接有:

spring security 超詳細使用教程(接入springboot、前后端分離) - 小程xy - 博客園

SpringSecurity+jwt實現權限認證功能_spring security + jwt-CSDN博客

1.引入相關依賴。我使用的是springboot3.3.5? ?springsecurity是6.x的? jwt 0.12.6

<dependencies><!--用于數據加密,默認啟用--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-crypto</artifactId></dependency>
</dependencies><!--依賴集中管理--><dependencyManagement><dependencies><!-- 使用jwt進行token驗證,包括了三個依賴--> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.12.6</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-impl</artifactId> <version>0.12.6</version> <scope>runtime</scope> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-jackson</artifactId> <version>0.12.6</version> <scope>runtime</scope> </dependency><dependencies></dependencyManagement>

2.配置SecurityConfig.java

package com.x.x.x.config;import com.x.x.x.filter.CustomFilter;
import com.x.x.x.filter.JwtAuthenticationTokenFilter;
import com.x.x.x.security.service.impl.UserDetailsServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.SessionManagementConfigurer;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;@Configuration
public class SecurityConfig {/*** 用戶名和密碼也可以在application.properties中設置。* @return*/@Beanpublic UserDetailsService userDetailsService() {// 創建基于內存的用戶信息管理器InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();// 創建UserDetails對象,用于管理用戶名、用戶密碼、用戶角色、用戶權限等內容manager.createUser(User.withUsername("admin").password("yourpassword").roles("ADMIN").build());return manager;}/*** 認證管理。     jwt的用戶驗證* @param authConfig* @return* @throws Exception*/@Beanpublic AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {return authConfig.getAuthenticationManager();}/*** 認證的token過濾器* @return*/@Beanpublic JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter(){return new JwtAuthenticationTokenFilter();}/*** 密碼加碼* @return*/@Beanpublic PasswordEncoder passwordEncoder() {// 也可用有參構造,取值范圍是 4 到 31,默認值為 10。數值越大,加密計算越復雜return new BCryptPasswordEncoder();}/*** 配置過濾鏈* 配置自動注銷功能必須在函數里加UserDetailsService userDetailsService,因為重寫了使用數據庫認證所以用baseuserserviceimpl* @param http* @return* @throws Exception*/@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http,  UserDetailsServiceImpl userDetailsService) throws Exception {http// 開啟授權保護,配置請求授權規則.authorizeHttpRequests(authorize -> authorize.requestMatchers("/login","/mylogin","/druid/**").permitAll()   // 不需要認證的地址有哪些  ("/blog/**", "/public/**", "/about").anyRequest()      // 對所有請求開啟授權保護.authenticated()   // 已認證的請求會被自動授權)// 配置自定義登錄頁面// 本處禁用前端頁面,使用功能RESTful風格前后端分離,就是不用登錄頁面.formLogin(form -> form.disable()).httpBasic(Customizer -> Customizer.disable())// 啟用記住我功能。允許用戶關閉瀏覽器后仍然保持登錄狀態,直到主動注銷或者查出設定過期時間//.rememberMe(Customizer.withDefaults()).rememberMe(rememberMe -> rememberMe.key("uniqueAndSecret") // 設置一個密鑰.tokenValiditySeconds(2 * 24 * 60 * 60) // 設置 RememberMe token 的有效期.userDetailsService(userDetailsService) // 顯式設置 UserDetailsService)// 配置注銷功能.logout(logout -> logout.logoutUrl("/perform_logout") // 自定義注銷請求路徑//.logoutSuccessUrl("/login?logout=true") // 注銷成功后的跳轉頁面.deleteCookies("JSESSIONID") // 刪除指定的 Cookie.permitAll() // 允許所有用戶注銷).sessionManagement(session -> session.sessionFixation(SessionManagementConfigurer.SessionFixationConfigurer::changeSessionId)  // 防止會話固定攻擊.maximumSessions(1) // 限制每個用戶只能有一個活躍會話.maxSessionsPreventsLogin(false)// 如果為 true,禁止新登錄;為 false,允許新登錄并終止舊會話.expiredUrl("/login?session=expired") // 當會話過期時跳轉到的頁面);// 關閉 csrf CSRF(跨站請求偽造)是一種網絡攻擊,攻擊者通過欺騙已登錄用戶,誘使他們在不知情的情況下向受信任的網站發送請求。http.csrf(csrf -> csrf.disable());// 注冊自定義的過濾器CustomFilter// 用于jwt 功能確保過濾器的邏輯在每個請求中只執行一次,非常適合需要對每個請求進行處理的場景http.addFilterBefore(new CustomFilter(), UsernamePasswordAuthenticationFilter.class);//已經在customfilter中重寫 http.addFilterBefore(new JwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class);//授權認證,基于角色在 Spring Security 6.x 版本中,antMatchers() 方法已被移除,取而代之的是使用新的基于 請求匹配器 (RequestMatchers) 的方法/*http.authorizeHttpRequests(authorize -> authorize.requestMatchers("/admin/**").hasRole("ADMIN")  // 只有 ADMIN 角色可以訪問.requestMatchers("/user/**").hasAnyRole("USER", "ADMIN")  // USER 和 ADMIN 角色可以訪問.anyRequest().authenticated());  // 其他請求需要認證//基于權限的授權,編輯權限還是只讀等http.authorizeHttpRequests(authorize -> authorize.requestMatchers("/edit/**").hasAuthority("EDIT_PRIVILEGE")  // 僅具有 EDIT_PRIVILEGE 權限的用戶可以訪問.anyRequest().authenticated());  // 其他請求需要認證*/return http.build();}
}

3.重寫loadUserByUsername的方法。

(1)UserDetailsImpl.java

package com.x.x.x.security.service.impl;import com.x.x.x.entity.BaseUsers;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;import java.util.Collection;
import java.util.List;@Data
@AllArgsConstructor
@NoArgsConstructor    // 這三個注解可以幫我們自動生成 get、set、有參、無參構造函數
public class UserDetailsImpl implements UserDetails {private BaseUsers baseUsers;@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return List.of();}@Overridepublic String getPassword() {return baseUsers.getPassword();}@Overridepublic String getUsername() {return baseUsers.getOaId();}@Overridepublic boolean isAccountNonExpired() {  // 檢查賬戶是否 沒過期。return true;}@Overridepublic boolean isAccountNonLocked() {   // 檢查賬戶是否 沒有被鎖定。return true;}@Overridepublic boolean isCredentialsNonExpired() {  //檢查憑據(密碼)是否 沒過期。return true;}@Overridepublic boolean isEnabled() {    // 檢查賬戶是否啟用。return true;}// 這個方法是 @Data注解 會自動幫我們生成,用來獲取 loadUserByUsername 中最后我們返回的創建UserDetailsImpl對象時傳入的User。// 如果你的字段包含 username和password 的話可以用強制類型轉換, 把 UserDetailsImpl 轉換成 User。如果不能強制類型轉換的話就需要用到這個方法了public BaseUsers getUser() {return baseUsers;}
}

(2)UserDetailsServiceImpl.java

package com.x.x.x.security.service.impl;import com.x.x.x.entity.BaseUsers;
import com.x.x.x.service.BaseUsersService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;import java.util.List;@Service
public class UserDetailsServiceImpl implements UserDetailsService {@Autowiredprivate BaseUsersService baseUsersService;/*** 重寫loadUserByUsername方法* @param username the username identifying the user whose data is required.* @return* @throws UsernameNotFoundException*/@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {BaseUsers baseUsers = new BaseUsers();baseUsers.setOaId(username);List<BaseUsers> baseUsersList = baseUsersService.queryUsersList(baseUsers);if (baseUsersList == null || baseUsersList.isEmpty()) {System.out.println("------------->       loadUserByUsername驗證失敗, "+baseUsers.getOaId()+" 不存在!");throw new UsernameNotFoundException(username);}return new UserDetailsImpl(baseUsersList.get(0));	// UserDetailsImpl 是我們實現的類}
}

4.JwtAuthenticationProvider.java繼承重新AuthenticationProvider的authenticate方法。這里注意可能未使用我們繼承的userDetailsService,所以使用@Qualifier("")指定

package com.x.x.x.security.handler;import io.micrometer.common.util.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;@Component
public class JwtAuthenticationProvider implements AuthenticationProvider {@Autowiredprivate PasswordEncoder passwordEncoder;@Autowired@Qualifier("userDetailsServiceImpl")//需要指定注入的是那個類,避免報錯。private UserDetailsService userDetailsService;@Overridepublic Authentication authenticate(Authentication authentication) {String username = String.valueOf(authentication.getPrincipal());String password = String.valueOf(authentication.getCredentials());UserDetails userDetails = userDetailsService.loadUserByUsername(username);System.out.println("------------->       JwtAuthenticationProvider:"+userDetails.getUsername()+","+userDetails.getPassword());if(userDetails != null && StringUtils.isNotBlank(userDetails.getPassword())&& userDetails.getPassword().equals(password)){return new UsernamePasswordAuthenticationToken(username,password,authentication.getAuthorities());}try {throw new Exception("RespCodeEnum.NAME_OR_PASSWORD_ERROR");} catch (Exception e) {throw new RuntimeException(e);}}@Overridepublic boolean supports(Class<?> authentication) {return UsernamePasswordAuthenticationToken.class.equals(authentication);}
}

5.攔截器實現。

(1)CustomFilter

package com.x.x.x.filter;import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.filter.OncePerRequestFilter;import java.io.IOException;/*** OncePerRequestFilter 是 Spring Security 提供的一個抽象類,確保在每個請求中只執行一次特定的過濾邏輯。* 它是實現自定義過濾器的基礎,通常用于對請求進行預處理或后處理。(實現 JWT 會用到這個接口)* 提供了一種機制,以確保過濾器的邏輯在每個請求中只執行一次,非常適合需要對每個請求進行處理的場景。* 通過繼承該類,可以輕松實現自定義過濾器適合用于記錄日志、身份驗證、權限檢查等場景。** 本處繼承 OncePerRequestFilter 類,并重寫 doFilterInternal 方法。* 但是需要再spring security配置類中注冊自定義的過濾器*/
public class CustomFilter extends OncePerRequestFilter {@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,FilterChain filterChain) throws ServletException, IOException {// 自定義過濾邏輯,例如記錄請求日志System.out.println("Request URI: " + request.getRequestURI());// 繼續執行過濾鏈filterChain.doFilter(request, response);}
}

(2)JwtAuthenticationTokenFilter

package com.x.x.x.filter;import com.x.x.x.dao.BaseUsersDao;
import io.jsonwebtoken.Claims;
import java.io.IOException;import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import com.x.x.x.until.JwtUtil;@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {/***  用于驗證賬號密碼,本處于數據庫交互*/@Autowiredprivate BaseUsersDao baseUsersDao;@Autowired@Qualifier("userDetailsServiceImpl")//需要指定注入的是那個類,避免報錯。private UserDetailsService userDetailsService;/*** 重寫了 OncePerRequestFilter 類中的抽象方法 doFilterInternal。* OncePerRequestFilter 是 Spring Security 提供的一個基礎類* ,設計用來確保過濾器在同一個請求中只執行一次。* @param request* @param response* @param filterChain* @throws ServletException* @throws IOException*/@Overrideprotected void doFilterInternal(HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull FilterChain filterChain) throws ServletException, IOException {// 獲取請求頭的驗證信息,即前端傳回的tokenString token = request.getHeader("Authorization");System.out.println("----》     JwtAuthenticationTokenFilter,驗證token過濾器,獲取到的token值:"+token);//為空時候繼續下一步過濾鏈,即進行登錄認證。后續進行格式驗證,如果以bearer開始去掉前面的前綴if (!StringUtils.hasText(token) ) {System.out.println("----》     JwtAuthenticationTokenFilter,token驗證:"+"token為空!");filterChain.doFilter(request, response);return;}if (token.startsWith("Bearer ")) {System.out.println("----》     JwtAuthenticationTokenFilter,token格式驗證中:"+"token格式以Bearer開頭,去掉開頭!");token = token.substring(7);}//驗證token是否過期boolean isValid = JwtUtil.validateJwtToken(token);//只在util中只驗證是否過期了。if (!isValid) {System.out.println("----》     token驗證失敗,token過期。");response(response, "驗證失敗");return;}//獲取token載荷中的用戶信息Claims claims = JwtUtil.parseClaim(token).getPayload();String userid = claims.get("username").toString();//查詢數據庫中用戶信息System.out.println("----》     數據庫驗證用戶信息。"+"userid:"+userid);UserDetails userDetails = userDetailsService.loadUserByUsername(userid);System.out.println("----》     數據庫中數據:"+userDetails.getUsername()+","+userDetails.getPassword());//設置安全上下文//創建一個自定義的 UserDetailsImpl 對象,將查詢到的用戶信息封裝。//創建一個 UsernamePasswordAuthenticationToken 對象,表示用戶的認證信息// ,并將其設置到 Spring Security 的 SecurityContextHolder 中,以便后續請求能夠訪問到用戶的認證信息。UsernamePasswordAuthenticationToken authenticationToken =new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());// 如果是有效的jwt,那么設置該用戶為認證后的用戶SecurityContextHolder.getContext().setAuthentication(authenticationToken);//繼續過濾鏈System.out.println("----》     jwt過濾器執行完畢!"+authenticationToken);filterChain.doFilter(request, response);}private void response(@NotNull HttpServletResponse response,String error) throws IOException {response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); // 或者使用自定義狀態碼response.setContentType(MediaType.APPLICATION_JSON_VALUE);response.setCharacterEncoding("UTF-8");response.getWriter().write("{\n" +"  \"states\": \""+error+"\",\n" +"  \"message\": \"無效token!\"\n" +"}");}}

6.jwt實現

package com.x.x.x.until;import com.x.x.x.enums.BaseInfoEnum;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import io.jsonwebtoken.security.SecureDigestAlgorithm;
import org.springframework.stereotype.Component;import javax.crypto.SecretKey;
import java.time.Instant;
import java.util.*;// @Component將這個類標記為 Spring 組件,允許 Spring 管理該類的生命周期,便于依賴注入。
@Component
public class JwtUtil {/*** 過期時間(單位:秒),4小時為14400s*/public static final int ACCESS_EXPIRE = Integer.parseInt(BaseInfoEnum.fiedIdOf("access_expire").getFiedIdInfo());//14400;/*** 加密算法*/private final static SecureDigestAlgorithm<SecretKey, SecretKey> ALGORITHM = Jwts.SIG.HS256;/*** 私鑰 / 生成簽名的時候使用的秘鑰secret,一般可以從本地配置文件中讀取,切記這個秘鑰不能外露,只在服務端使用,在任何場景都不應該流露出去。* 一旦客戶端得知這個secret, 那就意味著客戶端是可以自我簽發jwt了。* 應該大于等于 256位(長度32及以上的字符串),并且是隨機的字符串*/private final static String SECRET = BaseInfoEnum.fiedIdOf("secret").getFiedIdInfo();//"Cpj2cc09BRTstcISP5HtEAMxwuFEh-nJiL1mppdsz8k@lzgs";/*** 秘鑰實例,相比secretkeyspec方法base64編碼指定驗證方式,該種方式更加簡便安全。*/public static final SecretKey KEY = Keys.hmacShaKeyFor(SECRET.getBytes());/*** jwt簽發者*/private final static String JWT_ISS = BaseInfoEnum.fiedIdOf("jwt_iss").getFiedIdInfo();/*** jwt主題*/private final static String SUBJECT = "Peripherals";/*** jwt構建器,生成token* 這些是一組預定義的聲明,它們 不是強制性的,而是推薦的 ,以 提供一組有用的、可互操作的聲明 。* iss: jwt簽發者* sub: jwt所面向的用戶* aud: 接收jwt的一方* exp: jwt的過期時間,這個過期時間必須要大于簽發時間* nbf: 定義在什么時間之前,該jwt都是不可用的.* iat: jwt的簽發時間* jti: jwt的唯一身份標識,主要用來作為一次性token,從而回避重放攻擊*/public static String genAccessToken(String username ,String roleId,String company) {// 令牌idString uuid = UUID.randomUUID().toString();Date exprireDate = Date.from(Instant.now().plusSeconds(ACCESS_EXPIRE));//System.out.println("key:"+KEY);return Jwts.builder()// 設置頭部信息header.header().add("typ", "JWT").add("alg", "HS256").and()// 設置自定義負載信息payload.claim("username", username )//.claim("roleId",roleId ).claim("company",company )// 令牌ID.id(uuid)// 過期日期.expiration(exprireDate)// 簽發時間.issuedAt(new Date())// 主題.subject(SUBJECT)// 簽發者.issuer(JWT_ISS)// 簽名.signWith(KEY, ALGORITHM).compact();}/*** 解析token* @param token token* @return Jws<Claims>*/public static Jws<Claims> parseClaim(String token) {return Jwts.parser().verifyWith(KEY).build().parseSignedClaims(token);}/*** 獲取頭部信息* @param token* @return*/public static JwsHeader parseHeader(String token) {return parseClaim(token).getHeader();}/*** 獲取載荷信息* @param token* @return*/public static Claims parsePayload(String token) {return parseClaim(token).getPayload();}/*** token驗證,token是否過期正確* @param token* @return*/public static boolean validateJwtToken(String token) {try {// 解析 Token,驗證簽名。驗證載荷Claims claims = parseClaim(token).getPayload();//System.out.println("content:---"+claims.get("username"));// 驗證聲明(例如過期時間)if (claims.getExpiration().before(new Date())) {System.out.println("Token has expired.");return false;}// 在這里可以進行其他自定義驗證// 例如檢查用戶角色、權限等// Token 驗證通過return true;} catch (Exception e) {// 驗證失敗System.out.println("Token validation failed: " + e.getMessage());return false;}}/*** 直接獲取到載荷的具體內容* @param token* @return*/public static Map<String, Object> token2userInfo(String token){Map<String, Object> tokenMap = new HashMap<String, Object>();Claims claims = parseClaim(token).getPayload();tokenMap.put("company", claims.get("company"));tokenMap.put("loginName", claims.get("username"));tokenMap.put("roleId", claims.get("roleId"));return tokenMap;}//測試public static void main(String[] args){String token = genAccessToken("123","admin","123");System.out.println("token:"+token);boolean isValid = validateJwtToken(token);System.out.println(isValid);System.out.println(parseHeader(token));System.out.println(parsePayload(token));}}

7.接口實現

/*** 用戶登錄接口。* 本處調用spring security驗證功能。(但本項目是前后端分離的,禁用了security登錄頁功能,* 因為其重定向默認只能用“GET”方式請求)* @param request* @return* @throws Exception*/@PostMapping("/login")public Map<String, Object> login(HttpServletRequest request) throws Exception{Map<String, Object> modelMap = new HashMap<String, Object>();request.setCharacterEncoding("UTF8");//設置request獲取數據的編碼方式為utf-8String loginName = HttpServletRequestUtil.getString(request, "loginName");String password = HttpServletRequestUtil.getString(request, "password");if (loginName ==null || loginName.isBlank()  || password == null || password.isBlank()){modelMap.put("success", false);modelMap.put("msg", "用戶名和密碼均不能為空");logger.error("---->    登錄失敗,用戶名和密碼為空!");return modelMap;}//認證設置,在后續的方法中,已經設置了連接數據庫認證loadUserByUsername//先設置認證authentication  這一步Authenticated=falseUsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginName, password);//自動調用loadUserByUsername驗證用戶名和密碼,從數據庫中對比查找,如果找到了會返回一個帶有認證的封裝后的用戶,否則會報錯,自動處理。(這里我們假設我們配置的security是基于數據庫查找的)try{Authentication authenticate = authenticationManager.authenticate(authenticationToken);SecurityContextHolder.getContext().setAuthentication(authenticate);String token = genAccessToken(loginName,"admin","123");modelMap.put("token",token);modelMap.put("success", true);return modelMap;} catch (Exception e) {modelMap.put("success", false);modelMap.put("msg", "用戶名或密碼錯誤");logger.error("---->    登錄失敗,用戶名或密碼錯誤!");return modelMap;}}

這里需要注意:

1.一般是url請求帶token,直接驗證token,通過則授權,在過濾器JwtAuthenticationTokenFilter中UsernamePasswordAuthenticationToken authenticationToken =
? ? ? ? ? ? ? ? new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());? 驗證結果是true的。

2.不帶token則在控制器中對用戶密碼進行驗證,因為在loadUserByUsername方法中設置了對用戶名密碼的驗證,所以使用UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginName, password);后,需要手動使用?Authentication authenticate = authenticationManager.authenticate(authenticationToken);進行驗證,驗證通過則驗證結果是true的。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/895172.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/895172.shtml
英文地址,請注明出處:http://en.pswp.cn/news/895172.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

清華DeepSeek手冊:從入門到精通(網頁版便于閱讀)

目錄 一、產品概述二、清華DeepSeek從入門到精通三、PDF文件路徑 一、產品概述 DeepSeek是國產領先的人工智能技術平臺&#xff0c;提供從數據分析到大型語言模型的全棧解決方案。其核心產品包括網頁端數據分析工具[1] 、視覺語言模型(DeepSeek-VL)[2] 和670億參數大型語言模型…

阿里云百煉初探DeepSeek模型調用

阿里云百煉初探DeepSeek模型調用 阿里云百煉為什么選擇百煉開始使用百煉方式一&#xff1a;文本對話方式二&#xff1a;文本調試方式三&#xff1a;API調用 DeepSeek調用1、搜索模型2、查看API調用3、開始調用安裝依賴查看API Key運行以下代碼 4、流式輸出 總結 阿里云百煉 阿…

【網絡安全】服務器安裝Docker及拉取鏡像教程

文章目錄 1. 安裝 Docker2. 拉取鏡像3. 運行 Ubuntu 容器4. 執行相關操作5. 退出并停止容器1. 安裝 Docker # 更新軟件包索引 sudo apt update# 安裝必要的依賴 sudo apt install -y ca-certificates curl gnupg

AI刷題-子數組和的最大值問題

目錄 問題描述 輸入格式 輸出格式 輸入樣例 輸出樣例 說明 數據范圍 解題思路&#xff1a; 問題理解 數據結構選擇 算法步驟 具體步驟 代碼實現&#xff1a; 1.特判&#xff1a; 不需要刪除元素的時候 2.在前面的判斷結束后&#xff1a;k1&#xff0c;&#xff…

pytest.fixture

pytest.fixture 是 pytest 測試框架中的一個非常強大的功能,它允許你在測試函數運行前后執行一些設置或清理代碼。以下是關于 pytest.fixture 的詳細介紹: 一、定義與用途 pytest.fixture 是一個裝飾器,用于標記一個函數為 fixture。Fixture 函數中的代碼可以在測試函數運…

Swift的方法派發機制

1. 靜態派發&#xff08;Static Dispatch&#xff09; 靜態派發在編譯時確定方法的具體實現&#xff0c;調用時直接跳轉到該實現。靜態派發的優點是性能高&#xff0c;因為不需要運行時查找方法實現。 適用場景&#xff1a; 值類型&#xff08;Struct 和 Enum&#xff09;&am…

C++并發編程指南 09(共享數據)

文章目錄 第3章 共享數據本章主要內容共享數據的問題使用互斥保護數據保護數據的替代方案 3.1 共享數據的問題共享數據的核心問題不變量的重要性示例&#xff1a;刪除雙鏈表中的節點多線程環境中的問題條件競爭的后果總結3.1.1 條件競爭3.1.2 避免惡性條件競爭 3.2 使用互斥量3…

ZooKeeper 技術全解:概念、功能、文件系統與主從同步

引言 隨著分布式系統變得越來越復雜&#xff0c;對協調服務的需求也在不斷增長。ZooKeeper 作為一個由 Apache 維護的開源分布式協調服務框架&#xff0c;廣泛用于 Hadoop 生態系統和其他需要協調的分布式環境中。這一系統旨在解決分布式應用中常見的挑戰&#xff0c;如配置管…

設計方案主要做哪些事情?

目錄 1. 需求分析 2. 系統架構設計 3. 數據庫設計 4. 接口設計 5. 緩存設計 6. 安全設計 7. 性能優化 8. 高可用與容災 9. 監控與日志 10. 測試方案 11. 部署方案 12. 文檔編寫 13. 風險評估 14. 項目管理 總結 設計方案是項目開發的關鍵步驟,確保項目按計劃進…

【語法】C++的內存管理 模板

內存管理&#xff1a; 在C語言中&#xff0c;動態開辟空間可以用malloc&#xff0c;calloc&#xff0c;realloc這三個函數&#xff0c;下面先來復習一下這三者的區別 malloc和calloc都是用來開辟新空間&#xff0c;calloc在malloc的基礎上還會初始化該空間為0&#xff0c;用法…

30~32.ppt

目錄 30.導游小姚-介紹首都北京? 題目? 解析 31.小張-旅游產品推廣文章 題目 解析 32.小李-水的知識? 題目? 解析 30.導游小姚-介紹首都北京? 題目 解析 新建幻燈片-從大綱-重置-檢查設計→主題對話框→瀏覽主題&#xff1a;考生文件夾&#xff08;注意&#x…

深度學習-交易預測

下面為你詳細介紹如何使用Python結合深度學習庫TensorFlow和Keras來構建一個簡單的交易預測模型。在這個示例中&#xff0c;我們以股票價格預測為例&#xff0c;假設我們要根據過去一段時間的股票價格數據來預測未來的價格走勢。 步驟分析 數據準備&#xff1a;獲取股票價格數…

C++ STL Map 學習學案(提高版)

C++ STL Map 學案(初中生版) 一、學習目標 深入理解 STL 中 map 容器的概念、特點和用途。熟練掌握 map 容器的基本操作,如插入、查找、刪除和遍歷元素。能夠運用 map 容器解決實際編程問題,提升邏輯思維和編程實踐能力。二、知識講解 引入 在日常生活中,我們常常會遇到…

uniapp實現人臉識別(不使用三方插件)

uniapp實現人臉識別 內容簡介功能實現上傳身份證進行人臉比對 遇到的問題 內容簡介 1.拍攝/相冊將身份證照片上傳到接口進行圖片解析 2.使用live-pusher組件拍攝人臉照片&#xff0c;上傳接口與身份證人臉進行比對 功能實現 上傳身份證 先看下效果 點擊按鈕調用chooseImage…

Evaluating Very Long-Term Conversational Memory of LLM Agents 論文

Abstract : 長期開放域對話的現有作品著重于評估不超過五個聊天會議的上下文中的模型響應。盡管LongContext大語言模型&#xff08;LLM&#xff09;和檢索增強發電&#xff08;RAG&#xff09;技術的進步&#xff0c;但在長期對話中的功效仍未得到探索。為了解決這一研究差距&a…

相對收益-固定收益組合歸因-Campisi模型

固定收益組合歸因-Campisi模型 1 Campisi模型11.1 Campisi歸因框架1.2 Campisi模型絕對收益分解1.2.1 票息收益1. 2.2 收斂收益1. 2.3 騎乘收益1. 2.4 平移收益1. 2.5 扭曲收益1. 2.6 利差收益1. 2.7 殘差收益 1.3 Campisi模型超額收益分解 2 Campisi模型22.1 分解框架2.2 模型…

IntelliJ IDEA使用經驗(十三):使用Git克隆github的開源項目

文章目錄 問題背景辦法1、設置git代理&#xff1b;2、再次克隆項目&#xff1b;3、再次按常規方式進行git克隆即可。 問題背景 由于github在國外&#xff0c;很多時候我們在使用idea克隆開源項目的時候&#xff0c;沒辦法檢出&#xff0c;提示 連接重置。 辦法 1、設置git代…

JAVA安全之Java Agent打內存馬

基本介紹 Java Agent是一種特殊的Java程序&#xff0c;它允許開發者在Java虛擬機(JVM)啟動時或運行期間通過java.lang.instrument包提供的Java標準接口進行代碼插樁&#xff0c;從而實現在Java應用程序類加載和運行期間動態修改已加載或者未加載的類&#xff0c;包括類的屬性、…

RabbitMQ 消息順序性保證

方式一&#xff1a;Consumer設置exclusive 注意條件 作用于basic.consume不支持quorum queue 當同時有A、B兩個消費者調用basic.consume方法消費&#xff0c;并將exclusive設置為true時&#xff0c;第二個消費者會拋出異常&#xff1a; com.rabbitmq.client.AlreadyClosedEx…

SQL自學,mysql從入門到精通 --- 第 14天,主鍵、外鍵的使用

1.主鍵 PRIMARY KEY 主鍵的使用 字段值不允許重復&#xff0c;且不允許賦NULL值 創建主鍵 rootmysqldb 10:11: [d1]> CREATE TABLE t3(-> name varchar(10) PRIMARY KEY,-> age int,-> class varchar(8)-> ); Query OK, 0 rows affected (0.01 sec)rootmys…