Keycloak Spring适配器:每次HTTP请求校验AuthToken有效性需求
Got it, let's get this token validation and redirection working in your Grails 3.2.9 + Keycloak 3.4.3 setup. Since you already started with a TokenAuthenticationFilter, here's how to complete and integrate it properly:
Step 1: Add Keycloak Spring Security Dependencies
First, ensure your build.gradle includes the necessary Keycloak adapters to handle JWT validation:
dependencies { // Existing dependencies... compile 'org.keycloak:keycloak-spring-security-adapter:3.4.3' compile 'org.keycloak:keycloak-core:3.4.3' }
Step 2: Complete the TokenAuthenticationFilter
Extend Spring's OncePerRequestFilter to extract, validate tokens, and handle redirection. This filter checks for a Bearer token in the Authorization header, validates it against Keycloak, and redirects to login if the token is missing or invalid:
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken; import org.keycloak.common.VerificationException; import org.keycloak.representations.AccessToken; import org.keycloak.adapters.springsecurity.token.KeycloakTokenVerifier; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; 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; public class TokenAuthenticationFilter extends OncePerRequestFilter { private static final String AUTHORIZATION_HEADER = "Authorization"; private static final String BEARER_PREFIX = "Bearer "; private final String keycloakAuthServerUrl; private final String realm; private final String clientId; // Inject Keycloak config values via constructor public TokenAuthenticationFilter(String keycloakAuthServerUrl, String realm, String clientId) { this.keycloakAuthServerUrl = keycloakAuthServerUrl; this.realm = realm; this.clientId = clientId; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String token = extractTokenFromRequest(request); if (token != null) { try { // Validate token using Keycloak's built-in verifier AccessToken accessToken = KeycloakTokenVerifier.verifyToken(token, keycloakAuthServerUrl, realm, clientId); // Set authenticated user in security context Authentication auth = new KeycloakAuthenticationToken(accessToken, false); SecurityContextHolder.getContext().setAuthentication(auth); } catch (VerificationException e) { // Invalid token - redirect to login response.sendRedirect("/login"); return; } } else { // No token present - redirect to login response.sendRedirect("/login"); return; } filterChain.doFilter(request, response); } private String extractTokenFromRequest(HttpServletRequest request) { String authHeader = request.getHeader(AUTHORIZATION_HEADER); if (authHeader != null && authHeader.startsWith(BEARER_PREFIX)) { return authHeader.substring(BEARER_PREFIX.length()); } return null; } }
Step 3: Register the Filter in Grails Security Configuration
Integrate the filter into Grails' Spring Security setup. Choose one of these options based on your config style:
Option 1: Java Configuration Class
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; 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.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http // Add our token filter before the default username/password filter .addFilterBefore(authenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class) // Define access rules (adjust as needed) .authorizeRequests() .anyRequest().authenticated(); } @Bean public TokenAuthenticationFilter authenticationTokenFilter() { // Replace with your actual Keycloak server and client values return new TokenAuthenticationFilter( "http://your-keycloak-server/auth", "your-realm-name", "your-client-id" ); } }
Option 2: Groovy resources.groovy
If you use Groovy-based bean configuration:
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter beans = { tokenAuthenticationFilter(TokenAuthenticationFilter, "http://your-keycloak-server/auth", "your-realm-name", "your-client-id" ) springSecurityFilterChain(org.springframework.security.web.FilterChainProxy) { filterChains = [ new org.springframework.security.web.DefaultSecurityFilterChain( new AntPathRequestMatcher("/**"), [ref('tokenAuthenticationFilter'), ref('usernamePasswordAuthenticationFilter')] // Add other required filters here ) ] } }
Step 4: Fine-Tune Edge Cases
- Public Endpoints: To skip validation for certain paths (e.g.,
/public/**), add a check at the start ofdoFilterInternal:if (new AntPathRequestMatcher("/public/**").matches(request)) { filterChain.doFilter(request, response); return; } - Direct Keycloak Login Redirect: Instead of your app's login page, redirect users directly to Keycloak's login form using this URL pattern:
String keycloakLoginUrl = String.format( "%s/realms/%s/protocol/openid-connect/auth?client_id=%s&redirect_uri=%s&response_type=code", keycloakAuthServerUrl, realm, clientId, "http://your-app-url/callback" ); response.sendRedirect(keycloakLoginUrl); - Token Expiry: Keycloak's
KeycloakTokenVerifierautomatically checks token expiration, so expired tokens will trigger the redirect.
内容的提问来源于stack exchange,提问作者JoeyHolloway

