如何在非Spring Boot的Spring应用中实现多认证流(SAML、OIDC等)
非Spring Boot Spring应用多认证方案实现指导
整体核心思路
基于Spring Security的多认证机制,通过多个SecurityFilterChain分别处理不同认证类型的请求,同时共享用户信息与授权上下文。核心是为SAML、OIDC、表单登录分别配置独立的过滤器链,通过请求路径匹配区分不同认证入口,最终统一用户身份转换为系统内的Authentication对象,支持JWT令牌的生成与校验。
一、集成Azure AD SAML 2.0 SSO
1. 依赖配置
在pom.xml中引入Spring Security SAML相关依赖:
<dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-saml2-service-provider</artifactId> <version>5.8.0</version> </dependency>
2. 元数据与客户端配置
手动配置Azure AD的IDP元数据(可从Azure门户下载),并配置SP(服务提供者)元数据:
@Configuration public class SamlSecurityConfig { @Bean public Saml2MetadataFilter saml2MetadataFilter(RelyingPartyRegistrationRepository repos) { return new Saml2MetadataFilter(repos, new OpenSamlMetadataResolver()); } @Bean public RelyingPartyRegistrationRepository relyingPartyRegistrationRepository() { RelyingPartyRegistration azureAdRegistration = RelyingPartyRegistration.withRegistrationId("azure-ad") .entityId("your-sp-entity-id") .assertionConsumerServiceLocation("https://your-app.com/login/saml2/sso/azure-ad") .idpEntityId("https://login.microsoftonline.com/{tenant-id}/v2.0") .idpWebSsoUrl("https://login.microsoftonline.com/{tenant-id}/saml2") .idpMetadataLocation("classpath:azure-ad-idp-metadata.xml") .build(); return new InMemoryRelyingPartyRegistrationRepository(azureAdRegistration); } }
3. SAML认证过滤器链
配置独立的过滤器链,匹配SAML相关请求:
@Configuration public class SecurityFilterChainConfig { @Bean public SecurityFilterChain samlFilterChain(HttpSecurity http) throws Exception { http .securityMatcher("/login/saml2/**", "/saml2/**") .authorizeHttpRequests(auth -> auth.anyRequest().authenticated()) .saml2Login(saml2 -> saml2 .registrationId("azure-ad") .successHandler((request, response, authentication) -> { // 登录成功后转换用户身份,生成JWT或存入会话 handleAuthenticationSuccess(authentication, response); })); return http.build(); } }
二、集成Okta OpenID Connect SSO
1. 依赖配置
引入Spring Security OAuth2 Client依赖:
<dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-oauth2-client</artifactId> <version>5.8.0</version> </dependency>
2. OIDC客户端配置
手动注册Okta客户端信息:
@Configuration public class OidcSecurityConfig { @Bean public ClientRegistrationRepository clientRegistrationRepository() { ClientRegistration oktaRegistration = ClientRegistration.withRegistrationId("okta") .clientId("your-okta-client-id") .clientSecret("your-okta-client-secret") .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) .redirectUri("https://your-app.com/login/oauth2/code/okta") .scope("openid", "profile", "email") .authorizationUri("https://your-okta-domain.com/oauth2/v1/authorize") .tokenUri("https://your-okta-domain.com/oauth2/v1/token") .userInfoUri("https://your-okta-domain.com/oauth2/v1/userinfo") .userNameAttributeName(IdTokenClaimNames.SUB) .jwkSetUri("https://your-okta-domain.com/oauth2/v1/keys") .clientName("Okta") .build(); return new InMemoryClientRegistrationRepository(oktaRegistration); } }
3. OIDC认证过滤器链
配置独立的过滤器链处理OIDC请求:
@Configuration public class SecurityFilterChainConfig { // (接上SAML的过滤器链) @Bean public SecurityFilterChain oidcFilterChain(HttpSecurity http) throws Exception { http .securityMatcher("/login/oauth2/**", "/oauth2/**") .authorizeHttpRequests(auth -> auth.anyRequest().authenticated()) .oauth2Login(oauth2 -> oauth2 .registrationId("okta") .successHandler((request, response, authentication) -> { handleAuthenticationSuccess(authentication, response); })); return http.build(); } }
三、表单登录与JWT API授权
1. 表单登录配置
基于数据库用户的表单登录,配置DaoAuthenticationProvider:
@Configuration public class FormLoginSecurityConfig { @Bean public AuthenticationProvider formAuthProvider(UserDetailsService userDetailsService, PasswordEncoder passwordEncoder) { DaoAuthenticationProvider provider = new DaoAuthenticationProvider(); provider.setUserDetailsService(userDetailsService); provider.setPasswordEncoder(passwordEncoder); return provider; } }
2. 表单登录过滤器链
配置表单登录的过滤器链,匹配表单登录请求:
@Configuration public class SecurityFilterChainConfig { // (接SAML、OIDC的过滤器链) @Bean public SecurityFilterChain formLoginFilterChain(HttpSecurity http, AuthenticationProvider formAuthProvider) throws Exception { http .securityMatcher("/login", "/logout") .authorizeHttpRequests(auth -> auth .requestMatchers("/login").permitAll() .anyRequest().authenticated()) .authenticationProvider(formAuthProvider) .formLogin(form -> form .loginPage("/login") .successHandler((request, response, authentication) -> { handleAuthenticationSuccess(authentication, response); })) .logout(logout -> logout.permitAll()); return http.build(); } }
3. JWT令牌生成与校验
令牌生成(登录成功后)
在handleAuthenticationSuccess方法中生成JWT:
private void handleAuthenticationSuccess(Authentication authentication, HttpServletResponse response) throws IOException { // 转换Authentication为系统用户信息 UserDetails userDetails = (UserDetails) authentication.getPrincipal(); // 生成JWT令牌(使用JJWT库) String jwt = Jwts.builder() .setSubject(userDetails.getUsername()) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + 3600000)) .signWith(SignatureAlgorithm.HS256, "your-jwt-secret") .compact(); // 返回令牌到前端或存入Cookie response.addHeader("Authorization", "Bearer " + jwt); response.sendRedirect("/dashboard"); }
JWT API授权过滤器链
配置独立的过滤器链保护API接口,校验JWT:
@Configuration public class SecurityFilterChainConfig { // (接其他过滤器链) @Bean public SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception { http .securityMatcher("/api/**") .authorizeHttpRequests(auth -> auth.anyRequest().hasRole("USER")) .oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt -> jwt .decoder(jwtDecoder()) .jwtAuthenticationConverter(jwtAuthenticationConverter()))); return http.build(); } @Bean public JwtDecoder jwtDecoder() { SecretKey secretKey = Keys.hmacShaKeyFor("your-jwt-secret".getBytes()); return NimbusJwtDecoder.withSecretKey(secretKey).build(); } @Bean public JwtAuthenticationConverter jwtAuthenticationConverter() { JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter(); grantedAuthoritiesConverter.setAuthorityPrefix("ROLE_"); JwtAuthenticationConverter converter = new JwtAuthenticationConverter(); converter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter); return converter; } }
四、关键注意事项
- 非Spring Boot适配:所有Security相关Bean需手动配置,无自动配置支持,需确保
@Configuration类被正确扫描。 - 用户身份统一:三种认证方式成功后,需将外部身份(SAML断言、OIDC用户信息、数据库用户)转换为统一的
UserDetails对象,保证后续授权逻辑一致。 - 认证路由区分:通过
securityMatcher为不同过滤器链指定匹配路径,避免冲突。 - 会话管理:若无需会话,可配置
sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))(API链适用)。
内容的提问来源于stack exchange,提问作者Decypher
相关产品推荐
相关产品推荐

