SSM(Spring+Springmvc+MyBatis)+ajax+JWT,实现登录JWT(token)认证获取权限

SSM(Spring+Springmvc+MyBatis)+ajax+JWT,实现登录JWT(token)认证获取权限

一、背景与问题

在分布式系统中,传统基于Session的认证机制面临诸多挑战。当系统部署在多个服务器节点时,Session数据需要通过Redis或数据库共享,导致性能瓶颈。而JWT(JSON Web Token)作为一种自包含的令牌机制,能够有效解决分布式系统的身份认证问题。

本方案采用SSM框架(Spring+SpringMVC+MyBatis)结合JWT实现基于Token的认证系统,主要解决以下问题:

  1. 跨域认证需求
  2. 分布式系统状态同步问题
  3. 无状态的认证机制
  4. 权限动态控制需求

二、基本原理

JWT由三部分组成:

  1. Header(头部):包含签名算法和Token类型
  2. Payload(载荷):包含用户信息和权限数据
  3. Signature(签名):通过密钥对前两部分进行加密

JWT结构示意图JWT结构示意图

认证流程如下:

  1. 用户登录时,服务端生成JWT
  2. 客户端存储Token(通常存入localStorage)
  3. 后续请求在Header中携带Authorization: Bearer
  4. 服务端验证Token有效性
  5. 根据Token中的权限信息控制接口访问

三、环境准备

项目依赖(Spring Boot 2.7+):

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
        <version>0.11.5</version>
    </dependency>
</dependencies>

四、核心实现

1. JWT生成器(TokenGenerator)

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import java.security.Key;
import java.util.Date;

public class TokenGenerator {
    private static final String SECRET_KEY = "your-secret-key-here";
    private static final long EXPIRATION_MS = 86400000; // 24小时

    public static String generateToken(String username, String[] roles) {
        Key key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(SECRET_KEY));
        return Jwts.builder()
                .setSubject(username)
                .claim("roles", roles)
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_MS))
                .signWith(key, SignatureAlgorithm.HS512)
                .compact();
    }

    public static String getUsernameFromToken(String token) {
        Key key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(SECRET_KEY));
        return Jwts.parserBuilder()
                .setSigningKey(key)
                .build()
                .parseClaimsJws(token)
                .getBody()
                .getSubject();
    }

    public static boolean isTokenValid(String token) {
        try {
            Key key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(SECRET_KEY));
            Jwts.parserBuilder()
                    .setSigningKey(key)
                    .build()
                    .parseClaimsJws(token);
            return true;
        } catch (Exception e) {
            return false;
        }
    }
}

关键点解释:

  • 使用HMACSHA256算法确保签名安全性
  • 通过claim存储用户角色信息
  • 设置24小时过期时间保证安全性
  • 异常处理确保不会因无效Token导致服务崩溃

2. JWT拦截器(JWTInterceptor)

import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
public class JWTInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String token = request.getHeader("Authorization");
        if (token == null || !token.startsWith("Bearer ")) {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            return false;
        }
        token = token.substring(7);
        if (!TokenGenerator.isTokenValid(token)) {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            return false;
        }
        return true;
    }
}

3. 权限控制实现(结合Spring Security)

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.stream.Collectors;

@Service
public class CustomUserDetailsService implements UserDetailsService {
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 实际开发中应从数据库查询用户信息
        String[] roles = {"ROLE_USER", "ROLE_ADMIN"};
        return new org.springframework.security.core.userdetails.User(
                username, "password", 
                Arrays.stream(roles)
                        .map(SimpleGrantedAuthority::new)
                        .collect(Collectors.toList())
        );
    }
}

五、完整案例

1. 项目结构

src
├── main
│   ├── java
│   │   └── com.example.demo
│   │       ├── controller
│   │       ├── service
│   │       ├── config
│   │       └── JWTDemoApplication.java
│   └── resources
│       └── application.properties

2. 核心代码

登录接口(UserController.java)

@RestController
public class UserController {
    @PostMapping("/login")
    public ResponseEntity<?> login(@RequestBody LoginRequest request) {
        // 验证用户名密码(实际开发中应连接数据库验证)
        if ("admin".equals(request.getUsername()) && "123456".equals(request.getPassword())) {
            String[] roles = {"ROLE_USER", "ROLE_ADMIN"};
            String token = TokenGenerator.generateToken(request.getUsername(), roles);
            return ResponseEntity.ok().header("Authorization", "Bearer " + token).body("登录成功");
        } else {
            return ResponseEntity.status(401).body("用户名或密码错误");
        }
    }
}

权限控制接口(PermissionController.java)

@RestController
public class PermissionController {
    @GetMapping("/permissions")
    public ResponseEntity<?> getPermissions() {
        return ResponseEntity.ok().body("用户权限信息");
    }
}

配置类(SecurityConfig.java)

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private CustomUserDetailsService userDetailsService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/login").permitAll()
                .anyRequest().authenticated()
                .and()
            .addFilterBefore(new JWTInterceptor(), UsernamePasswordAuthenticationFilter.class);
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService);
    }
}

六、源码解析

1. JWT生成流程

public static String generateToken(String username, String[] roles) {
    Key key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(SECRET_KEY));
    return Jwts.builder()
        .setSubject(username)
        .claim("roles", roles)
        .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_MS))
        .signWith(key, SignatureAlgorithm.HS512)
        .compact();
}
  • 使用Keys.hmacShaKeyFor生成安全密钥
  • 通过claim方法存储用户角色信息
  • 设置Token有效期并进行签名
  • 最终返回base64编码的字符串

2. 权限控制实现

public class CustomUserDetailsService implements UserDetailsService {
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        String[] roles = {"ROLE_USER", "ROLE_ADMIN"};
        return new org.springframework.security.core.userdetails.User(
            username, "password", 
            Arrays.stream(roles)
                .map(SimpleGrantedAuthority::new)
                .collect(Collectors.toList())
        );
    }
}
  • 返回的UserDetails对象包含用户角色信息
  • Spring Security会根据这些信息进行权限校验
  • 实际开发中应从数据库查询用户信息

七、进阶使用

1. Token刷新机制

@RestController
public class TokenController {
    @PostMapping("/refresh")
    public ResponseEntity<?> refreshToken() {
        // 实际开发中应从数据库获取用户信息
        String[] roles = {"ROLE_USER", "ROLE_ADMIN"};
        String newToken = TokenGenerator.generateToken("admin", roles);
        return ResponseEntity.ok().header("Authorization", "Bearer " + newToken).body("Token刷新成功");
    }
}

2. 权限动态控制

@RestController
public class DynamicPermissionController {
    @GetMapping("/dynamic")
    public ResponseEntity<?> getDynamicPermission() {
        // 实际开发中应从数据库获取动态权限
        String[] roles = {"ROLE_ADMIN"};
        String token = TokenGenerator.generateToken("admin", roles);
        return ResponseEntity.ok().header("Authorization", "Bearer " + token).body("动态权限接口");
    }
}

八、性能与工程实践

1. 性能优化方案

  • 使用Redis缓存用户信息,减少数据库查询
  • 对Token进行预验证,避免重复验证
  • 使用异步处理Token刷新请求
  • 增加Token过期时间的配置选项

2. 安全风险分析

  • 密钥泄露风险:应使用强密钥并定期更换
  • Token篡改风险:使用HMAC签名确保数据完整性
  • 跨站攻击防范:严格校验请求来源
  • 防止Token泄露:避免将Token存储在URL中

3. 异常处理策略

@ExceptionHandler
public ResponseEntity<?> handleException(Exception e) {
    return ResponseEntity.status(500).body("系统异常:" + e.getMessage());
}

九、常见问题与踩坑

1. Token过期问题

错误代码:

String token = TokenGenerator.generateToken("admin", roles);

问题分析:
未设置过期时间导致Token永不过期,存在安全隐患

改进方案:

.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_MS))

2. 权限校验失效

错误代码:

return ResponseEntity.ok().body("用户权限信息");

问题分析:
未在Controller中校验用户权限

改进方案:

@PreAuthorize("hasRole('ROLE_ADMIN')")
public ResponseEntity<?> getPermissions() {
    return ResponseEntity.ok().body("用户权限信息");
}

十、最佳实践

  1. 密钥管理:使用强密钥并定期更换,避免使用硬编码
  2. Token有效期:根据业务需求设置合理有效期(建议24小时)
  3. 异常处理:对所有可能的异常进行捕获和处理
  4. 权限控制:使用Spring Security的注解进行细粒度控制
  5. 日志记录:记录关键操作日志,便于排查问题
  6. 安全传输:确保所有通信使用HTTPS
  7. 前端处理:在前端使用拦截器处理Token的存储和发送

十一、总结

通过SSM框架与JWT的结合,我们实现了一个完整的分布式系统认证方案。该方案具有以下特点:

  1. 无状态:无需服务器存储用户信息
  2. 可扩展:适用于分布式系统和微服务架构
  3. 安全性:通过签名和加密确保数据完整性
  4. 灵活性:支持动态权限控制

在实际开发中,应根据业务需求选择合适的认证方案。对于需要频繁更新用户状态的系统,可能更适合使用传统的Session机制。但对于分布式系统和移动端应用,JWT方案是更优选择。

需要特别注意的是,JWT方案虽然具有诸多优势,但也存在Token泄露风险,因此在实际应用中应配合安全传输、密钥管理等措施,确保系统的整体安全性。

评论已关闭

推荐阅读

AIGC实战——Transformer模型
2024年12月01日
Socket TCP 和 UDP 编程基础(Python)
2024年11月30日
python , tcp , udp
如何使用 ChatGPT 进行学术润色?你需要这些指令
2024年12月01日
AI
最新 Python 调用 OpenAi 详细教程实现问答、图像合成、图像理解、语音合成、语音识别(详细教程)
2024年11月24日
ChatGPT 和 DALL·E 2 配合生成故事绘本
2024年12月01日
omegaconf,一个超强的 Python 库!
2024年11月24日
【视觉AIGC识别】误差特征、人脸伪造检测、其他类型假图检测
2024年12月01日
[超级详细]如何在深度学习训练模型过程中使用 GPU 加速
2024年11月29日
Python 物理引擎pymunk最完整教程
2024年11月27日
MediaPipe 人体姿态与手指关键点检测教程
2024年11月27日
深入了解 Taipy:Python 打造 Web 应用的全面教程
2024年11月26日
基于Transformer的时间序列预测模型
2024年11月25日
Python在金融大数据分析中的AI应用(股价分析、量化交易)实战
2024年11月25日
AIGC Gradio系列学习教程之Components
2024年12月01日
Python3 `asyncio` — 异步 I/O,事件循环和并发工具
2024年11月30日
llama-factory SFT系列教程:大模型在自定义数据集 LoRA 训练与部署
2024年12月01日
Python 多线程和多进程用法
2024年11月24日
Python socket详解,全网最全教程
2024年11月27日
python之plot()和subplot()画图
2024年11月26日
理解 DALL·E 2、Stable Diffusion 和 Midjourney 工作原理
2024年12月01日