iT邦幫忙

2021 iThome 鐵人賽

DAY 10
0
永豐金融APIs

30天全端挑戰!React+Spring Boot+Mongo DB 串接永豐API 打造金融網站系列 第 10

[Day 10] - Spring Boot 實作登入驗證(四)(JWT登入驗證)

今天就來完成登入驗證的部分!

昨天已經完成發送帳號密碼到api,驗證ok即發送一筆JWT給client,
接下來要實作的部分就是要讓除了登入url(/api/v1/user/login)以外的url
都需先驗證是否帶有正確的JWT,否則不放行。

為了做到以上功能,需要實作filter,這邊繼承OncePerRequestFilter,確保request只會被filter過濾一次

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.fasterxml.jackson.databind.ObjectMapper;

import org.springframework.web.filter.OncePerRequestFilter;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;

import static org.springframework.http.HttpHeaders.AUTHORIZATION;
import static org.springframework.http.HttpStatus.FORBIDDEN;
import static org.springframework.http.HttpStatus.UNAUTHORIZED;
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;

public class AuthorizationCheckFilter extends OncePerRequestFilter{

    @Override
    protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws ServletException, IOException {
                //如果不是登入就攔截
                if(!req.getServletPath().equals("/api/v1/user/login")){
                    String authorHeader =  req.getHeader(AUTHORIZATION);
                    String bearer ="Bearer ";
                    //以jjwt驗證token,只要驗證成功就放行
                    //驗證失敗會拋exception,直接將錯誤訊息傳回
                    if(authorHeader!= null && authorHeader.startsWith(bearer)){
                        try{
                        String token = authorHeader.substring(bearer.length());
                        Claims claims = Jwts.parser().setSigningKey("MySecret")
                        .parseClaimsJws(token).getBody();

                        System.out.println("JWT payload:"+claims.toString());

                        chain.doFilter(req, res);
                        
                        }catch(Exception e){
                            System.err.println("Error : "+e);
                            res.setStatus(FORBIDDEN.value());
                            
                            Map<String, String> err = new HashMap<>();
                            err.put("jwt_err", e.getMessage());
                            res.setContentType(APPLICATION_JSON_VALUE);
                            new ObjectMapper().writeValue(res.getOutputStream(), err);
                        }
                    }else{
                        res.setStatus(UNAUTHORIZED.value());
                    }
                }else{
                    chain.doFilter(req, res);
                }
        
    }

}

這裡我以最符合JWT精神的方式來做驗證,只要驗證傳來的token確實是我發出的,就直接放行,
查資料時看有人分享的實作範例除了驗證jwt之外,還會取出sub來跟資料庫做比對,確認是不是真的有這個用戶,個人覺得算是比較嚴謹的作法。因為就是已經驗證過了,才會發出jwt給客戶,除非我們的私鑰被盜,否則不可能會有不存在的客戶的jwt出現。

說到私鑰,正常來講應該要把它放在資料庫中才是符合資安規範的做法
之後有空再改為從資料庫讀取參數/images/emoticon/emoticon25.gif

接下來就是Spring Security

我在[Day 06] - 用Spring Boot 建立Controller建立了WebSecurityConfig,java

將剛剛寫好的filter加進來:

import com.rei1997.vault.util.filter.AuthorizationCheckFilter;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;


@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //關掉csrf保護
        http.csrf().disable();
        //不寫session了
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        //加上剛剛寫的filter
        http.addFilterBefore(new AuthorizationCheckFilter(), BasicAuthenticationFilter.class);
    }  

}

就這樣

來驗證一下,我在Controller寫了一個簡單的getMapping的方法,會回傳User的資訊

當不帶Authorization header時,會回傳401,表示有成功擋下來未授權的request
https://ithelp.ithome.com.tw/upload/images/20210925/20128973cibqP13WeT.png

帶上jwt token時,回200,看來ok
https://ithelp.ithome.com.tw/upload/images/20210925/201289732HZLTGeKsy.png

當jwt過期也會被阻擋:
https://ithelp.ithome.com.tw/upload/images/20210925/20128973qaDhekvFiU.png

說來慚愧,這樣寫下來發現其實根本沒什麼沒用到Spring Security的功能/images/emoticon/emoticon20.gif
未來有機會再看看有什麼地方可以用到
今天就先這樣啦~


上一篇
[Day 09] - Spring Boot 實作登入驗證(三)(JWT實作)
下一篇
[Day 11] - 準備串接永豐匯率API!
系列文
30天全端挑戰!React+Spring Boot+Mongo DB 串接永豐API 打造金融網站30

尚未有邦友留言

立即登入留言