基于SpringBoot+JWT实现单点登录解决方案

基于Spring Boot和JWT(JSON Web Tokens)实现单点登录(SSO, Single Sign-On)是一个流行的选择,因为它能够简化身份验证流程,并在多个应用之间共享用户的登录状态。

1. 添加依赖

在你的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
    

2. 配置数据库

在你的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

3. 实体类和仓库

创建用户实体类和相应的仓库:

@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);
}

4. 配置Spring Security

在Spring Boot应用中配置Spring Security以使用JWT进行身份验证。

这通常涉及自定义UserDetailsServiceAuthenticationManagerAuthenticationEntryPointAccessDeniedHandler以及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());
    }
}

5. 实现JWT工具类

创建一个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;  
        }  
    }  
}

6. JWT过滤器

创建一个过滤器来验证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);
    }
}

7. 配置过滤器

在你的安全配置类中添加过滤器:


@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);
    }
}

8. 编写REST API

编写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密钥,不要将其硬编码在代码中或存储在可公开访问的地方。

你可能感兴趣的:(spring,boot,后端,java)