如何创建@AnonymousAllowed端点:通过Access Token实现免登录跳转至Web应用首页
解决方案:通过匿名端点实现移动端到Web应用的免登录跳转
核心思路
直接创建一个匿名可访问的端点,接收移动端传递的Cognito Access Token,通过Spring Security完成Token验证、用户认证绑定,并创建Session,最后重定向到Web应用首页。同时利用Spring Security的OAuth2自动刷新机制处理Token过期问题,无需手动实现刷新逻辑。
步骤1:Spring Security配置(放行端点+整合Cognito)
首先在安全配置类中放行目标端点,并配置Cognito作为OAuth2资源服务器和客户端,支持Token自动刷新:
@Configuration @EnableWebSecurity public class SecurityConfig { @Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}") private String cognitoIssuerUri; @Value("${spring.security.oauth2.client.registration.cognito.client-id}") private String clientId; @Value("${spring.security.oauth2.client.registration.cognito.client-secret}") private String clientSecret; @Value("${spring.security.oauth2.client.provider.cognito.token-uri}") private String cognitoTokenUri; @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth .requestMatchers("/mobile/login").permitAll() // 匿名允许的端点 .anyRequest().authenticated() ) .oauth2ResourceServer(oauth2 -> oauth2 .jwt(jwt -> jwt .jwtAuthenticationConverter(jwtAuthConverter()) .issuerUri(cognitoIssuerUri) ) ) .oauth2Client(oauth2 -> oauth2 // 配置客户端用于Token自动刷新 .clientRegistrationRepository(clientRegistrationRepo()) .authorizedClientService(authorizedClientService()) ) .sessionManagement(session -> session .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) // 按需创建Session ); return http.build(); } // 转换Cognito的JWT声明为Spring Security权限 private JwtAuthenticationConverter jwtAuthConverter() { JwtGrantedAuthoritiesConverter authoritiesConverter = new JwtGrantedAuthoritiesConverter(); authoritiesConverter.setAuthorityPrefix("ROLE_"); authoritiesConverter.setAuthoritiesClaimName("cognito:groups"); JwtAuthenticationConverter converter = new JwtAuthenticationConverter(); converter.setJwtGrantedAuthoritiesConverter(authoritiesConverter); return converter; } @Bean public ClientRegistrationRepository clientRegistrationRepo() { return new InMemoryClientRegistrationRepository(cognitoClientRegistration()); } private ClientRegistration cognitoClientRegistration() { return ClientRegistration.withRegistrationId("cognito") .clientId(clientId) .clientSecret(clientSecret) .tokenUri(cognitoTokenUri) .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) .build(); } @Bean public AuthorizedClientService authorizedClientService(ClientRegistrationRepository repo) { return new InMemoryOAuth2AuthorizedClientService(repo); } }
步骤2:实现匿名认证端点
创建控制器端点,接收移动端传递的Access Token,完成认证绑定并创建Session:
@RestController public class MobileLoginController { private final JwtDecoder jwtDecoder; private final OAuth2AuthorizedClientService authorizedClientService; private final ClientRegistrationRepository clientRegistrationRepo; public MobileLoginController(JwtDecoder jwtDecoder, OAuth2AuthorizedClientService authorizedClientService, ClientRegistrationRepository clientRegistrationRepo) { this.jwtDecoder = jwtDecoder; this.authorizedClientService = authorizedClientService; this.clientRegistrationRepo = clientRegistrationRepo; } @GetMapping("/mobile/login") public void mobileLogin(@RequestParam("access_token") String accessToken, HttpServletRequest request, HttpServletResponse response) throws IOException { // 1. 验证Cognito Access Token的有效性(自动校验签名、过期时间等) Jwt jwt = jwtDecoder.decode(accessToken); // 2. 构建Spring Security认证对象 JwtAuthenticationToken authentication = new JwtAuthenticationToken(jwt, jwtAuthConverter().convert(jwt)); // 3. 将认证信息存入SecurityContext并绑定到Session SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); securityContext.setAuthentication(authentication); HttpSession session = request.getSession(true); // 强制创建Session session.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, securityContext); // 4. 保存授权客户端信息,让Spring Security自动处理Token过期刷新 ClientRegistration clientRegistration = clientRegistrationRepo.findByRegistrationId("cognito"); if (clientRegistration != null) { OAuth2AccessToken oAuth2Token = OAuth2AccessToken.withToken(accessToken) .tokenType(OAuth2AccessToken.TokenType.BEARER) .expiresAt(jwt.getExpiresAt()) .build(); // 如果移动端能提供Refresh Token,这里可以传入,进一步优化自动刷新逻辑 OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient( clientRegistration, authentication.getName(), oAuth2Token, null); authorizedClientService.saveAuthorizedClient(authorizedClient, authentication); } // 5. 重定向到Web应用首页 response.sendRedirect("/"); } // 复用权限转换逻辑 private JwtAuthenticationConverter jwtAuthConverter() { JwtGrantedAuthoritiesConverter authoritiesConverter = new JwtGrantedAuthoritiesConverter(); authoritiesConverter.setAuthorityPrefix("ROLE_"); authoritiesConverter.setAuthoritiesClaimName("cognito:groups"); JwtAuthenticationConverter converter = new JwtAuthenticationConverter(); converter.setJwtGrantedAuthoritiesConverter(authoritiesConverter); return converter; } }
关键注意事项
- Token传递方式:移动端可以通过链接参数传递Access Token(如
https://your-web-app.com/mobile/login?access_token=xxx),这种方式最适合跳转场景。 - Refresh Token优化:如果移动端能同步传递Refresh Token,在创建
OAuth2AuthorizedClient时传入,Spring Security会在Access Token过期时自动调用Cognito的Token接口刷新,无需用户重新登录。 - 安全防护:可以给这个端点添加额外验证(如移动端专属签名、IP白名单),防止Token泄露被滥用。
- 之前方案的问题:返回jsessionid再手动设置Cookie的方式不可靠,因为Session的上下文绑定依赖当前请求环境,直接在认证端点内完成Session创建和认证绑定才是正确路径。
内容的提问来源于stack exchange,提问作者Abdullah Gheith
相关产品推荐
相关产品推荐

