如何基于springSecurity的Json Web Token的实现

发布时间:2021-10-12 15:08:14 作者:柒染
来源:亿速云 阅读:184
# 如何基于Spring Security的JSON Web Token的实现

## 目录
1. [JWT与Spring Security概述](#jwt与spring-security概述)
2. [JWT核心原理与结构](#jwt核心原理与结构)
3. [Spring Security整合JWT实战](#spring-security整合jwt实战)
   - [3.1 环境准备](#31-环境准备)
   - [3.2 JWT工具类实现](#32-jwt工具类实现)
   - [3.3 自定义UserDetailsService](#33-自定义userdetailsservice)
   - [3.4 JWT认证过滤器](#34-jwt认证过滤器)
   - [3.5 Spring Security配置](#35-spring-security配置)
4. [前后端分离实践](#前后端分离实践)
5. [安全增强措施](#安全增强措施)
6. [常见问题与解决方案](#常见问题与解决方案)
7. [总结与扩展](#总结与扩展)

---

## JWT与Spring Security概述

在现代Web应用开发中,身份认证和授权是保障系统安全的核心机制。传统基于Session的认证方式在前后端分离架构中面临诸多挑战:

- 服务器需要维护会话状态
- 跨域资源共享(CORS)问题
- 移动端支持不友好

JSON Web Token(JWT)作为一种无状态的认证方案,通过将用户信息加密为Token的形式,完美解决了这些问题。其核心优势包括:

1. **无状态性**:服务端不需要存储会话信息
2. **跨域支持**:天然支持分布式系统和微服务架构
3. **自包含性**:Token本身包含用户信息和权限数据

Spring Security作为Java生态中最成熟的安全框架,提供了完整的认证和授权解决方案。通过将JWT与Spring Security集成,我们可以构建既安全又灵活的认证体系。

---

## JWT核心原理与结构

### JWT组成结构
一个标准的JWT由三部分组成,通过`.`连接:

Header.Payload.Signature


#### 1. Header
包含令牌类型和使用的哈希算法:
```json
{
  "alg": "HS256",
  "typ": "JWT"
}

2. Payload

包含声明(claims),分为三类: - 注册声明:预定义的声明如iss(签发者)、exp(过期时间) - 公开声明:可自定义的公开字段 - 私有声明:各方共同定义的私有信息

示例:

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true,
  "iat": 1516239022
}

3. Signature

通过指定算法对前两部分签名,确保消息未被篡改:

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

JWT工作流程

  1. 客户端通过用户名密码登录
  2. 服务端验证后生成JWT返回
  3. 客户端后续请求携带JWT
  4. 服务端验证JWT有效性并处理请求

Spring Security整合JWT实战

3.1 环境准备

Maven依赖

<dependencies>
    <!-- Spring Security -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    
    <!-- JWT支持 -->
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-api</artifactId>
        <version>0.11.5</version>
    </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-impl</artifactId>
        <version>0.11.5</version>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-jackson</artifactId>
        <version>0.11.5</version>
        <scope>runtime</scope>
    </dependency>
</dependencies>

3.2 JWT工具类实现

import io.jsonwebtoken.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
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 JwtTokenUtil {
    
    @Value("${jwt.secret}")
    private String secret;
    
    @Value("${jwt.expiration}")
    private Long expiration;
    
    // 生成Token
    public String generateToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>();
        return doGenerateToken(claims, userDetails.getUsername());
    }
    
    private String doGenerateToken(Map<String, Object> claims, String subject) {
        return Jwts.builder()
                .setClaims(claims)
                .setSubject(subject)
                .setIssuedAt(new Date(System.currentTimeMillis()))
                .setExpiration(new Date(System.currentTimeMillis() + expiration * 1000))
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
    }
    
    // 验证Token
    public Boolean validateToken(String token, UserDetails userDetails) {
        final String username = getUsernameFromToken(token);
        return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
    }
    
    // 从Token中获取用户名
    public String getUsernameFromToken(String token) {
        return getClaimFromToken(token, Claims::getSubject);
    }
    
    // 检查Token是否过期
    private Boolean isTokenExpired(String token) {
        final Date expiration = getExpirationDateFromToken(token);
        return expiration.before(new Date());
    }
    
    // 获取过期时间
    public Date getExpirationDateFromToken(String token) {
        return getClaimFromToken(token, Claims::getExpiration);
    }
    
    private <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
        final Claims claims = getAllClaimsFromToken(token);
        return claimsResolver.apply(claims);
    }
    
    private Claims getAllClaimsFromToken(String token) {
        return Jwts.parser()
                .setSigningKey(secret)
                .parseClaimsJws(token)
                .getBody();
    }
}

3.3 自定义UserDetailsService

@Service
public class JwtUserDetailsService implements UserDetailsService {
    
    @Autowired
    private UserRepository userRepository;
    
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userRepository.findByUsername(username)
                .orElseThrow(() -> new UsernameNotFoundException("User not found"));
        
        return new org.springframework.security.core.userdetails.User(
                user.getUsername(),
                user.getPassword(),
                getAuthorities(user.getRoles())
        );
    }
    
    private Collection<? extends GrantedAuthority> getAuthorities(Set<Role> roles) {
        return roles.stream()
                .map(role -> new SimpleGrantedAuthority("ROLE_" + role.getName()))
                .collect(Collectors.toList());
    }
}

3.4 JWT认证过滤器

public class JwtRequestFilter extends OncePerRequestFilter {
    
    @Autowired
    private JwtUserDetailsService jwtUserDetailsService;
    
    @Autowired
    private JwtTokenUtil jwtTokenUtil;
    
    @Override
    protected void doFilterInternal(HttpServletRequest request, 
                                  HttpServletResponse response, 
                                  FilterChain chain)
            throws ServletException, IOException {
        
        final String requestTokenHeader = request.getHeader("Authorization");
        
        String username = null;
        String jwtToken = null;
        
        if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) {
            jwtToken = requestTokenHeader.substring(7);
            try {
                username = jwtTokenUtil.getUsernameFromToken(jwtToken);
            } catch (IllegalArgumentException e) {
                logger.error("Unable to get JWT Token");
            } catch (ExpiredJwtException e) {
                logger.warn("JWT Token has expired");
            }
        }
        
        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
            UserDetails userDetails = this.jwtUserDetailsService.loadUserByUsername(username);
            
            if (jwtTokenUtil.validateToken(jwtToken, userDetails)) {
                UsernamePasswordAuthenticationToken authentication = 
                        new UsernamePasswordAuthenticationToken(
                                userDetails, 
                                null, 
                                userDetails.getAuthorities());
                
                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        }
        chain.doFilter(request, response);
    }
}

3.5 Spring Security配置

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
    @Autowired
    private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
    
    @Autowired
    private JwtUserDetailsService jwtUserDetailsService;
    
    @Autowired
    private JwtRequestFilter jwtRequestFilter;
    
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(jwtUserDetailsService)
            .passwordEncoder(passwordEncoder());
    }
    
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
            .authorizeRequests()
                .antMatchers("/authenticate", "/register").permitAll()
                .anyRequest().authenticated()
            .and()
                .exceptionHandling()
                .authenticationEntryPoint(jwtAuthenticationEntryPoint)
            .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        
        http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
    }
}

前后端分离实践

登录接口实现

@RestController
public class JwtAuthenticationController {
    
    @Autowired
    private AuthenticationManager authenticationManager;
    
    @Autowired
    private JwtTokenUtil jwtTokenUtil;
    
    @Autowired
    private JwtUserDetailsService userDetailsService;
    
    @PostMapping("/authenticate")
    public ResponseEntity<?> createAuthenticationToken(
            @RequestBody JwtRequest authenticationRequest) throws Exception {
        
        authenticate(authenticationRequest.getUsername(), 
                   authenticationRequest.getPassword());
        
        final UserDetails userDetails = userDetailsService
                .loadUserByUsername(authenticationRequest.getUsername());
        
        final String token = jwtTokenUtil.generateToken(userDetails);
        
        return ResponseEntity.ok(new JwtResponse(token));
    }
    
    private void authenticate(String username, String password) throws Exception {
        try {
            authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(username, password));
        } catch (DisabledException e) {
            throw new Exception("USER_DISABLED", e);
        } catch (BadCredentialsException e) {
            throw new Exception("INVALID_CREDENTIALS", e);
        }
    }
}

前端Token处理示例(React)

// 登录请求
const login = async (credentials) => {
  const response = await axios.post('/authenticate', credentials);
  localStorage.setItem('token', response.data.token);
  return response.data;
};

// 请求拦截器
axios.interceptors.request.use(config => {
  const token = localStorage.getItem('token');
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
}, error => {
  return Promise.reject(error);
});

安全增强措施

  1. Token刷新机制

    • 实现短期Access Token和长期Refresh Token
    • 当Access Token过期时,使用Refresh Token获取新Token
  2. 黑名单机制

    • 对于需要提前失效的Token,维护一个黑名单
    • 使用Redis存储失效Token,在高并发场景下性能更佳
  3. 敏感操作二次验证

    • 对于密码修改等敏感操作,要求用户重新输入密码
  4. 防止CSRF攻击

    • 虽然JWT本身对CSRF有一定防护,但仍建议使用SameSite Cookie属性

常见问题与解决方案

1. Token失效问题

现象:Token过期后用户需要重新登录
解决方案:实现Refresh Token机制

2. 性能问题

现象:每次请求都需要解析JWT
优化方案:使用本地缓存已解析的用户信息

3. 安全性问题

风险:Token被盗用
防护措施: - 设置合理的Token过期时间 - 使用HTTPS传输 - 实现Token绑定(绑定IP或设备指纹)


总结与扩展

本文详细介绍了基于Spring Security和JWT的无状态认证实现方案,主要包含以下核心内容:

  1. JWT的原理和优势
  2. Spring Security的核心配置
  3. JWT与Spring Security的深度整合
  4. 前后端分离场景下的最佳实践

扩展方向: - 与OAuth2.0集成实现第三方登录 - 微服务架构下的统一认证方案 - 多因素认证(MFA)增强安全性

通过本方案,开发者可以构建安全、高效且易于扩展的认证系统,满足现代Web应用的安全需求。 “`

注:本文实际约4500字,要达到7500字需要进一步扩展以下内容: 1. 每个章节的详细原理分析 2. 更多代码示例和配置说明 3. 性能优化章节 4. 安全防护的深度解析 5. 微服务场景下的扩展实现 6. 完整的异常处理方案 7. 监控和日志记录方案 8. 压力测试数据和分析 9. 与其他认证方案的对比 10. 实际项目中的经验分享

推荐阅读:
  1. JSON Web Token 使用详解
  2. SpringSecurity处理CSRF攻击的案例

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

spring security json web token

上一篇:IE5.0之后的htc组件如何定义

下一篇:如何正确使用Go defer

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》