1. 程式人生 > 程式設計 >springboot以FTP方式上傳檔案到遠端伺服器的流程

springboot以FTP方式上傳檔案到遠端伺服器的流程

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

一、環境準備工作

建立Spring Boot專案並集成了Spring Security,專案可以正常啟動

通過controller寫一個HTTP的GET方法服務介面,比如:“/hello”

實現最基本的動態資料驗證及許可權分配,即實現UserDetailsService介面和UserDetails介面。這兩個介面都是向Spring Security提供使用者、角色、許可權等校驗資訊的介面

如果你學習過Spring Security的formLogin登入模式,請將HttpSecurity配置中的formLogin()配置段全部去掉。因為JWT完全使用JSON介面,沒有from表單提交。

HttpSecurity配置中一定要加上csrf().disable(),即暫時關掉跨站攻擊CSRF的防禦。這樣是不安全的,我們後續章節再做處理。

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

## 二、開發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提供的方法開發JWT令牌生成、重新整理的工具類。

三、開發登入介面(獲取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業務邏輯寫在JwtAuthService 中

login方法中首先使用使用者名稱、密碼進行登入驗證。如果驗證失敗丟擲BadCredentialsException異常。如果驗證成功,程式繼續向下走,生成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獲得系統使用者(從資料庫、或其他其儲存介質)

根據使用者資訊和JWT令牌,驗證系統使用者與使用者輸入的一致性,並判斷JWT是否過期。如果沒有過期,至此表明了該使用者的確是該系統的使用者。

但是,你是系統使用者不代表你可以訪問所有的介面。所以需要構造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的介面結果。

總結

以上所述是小編給大家介紹的springboot以FTP方式上傳檔案到遠端伺服器的流程,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回覆大家的。在此也非常感謝大家對我們網站的支援!
如果你覺得本文對你有幫助,歡迎轉載,煩請註明出處,謝謝!