二 、 Springboot2.7 + springsecurityoauth2 + jwt (业务验证)

119 阅读2分钟

创建模块工程 spring-service-user

pom.xml 依赖
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.security.oauth.boot</groupId>
        <artifactId>spring-security-oauth2-autoconfigure</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <scope>provided</scope>
    </dependency>
</dependencies>
项目配置文件
server:
  port: 8081

jwt:
  signingKey: elewkey
资源验证类配置 ResourceServerConfig.java
package com.elew.springserviceuser.conf;


import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;


@Configuration
@EnableResourceServer // 启用资源服务器配置
@EnableGlobalMethodSecurity(prePostEnabled = true) // 启用方法级别的安全配置
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    @Value("${jwt.signingKey}")
    private String signingKey;

    private final AuthenticationEntryPoint authenticationEntryPoint;
    private final AccessDeniedHandler accessDeniedHandler;

    public ResourceServerConfig(AuthenticationEntryPoint authenticationEntryPoint, AccessDeniedHandler accessDeniedHandler) {
        this.authenticationEntryPoint = authenticationEntryPoint;
        this.accessDeniedHandler = accessDeniedHandler;
    }


    /**
     * 配置安全规则
     * @param http HttpSecurity 实例
     * @throws Exception
     */
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
                .addFilterBefore(new JwtTokenLoggingFilter(), BasicAuthenticationFilter.class) // 添加自定义过滤器
                .authorizeRequests(auth -> auth
                        .antMatchers("/api/**").permitAll() // 允许访问登录页面
                        .anyRequest().authenticated() // 其他请求需要认证
                )
                .csrf().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .exceptionHandling()
                .authenticationEntryPoint(authenticationEntryPoint)
                .accessDeniedHandler(accessDeniedHandler)
        ;
    }




    /**
     * 配置资源服务器的安全信息
     * @param resources ResourceServerSecurityConfigurer 实例
     * @throws Exception
     */
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.resourceId("user").tokenStore(tokenStore()).stateless(true);
    }

    /**
     * 配置 JWT 令牌存储
     * @return TokenStore 实例
     */
    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    /**
     * 配置 JWT 访问令牌转换器。
     * @return JwtAccessTokenConverter 实例
     */
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        // 设置签名密钥,实际应用中应使用更安全的方式
        converter.setSigningKey(signingKey);
        return converter;
    }

}
认证、授权失败处理配置类 NoAuthedConfig.java
package com.elew.springserviceuser.conf;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;

import java.util.HashMap;
import java.util.Map;

@Configuration
public class NoAuthedConfig {

    /**
     * 自定义认证失败处理逻辑
     * @return AuthenticationEntryPoint 实例
     */
    @Bean
    public AuthenticationEntryPoint authenticationEntryPoint() {
        return (request, response, authException) -> {
            response.setStatus(HttpStatus.UNAUTHORIZED.value());
            response.setContentType(MediaType.APPLICATION_JSON_VALUE);
            Map<String, Object> body = new HashMap<>();
            body.put("status", HttpStatus.UNAUTHORIZED.value());
            body.put("error", "Unauthorized");
            body.put("message", authException.getMessage());
            body.put("path", request.getServletPath());
            ObjectMapper mapper = new ObjectMapper();
            mapper.writeValue(response.getOutputStream(), body);
        };
    }

    /**
     * 自定义授权失败处理逻辑
     * @return AccessDeniedHandler 实例
     */
    @Bean
    public AccessDeniedHandler accessDeniedHandler() {
        return (request, response, accessDeniedException) -> {
            response.setStatus(HttpStatus.FORBIDDEN.value());
            response.setContentType(MediaType.APPLICATION_JSON_VALUE);
            Map<String, Object> body = new HashMap<>();
            body.put("status", HttpStatus.FORBIDDEN.value());
            body.put("error", "Forbidden");
            body.put("message", accessDeniedException.getMessage());
            body.put("path", request.getServletPath());

            ObjectMapper mapper = new ObjectMapper();
            mapper.writeValue(response.getOutputStream(), body);
        };
    }

}
帮助调试查看数据的jwttoken过滤器 JwtTokenLoggingFilter.java
package com.elew.springserviceuser.conf;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.jwt.Jwt;
import org.springframework.security.jwt.JwtHelper;
import org.springframework.security.jwt.crypto.sign.MacSigner;
import org.springframework.security.jwt.crypto.sign.SignatureVerifier;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;
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.List;
import java.util.stream.Collectors;

/**
 * 自定义过滤器,用于打印 JWT 令牌和认证信息
 * @author Elew
 */
public class JwtTokenLoggingFilter extends OncePerRequestFilter {
    private Logger logger = LoggerFactory.getLogger(JwtTokenLoggingFilter.class);

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException, ServletException, IOException {
        String authorizationHeader = request.getHeader("Authorization");
        if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
            String jwtToken = authorizationHeader.substring(7);
            logger.info("JWTToken=== {}", jwtToken);
            Jwt jwt = jwtInfo(jwtToken);
            if (jwt != null) {
                // 获取 JWT 的载荷部分
                String claims = jwt.getClaims();
                logger.info("JWT Claims=== {} ", claims);
            }

            // 获取当前认证信息
            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            if (authentication instanceof OAuth2Authentication) {
                OAuth2Authentication oauth2Authentication = (OAuth2Authentication) authentication;
                OAuth2Request oauth2Request = oauth2Authentication.getOAuth2Request();
                // 获取资源 ID
                List resourceIds = oauth2Request.getResourceIds().stream().collect(Collectors.toList());
                if (resourceIds != null) {
                    logger.info("Authenticated Resource IDs=== {} ", resourceIds);
                } else {
                    logger.info("No Resource ID found in the authentication.");
                }
            }
        }
        filterChain.doFilter(request, response);
    }

    private Jwt jwtInfo(String jwtToken) {
        try {
            // 创建签名验证器
            SignatureVerifier verifier = new MacSigner("your-signing-key");
            // 验证并解析 JWT
            Jwt jwt = JwtHelper.decodeAndVerify(jwtToken, verifier);
            return jwt;

        } catch (Exception e) {
            logger.error("JWT 验证或解析失败:{} ", e.getMessage());
        }
        return null;
    }
}
控制器 ResourceController.java
package com.elew.springserviceuser.controller;


import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api")
public class ResourceController {

    @GetMapping("/public")
    @PreAuthorize("hasAnyAuthority('ROLE_MANAGE','USER_PROFILE_VIEW')")
    public String publicResource() {
        return "This is a public resource.";
    }

    @GetMapping("/private")
    @PreAuthorize("hasAnyAuthority('USER_MANAGE','USER_PROFILE_VIEW')")
    public String privateResource() {
        return "This is a private resource, only accessible by users with USER role.";
    }
}
主类 UserApp.java
package com.elew.springserviceuser;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class UserApp {

    public static void main(String[] args) {
        SpringApplication.run(UserApp.class, args);
    }
}

项目应该可以正常启动了

测试说明

http://localhost:8081/api/private

http://localhost:8081/api/public

参数设置

headerheaderValue
AuthorizationBearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsidXNlciIsIm9yZGVyIl0sInVzZXJfaWQiOjEsInVzZXJfbmFtZSI6ImFkbWluIiwic2NvcGUiOlsicmVhZF9wcm9maWxlIiwid3JpdGVfb3JkZXIiXSwiZXhwIjoxNzUyMTk2NjU0LCJhdXRob3JpdGllcyI6WyJST0xFX1JPTEVfQURNSU4iLCJVU0VSX01BTkFHRSIsIlBFUk1JU1NJT05fTUFOQUdFIiwiUk9MRV9NQU5BR0UiXSwianRpIjoiYW5KUThET1lIcU1OQ2czeHRCYUZvaFUyUDJNIiwiY2xpZW50X2lkIjoiYXBwMSIsInVzZXJuYW1lIjoiYWRtaW4ifQ.7SPSrbTbj2rP9qXZxu8E_Nbo3T_bE1KGZmJ4b_3f1jY

结果图:

授权通过

image.png

授权不过

image.png