SpringBoot 如何使用 JWT 保護 Rest Api 接口
用 spring-boot 開發(fā) RESTful API 非常的方便,在生產(chǎn)環(huán)境中,對發(fā)布的 API 增加授權(quán)保護是非常必要的?,F(xiàn)在我們來看如何利用 JWT 技術(shù)為 API 增加授權(quán)保護,保證只有獲得授權(quán)的用戶才能夠訪問 API。
一、Jwt 介紹
JSON Web Token (JWT)是一個開放標準(RFC 7519),它定義了一種緊湊的、自包含的方式,
用于作為 JSON 對象在各方之間安全地傳輸信息。該信息可以被驗證和信任,因為它是數(shù)字簽名的。
Jwt 主要應用場景:授權(quán)
Authorization (授權(quán)) : 這是使用 JWT 的最常見場景。一旦用戶登錄,后續(xù)每個請求都將包含 JWT,允許用戶訪問該令牌允許的路由、服務和資源。單點登錄是現(xiàn)在廣泛使用的 JWT 的
一個特性,因為它的開銷很小,并且可以輕松地跨域使用。
在認證的時候,當用戶用他們的憑證成功登錄以后,一個 JSON Web Token 將會被返回。此后,token 就是用戶憑證了。為什么不用 session:因為做了完全的前后端分離,前段頁面每次發(fā)出 Ajax 請求都會建立一個新的回話請求,沒辦法通過 session 來記錄跟蹤用戶回話狀態(tài)。所以采用 JWT,來完成回話跟蹤、身份驗證。Session 是在服務器端的,而 JWT 是在客戶端的。
JWT 使用流程:
- 用戶攜帶用戶名和密碼請求訪問
- 服務器校驗用戶憑據(jù)
- 應用提供一個 token 給客戶端
- 客戶端存儲 token,并且在隨后的每一次請求中都帶著它
- 服務器校驗 token 并返回數(shù)據(jù)
二、搭建基礎(chǔ) SpringBoot 工程
2.1、新建一個 SpringBoot 工程,引入所需依賴包
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
2.2、編寫測試 Controller HelloController
package com.offcn.controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class HelloController { @RequestMapping("/hello") public String hello(){ return "hello"; } }
2.3、編寫應用主啟動類
package com.offcn; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; @SpringBootApplication public class SpringbootSecurityJwtApplication { public static void main(String[] args) { SpringApplication.run(SpringbootSecurityJwtApplication.class, args); } }
2.4、測試應用
訪問地址:http://localhost:8080/hello
至此,我們的接口就開發(fā)完成了。但是這個接口沒有任何授權(quán)防護,任何人都可以訪問,這
樣是不安全的,下面我們開始加入授權(quán)機制。
三、增加用戶注冊功能
3.1、導入數(shù)據(jù)庫所需依賴包
<!-- spring-data-jpa --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <!-- mysql --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.30</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency>
3.2、修改配置文件 application.yml 配置數(shù)據(jù)庫連接
spring: datasource: url: jdbc:mysql://localhost:3306/springboot-security?serverTimezone=GMT%2B8 username: root password: 123 driver-class-name: com.mysql.jdbc.Driver jpa: hibernate: ddl-auto: update show-sql: true application: name: demo1 #配置應用名稱
3.3、新建一個實體類 User
@Entity @Table(name = "tb_user") @Data @NoArgsConstructor @AllArgsConstructor public class User { @Id @GeneratedValue private Long id; private String username; private String password; }
3.4、新建一個 dao 實現(xiàn) JpaRepository 接口
package com.offcn.dao; import com.offcn.po.User; import org.springframework.data.jpa.repository.JpaRepository; public interface UserDao extends JpaRepository<User,Long> { User findByUsername(String username); }
3.5、新建 UserController 類中增加注冊方法,實現(xiàn)用戶注冊的接口
package com.offcn.controller; import com.offcn.dao.UserDao; import com.offcn.po.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; @Controller @RequestMapping("/users") public class UserController { @Autowired private UserDao userDao; /** * 該方法是注冊用戶的方法,默認放開訪問控制 * @param user */ @PostMapping("/signup") @ResponseBody public String signUp(@RequestBody User user) { user.setPassword(user.getPassword()); try { userDao.save(user); return "success"; } catch (Exception e) { e.printStackTrace(); return "error"; } } }
3.6 測試用戶注冊
請求地址:http://localhost:8080/users/signup
四、添加 JWT 認證
用戶填入用戶名密碼后,與數(shù)據(jù)庫里存儲的用戶信息進行比對,如果通過,則認證成功。傳統(tǒng)的方法是在認證通過后,創(chuàng)建 sesstion,并給客戶端返回 cookie?,F(xiàn)在我們采用 JWT 來處理用戶名密碼的認證。區(qū)別在于,認證通過后,服務器生成一個 token,將 token 返回給客戶端,客戶端以后的所有請求都需要在 http 頭中指定該 token。服務器接收的請求后,會對
token 的合法性進行驗證。驗證的內(nèi)容包括:內(nèi)容是一個正確的 JWT 格式 、檢查簽名 、檢查 claims 、檢查權(quán)限。
4.1、導入 JWT 及 SpringSecurity 所需依賴包
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.7.0</version> </dependency>
4.2、編寫登錄處理過濾器類 JWTLoginFilter
核心功能是在驗證用戶名密碼正確后,生成一個 token,并將 token 返回給客戶端 該類繼承自 UsernamePasswordAuthenticationFilter,重寫了其中的 2 個方法:
- attemptAuthentication :接收并解析用戶憑證。
- successfulAuthentication :用戶成功登錄后,這個方法會被調(diào)用,我們在這個方法里生成 token。
/** * 驗證用戶名密碼正確后,生成一個 token,并將 token 返回給客戶端 * 該類繼承自 UsernamePasswordAuthenticationFilter,重寫了其中的 2 個方法 * attemptAuthentication :接收并解析用戶憑證。 * successfulAuthentication :用戶成功登錄后,這個方法會被調(diào)用,我們在這個方法里生成 token。 * */ public class JWTLoginFilter extends UsernamePasswordAuthenticationFilter { private AuthenticationManager authenticationManager; public JWTLoginFilter(AuthenticationManager authenticationManager) { this.authenticationManager = authenticationManager; } // 接收并解析用戶憑證 @Override public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res) throws AuthenticationException { try { User user = new ObjectMapper() .readValue(req.getInputStream(), User.class); return authenticationManager.authenticate( new UsernamePasswordAuthenticationToken( user.getUsername(), user.getPassword(), new ArrayList<>()) ); } catch (IOException e) { throw new RuntimeException(e); } } // 用戶成功登錄后,這個方法會被調(diào)用,我們在這個方法里生成 token @Override protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res, FilterChain chain, Authentication auth) throws IOException, ServletException { String token = Jwts.builder() .setSubject(((org.springframework.security.core.userdetails.User) auth.getPrincipal()).getUsername()) .setExpiration(new Date(System.currentTimeMillis() + 60 * 60 * 24 * 1000)) .signWith(SignatureAlgorithm.HS512, "MyJwtSecret") .compact(); res.addHeader("Authorization", "Bearer " + token); } }
4.3、編寫 Token 校驗過濾器類 JWTAuthenticationFilter
用戶一旦登錄成功后,會拿到 token,后續(xù)的請求都會帶著這個 token,服務端會驗證 token的合法性。該類繼承自 BasicAuthenticationFilter,在 doFilterInternal 方法中,從 http 頭的 Authorization 項讀取 token 數(shù)據(jù),然后用 Jwts 包提供的方法校驗 token 的合法性。如果校驗通過,就認為這是一個取得授權(quán)的合法請求。
/** * token 的校驗 * 該類繼承自 BasicAuthenticationFilter,在 doFilterInternal 方法中, * 從 http 頭的 Authorization 項讀取 token 數(shù)據(jù),然后用 Jwts 包提供的方法校驗 token 的合 法性。 * 如果校驗通過,就認為這是一個取得授權(quán)的合法請求 * */ public class JWTAuthenticationFilter extends BasicAuthenticationFilter { public JWTAuthenticationFilter(AuthenticationManager authenticationManager) { super(authenticationManager); } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { String header = request.getHeader("Authorization"); if (header == null || !header.startsWith("Bearer ")) { chain.doFilter(request, response); return; } UsernamePasswordAuthenticationToken authentication = getAuthentication(request); SecurityContextHolder.getContext().setAuthentication(authentication); chain.doFilter(request, response); } private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) { String token = request.getHeader("Authorization"); if (token != null) { // parse the token. String user = Jwts.parser() .setSigningKey("MyJwtSecret") .parseClaimsJws(token.replace("Bearer ", "")) .getBody() .getSubject(); if (user != null) { return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>()); } return null; } return null; } }
五、SpringSecurity 配置集成 JWT 認證
5.1、編寫 SpringSecurity 用戶及權(quán)限驗證類UserDetailServiceImpl
@Service("userDetailServiceImpl") public class UserDetailServiceImpl implements UserDetailsService { @Autowired private UserDao userDao; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { //根據(jù)用戶名查詢用戶信息 com.offcn.po.User user = userDao.findByUsername(username); //為用戶授權(quán)(暫時未驗證權(quán)限) List<GrantedAuthority> grantedAuthorityList=new ArrayList<>(); grantedAuthorityList.add(new SimpleGrantedAuthority("ROLE_USER")); return new User(username,user.getPassword(),grantedAuthorityList); } }
5.2、修改程序主啟動類,增加密碼加密生成器配置
@SpringBootApplication public class SpringbootSecurityJwtApplication { public static void main(String[] args) { SpringApplication.run(SpringbootSecurityJwtApplication.class, args); } @Bean public BCryptPasswordEncoder bCryptPasswordEncoder() { return new BCryptPasswordEncoder(); } }
5.3、編寫 SpringSecurity 配置類 WebSecurityConfig
通過 SpringSecurity 的配置,將上面的 JWT 過濾器類組合在一起。
/** * SpringSecurity 的配置 * 通過 SpringSecurity 的配置,將 JWTLoginFilter,JWTAuthenticationFilter 組合在一起 * */ @Configuration @Order(SecurityProperties.DEFAULT_FILTER_ORDER) public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsService userDetailServiceImpl; @Autowired private BCryptPasswordEncoder bCryptPasswordEncoder; @Override protected void configure(HttpSecurity http) throws Exception { http.cors().and().csrf().disable().authorizeRequests() .antMatchers(HttpMethod.POST, "/users/signup").permitAll() .anyRequest().authenticated() .and() .addFilter(new JWTLoginFilter(authenticationManager())) .addFilter(new JWTAuthenticationFilter(authenticationManager())); } @Override public void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailServiceImpl).passwordEncoder(bCryptPasswordEncod er); } }
5.4、修改 UserController 類 增加注冊加密密碼
@Controller @RequestMapping("/users") public class UserController { @Autowired private BCryptPasswordEncoder bCryptPasswordEncoder; @Autowired private UserDao userDao; /** * 該方法是注冊用戶的方法,默認放開訪問控制 * @param user */ @PostMapping("/signup") @ResponseBody public String signUp(@RequestBody User user) { user.setPassword(bCryptPasswordEncoder.encode(user.getPassword())); try { userDao.save(user); return "success"; } catch (Exception e) { e.printStackTrace(); return "error"; } } }
六、測試 Token
6.1、測試請求 hello 接口
請求地址:http://localhost:8080/hello
6.2、重新注冊一個賬號
清空數(shù)據(jù)表
重新注冊一個賬號:
請求地址:http://localhost:8080/users/signup
查看數(shù)據(jù)庫
6.3、測試登錄
請求地址:http://localhost:8080/login
發(fā) post 請求
響應的請求頭 Authorization 的值就是 token
Bearer
eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ0ZXN0IiwiZXhwIjoxNTY1MjY0OTQxfQ.CW-QwtE1Q2
Z69NNUnH_wPIaJjJpTFnh8eR3z03ujw-hb3aMO61yuir6w-T0X0FdV9k2WQrj903J9VDz6ijPJt
Q
6.4、用登錄后的 token 再次請求 hello 接口
注意:在請求頭中攜帶 token
請求頭名稱:Authorization
攜帶對應的 token 值。
可以看到正常響應結(jié)果。
到此這篇關(guān)于SpringBoot 如何使用 JWT 保護 Rest Api 接口的文章就介紹到這了,更多相關(guān)SpringBoot 使用 JWT 內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot集成redis實現(xiàn)共享存儲session
這篇文章主要介紹了SpringBoot集成redis實現(xiàn)共享存儲session的流程步驟,文中通過代碼示例介紹的非常詳細,并總結(jié)了一些常見的錯誤及解決方法,需要的朋友可以參考下2024-03-03java.util.Date與java.sql.Date的區(qū)別
這篇文章主要介紹了java.util.Date與java.sql.Date的區(qū)別的相關(guān)資料,需要的朋友可以參考下2015-07-07SpringBoot-Admin實現(xiàn)微服務監(jiān)控+健康檢查+釘釘告警
本文主要介紹了SpringBoot-Admin實現(xiàn)微服務監(jiān)控+健康檢查+釘釘告警,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-10-10Java根據(jù)前端返回的字段名進行查詢數(shù)據(jù)的實現(xiàn)方法
在Java后端開發(fā)中,我們經(jīng)常需要根據(jù)前端傳遞的參數(shù)(如字段名)來動態(tài)查詢數(shù)據(jù)庫中的數(shù)據(jù),這種需求通常出現(xiàn)在需要實現(xiàn)通用查詢功能或者復雜查詢接口的場景中,所以本文介紹了Java根據(jù)前端返回的字段名進行查詢數(shù)據(jù)的實現(xiàn)方法,需要的朋友可以參考下2024-12-12SpringBoot2使用WebFlux函數(shù)式編程的方法
這篇文章主要介紹了SpringBoot2使用WebFlux函數(shù)式編程的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-08-08Java基礎(chǔ)之FileInputStream和FileOutputStream流詳解
這篇文章主要介紹了Java基礎(chǔ)之FileInputStream和FileOutputStream流詳解,文中有非常詳細的代碼示例,對正在學習java基礎(chǔ)的小伙伴們有非常好的幫助,需要的朋友可以參考下2021-04-04