📜  如何在 Spring Boot 中为单独的 jwt 令牌设置两个不同的表管理员和用户 - Java (1)

📅  最后修改于: 2023-12-03 15:38:26.659000             🧑  作者: Mango

如何在 Spring Boot 中为单独的 JWT 令牌设置两个不同的表管理员和用户

在 Spring Boot 中使用 JWT(Json Web Token)是非常常见的,JWT 用于进行身份验证和授权。在一些特定场景下,可能需要为不同的用户设置不同的身份验证表,在这个过程中需要使用不同的 JWT 令牌,例如管理员用户和非管理员用户需要使用不同的 JWT 令牌才能完成身份验证和权限控制。

步骤说明

下面是为在 Spring Boot 中为单独的 JWT 令牌设置两个不同的表管理员和用户的步骤说明:

1. 添加依赖项

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>
2. 编写 JWT 配置文件

在 Spring Boot 的配置文件中,按照以下格式进行配置:

jwt:
    # token过期时间,默认为1小时
    expire-time: 3600000
    # token在请求头中的名称,默认为Authorization
    token-header: Authorization
    # token前缀,例如Bearer+空格,就是Bearer这个前缀加上一个空格,如果没有则为空
    token-prefix: Bearer 
3. 编写公共 JWT 工具类

JwtUtil.java 文件中编写 JWT 工具类,代码如下:

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;

import javax.crypto.SecretKey;
import java.util.Date;

@Configuration
public class JwtUtil {
    @Autowired
    private JwtConfig jwtConfig;

    // 生成JWT token
    public String generateToken(long userId, String email, boolean isAdmin) {
        Date now = new Date();
        SecretKey key = Keys.hmacShaKeyFor(jwtConfig.getSecret().getBytes());

        String token = Jwts.builder()
                .signWith(key, SignatureAlgorithm.HS256)
                .claim("user_id", userId)
                .claim("email", email)
                .claim("is_admin", isAdmin)
                .setIssuedAt(now)
                .setExpiration(new Date(now.getTime() + jwtConfig.getExpireTime()))
                .compact();

        return jwtConfig.getTokenPrefix() + token;
    }

    // 解析JWT token
    public Claims parseToken(String token) {
        SecretKey key = Keys.hmacShaKeyFor(jwtConfig.getSecret().getBytes());

        return Jwts.parserBuilder()
                .setSigningKey(key)
                .build()
                .parseClaimsJws(token.replace(jwtConfig.getTokenPrefix(), ""))
                .getBody();
    }
}
4. 分别编写管理员和用户的身份验证过滤器
  • 编写管理员身份验证过滤器

AdminAuthenticationFilter.java 文件中编写管理员身份验证过滤器,代码如下:

import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.event.AuthenticationSuccessEvent;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Slf4j
@Component
public class AdminAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
    @Autowired
    private JwtUtil jwtUtil;

    public AdminAuthenticationFilter(AuthenticationManager authenticationManager) {
        super("/admin/**");
        setAuthenticationManager(authenticationManager);
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        String token = getTokenFromRequestHeader(request);

        if (StringUtils.isBlank(token)) {
            log.error("Admin token is blank");
            throw new AdminAuthenticationException("Unauthorized");
        }

        Claims claims = jwtUtil.parseToken(token);

        if (claims == null || !claims.get("is_admin", Boolean.class)) {
            log.error("Admin authentication fail");
            throw new AdminAuthenticationException("Unauthorized");
        }

        // 将身份验证信息传递给 AuthenticationManager 并进行身份验证
        AdminAuthenticationToken authRequest = new AdminAuthenticationToken(claims.get("user_id", Long.class), token);
        authRequest.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

        return getAuthenticationManager().authenticate(authRequest);
    }

    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
        super.successfulAuthentication(request, response, chain, authResult);

        // 验证成功后,将 Authentication 存储在 SecurityContextHolder 中
        SecurityContextHolder.getContext().setAuthentication(authResult);

        if (this.eventPublisher != null) {
            eventPublisher.publishEvent(new AuthenticationSuccessEvent(authResult));
        }

        chain.doFilter(request, response);
    }

    private String getTokenFromRequestHeader(HttpServletRequest request) {
        String authHeader = request.getHeader(jwtUtil.getJwtConfig().getTokenHeader());
        if (StringUtils.isNotBlank(authHeader) && authHeader.startsWith(jwtUtil.getJwtConfig().getTokenPrefix())) {
            return authHeader.replace(jwtUtil.getJwtConfig().getTokenPrefix(), "");
        }
        return null;
    }
}
  • 编写用户身份验证过滤器

UserAuthenticationFilter.java 文件中编写用户身份验证过滤器,代码如下:

import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.event.AuthenticationSuccessEvent;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Slf4j
@Component
public class UserAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
    @Autowired
    private JwtUtil jwtUtil;

    public UserAuthenticationFilter(AuthenticationManager authenticationManager) {
        super("/user/**");
        setAuthenticationManager(authenticationManager);
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        String token = getTokenFromRequestHeader(request);

        if (StringUtils.isBlank(token)) {
            log.error("User token is blank");
            throw new UserAuthenticationException("Unauthorized");
        }

        Claims claims = jwtUtil.parseToken(token);

        if (claims == null || claims.get("is_admin", Boolean.class)) {
            log.error("User authentication fail");
            throw new UserAuthenticationException("Unauthorized");
        }

        // 将身份验证信息传递给 AuthenticationManager 并进行身份验证
        UserAuthenticationToken authRequest = new UserAuthenticationToken(claims.get("user_id", Long.class), token);
        authRequest.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

        return getAuthenticationManager().authenticate(authRequest);
    }

    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
        super.successfulAuthentication(request, response, chain, authResult);

        // 验证成功后,将 Authentication 存储在 SecurityContextHolder 中
        SecurityContextHolder.getContext().setAuthentication(authResult);

        if (this.eventPublisher != null) {
            eventPublisher.publishEvent(new AuthenticationSuccessEvent(authResult));
        }

        chain.doFilter(request, response);
    }

    private String getTokenFromRequestHeader(HttpServletRequest request) {
        String authHeader = request.getHeader(jwtUtil.getJwtConfig().getTokenHeader());
        if (StringUtils.isNotBlank(authHeader) && authHeader.startsWith(jwtUtil.getJwtConfig().getTokenPrefix())) {
            return authHeader.replace(jwtUtil.getJwtConfig().getTokenPrefix(), "");
        }
        return null;
    }
}
5. 编写 AdminAuthenticationToken 和 UserAuthenticationToken 类

AdminAuthenticationToken.javaUserAuthenticationToken.java 文件中编写管理员和用户的身份验证 Token 类,代码如下:

  • AdminAuthenticationToken.java
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;

public class AdminAuthenticationToken extends UsernamePasswordAuthenticationToken {
    private String token;

    public AdminAuthenticationToken(long userId, String token) {
        super(userId, null);
        this.token = token;
    }

    public String getToken() {
        return token;
    }

    public void setToken(String token) {
        this.token = token;
    }
}
  • UserAuthenticationToken.java
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;

public class UserAuthenticationToken extends UsernamePasswordAuthenticationToken {
    private String token;

    public UserAuthenticationToken(long userId, String token) {
        super(userId, null);
        this.token = token;
    }

    public String getToken() {
        return token;
    }

    public void setToken(String token) {
        this.token = token;
    }
}
6. 编写 AdminAuthenticationException 和 UserAuthenticationException 异常类

AdminAuthenticationException.javaUserAuthenticationException.java 文件中编写管理员和用户的身份验证异常类,代码如下:

  • AdminAuthenticationException.java
import org.springframework.security.core.AuthenticationException;

public class AdminAuthenticationException extends AuthenticationException {
    public AdminAuthenticationException(String message) {
        super(message);
    }

    public AdminAuthenticationException(String message, Throwable cause) {
        super(message, cause);
    }
}
  • UserAuthenticationException.java
import org.springframework.security.core.AuthenticationException;

public class UserAuthenticationException extends AuthenticationException {
    public UserAuthenticationException(String message) {
        super(message);
    }

    public UserAuthenticationException(String message, Throwable cause) {
        super(message, cause);
    }
}

注意:这两个异常类必须继承自 AuthenticationException 类。

7. 配置 Spring Security

SecurityConfig.java 文件中配置 Spring Security,代码如下:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private JwtUtil jwtUtil;

    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/static/**");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 用户身份验证
        http.addFilter(new UserAuthenticationFilter(authenticationManager())).authorizeRequests().antMatchers("/user/**").authenticated();

        // 管理员身份验证
        http.addFilter(new AdminAuthenticationFilter(authenticationManager())).authorizeRequests().antMatchers("/admin/**").authenticated();

        http.httpBasic().disable()
                .csrf().disable()
                .formLogin().disable()
                .logout().disable();
    }

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

    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

以上配置将 /user/**/admin/** 下的请求分别交给 UserAuthenticationFilterAdminAuthenticationFilter 进行处理,同时在 configure(AuthenticationManagerBuilder auth) 方法中设置了加密方式为 BCryptPasswordEncoder。

总结

本文介绍了如何在 Spring Boot 中为单独的 JWT 令牌设置两个不同的表管理员和用户。关键在于分别编写管理员和用户的身份验证过滤器,并分别设置验证处理类和异常类,最后结合 Spring Security 进行认证和授权管理。