前後端分離之Springboot後端
阿新 • • 發佈:2019-01-31
這是上一篇部落格前後端分離之Java後端的重寫.
原始碼
前後端分離的後端主要解決的就2個問題 : 跨域訪問(CORS)和token校驗,下面快速說明.
1.專案環境
使用Intellij IDE.
專案結構:
2.跨域訪問
解決跨域很簡單,翻一下官方文件很容易解決,我們就使用全域性的通過註解實現的方式:
@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
@Override
public void addCorsMappings(CorsRegistry registry) {
//允許全部請求跨域
registry.addMapping("/**");
}
}
3.Token驗證
這節分為2個部分,一是原理,二是程式碼實現.
3.1 原理
在第一篇文章裡,我是這樣說的:
在使用者第一次登入成功後,服務端返回一個token回來,這個token是根據userId進行加密的,金鑰只有伺服器知道,然後瀏覽器每次請求都把這個token放在Header裡請求,這樣伺服器只需進行簡單的解密就知道是哪個使用者了。
3.2 程式碼實現
避免重複造輪子,我們依然使用JWT,這個標準在2015年提出,檢視RFC文件,它的一個實現JJWT
<!-- JJWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
我們要做的很簡單 :登入時生成Token,攔截每次請求檢查token.
3.2.1 生成Token與驗證
詳情檢視程式碼註釋
public class JwtUtil {
final static String base64EncodedSecretKey = "base64EncodedSecretKey";//私鑰
final static long TOKEN_EXP = 1000 * 60;//過期時間,測試使用60秒
public static String getToken(String userName) {
return Jwts.builder()
.setSubject(userName)
.claim("roles", "user")
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + TOKEN_EXP)) /*過期時間*/
.signWith(SignatureAlgorithm.HS256, base64EncodedSecretKey)
.compact();
}
/**
* @Date:17-12-12 下午6:21
* @Author:root
* @Desc:檢查token,只要不正確就會丟擲異常
**/
public static void checkToken(String token) throws ServletException {
try {
final Claims claims = Jwts.parser().setSigningKey(base64EncodedSecretKey).parseClaimsJws(token).getBody();
} catch (ExpiredJwtException e1) {
throw new ServletException("token expired");
} catch (Exception e) {
throw new ServletException("other token exception");
}
}
}
3.2.2 攔截token
在spring裡很好實現全域性攔截,過濾器,攔截器,AOP都可以實現.
因為filter是對資源過濾,我們這裡沒有資源了,只有URL,而AOP著重處理過程.綜合考慮,這裡選擇攔截器比較合適.
我們的攔截器:先檢查header,取出token,驗證.
public class JwtInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String authHeader = request.getHeader("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
throw new ServletException("invalid Authorization header");
}
//取得token
String token = authHeader.substring(7);
try {
JwtUtil.checkToken(token);
return true;
} catch (Exception e) {
throw new ServletException(e.getMessage());
}
}
}
註冊攔截器:將登入排除
@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
@Override
public void addCorsMappings(CorsRegistry registry) {
//允許全部請求跨域
registry.addMapping("/**");
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
//新增攔截器
registry.addInterceptor(new JwtInterceptor()).excludePathPatterns("/user/login");
}
}
全域性異常處理:
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public String handleException(Exception e) {
return "err:" + e.getMessage();
}
}
登入:
@PostMapping("/login")
public String login(User user) throws ServletException {
String name = user.getUsername();
String pass = user.getPassword();
if (!"admin".equals(name)) {
throw new ServletException("no such user");
}
if (!"1234".equals(pass)) {
throw new ServletException("wrong password");
}
return JwtUtil.getToken(name);
}
其他根據需要可以檢視原始碼.
當然,整個系統我沒有使用RESTful的統一API,可以自定義一個類去處理,這裡不重要.
4.如何請求
將得到的token封裝在header裡,如下:
這種請求放在Axios這樣的請求框架很好實現,特別是在React或Vue裡.