SpringBoot Secure

SpringBoot 集成JWT以及相关认证

1. jwt 工具类

生成 jwt 目前有两类比较流行的工具库:

  1. jjwt (io.jsonwebtoken)
  2. java-jwt (com.auth0)

以上两种方式均可,java-jwt 相对比较简单些。但目前推荐使用 jjwt,因此项目中将使用 jjwt 作为生产 jwt token 的工具。

TokenBuilder.java:

public class TokenBuilder {

    private final String secret = "xxxx";
    private final SecretKey secretKey = Keys.hmacShaKeyFor(secret.getBytes());

    public TokenBuilder() {

    }

    public String generate(PayloadData payloadData) {
        payloadData.checkParam();

        String token = Jwts.builder()
            .claim("payload", payloadData)
            .signWith(secretKey)
            .compact();
        log.info(token);
        return Base64.getUrlEncoder().withoutPadding().encodeToString(token.getBytes(StandardCharsets.UTF_8));
    }

    public PayloadData parse(String vtoken) {
        String token = new  String(Base64.getUrlDecoder().decode(vtoken), StandardCharsets.UTF_8);
        try {
            Jws<Claims> claimsJws = Jwts.parser()
                .verifyWith(secretKey)
                .build()
                .parseSignedClaims(token);

            Claims body = claimsJws.getPayload();
            Map<String, Object> payloadMap = body.get("payload", Map.class);
            if(payloadMap != null) {
                return PayloadData.builder()
                    .userId(((Number) payloadMap.get("userId")).longValue())
                    .projectName((String) payloadMap.get("projectName"))
                    .loginType((String) payloadMap.get("loginType"))
                    .ctime((Long) payloadMap.get("ctime"))
                    .build();
            }
        } catch (SecurityException e) {
            // 签名无效
            log.error("{}", e.getMessage());
            JDZException.reponse(GlobalCode.ERR_TOKEN_PARSE_SIGNAL);
        } catch (ExpiredJwtException e) {
            // Token 过期
            log.error("{}", e.getMessage());
            JDZException.reponse(GlobalCode.ERR_TOKEN_PARSE_EXP);
        } catch (Exception e) {
            log.error("{}", e.getMessage());
            JDZException.reponse(GlobalCode.ERR_TOKEN_PARSE_ERR);
        }
        JDZException.reponse(GlobalCode.ERR_TOKEN_PARSE_NO_DATA);
        return null;
    }
}

2. 配置 Authentication Filter 请求时,解析 token 并验证

public abstract class BaseAuthenticationFilter extends OncePerRequestFilter {

    private final TokenBuilder tokenBuilder;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
        throws ServletException, IOException {
        String token = getTokenFromRequest(request);
        if(!StringUtils.hasText(token)) {
            // 无Token,放行(交给Spring Security处理未认证)
            filterChain.doFilter(request, response);
            return;
        }

        try {
            PayloadData payloadData = tokenBuilder.parse(token);

            handleAuth(payloadData, request);
        } catch (JDZException e) {
            log.info("-------{}------", e.getMessage());
        } catch (JwtAuthenticationException e) {
            log.info("JwtAuthenticationException caught:{} {}", e.getErrorCode().getCode(), e.getMessage());
        }catch (Exception e) {
            log.info("-------{}--55555----", e.getMessage());
        }

        try {
            filterChain.doFilter(request, response);
        } finally {
            SecurityContextHolder.clearContext();
        }

    }

    protected String getTokenFromRequest(HttpServletRequest request) {
        String bearerToken = request.getHeader("Authorization");
        if(StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
            return bearerToken.substring(7);
        }
        return null;
    }

    // 子类去实现
    protected abstract void handleAuth(PayloadData payloadData, HttpServletRequest request);
}

@Override
    protected void handleAuth(PayloadData payloadData, HttpServletRequest request) {
        AccountDO accountDO = accountService.getById(payloadData.getUserId());
        if(accountDO == null) {
            throw new BadCredentialsException(GlobalCode.ERR_TOKEN_UID.getMsgKey());
        }
        if(!accountDO.getId().equals(payloadData.getUserId())) {
            throw new JwtAuthenticationException(GlobalCode.ERR_TOKEN_UID);
        }
        UserDetails userDetails = User
            .withUsername(accountDO.getUsername())
            .password("N/A")
            //.authorities(getAuthorities(accountDO)) // 确保有正确的权限
            .build();

        UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
            userDetails,
            null,
            userDetails.getAuthorities()
        );
        authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
        SecurityContextHolder.getContext().setAuthentication(authentication);
    }

3. 将上面的 jwt Authentication Filter 配置进入 SecurityFilterChain:

@Component
@RequiredArgsConstructor
public class SecurityConfigurator {
    private final BaseAuthenticationFilter authenticationFilter;
    private final JDZAuthExceptionHandler authExceptionHandler;

    private final List<AuthorizationCustomizer> authorizationCustomizers = new ArrayList<>();
    private final List<String> publicPaths = new ArrayList<>();

    public void addPublicPath(String path) {
        publicPaths.add(path);
    }

    public void addAuthorizationCustomizer(AuthorizationCustomizer customizer) {
        authorizationCustomizers.add(customizer);
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf(AbstractHttpConfigurer::disable)
            .sessionManagement(session ->
                session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .authorizeHttpRequests(auth -> {
                // 公共公开路径
                auth.requestMatchers(
                    "/swagger-ui/**",
                    "/v3/api-docs/**"
                ).permitAll();

                // 动态添加的业务公开路径
                if(!publicPaths.isEmpty()) {
                    auth.requestMatchers(publicPaths.toArray(new String[0])).permitAll();
                }

                // 应用所有授权定制器
                for (AuthorizationCustomizer customizer : authorizationCustomizers) {
                    customizer.customize(auth);
                }

                auth.anyRequest().authenticated();
            })
            .exceptionHandling(ex -> ex
                // 配置认证失败处理器(401)
                .authenticationEntryPoint(authExceptionHandler)
                // 配置授权失败处理器(403)
                .accessDeniedHandler(authExceptionHandler)
            )
            .addFilterBefore(authenticationFilter, UsernamePasswordAuthenticationFilter.class);
        return http.build();
    }

    @FunctionalInterface
    public interface AuthorizationCustomizer {
        void customize(AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry auth);
    }
}
本文为原创内容,作者:闲鹤,原文链接:https://blog.uwenya.cc/1664.html,转载请注明出处。

发表评论