SpringBoot 如何使用 JWT 保護(hù) Rest Api 接口
用 spring-boot 開發(fā) RESTful API 非常的方便,在生產(chǎn)環(huán)境中,對(duì)發(fā)布的 API 增加授權(quán)保護(hù)是非常必要的?,F(xiàn)在我們來看如何利用 JWT 技術(shù)為 API 增加授權(quán)保護(hù),保證只有獲得授權(quán)的用戶才能夠訪問 API。
一、Jwt 介紹
JSON Web Token (JWT)是一個(gè)開放標(biāo)準(zhǔn)(RFC 7519),它定義了一種緊湊的、自包含的方式,
用于作為 JSON 對(duì)象在各方之間安全地傳輸信息。該信息可以被驗(yàn)證和信任,因?yàn)樗菙?shù)字簽名的。
Jwt 主要應(yīng)用場(chǎng)景:授權(quán)
Authorization (授權(quán)) : 這是使用 JWT 的最常見場(chǎng)景。一旦用戶登錄,后續(xù)每個(gè)請(qǐng)求都將包含 JWT,允許用戶訪問該令牌允許的路由、服務(wù)和資源。單點(diǎn)登錄是現(xiàn)在廣泛使用的 JWT 的
一個(gè)特性,因?yàn)樗拈_銷很小,并且可以輕松地跨域使用。
在認(rèn)證的時(shí)候,當(dāng)用戶用他們的憑證成功登錄以后,一個(gè) JSON Web Token 將會(huì)被返回。此后,token 就是用戶憑證了。為什么不用 session:因?yàn)樽隽送耆那昂蠖朔蛛x,前段頁面每次發(fā)出 Ajax 請(qǐng)求都會(huì)建立一個(gè)新的回話請(qǐng)求,沒辦法通過 session 來記錄跟蹤用戶回話狀態(tài)。所以采用 JWT,來完成回話跟蹤、身份驗(yàn)證。Session 是在服務(wù)器端的,而 JWT 是在客戶端的。
JWT 使用流程:
- 用戶攜帶用戶名和密碼請(qǐng)求訪問
- 服務(wù)器校驗(yàn)用戶憑據(jù)
- 應(yīng)用提供一個(gè) token 給客戶端
- 客戶端存儲(chǔ) token,并且在隨后的每一次請(qǐng)求中都帶著它
- 服務(wù)器校驗(yàn) token 并返回?cái)?shù)據(jù)
二、搭建基礎(chǔ) SpringBoot 工程
2.1、新建一個(gè) SpringBoot 工程,引入所需依賴包
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
2.2、編寫測(cè)試 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、編寫應(yīng)用主啟動(dòng)類
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、測(cè)試應(yīng)用
訪問地址:http://localhost:8080/hello
至此,我們的接口就開發(fā)完成了。但是這個(gè)接口沒有任何授權(quán)防護(hù),任何人都可以訪問,這
樣是不安全的,下面我們開始加入授權(quán)機(jī)制。
三、增加用戶注冊(cè)功能
3.1、導(dǎo)入數(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 #配置應(yīng)用名稱
3.3、新建一個(gè)實(shí)體類 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、新建一個(gè) dao 實(shí)現(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 類中增加注冊(cè)方法,實(shí)現(xiàn)用戶注冊(cè)的接口
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;
/**
*
該方法是注冊(cè)用戶的方法,默認(rèn)放開訪問控制
*
@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 測(cè)試用戶注冊(cè)
請(qǐng)求地址:http://localhost:8080/users/signup
四、添加 JWT 認(rèn)證
用戶填入用戶名密碼后,與數(shù)據(jù)庫里存儲(chǔ)的用戶信息進(jìn)行比對(duì),如果通過,則認(rèn)證成功。傳統(tǒng)的方法是在認(rèn)證通過后,創(chuàng)建 sesstion,并給客戶端返回 cookie?,F(xiàn)在我們采用 JWT 來處理用戶名密碼的認(rèn)證。區(qū)別在于,認(rèn)證通過后,服務(wù)器生成一個(gè) token,將 token 返回給客戶端,客戶端以后的所有請(qǐng)求都需要在 http 頭中指定該 token。服務(wù)器接收的請(qǐng)求后,會(huì)對(duì)
token 的合法性進(jìn)行驗(yàn)證。驗(yàn)證的內(nèi)容包括:內(nèi)容是一個(gè)正確的 JWT 格式 、檢查簽名 、檢查 claims 、檢查權(quán)限。
4.1、導(dǎo)入 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
核心功能是在驗(yàn)證用戶名密碼正確后,生成一個(gè) token,并將 token 返回給客戶端 該類繼承自 UsernamePasswordAuthenticationFilter,重寫了其中的 2 個(gè)方法:
- attemptAuthentication :接收并解析用戶憑證。
- successfulAuthentication :用戶成功登錄后,這個(gè)方法會(huì)被調(diào)用,我們?cè)谶@個(gè)方法里生成 token。
/**
* 驗(yàn)證用戶名密碼正確后,生成一個(gè) token,并將 token 返回給客戶端
*
該類繼承自 UsernamePasswordAuthenticationFilter,重寫了其中的 2 個(gè)方法
*
attemptAuthentication :接收并解析用戶憑證。
* successfulAuthentication :用戶成功登錄后,這個(gè)方法會(huì)被調(diào)用,我們?cè)谶@個(gè)方法里生成
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);
}
}
// 用戶成功登錄后,這個(gè)方法會(huì)被調(diào)用,我們?cè)谶@個(gè)方法里生成 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 校驗(yàn)過濾器類 JWTAuthenticationFilter
用戶一旦登錄成功后,會(huì)拿到 token,后續(xù)的請(qǐng)求都會(huì)帶著這個(gè) token,服務(wù)端會(huì)驗(yàn)證 token的合法性。該類繼承自 BasicAuthenticationFilter,在 doFilterInternal 方法中,從 http 頭的 Authorization 項(xiàng)讀取 token 數(shù)據(jù),然后用 Jwts 包提供的方法校驗(yàn) token 的合法性。如果校驗(yàn)通過,就認(rèn)為這是一個(gè)取得授權(quán)的合法請(qǐng)求。
/**
*
token 的校驗(yàn)
*
該類繼承自 BasicAuthenticationFilter,在 doFilterInternal 方法中,
*
從 http 頭的 Authorization 項(xiàng)讀取 token 數(shù)據(jù),然后用 Jwts 包提供的方法校驗(yàn) token 的合
法性。
* 如果校驗(yàn)通過,就認(rèn)為這是一個(gè)取得授權(quán)的合法請(qǐng)求
*
*/
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 認(rèn)證
5.1、編寫 SpringSecurity 用戶及權(quán)限驗(yà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)(暫時(shí)未驗(yàn)證權(quán)限)
List<GrantedAuthority> grantedAuthorityList=new ArrayList<>();
grantedAuthorityList.add(new SimpleGrantedAuthority("ROLE_USER"));
return new User(username,user.getPassword(),grantedAuthorityList);
}
}5.2、修改程序主啟動(dòng)類,增加密碼加密生成器配置
@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 類 增加注冊(cè)加密密碼
@Controller
@RequestMapping("/users")
public class UserController {
@Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
@Autowired
private UserDao userDao;
/**
*
該方法是注冊(cè)用戶的方法,默認(rèn)放開訪問控制
*
@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";
} } }六、測(cè)試 Token
6.1、測(cè)試請(qǐng)求 hello 接口
請(qǐng)求地址:http://localhost:8080/hello
6.2、重新注冊(cè)一個(gè)賬號(hào)
清空數(shù)據(jù)表
重新注冊(cè)一個(gè)賬號(hào):
請(qǐng)求地址:http://localhost:8080/users/signup
查看數(shù)據(jù)庫
6.3、測(cè)試登錄
請(qǐng)求地址:http://localhost:8080/login
發(fā) post 請(qǐng)求
響應(yīng)的請(qǐng)求頭 Authorization 的值就是 token
Bearer
eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ0ZXN0IiwiZXhwIjoxNTY1MjY0OTQxfQ.CW-QwtE1Q2
Z69NNUnH_wPIaJjJpTFnh8eR3z03ujw-hb3aMO61yuir6w-T0X0FdV9k2WQrj903J9VDz6ijPJt
Q
6.4、用登錄后的 token 再次請(qǐng)求 hello 接口
注意:在請(qǐng)求頭中攜帶 token
請(qǐng)求頭名稱:Authorization
攜帶對(duì)應(yīng)的 token 值。
可以看到正常響應(yīng)結(jié)果。
到此這篇關(guān)于SpringBoot 如何使用 JWT 保護(hù) Rest Api 接口的文章就介紹到這了,更多相關(guān)SpringBoot 使用 JWT 內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringMVC通過攔截器實(shí)現(xiàn)IP黑名單
這篇文章主要為大家詳細(xì)介紹了SpringMVC通過攔截器實(shí)現(xiàn)IP黑名單,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-08-08
SpringBoot集成redis實(shí)現(xiàn)共享存儲(chǔ)session
這篇文章主要介紹了SpringBoot集成redis實(shí)現(xiàn)共享存儲(chǔ)session的流程步驟,文中通過代碼示例介紹的非常詳細(xì),并總結(jié)了一些常見的錯(cuò)誤及解決方法,需要的朋友可以參考下2024-03-03
java.util.Date與java.sql.Date的區(qū)別
這篇文章主要介紹了java.util.Date與java.sql.Date的區(qū)別的相關(guān)資料,需要的朋友可以參考下2015-07-07
SpringBoot-Admin實(shí)現(xiàn)微服務(wù)監(jiān)控+健康檢查+釘釘告警
本文主要介紹了SpringBoot-Admin實(shí)現(xiàn)微服務(wù)監(jiān)控+健康檢查+釘釘告警,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-10-10
Java根據(jù)前端返回的字段名進(jìn)行查詢數(shù)據(jù)的實(shí)現(xiàn)方法
在Java后端開發(fā)中,我們經(jīng)常需要根據(jù)前端傳遞的參數(shù)(如字段名)來動(dòng)態(tài)查詢數(shù)據(jù)庫中的數(shù)據(jù),這種需求通常出現(xiàn)在需要實(shí)現(xiàn)通用查詢功能或者復(fù)雜查詢接口的場(chǎng)景中,所以本文介紹了Java根據(jù)前端返回的字段名進(jìn)行查詢數(shù)據(jù)的實(shí)現(xiàn)方法,需要的朋友可以參考下2024-12-12
Java中如何對(duì)字符串進(jìn)行utf-8編碼
這篇文章主要介紹了Java中如何對(duì)字符串進(jìn)行utf-8編碼問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-04-04
SpringBoot2使用WebFlux函數(shù)式編程的方法
這篇文章主要介紹了SpringBoot2使用WebFlux函數(shù)式編程的方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-08-08
Java基礎(chǔ)之FileInputStream和FileOutputStream流詳解
這篇文章主要介紹了Java基礎(chǔ)之FileInputStream和FileOutputStream流詳解,文中有非常詳細(xì)的代碼示例,對(duì)正在學(xué)習(xí)java基礎(chǔ)的小伙伴們有非常好的幫助,需要的朋友可以參考下2021-04-04

