我們花了很多的時間在處理token相關的設定,接下來我們要使用token通過Spring Security的驗證。
首先要修改SecurityConfig.java,在csrf的下一行添加這些內容,讓Spring Security優先處理JWT認證。
在基本權限驗證的filter前,加上JWT認證的filter
.addFilterBefore(new JWTAuthenticationFilter(), BasicAuthenticationFilter.class)
接著,我們在Config下建立JWTAuthenticationFilter.java,讓它攔截帶有Authorization的header。
import Authentication時注意,選org.springframework才是我們用的,apache那個是錯誤的。
同一個request只會執行一次,避免重複執行
public class JWTAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
取得header中Authorization中的內容,裡面有JWT
String jwt = request.getHeader("Authorization");
if(jwt != null){
去除開頭的Bearer
我們的token傳輸採用Bearer token的標準,所以傳來的token的前7個字是”Bearer “,後面才是JWT的內容,需要去除這7個字,只留下JWT的部分。
因為substring是從0開始,所以-1。
jwt = jwt.substring("Bearer ".length() - 1);
try{
SecretKey key = Keys.hmacShaKeyFor(JWTConstant.SECRET != null ? JWTConstant.SECRET.getBytes() : null);
我們驗證JWT是否有效,有效就可以提供授權給這個用戶,無效或過期的JWT會發生例外,會跳到catch部分。
同時,解讀JWT中的payload區塊內容
Claims claims = Jwts.parserBuilder()//建立JWT解讀器
.setSigningKey(key)//設定檢驗的密鑰
.build()//產生解讀器
.parseClaimsJws(jwt)//解讀JWT
.getBody();//取得payload內容
從JWT payload取得email
String email = String.valueOf(claims.get("email"));
有效的JWT就等於本人輸入正確的email和密碼,可以通過驗證
授權中帶有用戶的email,代表授權是只給這個用戶使用。
Authentication authentication = new UsernamePasswordAuthenticationToken(email, null, null);
設定通過Spring Security的認證
SecurityContextHolder.getContext().setAuthentication(authentication);
}
驗證時發現JWT無效或過期,就顯示錯誤
catch (Exception e){
throw new BadCredentialsException("Invalid JWT or expired");
}
}
把request傳給下個filter使用
filterChain.doFilter(request, response);
}
}
我們來建立取得授權才能讀取的內容。
建立UserController.java,驗證通過可讀取登入的用戶資訊,只認JWT,沒JWT就算剛登入也不給過。
@RestController
@RequestMapping("/api/user")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/")
public ResponseEntity<User> getUserInfo(@RequestHeader("Authorization") String jwt) throws Exception {
User user = userService.findUserByJWT(jwt);
return new ResponseEntity<>(user, HttpStatus.OK);
}
}
在UserService.java添加findUserByJWT,用JWT包含的email找用戶,別忘了添加用到的jwtProvider。
private final JWTProvider jwtProvider;
public UserService(/*skip*/, JWTProvider jwtProvider) {
//skip
this.jwtProvider = jwtProvider;
}
public User findUserByJWT(String jwt) throws Exception{
String email = jwtProvider.getEmailFromJWT(jwt);
User user = userRepository.findByEmail(email);
if(user == null){
throw new Exception("Error: Invalid JWT");
}
return user;
}
在JWTProvider.java,添加從jwt取得email的程式碼
public String getEmailFromJWT(String jwt) {
jwt = jwt.substring("Bearer ".length() - 1);
Claims claims = Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(jwt).getBody();
return String.valueOf(claims.get("email"));
}
我們啟動專案來測試API
我們就能看到用戶的id、email和加密過的密碼。
我們剛才登入過,取消Authorization的Enabled後,送出請求。
發現如果沒有JWT,即使登入過還是會出現403錯誤。
我們常使用的網路服務都有採用token,最著名的就是Youtube。
有些Youtube頻道被盜用後,開啟直播進行比特幣詐騙,就是因為token被洩露造成的。
有了token,不用知道密碼就能取得用戶的資訊。
這點我們剛才也在專案體驗過了,所以大家要保護好自己的token,不要執行可疑的程式。