Spring Security基于JWT实现SSO单点登录详解
什么是单点登录(SSO)?
单点登录(SSO)指的是用户只需要一次登录,就可以访问多个应用系统。在传统的系统中,我们需要为每一个系统单独注册,单独登录,对于用户来说,这是一种不便。
JWT是什么?
JWT(JSON Web Token)是一种用于身份验证的开放标准。它是由 IETF(Internet 工程任务组)制定的 RFC 7519。JWT主要用于应用之间的身份验证和授权,它通过加密来保护信息的传输过程中的安全性。
Spring Security与JWT的配合使用
Spring Security是一个功能强大的安全框架,可以帮助我们实现应用程序和用户之间的安全交互。Spring Security可以与多种身份认证和授权机制配合使用,其中JWT是目前最流行的一种。
实现思路
本文的实现思路如下:
- 用户在访问系统A时,输入用户名和密码,系统A根据用户名和密码生成JWT,将JWT返回给用户;
- 用户需要访问系统B时,将在请求头中添加JWT信息并发送请求给系统B;
- 系统B解析JWT,验证用户的身份,并将用户访问系统B的信息返回给用户。
实现步骤
1. 引入相关依赖
在pom.xml
文件中,添加以下依赖:
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
<version>1.1.1.RELEASE</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
2. 创建配置类
在Config
包中,创建一个JwtConfig
类,用于配置JWT的密钥、过期时间等信息。
@Configuration
public class JwtConfig {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private Long expiration;
public String getSecret() {
return secret;
}
public Long getExpiration() {
return expiration;
}
}
其中 @Value
注解用于获取配置文件中的属性值。
3. 创建过滤器
在Filter
包中,创建一个JwtTokenFilter
类,用于拦截请求并验证JWT信息。
public class JwtTokenFilter extends OncePerRequestFilter {
@Autowired
private JwtConfig jwtConfig;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
String header = request.getHeader(jwtConfig.getHeader());
if (StringUtils.isBlank(header) || !header.startsWith(jwtConfig.getTokenHead())) {
chain.doFilter(request, response);
return;
}
String token = header.replace(jwtConfig.getTokenHead(), "");
try {
Claims claims = Jwts.parser()
.setSigningKey(jwtConfig.getSecret())
.parseClaimsJws(token)
.getBody();
String username = claims.getSubject();
if (StringUtils.isNotBlank(username) && SecurityContextHolder.getContext().getAuthentication() == null) {
User principal = new User(username, "", new ArrayList<>());
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(principal, null, principal.getAuthorities());
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
} catch (Exception e) {
logger.error("Token 验证失败:" + e.getMessage());
SecurityContextHolder.clearContext();
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.getWriter().write(e.getMessage());
return;
}
chain.doFilter(request, response);
}
}
其中包括获取JWT Token的信息、验证Token并设置认证信息的过程。
4. 配置Spring Security
在WebSecurityConfig
类中,添加以下内容:
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtConfig jwtConfig;
@Autowired
private JwtTokenFilter jwtTokenFilter;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll() // 解决跨域问题
.antMatchers("/").permitAll()
.antMatchers("/auth/**").permitAll()
.anyRequest().authenticated();
// 添加 JWT 过滤器
http.addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService()).passwordEncoder(new BCryptPasswordEncoder());
}
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
其中包括配置Session、过滤器链、安全策略等内容。
5. 创建JWT工具类
在Utils
包中,创建一个JwtTokenUtils
类,用于生成、验证JWT。
public class JwtTokenUtils {
public static final String TOKEN_HEADER = "Authorization";
public static final String TOKEN_PREFIX = "Bearer ";
public static String generateToken(String username, String secret, Date expireDate) {
Map<String, Object> map = new HashMap<>();
map.put("alg", "HS256");
map.put("typ", "JWT");
String token = Jwts.builder()
.setHeader(map)
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(expireDate)
.signWith(SignatureAlgorithm.HS256, secret)
.compact();
return token;
}
public static Claims getClaimByToken(String token, String secret) {
try {
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public static boolean isTokenExpired(Date expiration) {
return expiration.before(new Date());
}
}
其中包括生成Token、解析Token、验证Token等方法。
示例1:基于JWT的身份认证
在UserController
中添加以下内容:
@RestController
@RequestMapping("/auth")
public class UserController {
@Autowired
private UserService userService;
@Autowired
private JwtConfig jwtConfig;
@PostMapping("/login")
public String login(@RequestParam String username, @RequestParam String password) {
if (StringUtils.isBlank(username) || StringUtils.isBlank(password)) {
return "用户名或密码不能为空";
}
User user = userService.getUserByUsername(username);
if (user == null || !new BCryptPasswordEncoder().matches(password, user.getPassword())) {
return "用户名或密码错误";
}
String token = JwtTokenUtils.generateToken(username, jwtConfig.getSecret(), new Date(System.currentTimeMillis() + jwtConfig.getExpiration() * 1000));
return "Bearer " + token;
}
}
其中包括用户登录逻辑,生成JWT并返回前端。
示例2:基于JWT的单点登录
在AdminController
中添加以下内容:
@RestController
@RequestMapping("/admin")
public class AdminController {
@GetMapping("/hello")
public String hello() {
return "Hello, admin!";
}
}
在UserController
中添加以下内容:
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/hello")
public String hello(@RequestHeader(AUTHORIZATION) String token) {
HttpHeaders headers = new HttpHeaders();
headers.set(AUTHORIZATION, token);
HttpEntity<String> entity = new HttpEntity<>(headers);
ResponseEntity<String> responseEntity = restTemplate.exchange("http://localhost:8081/admin/hello", HttpMethod.GET, entity, String.class);
return responseEntity.getBody();
}
}
其中包括调用AdminController
的逻辑,获取JWT Token,并在请求头中添加。
至此,基于JWT的单点登录就实现了。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Spring Security基于JWT实现SSO单点登录详解 - Python技术站