下面是详细讲解“SpringBoot集成Spring security JWT实现接口权限认证”的完整攻略。
概述
在实际项目中,对于接口权限认证一直是非常重要的问题。在 SpringBoot 中使用 Spring Security 与 JWT(JSON Web Token)完成接口权限认证是一种常见的方式。本文将介绍如何在 SpringBoot 中集成 Spring Security 和 JWT,实现接口权限认证。
前提条件
在开始之前,需要具备如下知识:
- SpringBoot 基础知识
- Spring Security 基础知识
- JWT 基础知识
步骤
1. 添加相关依赖
首先需要在 pom.xml
文件中添加如下依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<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>
spring-boot-starter-security
用于集成 Spring Securityjjwt
用于生成和解析 JWT
2. 实现用户认证
实现用户认证的方式有很多,本文使用内存数据库实现用户认证。在 SecurityConfig
类中配置了两个用户,一个是管理员,一个是普通用户。
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
String password = passwordEncoder().encode("123456");
auth.inMemoryAuthentication()
.withUser("admin").password(password).roles("ADMIN")
.and()
.withUser("user").password(password).roles("USER");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors()
.and().csrf().disable().authorizeRequests()
.antMatchers("/api/admin/**").hasRole("ADMIN")
.antMatchers("/api/user/**").hasAnyRole("ADMIN", "USER")
.anyRequest().authenticated()
.and().addFilter(new JwtAuthenticationFilter(authenticationManager()))
.addFilter(new JwtAuthorizationFilter(authenticationManager()))
.sessionManagement().sessionCreationPolicy(STATELESS);
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
3. 实现 JWT 认证
通过 JWT 认证,可以实现无状态的分布式认证。在本文中实现 JWT 认证方式有两种,一种是使用 BasicAuthenticationFilter 处理,一种是使用自定义 Filter 处理。
3.1 使用 BasicAuthenticationFilter
基于 BasicAuthenticationFilter 的方式,需要在 JwtAuthenticationFilter
类中实现用户认证方法。在使用该方式的时候,需要注意在请求头中设置 Authorization
字段。
public class JwtAuthenticationFilter extends BasicAuthenticationFilter {
public JwtAuthenticationFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
String header = request.getHeader("Authorization");
if (header == null || !header.startsWith("Bearer ")) {
chain.doFilter(request, response);
return;
}
UsernamePasswordAuthenticationToken authenticationToken = getAuthenticationToken(request);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
chain.doFilter(request, response);
}
private UsernamePasswordAuthenticationToken getAuthenticationToken(HttpServletRequest request) {
String token = request.getHeader("Authorization").replace("Bearer ", "");
if (token != null) {
String username = JWT.require(Algorithm.HMAC256(SECRET.getBytes()))
.build()
.verify(token)
.getSubject();
if (username != null) {
return new UsernamePasswordAuthenticationToken(username, null, new ArrayList<>());
}
return null;
}
return null;
}
}
3.2 使用自定义 Filter
自定义 Filter 的方式可以多端共用,比如 web 端、移动端等。自定义 JWT 认证过滤器的代码如下:
public class JwtAuthorizationFilter extends BasicAuthenticationFilter {
public JwtAuthorizationFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
String header = request.getHeader("Authorization");
if (header == null || !header.startsWith("Bearer ")) {
chain.doFilter(request, response);
return;
}
UsernamePasswordAuthenticationToken authenticationToken = getAuthenticationToken(request);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
chain.doFilter(request, response);
}
private UsernamePasswordAuthenticationToken getAuthenticationToken(HttpServletRequest request) {
String token = request.getHeader("Authorization").replace("Bearer ", "");
if (token != null) {
try {
Jws<Claims> claimsJws = Jwts.parser()
.setSigningKey(SECRET.getBytes())
.parseClaimsJws(token);
String username = claimsJws.getBody().getSubject();
List<SimpleGrantedAuthority> authorities = ((List<?>) claimsJws.getBody()
.get("role")).stream()
.map(authority -> new SimpleGrantedAuthority((String) authority))
.collect(Collectors.toList());
if (username != null) {
return new UsernamePasswordAuthenticationToken(username, null, authorities);
}
return null;
} catch (ExpiredJwtException | UnsupportedJwtException | MalformedJwtException | SignatureException | IllegalArgumentException e) {
throw new JwtAuthenticationException("JWT token is invalid", HttpStatus.UNAUTHORIZED);
}
}
return null;
}
}
4. 接口权限控制
在 SecurityConfig
类中,可以配置接口访问权限:
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors()
.and().csrf().disable().authorizeRequests()
.antMatchers("/api/admin/**").hasRole("ADMIN")
.antMatchers("/api/user/**").hasAnyRole("ADMIN", "USER")
.anyRequest().authenticated()
.and().addFilter(new JwtAuthenticationFilter(authenticationManager()))
.addFilter(new JwtAuthorizationFilter(authenticationManager()))
.sessionManagement().sessionCreationPolicy(STATELESS);
}
通过上述配置,所有以 /api/admin/
开头的接口都需要 ADMIN 角色才能访问,以 /api/user/
开头的接口需要 USER 或 ADMIN 角色。
5. JWT Token 生成和校验
在本小节中,将简单介绍 JWT Token 的生成和校验代码。
5.1 Token 生成
在用户登录成功之后,可以通过如下代码生成 Token:
String token = JWT.create()
.withSubject(user.getUsername())
.withExpiresAt(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.withClaim("role", user.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toList()))
.sign(Algorithm.HMAC256(SECRET.getBytes()));
EXPIRATION_TIME
是 Token 过期时间,SECRET
是使用的密钥。
5.2 Token 校验
在用户请求接口的时候,需要根据请求头中的 Token 校验用户身份是否有效。校验代码如下:
String token = request.getHeader("Authorization");
if (token != null && token.startsWith("Bearer ")) {
try {
Jws<Claims> claims = Jwts.parser()
.setSigningKey(SECRET.getBytes())
.parseClaimsJws(token.replace("Bearer ", ""));
String username = claims.getBody().getSubject();
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities()
);
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
} catch (ExpiredJwtException | UnsupportedJwtException | MalformedJwtException | SignatureException | IllegalArgumentException e) {
logger.error("JWT token is invalid: {}", e.getMessage(), e);
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.getWriter().write(e.getMessage());
return;
}
}
6. 示例代码
本篇文章中使用了两种方式实现 JWT 认证,下面提供方式二的完整代码:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private static final String SECRET = "JWT_SECRET";
private static final long EXPIRATION_TIME = 86400000L; // 1 day
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors()
.and().csrf().disable().authorizeRequests()
.antMatchers("/api/admin/**").hasRole("ADMIN")
.antMatchers("/api/user/**").hasAnyRole("ADMIN", "USER")
.anyRequest().authenticated()
.and().addFilterAfter(new JwtAuthorizationFilter(), UsernamePasswordAuthenticationFilter.class);
}
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(SECRET);
return converter;
}
@Bean
public JwtTokenEnhancer jwtTokenEnhancer() {
return new JwtTokenEnhancer();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
private class JwtTokenEnhancer implements TokenEnhancer {
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
User user = (User) authentication.getPrincipal();
Map<String, Object> info = new HashMap<>();
info.put("username", user.getUsername());
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(info);
return accessToken;
}
}
private class JwtAuthorizationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
String token = request.getHeader("Authorization");
if (token != null && token.startsWith("Bearer ")) {
try {
Jwt jwt = JwtHelper.decode(token.replace("Bearer ", ""));
String jwtClaimsStr = jwt.getClaims();
JSONObject jsonObject = JSON.parseObject(jwtClaimsStr);
String username = jsonObject.getString("username");
User user = (User) userDetailsService.loadUserByUsername(username);
if (user != null) {
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
user, null, user.getAuthorities()
);
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
} catch (ExpiredJwtException | UnsupportedJwtException | MalformedJwtException | SignatureException | IllegalArgumentException e) {
logger.error("JWT token is invalid: {}", e.getMessage(), e);
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.getWriter().write(e.getMessage());
return;
}
}
chain.doFilter(request, response);
}
}
}
完整的示例代码请移步:https://github.com/jackson0714/springboot-security-jwt-demo。
至此,本文介绍了在 SpringBoot 中集成 Spring Security 和 JWT 实现接口权限认证的完整攻略,希望对读者有所帮助。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:SpringBoot集成Spring security JWT实现接口权限认证 - Python技术站