java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > SpringBoot 使用 JWT

SpringBoot 如何使用 JWT 保护 Rest Api 接口

作者:nihui123

使用spring-boot开发RESTful API非常的方便,在生产环境中,对发布的 API 增加授权保护是非常必要的,现在我们来看如何利用JWT技术为API 增加授权保护,保证只有获得授权的用户才能够访问 API,感兴趣的朋友跟随小编一起看看吧

用 spring-boot 开发 RESTful API 非常的方便,在生产环境中,对发布的 API 增加授权保护是非常必要的。现在我们来看如何利用 JWT 技术为 API 增加授权保护,保证只有获得授权的用户才能够访问 API。

一、Jwt 介绍

  JSON Web Token (JWT)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,
用于作为 JSON 对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。
Jwt 主要应用场景:授权
  Authorization (授权) : 这是使用 JWT 的最常见场景。一旦用户登录,后续每个请求都将包含 JWT,允许用户访问该令牌允许的路由、服务和资源。单点登录是现在广泛使用的 JWT 的
一个特性,因为它的开销很小,并且可以轻松地跨域使用。
  在认证的时候,当用户用他们的凭证成功登录以后,一个 JSON Web Token 将会被返回。此后,token 就是用户凭证了。为什么不用 session:因为做了完全的前后端分离,前段页面每次发出 Ajax 请求都会建立一个新的回话请求,没办法通过 session 来记录跟踪用户回话状态。所以采用 JWT,来完成回话跟踪、身份验证。Session 是在服务器端的,而 JWT 是在客户端的。

JWT 使用流程:

二、搭建基础 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
  至此,我们的接口就开发完成了。但是这个接口没有任何授权防护,任何人都可以访问,这
样是不安全的,下面我们开始加入授权机制。

三、增加用户注册功能

3.1、导入数据库所需依赖包

<!-- 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 配置数据库连接

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 实现 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 类中增加注册方法,实现用户注册的接口

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 认证

  用户填入用户名密码后,与数据库里存储的用户信息进行比对,如果通过,则认证成功。传统的方法是在认证通过后,创建 sesstion,并给客户端返回 cookie。现在我们采用 JWT 来处理用户名密码的认证。区别在于,认证通过后,服务器生成一个 token,将 token 返回给客户端,客户端以后的所有请求都需要在 http 头中指定该 token。服务器接收的请求后,会对
token 的合法性进行验证。验证的内容包括:内容是一个正确的 JWT 格式 、检查签名 、检查 claims 、检查权限。

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 个方法:

/**
* 验证用户名密码正确后,生成一个 token,并将 token 返回给客户端
* 
该类继承自 UsernamePasswordAuthenticationFilter,重写了其中的 2 个方法 
* 
attemptAuthentication :接收并解析用户凭证。 
* successfulAuthentication :用户成功登录后,这个方法会被调用,我们在这个方法里生成 
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); 
	} 
}
// 用户成功登录后,这个方法会被调用,我们在这个方法里生成 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,后续的请求都会带着这个 token,服务端会验证 token的合法性。该类继承自 BasicAuthenticationFilter,在 doFilterInternal 方法中,从 http 头的 Authorization 项读取 token 数据,然后用 Jwts 包提供的方法校验 token 的合法性。如果校验通过,就认为这是一个取得授权的合法请求。

/**
* 
token 的校验 
* 
该类继承自 BasicAuthenticationFilter,在 doFilterInternal 方法中, 
* 
从 http 头的 Authorization 项读取 token 数据,然后用 Jwts 包提供的方法校验 token 的合 
法性。
* 如果校验通过,就认为这是一个取得授权的合法请求 
*
*/
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 用户及权限验证类UserDetailServiceImpl

@Service("userDetailServiceImpl") 
public class UserDetailServiceImpl implements UserDetailsService { 
@Autowired 
private UserDao userDao; 
@Override 
public UserDetails loadUserByUsername(String username) throws 
UsernameNotFoundException { 
//根据用户名查询用户信息 
com.offcn.po.User user = userDao.findByUsername(username); 
//为用户授权(暂时未验证权限) 
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、重新注册一个账号

清空数据表
重新注册一个账号:
请求地址:http://localhost:8080/users/signup

查看数据库

6.3、测试登录

请求地址:http://localhost:8080/login
发 post 请求

响应的请求头 Authorization 的值就是 token
Bearer
eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ0ZXN0IiwiZXhwIjoxNTY1MjY0OTQxfQ.CW-QwtE1Q2
Z69NNUnH_wPIaJjJpTFnh8eR3z03ujw-hb3aMO61yuir6w-T0X0FdV9k2WQrj903J9VDz6ijPJt
Q

6.4、用登录后的 token 再次请求 hello 接口

注意:在请求头中携带 token
请求头名称:Authorization
携带对应的 token 值。
可以看到正常响应结果。

到此这篇关于SpringBoot 如何使用 JWT 保护 Rest Api 接口的文章就介绍到这了,更多相关SpringBoot 使用 JWT 内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文