下面我会提供一个基于Spring Boot、Spring Security 和 JSON Web Token(JWT)的认证示例。
一、什么是JWT
JSON Web Token(JWT)是一个开放标准(RFC 7519),它定义了一种简单的、自包含的方式,用于在通过网络进行传输的两个实体之间安全传递信息。它被称为自包含是因为JWT包含了所有身份验证(Authentication)相关的信息,如用户id、用户名等,并且它被设计为在不需调用1次身份验证API的情况下即可用于多个操作(Authorization)。通常情况下,JWT在前端生成、存储并传输给后端进行校验,从而实现身份认证和授权。
JWT 的数据格式是一个由点(.)分割的三部分,这三部分是:
头部(Header):包含JWT的元数据,例如签名算法。
载荷(Payload):包含需要传输的数据。Payload 也被称为声明(claims)。
签名(Signature):由前两个部分的组合生成,从而确保信息在传输过程中没有被篡改。签名算法通常使用HMAC SHA256或RSA SHA256等加密算法。
对于JWT的生成和验证可以通过第三方库实现,例如Java中的jjwt、Python中的pyjwt等。
二、实现步骤
- 项目搭建并添加所需依赖
首先,我们在项目中添加 Spring Boot、Spring Security 和 jjwt 依赖:
<!-- SpringBoot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.4.0</version>
</dependency>
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>2.4.0</version>
</dependency>
<!-- jjwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.2</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.2</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.2</version>
<scope>runtime</scope>
</dependency>
- 创建用户实体类和DAO
我们需要有一个 User 实体类来存储用户相关信息,包括用户名、密码等。此外,还需要一个 UserDao 接口来实现用户操作的方法,在示例中,我们采用了基于 MySQL 数据库的数据存储方式。这里就不展开讲解实体类和DAO的实现了。
- 对密码进行加密
我们需要在用户注册时就对密码进行加密存储,而不是明文存储。在 Spring Security 中,我们可以通过实现PasswordEncoder接口来完成密码加密的工作。这里我们演示一下BCryptPasswordEncoder的使用方法:
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
- 创建JWT的Token生成工具类
创建一个 JWT 的 token 生成工具类,用于生成和解析 JWT 的 Token。这里采用JJWT库的API实现,上面我们已经在项目中添加了所需依赖。
示例代码如下(请注意,这里的 SECRET_KEY 的值需要根据实际情况设置):
@Component
public class JwtTokenUtil {
private static final String SECRET_KEY = "12345678";
public String generateToken(User user) {
Claims claims = Jwts.claims().setSubject(user.getUsername()); // 将用户名称放进去
claims.put("password", user.getPassword()); // 放入用户密码
return Jwts.builder()
.setClaims(claims) // 放入自定义信息
.setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 24)) // 设置过期时间为1天
.signWith(SignatureAlgorithm.HS256, SECRET_KEY) // 使用签名算法HS256生成签名
.compact();
}
public Claims parseToken(String token) {
try {
return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody();
} catch (Exception e) {
return null;
}
}
}
- 对用户进行身份验证
在Spring Security中,通常需要实现一个 UserDetailsService 接口以指定如何从数据库中获取用户信息,这样 Spring Security 就可以使用此接口来验证用户身份。
@Service
public class UserServiceImpl implements UserDetailsService {
@Autowired
private UserDao userDao;
// 实现接口方法
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Optional<User> user = userDao.findByUsername(username);
if (user.isPresent()) {
return new org.springframework.security.core.userdetails.User(
user.get().getUsername(),
user.get().getPassword(),
Collections.emptyList());
}
throw new UsernameNotFoundException("用户名或密码不正确");
}
}
- 创建登录接口及登录成功后生成 Token
处理用户登录的请求,并在登录成功后生成 JWT Token 并返回给客户端。在示例中,我们采用了表单提交的方式进行登录验证。
这里是一个基本的登录控制器的实现:
@RestController
public class LoginController {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserServiceImpl userService;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest loginRequest) throws Exception {
// 构造 Authentication 对象
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword());
// 通过 AuthenticationManager 进行验证
Authentication authentication = authenticationManager.authenticate(usernamePasswordAuthenticationToken);
SecurityContextHolder.getContext().setAuthentication(authentication); // 将用户信息保存在 SecurityContext 中
// 生成 JWT Token
String token = jwtTokenUtil.generateToken((User)authentication.getPrincipal());
return ResponseEntity.ok(new LoginResponse(token));
}
}
- 创建需要验证Token的API接口
创建需要进行 Token 验证的 API 接口。
示例代码如下:
@RestController
public class ApiController {
@GetMapping("/hello")
public ResponseEntity<?> hello() {
return ResponseEntity.ok("Hello, 这是被身份验证保护的内容!");
}
}
- 实现一个TokenAuthenticationFilter
自定义一个TokenAuthenticationFilter,用于从请求头中获取 jwt token 并进行校验。如果校验成功,则将已验证的 Authentication 对象放入 Spring Security 的 SecurityContext 中,便于后续的访问。
示例代码如下:
public class TokenAuthenticationFilter extends OncePerRequestFilter {
private static final String HEADER = "Authorization";
private static final String PREFIX = "Bearer ";
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Autowired
private UserServiceImpl userService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
try {
String token = getToken(request);
if (token != null && jwtTokenUtil.parseToken(token) != null) {
String username = jwtTokenUtil.parseToken(token).getSubject();
UserDetails userDetails = userService.loadUserByUsername(username);
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
filterChain.doFilter(request, response);
} catch (Exception e) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, e.getMessage());
}
}
private String getToken(HttpServletRequest request) {
String authHeader = request.getHeader(HEADER);
if (authHeader != null && authHeader.startsWith(PREFIX)) {
return authHeader.replace(PREFIX, "");
}
return null;
}
}
- 配置Security,添加TokenAuthenticationFilter
在 Spring Security 的配置文件中添加TokenAuthenticationFilter。
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserServiceImpl userService;
@Autowired
private TokenAuthenticationFilter tokenAuthenticationFilter;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers(HttpMethod.POST, "/login").permitAll() // 开放POST的/login接口
.anyRequest().authenticated() // 其它接口都需要身份验证
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 不用session机制
.and()
.addFilterBefore(tokenAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
.exceptionHandling()
.authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED)); // 认证失败返回 401 状态码
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
// 注册 TokenAuthenticationFilter
@Bean
public TokenAuthenticationFilter authenticationTokenFilterBean() {
return new TokenAuthenticationFilter();
}
}
三、实际应用
以上就是一个简单的 Spring Boot + Spring Security + JWT 实现身份验证的完整攻略。在实际应用中,可以依据需求进行安全策略的制定,加固授权机制等。
接下来给出一个基于上面实现的Token的简单API调用示例,为了方便,我们使用Postman进行调用。
示例一:调用 /login 接口并返回 Token
访问URL: http://localhost:8080/login
提交数据结构和示例:
{
"username": "admin",
"password": "123456"
}
返回Token:
{
"token": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsInBhc3N3b3JkIjoiJDJhJDA4JFlPcVV6dHVucDB5NG5qd1JCb0t0U3liWldRSlNTRkxiNG9EM3B5ZFFRUjdvU3drYWQzLlBycyIsImV4cCI6MTYwNzQzNjAzOH0.UgfjTTJFKuErdp0rJ-wVZowv0aT_3HbJ5BPn5xC2r-Y"
}
示例二:调用被保护的 /hello 接口
URL: http://localhost:8080/hello
Headers中添加下面这行:
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsInBhc3N3b3JkIjoiJDJhJDA4JFlPcVV6dHVucDB5NG5qd1JCb0t0U3liWldRSlNTRkxiNG9EM3B5ZFFRUjdvU3drYWQzLlBycyIsImV4cCI6MTYwNzQzNjAzOH0.UgfjTTJFKuErdp0rJ-wVZowv0aT_3HbJ5BPn5xC2r-Y
返回内容:
Hello, 这是被身份验证保护的内容!
好了,至此,我们已经实现了一个简单的 Token 验证示例。希望对你能有所帮助。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:SpringBoot+SpringSecurity+jwt实现验证 - Python技术站