網(wǎng)站維護(hù) 收錄湖南長沙最新疫情
一定要熟悉spring security原理和jwt無狀態(tài)原理,理解了才知道代碼作用。
在 Spring Security + JWT 認(rèn)證流程中,通常的做法是:
- 用戶提交用戶名和密碼
- Spring Security 認(rèn)證管理器 (
AuthenticationManager
) 進(jìn)行認(rèn)證 - 如果認(rèn)證成功,生成 JWT Token 并返回給用戶
更詳細(xì)一點(diǎn)
-
用戶首次登錄
- 發(fā)送
POST /login
請求,攜帶用戶名 + 密碼
authenticationManager.authenticate()
認(rèn)證成功后,返回JWT
- 前端存儲
JWT
(通常是localStorage
或sessionStorage
)
- 發(fā)送
-
用戶訪問受保護(hù)接口
- 前端在
Authorization
頭中附帶Bearer Token
- 過濾器
JWTFilter
解析JWT
,從數(shù)據(jù)庫
加載UserDetails
SecurityContextHolder.setAuthentication()
認(rèn)證成功,繼續(xù)訪問資源。
- 前端在
參考鏈接有:
spring security 超詳細(xì)使用教程(接入springboot、前后端分離) - 小程xy - 博客園
SpringSecurity+jwt實(shí)現(xiàn)權(quán)限認(rèn)證功能_spring security + jwt-CSDN博客
1.引入相關(guān)依賴。我使用的是springboot3.3.5? ?springsecurity是6.x的? jwt 0.12.6
<dependencies><!--用于數(shù)據(jù)加密,默認(rèn)啟用--><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進(jìn)行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中設(shè)置。* @return*/@Beanpublic UserDetailsService userDetailsService() {// 創(chuàng)建基于內(nèi)存的用戶信息管理器InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();// 創(chuàng)建UserDetails對象,用于管理用戶名、用戶密碼、用戶角色、用戶權(quán)限等內(nèi)容manager.createUser(User.withUsername("admin").password("yourpassword").roles("ADMIN").build());return manager;}/*** 認(rèn)證管理。 jwt的用戶驗證* @param authConfig* @return* @throws Exception*/@Beanpublic AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {return authConfig.getAuthenticationManager();}/*** 認(rèn)證的token過濾器* @return*/@Beanpublic JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter(){return new JwtAuthenticationTokenFilter();}/*** 密碼加碼* @return*/@Beanpublic PasswordEncoder passwordEncoder() {// 也可用有參構(gòu)造,取值范圍是 4 到 31,默認(rèn)值為 10。數(shù)值越大,加密計算越復(fù)雜return new BCryptPasswordEncoder();}/*** 配置過濾鏈* 配置自動注銷功能必須在函數(shù)里加UserDetailsService userDetailsService,因為重寫了使用數(shù)據(jù)庫認(rèn)證所以用baseuserserviceimpl* @param http* @return* @throws Exception*/@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http, UserDetailsServiceImpl userDetailsService) throws Exception {http// 開啟授權(quán)保護(hù),配置請求授權(quán)規(guī)則.authorizeHttpRequests(authorize -> authorize.requestMatchers("/login","/mylogin","/druid/**").permitAll() // 不需要認(rèn)證的地址有哪些 ("/blog/**", "/public/**", "/about").anyRequest() // 對所有請求開啟授權(quán)保護(hù).authenticated() // 已認(rèn)證的請求會被自動授權(quán))// 配置自定義登錄頁面// 本處禁用前端頁面,使用功能RESTful風(fēng)格前后端分離,就是不用登錄頁面.formLogin(form -> form.disable()).httpBasic(Customizer -> Customizer.disable())// 啟用記住我功能。允許用戶關(guān)閉瀏覽器后仍然保持登錄狀態(tài),直到主動注銷或者查出設(shè)定過期時間//.rememberMe(Customizer.withDefaults()).rememberMe(rememberMe -> rememberMe.key("uniqueAndSecret") // 設(shè)置一個密鑰.tokenValiditySeconds(2 * 24 * 60 * 60) // 設(shè)置 RememberMe token 的有效期.userDetailsService(userDetailsService) // 顯式設(shè)置 UserDetailsService)// 配置注銷功能.logout(logout -> logout.logoutUrl("/perform_logout") // 自定義注銷請求路徑//.logoutSuccessUrl("/login?logout=true") // 注銷成功后的跳轉(zhuǎn)頁面.deleteCookies("JSESSIONID") // 刪除指定的 Cookie.permitAll() // 允許所有用戶注銷).sessionManagement(session -> session.sessionFixation(SessionManagementConfigurer.SessionFixationConfigurer::changeSessionId) // 防止會話固定攻擊.maximumSessions(1) // 限制每個用戶只能有一個活躍會話.maxSessionsPreventsLogin(false)// 如果為 true,禁止新登錄;為 false,允許新登錄并終止舊會話.expiredUrl("/login?session=expired") // 當(dāng)會話過期時跳轉(zhuǎn)到的頁面);// 關(guān)閉 csrf CSRF(跨站請求偽造)是一種網(wǎng)絡(luò)攻擊,攻擊者通過欺騙已登錄用戶,誘使他們在不知情的情況下向受信任的網(wǎng)站發(fā)送請求。http.csrf(csrf -> csrf.disable());// 注冊自定義的過濾器CustomFilter// 用于jwt 功能確保過濾器的邏輯在每個請求中只執(zhí)行一次,非常適合需要對每個請求進(jìn)行處理的場景http.addFilterBefore(new CustomFilter(), UsernamePasswordAuthenticationFilter.class);//已經(jīng)在customfilter中重寫 http.addFilterBefore(new JwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class);//授權(quán)認(rèn)證,基于角色在 Spring Security 6.x 版本中,antMatchers() 方法已被移除,取而代之的是使用新的基于 請求匹配器 (RequestMatchers) 的方法/*http.authorizeHttpRequests(authorize -> authorize.requestMatchers("/admin/**").hasRole("ADMIN") // 只有 ADMIN 角色可以訪問.requestMatchers("/user/**").hasAnyRole("USER", "ADMIN") // USER 和 ADMIN 角色可以訪問.anyRequest().authenticated()); // 其他請求需要認(rèn)證//基于權(quán)限的授權(quán),編輯權(quán)限還是只讀等http.authorizeHttpRequests(authorize -> authorize.requestMatchers("/edit/**").hasAuthority("EDIT_PRIVILEGE") // 僅具有 EDIT_PRIVILEGE 權(quán)限的用戶可以訪問.anyRequest().authenticated()); // 其他請求需要認(rèn)證*/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、有參、無參構(gòu)造函數(shù)
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() { //檢查憑據(jù)(密碼)是否 沒過期。return true;}@Overridepublic boolean isEnabled() { // 檢查賬戶是否啟用。return true;}// 這個方法是 @Data注解 會自動幫我們生成,用來獲取 loadUserByUsername 中最后我們返回的創(chuàng)建UserDetailsImpl對象時傳入的User。// 如果你的字段包含 username和password 的話可以用強(qiáng)制類型轉(zhuǎn)換, 把 UserDetailsImpl 轉(zhuǎn)換成 User。如果不能強(qiáng)制類型轉(zhuǎn)換的話就需要用到這個方法了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 是我們實(shí)現(xiàn)的類}
}
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.攔截器實(shí)現(xiàn)。
(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 提供的一個抽象類,確保在每個請求中只執(zhí)行一次特定的過濾邏輯。* 它是實(shí)現(xiàn)自定義過濾器的基礎(chǔ),通常用于對請求進(jìn)行預(yù)處理或后處理。(實(shí)現(xiàn) JWT 會用到這個接口)* 提供了一種機(jī)制,以確保過濾器的邏輯在每個請求中只執(zhí)行一次,非常適合需要對每個請求進(jìn)行處理的場景。* 通過繼承該類,可以輕松實(shí)現(xiàn)自定義過濾器適合用于記錄日志、身份驗證、權(quán)限檢查等場景。** 本處繼承 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());// 繼續(xù)執(zhí)行過濾鏈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 {/*** 用于驗證賬號密碼,本處于數(shù)據(jù)庫交互*/@Autowiredprivate BaseUsersDao baseUsersDao;@Autowired@Qualifier("userDetailsServiceImpl")//需要指定注入的是那個類,避免報錯。private UserDetailsService userDetailsService;/*** 重寫了 OncePerRequestFilter 類中的抽象方法 doFilterInternal。* OncePerRequestFilter 是 Spring Security 提供的一個基礎(chǔ)類* ,設(shè)計用來確保過濾器在同一個請求中只執(zhí)行一次。* @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);//為空時候繼續(xù)下一步過濾鏈,即進(jìn)行登錄認(rèn)證。后續(xù)進(jìn)行格式驗證,如果以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();//查詢數(shù)據(jù)庫中用戶信息System.out.println("----》 數(shù)據(jù)庫驗證用戶信息。"+"userid:"+userid);UserDetails userDetails = userDetailsService.loadUserByUsername(userid);System.out.println("----》 數(shù)據(jù)庫中數(shù)據(jù):"+userDetails.getUsername()+","+userDetails.getPassword());//設(shè)置安全上下文//創(chuàng)建一個自定義的 UserDetailsImpl 對象,將查詢到的用戶信息封裝。//創(chuàng)建一個 UsernamePasswordAuthenticationToken 對象,表示用戶的認(rèn)證信息// ,并將其設(shè)置到 Spring Security 的 SecurityContextHolder 中,以便后續(xù)請求能夠訪問到用戶的認(rèn)證信息。UsernamePasswordAuthenticationToken authenticationToken =new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());// 如果是有效的jwt,那么設(shè)置該用戶為認(rèn)證后的用戶SecurityContextHolder.getContext().setAuthentication(authenticationToken);//繼續(xù)過濾鏈System.out.println("----》 jwt過濾器執(zhí)行完畢!"+authenticationToken);filterChain.doFilter(request, response);}private void response(@NotNull HttpServletResponse response,String error) throws IOException {response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); // 或者使用自定義狀態(tài)碼response.setContentType(MediaType.APPLICATION_JSON_VALUE);response.setCharacterEncoding("UTF-8");response.getWriter().write("{\n" +" \"states\": \""+error+"\",\n" +" \"message\": \"無效token!\"\n" +"}");}}
6.jwt實(shí)現(xiàn)
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將這個類標(biāo)記為 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,一般可以從本地配置文件中讀取,切記這個秘鑰不能外露,只在服務(wù)端使用,在任何場景都不應(yīng)該流露出去。* 一旦客戶端得知這個secret, 那就意味著客戶端是可以自我簽發(fā)jwt了。* 應(yīng)該大于等于 256位(長度32及以上的字符串),并且是隨機(jī)的字符串*/private final static String SECRET = BaseInfoEnum.fiedIdOf("secret").getFiedIdInfo();//"Cpj2cc09BRTstcISP5HtEAMxwuFEh-nJiL1mppdsz8k@lzgs";/*** 秘鑰實(shí)例,相比secretkeyspec方法base64編碼指定驗證方式,該種方式更加簡便安全。*/public static final SecretKey KEY = Keys.hmacShaKeyFor(SECRET.getBytes());/*** jwt簽發(fā)者*/private final static String JWT_ISS = BaseInfoEnum.fiedIdOf("jwt_iss").getFiedIdInfo();/*** jwt主題*/private final static String SUBJECT = "Peripherals";/*** jwt構(gòu)建器,生成token* 這些是一組預(yù)定義的聲明,它們 不是強(qiáng)制性的,而是推薦的 ,以 提供一組有用的、可互操作的聲明 。* iss: jwt簽發(fā)者* sub: jwt所面向的用戶* aud: 接收jwt的一方* exp: jwt的過期時間,這個過期時間必須要大于簽發(fā)時間* nbf: 定義在什么時間之前,該jwt都是不可用的.* iat: jwt的簽發(fā)時間* jti: jwt的唯一身份標(biāo)識,主要用來作為一次性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()// 設(shè)置頭部信息header.header().add("typ", "JWT").add("alg", "HS256").and()// 設(shè)置自定義負(fù)載信息payload.claim("username", username )//.claim("roleId",roleId ).claim("company",company )// 令牌ID.id(uuid)// 過期日期.expiration(exprireDate)// 簽發(fā)時間.issuedAt(new Date())// 主題.subject(SUBJECT)// 簽發(fā)者.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;}// 在這里可以進(jìn)行其他自定義驗證// 例如檢查用戶角色、權(quán)限等// Token 驗證通過return true;} catch (Exception e) {// 驗證失敗System.out.println("Token validation failed: " + e.getMessage());return false;}}/*** 直接獲取到載荷的具體內(nèi)容* @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.接口實(shí)現(xiàn)
/*** 用戶登錄接口。* 本處調(diào)用spring security驗證功能。(但本項目是前后端分離的,禁用了security登錄頁功能,* 因為其重定向默認(rèn)只能用“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");//設(shè)置request獲取數(shù)據(jù)的編碼方式為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;}//認(rèn)證設(shè)置,在后續(xù)的方法中,已經(jīng)設(shè)置了連接數(shù)據(jù)庫認(rèn)證loadUserByUsername//先設(shè)置認(rèn)證authentication 這一步Authenticated=falseUsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginName, password);//自動調(diào)用loadUserByUsername驗證用戶名和密碼,從數(shù)據(jù)庫中對比查找,如果找到了會返回一個帶有認(rèn)證的封裝后的用戶,否則會報錯,自動處理。(這里我們假設(shè)我們配置的security是基于數(shù)據(jù)庫查找的)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,通過則授權(quán),在過濾器JwtAuthenticationTokenFilter中UsernamePasswordAuthenticationToken authenticationToken =
? ? ? ? ? ? ? ? new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());? 驗證結(jié)果是true的。
2.不帶token則在控制器中對用戶密碼進(jìn)行驗證,因為在loadUserByUsername方法中設(shè)置了對用戶名密碼的驗證,所以使用UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginName, password);后,需要手動使用?Authentication authenticate = authenticationManager.authenticate(authenticationToken);進(jìn)行驗證,驗證通過則驗證結(jié)果是true的。