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的认证系统,主要解决以下问题:
- 跨域认证需求
- 分布式系统状态同步问题
- 无状态的认证机制
- 权限动态控制需求
二、基本原理
JWT由三部分组成:
- Header(头部):包含签名算法和Token类型
- Payload(载荷):包含用户信息和权限数据
- Signature(签名):通过密钥对前两部分进行加密
JWT结构示意图认证流程如下:
- 用户登录时,服务端生成JWT
- 客户端存储Token(通常存入localStorage)
- 后续请求在Header中携带Authorization: Bearer
- 服务端验证Token有效性
- 根据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.properties2. 核心代码
登录接口(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("用户权限信息");
}十、最佳实践
- 密钥管理:使用强密钥并定期更换,避免使用硬编码
- Token有效期:根据业务需求设置合理有效期(建议24小时)
- 异常处理:对所有可能的异常进行捕获和处理
- 权限控制:使用Spring Security的注解进行细粒度控制
- 日志记录:记录关键操作日志,便于排查问题
- 安全传输:确保所有通信使用HTTPS
- 前端处理:在前端使用拦截器处理Token的存储和发送
十一、总结
通过SSM框架与JWT的结合,我们实现了一个完整的分布式系统认证方案。该方案具有以下特点:
- 无状态:无需服务器存储用户信息
- 可扩展:适用于分布式系统和微服务架构
- 安全性:通过签名和加密确保数据完整性
- 灵活性:支持动态权限控制
在实际开发中,应根据业务需求选择合适的认证方案。对于需要频繁更新用户状态的系统,可能更适合使用传统的Session机制。但对于分布式系统和移动端应用,JWT方案是更优选择。
需要特别注意的是,JWT方案虽然具有诸多优势,但也存在Token泄露风险,因此在实际应用中应配合安全传输、密钥管理等措施,确保系统的整体安全性。
评论已关闭