微服务之Auth篇
auth服务重要是认证授权,签发jwt token使用。
生成密钥对
# 在项目 src/main/resources 下生成 jwt.jks(演示用,生产用更严格的密码/keystore)
keytool -genkeypair \
-alias jwt \
-keyalg RSA \
-keysize 2048 \
-keystore src/main/resources/jwt.jks \
-storepass 123456 \
-keypass 123456 \
-dname "CN=auth.lljing.com, OU=dev, O=lljing, L=City, ST=State, C=CN" \
-validity 3650
配置解释:
alias: 密钥轮询表示 ,用于生产环境 ,多组密钥对的时候来获取使用的密钥对
keyalg:签名算法,这里固定RSA,非对称加密
keysize:生成的key的长度
keystore:存放密钥对的文件
storepass : 密钥文件的密码
keypass :单个RSA密钥对的密码
dname:
CN Common Name 公共名称。通常是域名、服务名或用户名。例如:CN=auth.example.com。在浏览器证书里就是“通用名”。
OU Organizational Unit 部门或单位。例如:OU=dev 表示开发部门。
O Organization 组织/公司名称。例如:O=Example Inc.。
L Locality 城市 / 地理位置。例如:L=Shanghai。
ST State or Province 州/省。例如:ST=Shanghai。
C Country 国家代码(ISO 3166-1 alpha-2),例如:中国就是 C=CN。
validity :证书的有效期,单位:天
删除已有密钥
keytool -delete -alias jwt -keystore src/main/resources/jwt.jks -storepass [你实际的密码]
生成密钥对的时候alias不同,则会追加形成密钥箱,如果相同,需要先删除原有的密钥。
定义Jwt编解码(RSA)
注册Bean
这里@Bean可以注册为Spring管理的Bean,方便在使用的时候注入。
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.KeyUse;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.JWSKeySelector;
import com.nimbusds.jose.proc.JWSVerificationKeySelector;
import com.nimbusds.jose.proc.SecurityContext;
import com.nimbusds.jwt.proc.DefaultJWTProcessor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
import java.io.InputStream;
import java.security.*;
import java.security.cert.Certificate;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
@Configuration
public class JwkConfig {
@Value("${spring.auth.keystore.location}")
private Resource keystore;
@Value("${spring.auth.keystore.password}")
private String keystorePassword;
@Value("${spring.auth.keystore.alias}")
private String keyAlias;
@Value("${spring.auth.keystore.keypass}")
private String keyPassword;
@Bean
public RSAKey rsaKey() throws Exception {
KeyStore ks = KeyStore.getInstance("JKS");
try (InputStream in = keystore.getInputStream()) {
ks.load(in, keystorePassword.toCharArray());
}
Key key = ks.getKey(keyAlias, keyPassword.toCharArray());
Certificate cert = ks.getCertificate(keyAlias);
RSAPublicKey publicKey = (RSAPublicKey) cert.getPublicKey();
RSAPrivateKey privateKey = (RSAPrivateKey) key;
return new RSAKey.Builder(publicKey)
.privateKey(privateKey)
.keyUse(KeyUse.SIGNATURE)
.algorithm(JWSAlgorithm.RS256)
.keyID(keyAlias)
.build();
}
@Bean
public KeyPair keyPair() throws Exception {
KeyStore ks = KeyStore.getInstance("JKS");
try (InputStream in = keystore.getInputStream()) {
ks.load(in, keystorePassword.toCharArray());
}
Key key = ks.getKey(keyAlias, keyPassword.toCharArray());
if (!(key instanceof PrivateKey)) {
throw new IllegalStateException("Not a private key");
}
PublicKey publicKey = ks.getCertificate(keyAlias).getPublicKey();
return new KeyPair(publicKey, (PrivateKey) key);
}
/***
* @desc <Jwt签名配置>
* @param rsaKey
* @date 2025年9月23日 11:14
* @return org.springframework.security.oauth2.jwt.JwtEncoder
* @exception
*/
@Bean
public JwtEncoder jwtEncoder(RSAKey rsaKey) {
JWKSet jwkSet = new JWKSet(rsaKey);
JWKSource<SecurityContext> jwkSource = new ImmutableJWKSet<>(jwkSet);
return new NimbusJwtEncoder(jwkSource);
}
/**
* @desc <JWT令牌解码器配置>
* @param jwkSource 从JwkConfig中注入的RSAKey
* @date 2025年9月23日
* @return JwtDecoder
*/
@Bean
public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
// 创建 JWT Processor
DefaultJWTProcessor<SecurityContext> jwtProcessor = new DefaultJWTProcessor<>();
JWSKeySelector<SecurityContext> keySelector =
new JWSVerificationKeySelector<>(JWSAlgorithm.RS256, jwkSource);
jwtProcessor.setJWSKeySelector(keySelector);
return new NimbusJwtDecoder(jwtProcessor);
}
/***
* @desc <jwt秘钥箱配置>
* @param rsaKey
* @date 2025年9月23日 11:14
* @return com.nimbusds.jose.jwk.source.JWKSource<com.nimbusds.jose.proc.SecurityContext>
* @exception
*/
@Bean
public JWKSource<SecurityContext> jwkSource(RSAKey rsaKey) {
JWKSet jwkSet = new JWKSet(rsaKey);
return (selector, context) -> selector.select(jwkSet);
}
}
配置
spring:
auth:
keystore:
location: classpath:jwt.jks
password: 123456
alias: jwt
keypass: 123456
编写认证核心实现
认证主要流程就是这个步骤,需要拓展的时候,只需要添加一个AuthenticationProvider的实现类并在他的supports方法中定义好需要解析的实体类型,对应认证即可进入到当前Provider中。
1. 添加认证负载实体(UPAuthenticationToken)
没增加一个认证需要先添加一个认证实体,集成抽象类AbstractAuthenticationToken ,即可
principal方法用于获取用户信息负载,“用户名密码”认证的情况下就可以存放用户名,在认证通过后重新构建Principal用于存放用户信息。
credentials验证类,用于存放密码或者验证码
authorities 用于存放用户角色和权限信息
details 其他认证附加信息
import lombok.Setter;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
/**
* <用户名密码认证实体>
*
* @author:JingLonglong
* @date:2025/9/22
*/
public class UPAuthenticationToken extends AbstractAuthenticationToken {
@Setter
private String username;
@Setter
private UserDetails userDetails;
@Setter
private String password;
public UPAuthenticationToken(Collection<? extends GrantedAuthority> authorities) {
super(authorities);
}
@Override
public Object getCredentials() {
return isAuthenticated() ? null : password;
}
@Override
public Object getPrincipal() {
return isAuthenticated() ? userDetails : username;
}
}
2. 创建认证处理类(UPAuthenticationProvider)
这里主要是进行用户认证的核心处理类 ,至于数据从哪里来,后面会交代,这里面添加了验证码的相关逻辑,不需要或者自定义实现的可以去除掉。
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
/**
* <用户名密码认证处理>
* <功能详细描述>
*
* @author:JingLonglong
* @date:2025/9/22
*/
@Component
@RequiredArgsConstructor
public class UPAuthenticationProvider implements AuthenticationProvider {
private final CustomUserDetailsService userDetailsService;
private final RedisService redisService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
//获取登录信息
String username = authentication.getPrincipal().toString();
String password = authentication.getCredentials().toString();
//这里进行用户名密码判断
if (username == null || password == null) {
throw new AuthenticationCredentialsNotFoundException("用户名或密码不能为空");
}
if ("admin".equalsIgnoreCase(username) || "admin".equalsIgnoreCase(password)) {
UsernamePasswordAuthentication successAuthentication = new UsernamePasswordAuthentication(new ArrayList<>());
successAuthentication.setUsername(username);
AuthUserInfo authUserInfo = new AuthUserInfo(username, password);
successAuthentication.setDetails(authUserInfo);
return successAuthentication;
}
throw new BadCredentialsException("用户认证失败");
}
@Override
public boolean supports(Class<?> authentication) {
//这里会根据不同的实体类,分配不同的处理。UPAuthenticationToken就会交给当前处理器
return authentication.isAssignableFrom(UPAuthenticationToken.class);
}
}
3. 添加用户认证拦截器(LoginAuthenticationFilter)
这里就是数据构造,从request中拿到数据构造成不同的AbstractAuthenticationToken 实体,系统会自动找对应的AuthenticationProvider 进行处理。
import com.lljing.common.util.JsonUtil;
import com.lljing.common.util.ServletUtils;
import com.sinoprof.sinoauth.domain.UPAuthenticationToken;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import java.io.IOException;
import java.util.Map;
/**
* <用户认证拦截器>
* <功能详细描述>
*
* @author:JingLonglong
* @date:2025/9/22
*/
@Slf4j
public class LoginAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
private static final String AUTHENTICATION_SCHEME = "/oauth/token";
public LoginAuthenticationFilter(AuthenticationManager authenticationManager) {
super(request -> AUTHENTICATION_SCHEME.equalsIgnoreCase(request.getRequestURI()) &&
request.getMethod().equalsIgnoreCase(HttpMethod.POST.name()), authenticationManager);
}
/***
* @desc <获取登录参数,构建不通的请求实体交给不通的认证处理器去处理>
* <功能详细描述>
* @param request
* @param response
* @date 2025年9月22日 10:20
* @return org.springframework.security.core.Authentication
* @exception
*/
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
String postBody = ServletUtils.getPostBody(request);
Map<String, String> collect = JsonUtil.fromJson(postBody, new TypeReference<Map<String, String>>() {});
//根据请求类型来区分认证实体
String grantType = collect.get("grant_type");
UPAuthenticationToken authenticationToken;
//pathParams放在detail中
if ("cbc".equalsIgnoreCase(grantType)){
authenticationToken = new UPAuthenticationToken(null);
authenticationToken.setAuthenticated(false);
authenticationToken.setDetails(collect);
authenticationToken.setUsername(collect.get("username"));
authenticationToken.setPassword(collect.get("password"));
return super.getAuthenticationManager().authenticate(authenticationToken);
}
throw new AuthenticationServiceException("未经授权的认证方式");
}
}
4. 注册认证结果处理类(LoginResultHandler)
这里将认证成功和失败处理放在了一起 ,如果逻辑复杂,也可以分开。
认真成功后可以使用自定义的方法来签发Token,调用前面配置的Jwt方法即可 ,这里使用了Oauth Service内置的方法,方便后续拓展。具体实现见:Token签发
import com.lljing.common.api.R;
import com.lljing.common.util.JsonUtil;
import com.sinoprof.sinoauth.constant.LoginConstant;
import com.sinoprof.sinoauth.domain.SecurityUser;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtClaimsSet;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.JwtEncoderParameters;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Map;
import java.util.UUID;
/**
* <一句话功能简述>
* <功能详细描述>
*
* @author:JingLonglong
* @date:2025/9/22
*/
@Service
@RequiredArgsConstructor
@Slf4j
public class LoginResultHandler implements AuthenticationSuccessHandler, AuthenticationFailureHandler {
private final RegisteredClientRepository registeredClientRepository;
private final OAuth2AuthorizationService authorizationService;
private final JwtEncoder jwtEncoder;
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
throws IOException {
SecurityUser details = (SecurityUser) authentication.getDetails();
// 1. 获取 RegisteredClient
RegisteredClient registeredClient = registeredClientRepository.findByClientId(LoginConstant.DEFAULT_CLIENT_ID);
// 2. 构建 AuthorizationBuilder
OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.withRegisteredClient(registeredClient)
.principalName(details.getUsername())
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.attribute(Authentication.class.getName(), authentication);
// 3. 构建 AccessToken Claims
JwtClaimsSet claims = JwtClaimsSet.builder()
.subject(details.getUsername())
.issuedAt(Instant.now())
.expiresAt(Instant.now().plus(1, ChronoUnit.HOURS))
.claim("roles", authentication.getAuthorities().stream()
.map(GrantedAuthority::getAuthority).toList())
.claim("tenantId", details.getTenantId())
.claim("displayName", details.getDisplayName())
.claim("companyId", details.getCompanyId())
.claim("companyName", details.getCompanyName())
.claim("groupId", details.getGroupId())
.claim("groupName", details.getGroupName())
.claim("loginName", details.getLoginName())
.build();
// 4. 生成 AccessToken (JWT)
Jwt jwt = this.jwtEncoder.encode(JwtEncoderParameters.from(claims));
OAuth2AccessToken accessToken = new OAuth2AccessToken(
OAuth2AccessToken.TokenType.BEARER,
jwt.getTokenValue(),
jwt.getIssuedAt(),
jwt.getExpiresAt(),
registeredClient.getScopes()
);
authorizationBuilder.accessToken(accessToken);
// 5. 生成 RefreshToken
OAuth2RefreshToken refreshToken = new OAuth2RefreshToken(
UUID.randomUUID().toString(),
Instant.now(),
Instant.now().plus(30, ChronoUnit.DAYS)
);
authorizationBuilder.refreshToken(refreshToken);
// 6. 构建并保存 Authorization
OAuth2Authorization authorization = authorizationBuilder.build();
authorizationService.save(authorization);
// 7. 返回 JSON 给客户端
Map<String, Object> tokenResponse = Map.of(
"access_token", accessToken.getTokenValue(),
"token_type", accessToken.getTokenType().getValue(),
"expires_in", Duration.between(Instant.now(), accessToken.getExpiresAt()).getSeconds(),
"refresh_token", refreshToken.getTokenValue()
);
response.setContentType("application/json");
JsonUtil.getSerializerMapper().writeValue(response.getOutputStream(), tokenResponse);
}
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
log.warn("用户认证失败: {}", exception.getMessage());
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json;charset=UTF-8");
response.getWriter().println(JsonUtil.toJSONString(R.failed(exception.getMessage())));
}
}
5. 注册拦截器到web中(WebSecurityConfig)
这里面做的事情比较多 ,后面还有附加的资源认真的配置。主要做了下列是想
将所有的认证处理类添加到
AuthenticationManager
中(authenticationManager
方法)。添加认证拦截器
LoginAuthenticationFilter filter = new LoginAuthenticationFilter(authenticationManager); .addFilterBefore(filter, UsernamePasswordAuthenticationFilter.class)
添加资源验证拦截器(携带Token的时候进入时调用这里)
.oauth2ResourceServer(oauth2 -> oauth2 .jwt(jwt -> jwt.decoder(jwtDecoder)) .authenticationEntryPoint(unauthorizedHandler) // <- 指定自定义入口 )
设置白名单
设置统一返回信息处理类
关闭Session、csrf、httpBase
import com.sinoprof.sinoauth.filter.LoginAuthenticationFilter;
import com.sinoprof.sinoauth.filter.LoginResultHandler;
import com.sinoprof.sinoauth.granter.CustomAccessDeniedHandler;
import com.sinoprof.sinoauth.granter.CustomAuthenticationEntryPoint;
import lombok.AllArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import java.util.List;
/***
* @desc <pringSecurity配置>
* @date 2025年9月23日 16:58
* @return
* @exception
*/
@Configuration
@EnableWebSecurity
@AllArgsConstructor
public class WebSecurityConfig {
private final List<AuthenticationProvider> authenticationProvider;
private final LoginResultHandler loginResultHandler;
private final CustomAuthenticationEntryPoint unauthorizedHandler;
private final CustomAccessDeniedHandler customAccessDeniedHandler;
private final JwtDecoder jwtDecoder;
@Bean
public AuthenticationManager authenticationManager() {
return new ProviderManager(authenticationProvider);
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http, AuthenticationManager authenticationManager) throws Exception {
LoginAuthenticationFilter filter = new LoginAuthenticationFilter(authenticationManager);
filter.setAuthenticationSuccessHandler(loginResultHandler);
filter.setAuthenticationFailureHandler(loginResultHandler);
return http
// 基于token,不使用session
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.formLogin(AbstractHttpConfigurer::disable)
.httpBasic(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(auth -> auth
// 白名单
.requestMatchers(
"/rsa/publicKey",
"/oauth/token",
"/oauth/faceCheck",
"/oauth/getAuthImage",
"/oauth/getLoginOutUrl"
).permitAll()
.anyRequest().authenticated()
)
.csrf(AbstractHttpConfigurer::disable)
.cors(Customizer.withDefaults())
.exceptionHandling(exceptions -> exceptions
.authenticationEntryPoint(unauthorizedHandler)
.accessDeniedHandler(customAccessDeniedHandler)
)
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(jwt -> jwt.decoder(jwtDecoder))
.authenticationEntryPoint(unauthorizedHandler) // <- 指定自定义入口
)
.addFilterBefore(filter, UsernamePasswordAuthenticationFilter.class)
.build();
}
}
Token签发
这里使用Oauth内置方法进行签发 ,按照以下步骤 。
1. 配置客户端(AuthorizationServerConfig)
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
import java.time.Duration;
import java.util.UUID;
@Configuration
public class AuthorizationServerConfig {
@Bean
public RegisteredClientRepository registeredClientRepository() {
RegisteredClient client = RegisteredClient.withId(UUID.randomUUID().toString())
// 客户端 ID
.clientId(LoginConstant.DEFAULT_CLIENT_ID)
// 客户端密钥 (我是SPA应用,没有地方放登录和鉴权,这里暂时不需要)
// .clientSecret("{noop}demo-secret")
// 验证方式
.clientAuthenticationMethod("web-client")
// 授权方式
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
// 支持刷新
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
// 授权范围(后续使用springSecurity的权限认证,这个也可以不配置)
// .scope("demo.read")
.tokenSettings(TokenSettings.builder()
// Access Token 生命周期
.accessTokenTimeToLive(Duration.ofHours(1))
// Refresh Token 生命周期
.refreshTokenTimeToLive(Duration.ofDays(30))
// 是否重复使用 Refresh Token
.reuseRefreshTokens(true)
.build())
.build();
return new InMemoryRegisteredClientRepository(client);
}
}
认证信息存储(RedisOAuth2AuthorizationService)
这里使用Redis,系统提供了数据库存储和内存存储两种方式 ,直接拓展只需要实现OAuth2AuthorizationService
即可
import com.lljing.common.redis.service.RedisService;
import lombok.RequiredArgsConstructor;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
import org.springframework.stereotype.Component;
/**
* <Token管理实现>
*
* @author:JingLonglong
* @date:2025/9/23
*/
@Component
@RequiredArgsConstructor
public class RedisOAuth2AuthorizationService implements OAuth2AuthorizationService {
private static final String AUTH_KEY_PREFIX = "U:TK:%S";
private final RedisService redisService;
@Override
public void save(OAuth2Authorization authorization) {
redisService.set(String.format(AUTH_KEY_PREFIX, authorization.getId()), authorization.getRefreshToken(), 18000);
}
@Override
public void remove(OAuth2Authorization authorization) {
redisService.del(String.format(AUTH_KEY_PREFIX, authorization.getId()));
}
@Override
public OAuth2Authorization findById(String id) {
return null;
}
@Override
public OAuth2Authorization findByToken(String token, OAuth2TokenType tokenType) {
return null;
}
}
资源认证实现
在JwtConfig类中已经做了实现 ,这里强调一下 ,需要使用RSA密钥库的公钥进行验证。
/**
* @desc <JWT令牌解码器配置>
* @param rsaKey 从JwkConfig中注入的RSAKey
* @date 2025年9月23日
* @return JwtDecoder
*/
@Bean
public JwtDecoder jwtDecoder(RSAKey rsaKey) {
// 使用RSAKey中的公钥来创建JWT解码器
try {
return NimbusJwtDecoder.withPublicKey(rsaKey.toRSAPublicKey()).build();
} catch (JOSEException e) {
throw new AuthenticationFailedException("无效的请求token");
}
}
微服务下其他服务鉴权
微服务下其他服务鉴权逻辑和auth服务的资源认证部分基本类似。只不过公钥通过接口获取。一般只需要在网管中实现认证授权即可,授权通过后分发到其他服务进行请求。
1. auth服务暴露公钥
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
/**
* 获取RSA公钥接口
* Created by wkc on 2021/6/19.
*/
@RestController
@RequiredArgsConstructor
public class KeyPairController {
private final RSAKey rsaKey;
@GetMapping("/rsa/publicKey")
public Map<String, Object> getKey() {
return new JWKSet(rsaKey.toPublicJWK()).toJSONObject();
}
}
2. gateway注册认证(GatewaySecurityConfig)
这里使用了LoadBalance通过服务调用避免了部署不通环境导致url需要修改的问题。
并且网关使用的是WebFlux,认证写法也略有不同。
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.reactive.LoadBalancedExchangeFilterFunction;
import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer;
import org.springframework.cloud.client.loadbalancer.reactive.ReactorLoadBalancerExchangeFilterFunction;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder;
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.web.reactive.function.client.WebClient;
import java.util.Collections;
/***
* @desc <资源认证实现>
* <通过token进行认证>
* @date 2025年9月23日 18:52
* @return
* @exception
*/
@Configuration
@EnableWebFluxSecurity
@RequiredArgsConstructor
public class GatewaySecurityConfig {
private final IgnoreUrlsConfig ignoreUrlsConfig;
@Value("${spring.security.oauth2.resourceserver.jwt.jwk-set-uri}")
private String jwkUri;
/**
* 配置JwtDecoder,通过认证服务暴露的端点获取JWK Set URI
* 假设你的认证服务在/oauth/token_key端点返回密钥信息
*/
@Bean
public ReactiveJwtDecoder jwtDecoder(ReactiveLoadBalancer.Factory<ServiceInstance> lbFactory) {
LoadBalancedExchangeFilterFunction lbFunction = new ReactorLoadBalancerExchangeFilterFunction(lbFactory, Collections.emptyList());
WebClient webClient = WebClient.builder()
.filter(lbFunction)
.build();
// 服务名
return NimbusReactiveJwtDecoder.withJwkSetUri(jwkUri)
.webClient(webClient)
.build();
}
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http, ReactiveJwtDecoder jwtDecoder) {
http
.csrf(ServerHttpSecurity.CsrfSpec::disable)
.cors(ServerHttpSecurity.CorsSpec::disable)
.authorizeExchange(exchanges -> {
ignoreUrlsConfig.getUrls().forEach(url -> exchanges.pathMatchers(url).permitAll());
exchanges.anyExchange().authenticated();
})
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(jwt -> jwt.jwtDecoder(jwtDecoder))
);
return http.build();
}
}