1. 程式人生 > >前後端分離之Springboot後端

前後端分離之Springboot後端

這是上一篇部落格前後端分離之Java後端的重寫.
原始碼
前後端分離的後端主要解決的就2個問題 : 跨域訪問(CORS)token校驗,下面快速說明.

1.專案環境

使用Intellij IDE.
專案結構:
2

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裡,如下:
1

這種請求放在Axios這樣的請求框架很好實現,特別是在React或Vue裡.