实现单点登录(Single Sign-On, SSO)需要以下步骤:
- 用户登录后,生成JWT Token并将其存储到Redis中。
- 将JWT Token返回给客户端,并在响应头中设置Authorization字段,值为Bearer加上JWT Token。
- 客户端在每次请求时,在请求头中带上Authorization字段,值为Bearer加上JWT Token。
- 服务端在接收到请求时,先从请求头中获取JWT Token,并解析出其中的用户信息。然后再到Redis中验证Token的唯一性和有效期。
- 如果验证通过,则允许用户访问资源;否则拒绝访问。
- 首先,pom.xml文件中引入以下依赖
-
<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>0.11.2</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-impl</artifactId> <version>0.11.2</version> <scope>runtime</scope> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-jackson</artifactId> <version>0.11.2</version> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
然后,在application.yml文件中配置JWT Token的相关属性(secret为加密密钥,expiration为有效期):
jwt: secret: mysecretkey expiration: 600
-
然后,创建一个JwtUtil类,用于生成和解析JWT Token:
-
import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.security.Keys; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import javax.crypto.SecretKey; import java.util.Date; import java.util.HashMap; import java.util.Map; @Component public class JwtUtil { @Value("${jwt.secret}") private String secret; @Value("${jwt.expiration}") private Long expiration; private SecretKey key = Keys.hmacShaKeyFor(secret.getBytes()); public String generateToken(String username) { Date now = new Date(); Date expiryDate = new Date(now.getTime() + expiration * 1000); Map<String, Object> claims = new HashMap<>(); claims.put("sub", username); claims.put("iat", now); claims.put("exp", expiryDate); return Jwts.builder() .setClaims(claims) .signWith(key, SignatureAlgorithm.HS512) .compact(); } public String getUsernameFromToken(String token) { Claims claims = Jwts.parserBuilder() .setSigningKey(key) .build() .parseClaimsJws(token) .getBody(); return claims.getSubject(); } public Long getExpiration() { return expiration; } }
创建一个JwtFilter类,用于验证JWT Token的唯一性和有效性:
import com.alibaba.fastjson.JSONObject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.http.HttpStatus; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import org.springframework.web.filter.OncePerRequestFilter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.concurrent.TimeUnit; @Component public class JwtFilter extends OncePerRequestFilter { @Autowired private JwtUtil jwtUtil; @Autowired private RedisTemplate<String, Object> redisTemplate; @Override protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException { String authToken = httpServletRequest.getHeader("Authorization"); if (StringUtils.hasText(authToken) && authToken.startsWith("Bearer ")) { authToken = authToken.substring(7); String username = jwtUtil.getUsernameFromToken(authToken); Object tokenInRedis = redisTemplate.opsForValue().get(username); if (tokenInRedis == null || !tokenInRedis.equals(authToken)) { httpServletResponse.setContentType("application/json;charset=UTF-8"); httpServletResponse.setStatus(HttpStatus.UNAUTHORIZED.value()); JSONObject result = new JSONObject(); result.put("status", HttpStatus.UNAUTHORIZED.value()); result.put("message", "Token验证失败或已过期,请重新登录!"); httpServletResponse.getWriter().write(result.toString()); return; } if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, null, null); SecurityContextHolder.getContext().setAuthentication(authenticationToken); redisTemplate.opsForValue().set(username, authToken, jwtUtil.getExpiration(), TimeUnit.SECONDS); } } filterChain.doFilter(httpServletRequest, httpServletResponse); } }
在登录控制器中生成JWT Token并保存到Redis中:
@RestController public class LoginController { @Autowired private JwtUtil jwtUtil; @Autowired private RedisTemplate<String, Object> redisTemplate; @PostMapping("/login") public String login(@RequestParam String username, @RequestParam String password) { // TODO: 用户名和密码验证 String token = jwtUtil.generateToken(username); redisTemplate.opsForValue().set(username, token, jwtUtil.getExpiration(), TimeUnit.SECONDS); return token; } }
最后,在Spring Security的配置类中添加JWT Token的过滤器:
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private JwtFilter jwtFilter; @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests() .anyRequest() .authenticated() .and() .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class); } }
登录控制层
UserController类: ```java import cn.chatmind.demo.mapper.UserMapper; import cn.chatmind.demo.model.User; import com.alibaba.fastjson.JSONObject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api") public class UserController { @Autowired private UserMapper userMapper; @Autowired private JwtUtil jwtUtil; @PostMapping("/login") public JSONObject login(@RequestBody User user) { JSONObject responseJson = new JSONObject(); User existUser = userMapper.findByUsernameAndPassword(user); if (existUser != null) { String token = jwtUtil.generateToken(existUser.getUsername()); responseJson.put("token", token); return responseJson; } else { responseJson.put("error", "Bad Request"); responseJson.put("message", "Invalid username or password."); return responseJson; } } @GetMapping("/users/{id}") public User getUserById(@PathVariable("id") Integer id) { return userMapper.findUserById(id); } }
以上为一个基础的使用Spring Boot、MyBatis、Redis和JWT实现用户登录认证的完整示例。具体实现中可以根据需求进行更改和优化。