创建模块工程 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
参数设置
| header | headerValue |
|---|---|
| Authorization | Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsidXNlciIsIm9yZGVyIl0sInVzZXJfaWQiOjEsInVzZXJfbmFtZSI6ImFkbWluIiwic2NvcGUiOlsicmVhZF9wcm9maWxlIiwid3JpdGVfb3JkZXIiXSwiZXhwIjoxNzUyMTk2NjU0LCJhdXRob3JpdGllcyI6WyJST0xFX1JPTEVfQURNSU4iLCJVU0VSX01BTkFHRSIsIlBFUk1JU1NJT05fTUFOQUdFIiwiUk9MRV9NQU5BR0UiXSwianRpIjoiYW5KUThET1lIcU1OQ2czeHRCYUZvaFUyUDJNIiwiY2xpZW50X2lkIjoiYXBwMSIsInVzZXJuYW1lIjoiYWRtaW4ifQ.7SPSrbTbj2rP9qXZxu8E_Nbo3T_bE1KGZmJ4b_3f1jY |
结果图: