详解SpringBoot+SpringSecurity+jwt整合及初体验
本文将详细讲解如何将SpringBoot、SpringSecurity和jwt整合起来实现用户认证与授权功能,包含完整的代码和详细的步骤,最终实现一个简单的用户登录验证功能。
环境准备
- JDK 1.8
- Maven 3.x
- IDE: 推荐使用IntelliJ IDEA
- Postman:用于测试接口
创建SpringBoot项目
在IDE中创建一个SpringBoot项目,选择Maven作为构建工具,在pom.xml文件中添加如下依赖:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId> // web组件,提供web服务
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId> // security组件,提供安全控制
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId> // jwt组件,提供json web token相关功能
<version>0.9.1</version>
</dependency>
</dependencies>
配置SpringSecurity
在SpringBoot项目的application.properties
文件中添加如下配置:
spring.security.user.name=admin
spring.security.user.password=admin
spring.security.user.roles=ADMIN
该配置表示创建一个用户名为admin,密码为admin,角色为ADMIN的用户。在实际应用中应该使用加密后的密码。
配置JWT
在application.properties
文件中添加如下配置:
jwt.secret=secret-key
jwt.expiration-in-seconds=604800
该配置表示使用secret-key
作为jwt的密钥,jwt的过期时间为一周。
创建一个JwtUtils类用于生成和解析jwt:
@Component
public class JwtUtils {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration-in-seconds}")
private int expirationInSeconds;
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
return Jwts.builder()
.setClaims(claims)
.setSubject(userDetails.getUsername())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + expirationInSeconds * 1000))
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
public String getUsernameFromToken(String token) {
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody()
.getSubject();
}
public boolean validateToken(String token, UserDetails userDetails) {
String username = getUsernameFromToken(token);
return username.equals(userDetails.getUsername()) && !isTokenExpired(token);
}
private boolean isTokenExpired(String token) {
Date expirationDate = Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody()
.getExpiration();
return expirationDate.before(new Date());
}
}
编写登录接口
创建一个AuthController
类,实现用户登录接口:
@RestController
@RequestMapping("/api/auth")
public class AuthController {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtUtils jwtUtils;
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody AuthenticationRequest request) {
try {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword()));
SecurityContextHolder.getContext().setAuthentication(authentication);
String token = jwtUtils.generateToken((UserDetails) authentication.getPrincipal());
return ResponseEntity.ok(new AuthenticationResponse(token));
} catch (AuthenticationException e) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
}
}
其中AuthenticationRequest
表示前端传递的登录请求:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class AuthenticationRequest implements Serializable {
private String username;
private String password;
}
AuthenticationResponse
表示登录成功后返回的jwt:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class AuthenticationResponse implements Serializable {
private String jwtToken;
}
配置Spring Security
创建一个JwtAuthenticationFilter
类用于从Authorization
头部解析jwt,并将用户信息存入Spring Security上下文:
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private JwtUtils jwtUtils;
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String authorizationHeader = request.getHeader("Authorization");
if (StringUtils.hasText(authorizationHeader) && authorizationHeader.startsWith("Bearer ")) {
String jwt = authorizationHeader.substring(7);
String username = jwtUtils.getUsernameFromToken(jwt);
if (StringUtils.hasText(username) && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (jwtUtils.validateToken(jwt, userDetails)) {
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
}
}
filterChain.doFilter(request, response);
}
}
在WebSecurityConfigurerAdapter
中配置JwtAuthenticationFilter
,并启用csrf保护:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private JwtAuthenticationFilter jwtAuthenticationFilter;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/api/auth/**")
.permitAll()
.anyRequest()
.authenticated();
http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}
}
测试登录接口
在Postman中发送POST请求:http://localhost:8080/api/auth/login
,Headers中加入Content-Type: application/json
,请求体中加入:
{
"username": "admin",
"password": "admin"
}
运行后会得到如下结果:
{
"jwtToken": "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhZG1pbiIsImlhdCI6MTYzMzUzMjExOSwiZXhwIjoxNjMzNTU4MTE5fQ.2HVXYQNB5zWgR83SjlYvKvAD-VqyZTbEmyIy_t-sZJ9OdMCuLq0nLWOD4cT8QjL9wUyS9dUly4v6dFYJ7cmRXw"
}
将得到的jwt token放入请求头中继续发送请求,后端会自动将用户信息存入Spring Security上下文中,实现了用户认证与授权功能。
示例1:自定义UserDetailsService
如果需要使用自己实现的UserDetailsService
来加载用户信息,可以按照下面的步骤来实现:
- 创建一个
User
类,表示应用中的用户:
```java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements UserDetails {
private String username;
private String password;
private List<GrantedAuthority> authorities;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
```
- 创建一个实现了
UserDetailsService
接口的类,用于从数据源中加载用户信息:
```java
@Service
public class MyUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 查询数据库并返回UserDetails
return new User(username, "password", List.of(new SimpleGrantedAuthority("ROLE_USER")));
}
}
```
- 在
WebSecurityConfigurerAdapter
中注入UserDetailsService
实现类,进行配置:
```java
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private MyUserDetailsService userDetailsService;
// 省略其他配置
}
```
示例2:自定义加密
默认情况下,Spring Security使用BCrypt进行加密,如果需要使用自己的加密方式,可以按照下面的步骤来实现:
- 创建一个实现了
PasswordEncoder
接口的类,用于加密和校验密码:
```java
@Component
public class MyPasswordEncoder implements PasswordEncoder {
@Override
public String encode(CharSequence rawPassword) {
// 自定义加密方法
return rawPassword.toString() + "hashed";
}
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
// 自定义校验方法
return encodedPassword.equals(rawPassword.toString() + "hashed");
}
}
```
- 将
MyPasswordEncoder
类注入到Spring容器中:
java
@Bean
public MyPasswordEncoder myPasswordEncoder() {
return new MyPasswordEncoder();
}
- 在
WebSecurityConfigurerAdapter
中配置使用自定义的PasswordEncoder
:
```java
@Autowired
private MyPasswordEncoder passwordEncoder;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
}
```
这样就可以使用自己的加密方式来保护密码了。
总结
本文介绍了如何将SpringBoot、SpringSecurity和jwt整合起来实现用户验证与授权功能,包含完整的代码和详细的步骤,最终实现了一个简单的用户登录验证功能,同时提供了两个实际应用的示例。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:详解SpringBoot+SpringSecurity+jwt整合及初体验 - Python技术站