欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Spring Security代碼實現JWT接口權限授予與校驗功能

 更新時間:2019年12月03日 08:36:56   作者:字母哥  
本文給大家介紹Spring Security代碼實現JWT接口權限授予與校驗功能,本文通過實例代碼給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友參考下吧

通過筆者前兩篇文章的說明,相信大家已經知道JWT是什么,怎么用,該如何結合Spring Security使用。那么本節(jié)就用代碼來具體的實現一下JWT登錄認證及鑒權的流程。

一、環(huán)境準備工作

  • 建立Spring Boot項目并集成了Spring Security,項目可以正常啟動
  • 通過controller寫一個HTTP的GET方法服務接口,比如:“/hello”
  • 實現最基本的動態(tài)數據驗證及權限分配,即實現UserDetailsService接口和UserDetails接口。這兩個接口都是向Spring Security提供用戶、角色、權限等校驗信息的接口
  • 如果你學習過Spring Security的formLogin登錄模式,請將HttpSecurity配置中的formLogin()配置段全部去掉。因為JWT完全使用JSON接口,沒有from表單提交。
  • HttpSecurity配置中一定要加上csrf().disable(),即暫時關掉跨站攻擊CSRF的防御。這樣是不安全的,我們后續(xù)章節(jié)再做處理。

以上的內容,我們在之前的文章中都已經講過。如果仍然不熟悉,可以翻看本號之前的文章。

## 二、開發(fā)JWT工具類

通過maven坐標引入JWT工具包jjwt

<dependency>
 <groupId>io.jsonwebtoken</groupId>
 <artifactId>jjwt</artifactId>
 <version>0.9.0</version>
</dependency>

在application.yml中加入如下自定義一些關于JWT的配置

jwt: 
 header: JWTHeaderName
 secret: aabbccdd 
 expiration: 3600000 
  • 其中header是攜帶JWT令牌的HTTP的Header的名稱。雖然我這里叫做JWTHeaderName,但是在實際生產中可讀性越差越安全。
  • secret是用來為JWT基礎信息加密和解密的密鑰。雖然我在這里在配置文件寫死了,但是在實際生產中通常不直接寫在配置文件里面。而是通過應用的啟動參數傳遞,并且需要定期修改。
  • expiration是JWT令牌的有效時間。

寫一個Spring Boot配置自動加載的工具類。

@Data
@ConfigurationProperties(prefix = "jwt") //配置自動加載,prefix是配置的前綴
@Component
public class JwtTokenUtil implements Serializable {
 private String secret;
 private Long expiration;
 private String header;
 /**
 * 生成token令牌
 *
 * @param userDetails 用戶
 * @return 令token牌
 */
 public String generateToken(UserDetails userDetails) {
 Map<String, Object> claims = new HashMap<>(2);
 claims.put("sub", userDetails.getUsername());
 claims.put("created", new Date());
 return generateToken(claims);
 }
 /**
 * 從令牌中獲取用戶名
 *
 * @param token 令牌
 * @return 用戶名
 */
 public String getUsernameFromToken(String token) {
 String username;
 try {
  Claims claims = getClaimsFromToken(token);
  username = claims.getSubject();
 } catch (Exception e) {
  username = null;
 }
 return username;
 }
 /**
 * 判斷令牌是否過期
 *
 * @param token 令牌
 * @return 是否過期
 */
 public Boolean isTokenExpired(String token) {
 try {
  Claims claims = getClaimsFromToken(token);
  Date expiration = claims.getExpiration();
  return expiration.before(new Date());
 } catch (Exception e) {
  return false;
 }
 }
 /**
 * 刷新令牌
 *
 * @param token 原令牌
 * @return 新令牌
 */
 public String refreshToken(String token) {
 String refreshedToken;
 try {
  Claims claims = getClaimsFromToken(token);
  claims.put("created", new Date());
  refreshedToken = generateToken(claims);
 } catch (Exception e) {
  refreshedToken = null;
 }
 return refreshedToken;
 }
 /**
 * 驗證令牌
 *
 * @param token 令牌
 * @param userDetails 用戶
 * @return 是否有效
 */
 public Boolean validateToken(String token, UserDetails userDetails) {
 SysUser user = (SysUser) userDetails;
 String username = getUsernameFromToken(token);
 return (username.equals(user.getUsername()) && !isTokenExpired(token));
 }
 /**
 * 從claims生成令牌,如果看不懂就看誰調用它
 *
 * @param claims 數據聲明
 * @return 令牌
 */
 private String generateToken(Map<String, Object> claims) {
 Date expirationDate = new Date(System.currentTimeMillis() + expiration);
 return Jwts.builder().setClaims(claims)
    .setExpiration(expirationDate)
    .signWith(SignatureAlgorithm.HS512, secret)
    .compact();
 }
 /**
 * 從令牌中獲取數據聲明,如果看不懂就看誰調用它
 *
 * @param token 令牌
 * @return 數據聲明
 */
 private Claims getClaimsFromToken(String token) {
 Claims claims;
 try {
  claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
 } catch (Exception e) {
  claims = null;
 }
 return claims;
 }
}

上面的代碼就是使用io.jsonwebtoken.jjwt提供的方法開發(fā)JWT令牌生成、刷新的工具類。

三、開發(fā)登錄接口(獲取Token的接口)

  • "/authentication"接口用于登錄驗證,并且生成JWT返回給客戶端
  • "/REFRESHTOKEN"接口用于刷新JWT,更新JWT令牌的有效期
@RESTCONTROLLER
PUBLIC CLASS JWTAUTHCONTROLLER {
 @RESOURCE
 PRIVATE JWTAUTHSERVICE JWTAUTHSERVICE;
 @POSTMAPPING(VALUE = "/AUTHENTICATION")
 PUBLIC AJAXRESPONSE LOGIN(@REQUESTBODY MAP<STRING, STRING> MAP) {
  STRING USERNAME = MAP.GET("USERNAME");
  STRING PASSWORD = MAP.GET("PASSWORD");
  IF (STRINGUTILS.ISEMPTY(USERNAME) || STRINGUTILS.ISEMPTY(PASSWORD)) {
   RETURN AJAXRESPONSE.ERROR(
    NEW CUSTOMEXCEPTION(CUSTOMEXCEPTIONTYPE.USER_INPUT_ERROR,"用戶名密碼不能為空"));
  }
  RETURN AJAXRESPONSE.SUCCESS(JWTAUTHSERVICE.LOGIN(USERNAME, PASSWORD));
 }
 @POSTMAPPING(VALUE = "/REFRESHTOKEN")
 PUBLIC AJAXRESPONSE REFRESH(@REQUESTHEADER("${JWT.HEADER}") STRING TOKEN) {
  RETURN AJAXRESPONSE.SUCCESS(JWTAUTHSERVICE.REFRESHTOKEN(TOKEN));
 }
}

核心的token業(yè)務邏輯寫在JwtAuthService 中

  • login方法中首先使用用戶名、密碼進行登錄驗證。如果驗證失敗拋出BadCredentialsException異常。如果驗證成功,程序繼續(xù)向下走,生成JWT響應給前端
  • refreshToken方法只有在JWT token沒有過期的情況下才能刷新,過期了就不能刷新了。需要重新登錄。
@Service
public class JwtAuthService {
 @Resource
 private AuthenticationManager authenticationManager;
 @Resource
 private UserDetailsService userDetailsService;
 @Resource
 private JwtTokenUtil jwtTokenUtil;

 public String login(String username, String password) {
  //使用用戶名密碼進行登錄驗證
  UsernamePasswordAuthenticationToken upToken = 
     new UsernamePasswordAuthenticationToken( username, password );
  Authentication authentication = authenticationManager.authenticate(upToken); 
  SecurityContextHolder.getContext().setAuthentication(authentication);
  //生成JWT
  UserDetails userDetails = userDetailsService.loadUserByUsername( username );
  return jwtTokenUtil.generateToken(userDetails);
 }

 public String refreshToken(String oldToken) {
  if (!jwtTokenUtil.isTokenExpired(oldToken)) {
   return jwtTokenUtil.refreshToken(oldToken);
  }
  return null;
 }
}

因為使用到了AuthenticationManager ,所以在繼承WebSecurityConfigurerAdapter的SpringSecurity配置實現類中,將AuthenticationManager 聲明為一個Bean。并將"/authentication"和 "/refreshtoken"開放訪問權限,如何開放訪問權限,我們之前的文章已經講過了。

@Bean(name = BeanIds.AUTHENTICATION_MANAGER)
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
 return super.authenticationManagerBean();
}

四、接口訪問鑒權過濾器

當用戶第一次登陸之后,我們將JWT令牌返回給了客戶端,客戶端應該將該令牌保存起來。在進行接口請求的時候,將令牌帶上,放到HTTP的header里面,header的名字要和jwt.header的配置一致,這樣服務端才能解析到。下面我們定義一個攔截器:

  • 攔截接口請求,從請求request獲取token,從token中解析得到用戶名
  • 然后通過UserDetailsService獲得系統(tǒng)用戶(從數據庫、或其他其存儲介質)
  • 根據用戶信息和JWT令牌,驗證系統(tǒng)用戶與用戶輸入的一致性,并判斷JWT是否過期。如果沒有過期,至此表明了該用戶的確是該系統(tǒng)的用戶。
  • 但是,你是系統(tǒng)用戶不代表你可以訪問所有的接口。所以需要構造UsernamePasswordAuthenticationToken傳遞用戶、權限信息,并將這些信息通過authentication告知Spring Security。Spring Security會以此判斷你的接口訪問權限。
@Slf4j
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

 @Resource
 private MyUserDetailsService userDetailsService;

 @Resource
 private JwtTokenUtil jwtTokenUtil;

 @Override
 protected void doFilterInternal(HttpServletRequest request,
         HttpServletResponse response,
         FilterChain chain) throws ServletException, IOException {
 
  // 從這里開始獲取 request 中的 jwt token
  String authHeader = request.getHeader(jwtTokenUtil.getHeader());
  log.info("authHeader:{}", authHeader);
  // 驗證token是否存在
  if (authHeader != null && StringUtils.isNotEmpty(authHeader)) {
   // 根據token 獲取用戶名
   String username = jwtTokenUtil.getUsernameFromToken(authHeader);
   if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
    // 通過用戶名 獲取用戶的信息
    UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
    
    // 驗證JWT是否過期
    if (jwtTokenUtil.validateToken(authHeader, userDetails)) {
     //加載用戶、角色、權限信息,Spring Security根據這些信息判斷接口的訪問權限
     UsernamePasswordAuthenticationToken authentication 
       = new UsernamePasswordAuthenticationToken(userDetails, null, 
                  userDetails.getAuthorities());
     authentication.setDetails(new WebAuthenticationDetailsSource()
           .buildDetails(request));
     SecurityContextHolder.getContext().setAuthentication(authentication);
    }
   }
  }
  chain.doFilter(request, response);
 }
}

在spring Security的配置類(即WebSecurityConfigurerAdapter實現類的configure(HttpSecurity http)配置方法中,加入如下配置:

.sessionManagement()
 .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
 .and()
.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
  • 因為我們使用了JWT,表明了我們的應用是一個前后端分離的應用,所以我們可以開啟STATELESS禁止使用session。
  • 當然這并不絕對,前后端分離的應用通過一些辦法也是可以使用session的,這不是本文的核心內容不做贅述。
  • 將我們的自定義jwtAuthenticationTokenFilter,加載到UsernamePasswordAuthenticationFilter的前面。

五、測試一下:

測試登錄接口,即:獲取token的接口。輸入正確的用戶名、密碼即可獲取token。

下面我們訪問一個我們定義的簡單的接口“/hello”,但是不傳遞JWT令牌,結果是禁止訪問。當我們將上一步返回的token,傳遞到header中,就能正常響應hello的接口結果。

總結

以上所述是小編給大家介紹的Spring Security代碼實現JWT接口權限授予與校驗功能,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對腳本之家網站的支持!
如果你覺得本文對你有幫助,歡迎轉載,煩請注明出處,謝謝!

相關文章

  • @insert mybatis踩坑記錄,實體接收前端傳遞的參數

    @insert mybatis踩坑記錄,實體接收前端傳遞的參數

    這篇文章主要介紹了@insert mybatis踩坑記錄,實體接收前端傳遞的參數問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-07-07
  • Java中forEach使用lambda表達式,數組和集合的區(qū)別說明

    Java中forEach使用lambda表達式,數組和集合的區(qū)別說明

    這篇文章主要介紹了Java中forEach使用lambda表達式,數組和集合的區(qū)別說明,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-07-07
  • springboot整合JavaCV實現視頻截取第N幀并保存圖片

    springboot整合JavaCV實現視頻截取第N幀并保存圖片

    這篇文章主要為大家詳細介紹了springboot如何整合JavaCV實現視頻截取第N幀并保存為圖片,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起了解一下
    2023-08-08
  • java方法重載示例

    java方法重載示例

    方法重載是以統(tǒng)一的方式處理不同數據類型的一種手段,這篇文章主要介紹了java方法重載示例,需要的朋友可以參考下
    2014-03-03
  • 雙重檢查鎖定模式Java中的陷阱案例

    雙重檢查鎖定模式Java中的陷阱案例

    這篇文章主要介紹了雙重檢查鎖定模式Java中的陷阱,雙重檢查鎖定(也叫做雙重檢查鎖定優(yōu)化)是一種軟件設計模式,它的作用是減少延遲初始化在多線程環(huán)境下獲取鎖的次數,尤其是單例模式下比較突出,想具體了解的小伙伴可以參考下面文章內容,附呦詳細的舉例說明
    2021-10-10
  • classpath和classpath*的區(qū)別詳解

    classpath和classpath*的區(qū)別詳解

    這篇文章主要為大家介紹了classpath和classpath*的區(qū)別詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-10-10
  • Springboot工具類ReflectionUtils使用教程

    Springboot工具類ReflectionUtils使用教程

    這篇文章主要介紹了Springboot內置的工具類之ReflectionUtils的使用,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習吧
    2022-12-12
  • 分布式醫(yī)療掛號系統(tǒng)SpringCache與Redis為數據字典添加緩存

    分布式醫(yī)療掛號系統(tǒng)SpringCache與Redis為數據字典添加緩存

    這篇文章主要為大家介紹了分布式醫(yī)療掛號系統(tǒng)SpringCache與Redis為數據字典添加緩存,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-04-04
  • PowerJob LockService方法工作流程源碼解讀

    PowerJob LockService方法工作流程源碼解讀

    這篇文章主要為大家介紹了PowerJob LockService方法工作流程源碼解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2024-01-01
  • java.sql.Date和java.util.Date的區(qū)別詳解

    java.sql.Date和java.util.Date的區(qū)別詳解

    Java中有兩個Date類,一個是java.util.Date通常情況下用它獲取當前時間或構造時間,另一個是java.sql.Date是針對SQL語句使用的,它只包含日期而沒有時間部分,這篇文章主要給大家介紹了關于java.sql.Date和java.util.Date區(qū)別的相關資料,需要的朋友可以參考下
    2023-03-03

最新評論