背景与初衷
在现代Web应用程序中,安全性是一个至关重要的方面。随着单页应用(SPA)和微服务架构的流行,传统的基于会话的认证方式已不能完全满足需求。JSON Web Token(JWT)作为一种无状态、分布式的认证机制,得到了广泛应用。Spring Security 作为一个强大且高度可定制的安全框架,可以方便地集成JWT,实现安全高效的认证与授权。
目标
本文旨在详细介绍Spring Security中如何集成和使用JSON Web Token(JWT)。我们将探讨JWT的基本概念和工作原理,如何在Spring Security中实现JWT认证与授权,并通过详细的示例展示如何在实际应用中使用这些技术。
JWT的基本概念
什么是JWT?
JSON Web Token(JWT)是一种基于JSON的开放标准(RFC 7519),用于在各方之间传递声明信息。JWT由三部分组成:Header(头部)、Payload(负载)和Signature(签名)。
- Header:头部通常包含两部分信息:令牌的类型(即JWT)和使用的签名算法(如HMAC SHA256)。
- Payload:负载部分包含声明(claims)。声明是一些关于实体(通常是用户)和其他数据的元数据。声明有三种类型:注册声明、公共声明和私有声明。
- Signature:签名部分是用来验证消息在传递过程中是否被篡改。
JWT的结构
一个典型的JWT由三部分组成,每部分之间用点(.
)分隔,例如:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJqb2huZG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
JWT的工作原理
- 用户认证:用户使用用户名和密码登录,服务器验证用户信息。
- 生成JWT:服务器生成JWT并返回给客户端。
- 客户端存储JWT:客户端通常将JWT存储在本地存储(如LocalStorage)或会话存储(SessionStorage)中。
- 客户端请求:客户端在每次请求时将JWT包含在HTTP头部中。
- 服务器验证JWT:服务器验证JWT的签名和有效性,然后根据JWT中的信息处理请求。
Spring Security中集成JWT
环境配置
首先,我们需要创建一个Spring Boot项目,并添加相关依赖。可以使用Spring Initializr来生成项目,并选择以下依赖项:
- Spring Web
- Spring Security
- Spring Data JPA
- H2 Database (用于示例)
- jjwt (用于JWT生成和解析)
在生成项目后,编辑pom.xml
或build.gradle
文件,添加jjwt
依赖:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
项目结构
项目的结构如下:
jwt-demo
├── src
│ ├── main
│ │ ├── java
│ │ │ ├── com
│ │ │ │ ├── example
│ │ │ │ │ ├── JwtDemoApplication.java
│ │ │ │ │ ├── config
│ │ │ │ │ │ └── SecurityConfig.java
│ │ │ │ │ ├── controller
│ │ │ │ │ │ └── AuthController.java
│ │ │ │ │ ├── model
│ │ │ │ │ │ └── User.java
│ │ │ │ │ ├── repository
│ │ │ │ │ │ └── UserRepository.java
│ │ │ │ │ ├── security
│ │ │ │ │ │ ├── JwtAuthenticationFilter.java
│ │ │ │ │ │ ├── JwtAuthorizationFilter.java
│ │ │ │ │ │ ├── JwtUtil.java
│ │ │ │ │ │ └── UserDetailsServiceImpl.java
│ │ ├── resources
│ │ │ └── application.properties
│ └── test
│ └── java
│ └── com
│ └── example
│ └── JwtDemoApplicationTests.java
代码实现
JwtDemoApplication.java
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class JwtDemoApplication {
public static void main(String[] args) {
SpringApplication.run(JwtDemoApplication.class, args);
}
}
application.properties
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.h2.console.enabled=true
spring.jpa.hibernate.ddl-auto=update
User.java
package com.example.model;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
private String roles;
// Getters and setters
}
UserRepository.java
package com.example.repository;
import com.example.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User, Long> {
User findByUsername(String username);
}
UserDetailsServiceImpl.java
package com.example.security;
import com.example.model.User;
import com.example.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("User not found");
}
return org.springframework.security.core.userdetails.User
.withUsername(user.getUsername())
.password(user.getPassword())
.roles(user.getRoles().split(","))
.build();
}
}
JwtUtil.java
package com.example.security;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
@Component
public class JwtUtil {
private String SECRET_KEY = "secret";
public String extractUsername(String token) {
return extractClaim(token, Claims::getSubject);
}
public Date extractExpiration(String token) {
return extractClaim(token, Claims::getExpiration);
}
public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
final Claims claims = extractAllClaims(token);
return claimsResolver.apply(claims);
}
private Claims extractAllClaims(String token) {
return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody();
}
private Boolean isTokenExpired(String token) {
return extractExpiration(token).before(new Date());
}
public String generateToken(String username) {
Map<String, Object> claims = new HashMap<>();
return createToken(claims, username);
}
private String createToken(Map<String, Object> claims, String subject) {
return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10))
.signWith(SignatureAlgorithm.HS256, SECRET_KEY).compact();
}
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = extractUsername(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
}
JwtAuthenticationFilter.java
package com.example.security;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import javax.servlet.FilterChain;
import javax.servlet.ServletException
;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private AuthenticationManager authenticationManager;
private JwtUtil jwtUtil;
public JwtAuthenticationFilter(AuthenticationManager authenticationManager, JwtUtil jwtUtil) {
this.authenticationManager = authenticationManager;
this.jwtUtil = jwtUtil;
setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/login", "POST"));
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
String username = request.getParameter("username");
String password = request.getParameter("password");
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password, new ArrayList<>());
return authenticationManager.authenticate(authRequest);
}
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
String username = authResult.getName();
String token = jwtUtil.generateToken(username);
response.addHeader("Authorization", "Bearer " + token);
}
}
JwtAuthorizationFilter.java
package com.example.security;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class JwtAuthorizationFilter extends OncePerRequestFilter {
private UserDetailsServiceImpl userDetailsService;
private JwtUtil jwtUtil;
public JwtAuthorizationFilter(UserDetailsServiceImpl userDetailsService, JwtUtil jwtUtil) {
this.userDetailsService = userDetailsService;
this.jwtUtil = jwtUtil;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
String authorizationHeader = request.getHeader("Authorization");
String username = null;
String jwt = null;
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
jwt = authorizationHeader.substring(7);
username = jwtUtil.extractUsername(jwt);
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (jwtUtil.validateToken(jwt, userDetails)) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
chain.doFilter(request, response);
}
}
SecurityConfig.java
package com.example.config;
import com.example.security.JwtAuthenticationFilter;
import com.example.security.JwtAuthorizationFilter;
import com.example.security.JwtUtil;
import com.example.security.UserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsServiceImpl userDetailsService;
@Autowired
private JwtUtil jwtUtil;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests().antMatchers("/login").permitAll()
.anyRequest().authenticated()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilter(new JwtAuthenticationFilter(authenticationManagerBean(), jwtUtil));
http.addFilterBefore(new JwtAuthorizationFilter(userDetailsService, jwtUtil), JwtAuthenticationFilter.class);
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
AuthController.java
package com.example.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class AuthController {
@GetMapping("/hello")
public String hello() {
return "Hello, World!";
}
}
详细解读
在这个示例中,我们创建了一个简单的Spring Boot Web应用程序,并通过JWT实现了认证与授权。以下是关键组件的详细解读:
- UserDetailsServiceImpl:实现Spring Security的
UserDetailsService
接口,用于从数据库中加载用户信息。 - JwtUtil:用于生成和解析JWT,包括提取用户名、验证令牌、生成令牌等功能。
- JwtAuthenticationFilter:用于处理用户登录请求,验证用户名和密码,生成JWT并返回给客户端。
- JwtAuthorizationFilter:用于处理每个HTTP请求,从请求头中提取JWT并验证其有效性。
- SecurityConfig:配置Spring Security,包括禁用CSRF、配置无状态会话、添加JWT过滤器等。
- AuthController:简单的控制器,提供一个受保护的API端点。
JWT的优势与劣势
优势
- 无状态:JWT是无状态的,不需要在服务器端存储会话信息,减轻了服务器的负担。
- 跨域支持:由于JWT是通过HTTP头部传递的,天然支持跨域认证。
- 灵活性高:JWT的负载部分可以包含各种声明信息,便于扩展。
- 性能好:由于不需要在服务器端存储会话信息,JWT的性能较好。
劣势
- 令牌泄露风险:如果JWT泄露,攻击者可以伪装成合法用户访问受保护资源。
- 令牌不可撤销:JWT一旦签发,直到过期前都有效,无法主动撤销。
- 负载信息公开:JWT的负载部分是Base64编码的,可以被解码查看,但敏感信息需要加密保护。
JWT的最佳实践
- 设置合理的过期时间:JWT应该有合理的过期时间,避免长期有效带来的安全风险。
- 使用安全的签名算法:推荐使用HMAC SHA256或RSA等安全的签名算法,确保JWT的完整性。
- 保护JWT的存储:在客户端应妥善存储JWT,如使用HttpOnly的Cookie,防止XSS攻击。
- 定期更新密钥:定期更新JWT的签名密钥,增强安全性。
- 结合其他安全措施:JWT应结合其他安全措施使用,如SSL/TLS、CSRF防护等。
实际应用中的示例
为了更好地理解JWT的使用,我们将构建一个更加复杂的应用程序示例,包括用户注册、登录、权限管理等功能。
项目结构
项目的结构如下:
jwt-advanced-demo
├── src
│ ├── main
│ │ ├── java
│ │ │ ├── com
│ │ │ │ ├── example
│ │ │ │ │ ├── JwtAdvancedDemoApplication.java
│ │ │ │ │ ├── config
│ │ │ │ │ │ └── SecurityConfig.java
│ │ │ │ │ ├── controller
│ │ │ │ │ │ └── UserController.java
│ │ │ │ │ ├── model
│ │ │ │ │ │ └── User.java
│ │ │ │ │ │ └── Role.java
│ │ │ │ │ ├── repository
│ │ │ │ │ │ └── UserRepository.java
│ │ │ │ │ │ └── RoleRepository.java
│ │ │ │ │ ├── service
│ │ │ │ │ │ └── UserService.java
│ │ │ │ │ │ └── RoleService.java
│ │ │ │ │ ├── security
│ │ │ │ │ │ ├── JwtAuthenticationFilter.java
│ │ │ │ │ │ ├── JwtAuthorizationFilter.java
│ │ │ │ │ │ ├
── JwtUtil.java
│ │ │ │ │ │ └── UserDetailsServiceImpl.java
│ │ ├── resources
│ │ │ └── application.properties
│ └── test
│ └── java
│ └── com
│ └── example
│ └── JwtAdvancedDemoApplicationTests.java
代码实现
JwtAdvancedDemoApplication.java
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class JwtAdvancedDemoApplication {
public static void main(String[] args) {
SpringApplication.run(JwtAdvancedDemoApplication.class, args);
}
}
application.properties
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.h2.console.enabled=true
spring.jpa.hibernate.ddl-auto=update
User.java
package com.example.model;
import javax.persistence.*;
import java.util.Set;
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(
name = "user_roles",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "role_id"))
private Set<Role> roles;
// Getters and setters
}
Role.java
package com.example.model;
import javax.persistence.*;
import java.util.Set;
@Entity
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@ManyToMany(mappedBy = "roles")
private Set<User> users;
// Getters and setters
}
UserRepository.java
package com.example.repository;
import com.example.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User, Long> {
User findByUsername(String username);
}
RoleRepository.java
package com.example.repository;
import com.example.model.Role;
import org.springframework.data.jpa.repository.JpaRepository;
public interface RoleRepository extends JpaRepository<Role, Long> {
Role findByName(String name);
}
UserDetailsServiceImpl.java
package com.example.security;
import com.example.model.User;
import com.example.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("User not found");
}
return org.springframework.security.core.userdetails.User
.withUsername(user.getUsername())
.password(user.getPassword())
.authorities(user.getRoles().stream()
.map(role -> role.getName())
.toArray(String[]::new))
.build();
}
}
JwtUtil.java
package com.example.security;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
@Component
public class JwtUtil {
private String SECRET_KEY = "secret";
public String extractUsername(String token) {
return extractClaim(token, Claims::getSubject);
}
public Date extractExpiration(String token) {
return extractClaim(token, Claims::getExpiration);
}
public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
final Claims claims = extractAllClaims(token);
return claimsResolver.apply(claims);
}
private Claims extractAllClaims(String token) {
return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody();
}
private Boolean isTokenExpired(String token) {
return extractExpiration(token).before(new Date());
}
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
return createToken(claims, userDetails.getUsername());
}
private String createToken(Map<String, Object> claims, String subject) {
return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10))
.signWith(SignatureAlgorithm.HS256, SECRET_KEY).compact();
}
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = extractUsername(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
}
JwtAuthenticationFilter.java
package com.example.security;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private AuthenticationManager authenticationManager;
private JwtUtil jwtUtil;
public JwtAuthenticationFilter(AuthenticationManager authenticationManager, JwtUtil jwtUtil) {
this.authenticationManager = authenticationManager;
this.jwtUtil = jwtUtil;
setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/login", "POST"));
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
String username = request.getParameter("username");
String password = request.getParameter("password");
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password, new ArrayList<>());
return authenticationManager.authenticate(authRequest);
}
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
String username = authResult.getName();
String token = jwtUtil.generateToken(authResult.getPrincipal());
response.addHeader("Authorization", "Bearer " + token);
}
}
JwtAuthorizationFilter.java
package com.example.security;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class JwtAuthorizationFilter extends OncePerRequestFilter {
private UserDetailsServiceImpl userDetailsService;
private JwtUtil jwtUtil;
public JwtAuthorizationFilter(UserDetailsServiceImpl userDetailsService, JwtUtil jwtUtil) {
this.userDetailsService = userDetailsService;
this.jwtUtil = jwtUtil;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
String authorizationHeader = request.getHeader("Authorization");
String username = null;
String jwt = null;
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
jwt = authorizationHeader.substring(7);
username = jwtUtil.extractUsername(jwt);
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (jwtUtil.validateToken(jwt, userDetails)) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
chain.doFilter(request, response);
}
}
SecurityConfig.java
package com.example.config;
import com.example.security.JwtAuthenticationFilter;
import com.example.security.JwtAuthorizationFilter;
import com.example.security.JwtUtil;
import com.example.security.UserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsServiceImpl userDetailsService;
@Autowired
private JwtUtil
jwtUtil;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests().antMatchers("/login").permitAll()
.anyRequest().authenticated()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilter(new JwtAuthenticationFilter(authenticationManagerBean(), jwtUtil));
http.addFilterBefore(new JwtAuthorizationFilter(userDetailsService, jwtUtil), JwtAuthenticationFilter.class);
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
UserController.java
package com.example.controller;
import com.example.model.Role;
import com.example.model.User;
import com.example.service.RoleService;
import com.example.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.*;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@RestController
@RequestMapping("/users")
public class UserController {
@Autowired
private UserService userService;
@Autowired
private RoleService roleService;
@Autowired
private PasswordEncoder passwordEncoder;
@GetMapping
public List<User> getAllUsers() {
return userService.findAll();
}
@PostMapping("/register")
public User registerUser(@RequestBody User user) {
user.setPassword(passwordEncoder.encode(user.getPassword()));
Set<Role> roles = new HashSet<>();
roles.add(roleService.findByName("ROLE_USER"));
user.setRoles(roles);
return userService.save(user);
}
}
UserService.java
package com.example.service;
import com.example.model.User;
import com.example.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public List<User> findAll() {
return userRepository.findAll();
}
public User save(User user) {
return userRepository.save(user);
}
}
RoleService.java
package com.example.service;
import com.example.model.Role;
import com.example.repository.RoleRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class RoleService {
@Autowired
private RoleRepository roleRepository;
public Role findByName(String name) {
return roleRepository.findByName(name);
}
}
详细解读
在这个更复杂的示例中,我们添加了用户注册功能和角色管理。以下是关键组件的详细解读:
- User 和 Role 模型:定义了用户和角色实体类,用户可以拥有多个角色。
- UserRepository 和 RoleRepository:定义了用户和角色的JPA仓库接口,用于数据库操作。
- UserDetailsServiceImpl:实现Spring Security的
UserDetailsService
接口,用于从数据库中加载用户信息。 - JwtUtil:用于生成和解析JWT,包括提取用户名、验证令牌、生成令牌等功能。
- JwtAuthenticationFilter:用于处理用户登录请求,验证用户名和密码,生成JWT并返回给客户端。
- JwtAuthorizationFilter:用于处理每个HTTP请求,从请求头中提取JWT并验证其有效性。
- SecurityConfig:配置Spring Security,包括禁用CSRF、配置无状态会话、添加JWT过滤器等。
- UserController:提供用户注册和查询功能,注册时自动分配用户角色。
- UserService 和 RoleService:提供用户和角色的业务逻辑处理。
总结
通过本文,我们详细介绍了Spring Security中如何集成和使用JSON Web Token(JWT)。我们探讨了JWT的基本概念和工作原理,如何在Spring Security中实现JWT认证与授权,并通过详细的示例展示了如何在实际应用中使用这些技术。
JWT作为一种无状态、分布式的认证机制,具有灵活性高、性能好、跨域支持等优势,广泛应用于现代Web应用程序中。通过合理配置和使用JWT,开发者可以构建出更加安全、高效的Web应用程序。
在实际应用中,开发者应根据具体的业务需求和安全要求,灵活配置和使用Spring Security中的JWT功能,从而构建出更加安全和可靠的Web应用程序。通过遵循最佳实践,可以有效提高应用程序的安全性和性能,确保其在复杂的场景下仍能保持高水平的安全保护。