基于Spring Boot和JWT(JSON Web Tokens)实现单点登录(SSO, Single Sign-On)是一个流行的选择,因为它能够简化身份验证流程,并在多个应用之间共享用户的登录状态。
在你的pom.xml
中添加JWT和Spring Security的依赖:
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-security
io.jsonwebtoken
jjwt
0.9.1
mysql
mysql-connector-java
runtime
org.springframework.boot
spring-boot-starter-data-jpa
在你的application.properties或application.yml文件中配置数据库连接:
spring.datasource.url=jdbc:mysql://localhost:3306/your_database
spring.datasource.username=root
spring.datasource.password=your_password
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
创建用户实体类和相应的仓库:
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
private String role;
// Getters and Setters
}
@Repository
public interface UserRepository extends JpaRepository {
Optional findByUsername(String username);
}
在Spring Boot应用中配置Spring Security以使用JWT进行身份验证。
这通常涉及自定义UserDetailsService
、AuthenticationManager
、AuthenticationEntryPoint
、AccessDeniedHandler
以及JWT的生成和验证逻辑。
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/login", "/register").permitAll()
.anyRequest().authenticated()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
}
创建一个JWT工具类,用于生成和验证JWT令牌。这个类将使用jjwt
库来创建和解析JWT。
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
public class JwtUtil {
private String secretKey = "your_secret_key";
public String generateToken(String username) {
return Jwts.builder()
.setSubject(username)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10)) // 10 hours
.signWith(SignatureAlgorithm.HS256, secretKey)
.compact();
}
public Claims validateToken(String token) {
try {
return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody();
} catch (Exception e) {
return null;
}
}
}
创建一个过滤器来验证JWT:
@Component
public class JwtTokenFilter extends OncePerRequestFilter {
@Autowired
private UserService userService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String authHeader = request.getHeader("Authorization");
if (authHeader != null && authHeader.startsWith("Bearer ")) {
String token = authHeader.substring(7);
try {
Jws claims = Jwts.parser()
.setSigningKey("secretKey".getBytes())
.parseClaimsJws(token);
String username = claims.getBody().getSubject();
request.setAttribute("username", username);
} catch (JwtException | IllegalArgumentException e) {
throw new RuntimeException("Invalid token");
}
}
filterChain.doFilter(request, response);
}
}
在你的安全配置类中添加过滤器:
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtTokenFilter jwtTokenFilter;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/login", "/register").permitAll()
.anyRequest().authenticated()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class);
}
}
编写REST API来处理用户登录、注销和受保护的资源访问。
在登录API中,验证用户凭据,如果成功,则生成JWT令牌并返回给用户。
在其他API中,使用JWT过滤器来验证请求中的令牌。
创建一个用户服务来处理用户认证:
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private PasswordEncoder passwordEncoder;
public boolean register(UserDto userDto) {
User user = new User();
user.setUsername(userDto.getUsername());
user.setPassword(passwordEncoder.encode(userDto.getPassword()));
user.setRole("ROLE_USER");
userRepository.save(user);
return true;
}
public AuthenticationToken login(String username, String password) {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new RuntimeException("User not found"));
if (passwordEncoder.matches(password, user.getPassword())) {
return new AuthenticationToken(generateToken(user));
}
throw new RuntimeException("Invalid username or password");
}
private String generateToken(User user) {
return Jwts.builder()
.setSubject(user.getUsername())
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10)) // 10 hours
.signWith(SignatureAlgorithm.HS512, "secretKey".getBytes())
.compact();
}
}
到此设计完成,用户可以注册、登录并获取有效的JWT。
确保JWT令牌的安全,不要将敏感信息放入令牌中。
设置合理的令牌过期时间。
保护你的JWT密钥,不要将其硬编码在代码中或存储在可公开访问的地方。