Spring Boot OAuth2授权服务器登录后动态生成同意页与JWT作用域
Spring Boot OAuth2授权服务器:动态角色匹配的自定义同意页实现
核心思路
用户登录后拦截授权请求,根据当前用户角色过滤出其可访问的作用域,渲染自定义同意页供用户选择部分/全部权限,最终签发包含所选作用域的访问令牌。
一、自定义授权请求解析器(过滤作用域)
实现OAuth2AuthorizationRequestResolver,在用户登录后根据角色筛选允许的作用域,替换原请求中的作用域集合。
@Component public class DynamicScopeAuthorizationRequestResolver implements OAuth2AuthorizationRequestResolver { private final OAuth2AuthorizationRequestResolver defaultResolver; public DynamicScopeAuthorizationRequestResolver(OAuth2AuthorizationRequestResolver defaultResolver) { this.defaultResolver = defaultResolver; } @Override public OAuth2AuthorizationRequest resolve(HttpServletRequest request) { OAuth2AuthorizationRequest authRequest = defaultResolver.resolve(request); return processAuthRequest(authRequest); } @Override public OAuth2AuthorizationRequest resolve(HttpServletRequest request, String clientId) { OAuth2AuthorizationRequest authRequest = defaultResolver.resolve(request, clientId); return processAuthRequest(authRequest); } private OAuth2AuthorizationRequest processAuthRequest(OAuth2AuthorizationRequest authRequest) { if (authRequest == null) return null; // 获取当前登录用户的认证信息 Authentication auth = SecurityContextHolder.getContext().getAuthentication(); if (!(auth instanceof UserDetails)) return authRequest; UserDetails user = (UserDetails) auth; Set<String> allowedScopes = getScopesByRole(user.getAuthorities()); // 过滤客户端请求的作用域,仅保留用户允许的范围 Set<String> filteredScopes = authRequest.getScopes().stream() .filter(allowedScopes::contains) .collect(Collectors.toSet()); // 构建更新后的授权请求 return OAuth2AuthorizationRequest.from(authRequest) .scopes(filteredScopes) .build(); } // 角色-作用域映射逻辑 private Set<String> getScopesByRole(Collection<? extends GrantedAuthority> authorities) { Set<String> scopes = new HashSet<>(); for (GrantedAuthority authority : authorities) { switch (authority.getAuthority()) { case "ROLE_READER": scopes.add("read"); break; case "ROLE_ADMIN": scopes.add("read"); scopes.add("write"); break; case "ROLE_SUPER_ADMIN": scopes.add("read"); scopes.add("write"); scopes.add("delete"); break; default: break; } } return scopes; } }
在授权服务器配置中注册该解析器:
@Configuration @EnableAuthorizationServer public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { private final DynamicScopeAuthorizationRequestResolver dynamicScopeResolver; public AuthorizationServerConfig(DynamicScopeAuthorizationRequestResolver dynamicScopeResolver) { this.dynamicScopeResolver = dynamicScopeResolver; } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.authorizationRequestResolver(dynamicScopeResolver); // 补充其他配置:tokenStore、authenticationManager等 } }
二、自定义同意页控制器与视图
替换默认同意页,实现用户选择作用域的交互逻辑。
1. 同意页控制器
@Controller public class CustomConsentController { // 渲染同意页 @GetMapping("/oauth/confirm_access") public String showConsentPage(HttpServletRequest request, Model model) { OAuth2AuthorizationRequest authRequest = (OAuth2AuthorizationRequest) request.getAttribute( OAuth2AuthorizationRequestResolver.DEFAULT_AUTHORIZATION_REQUEST_ATTR_NAME ); model.addAttribute("clientId", authRequest.getClientId()); model.addAttribute("scopes", authRequest.getScopes()); model.addAttribute("state", authRequest.getState()); return "consent"; // 对应Thymeleaf视图文件名 } // 处理用户选择的作用域 @PostMapping("/oauth/confirm_access") public String processConsent(@RequestParam("scope") Set<String> selectedScopes, @RequestParam("state") String state, HttpServletRequest request) { OAuth2AuthorizationRequest originalRequest = (OAuth2AuthorizationRequest) request.getAttribute( OAuth2AuthorizationRequestResolver.DEFAULT_AUTHORIZATION_REQUEST_ATTR_NAME ); // 更新授权请求为用户选择的作用域 OAuth2AuthorizationRequest updatedRequest = OAuth2AuthorizationRequest.from(originalRequest) .scopes(selectedScopes) .build(); request.setAttribute( OAuth2AuthorizationRequestResolver.DEFAULT_AUTHORIZATION_REQUEST_ATTR_NAME, updatedRequest ); // 转发回授权端点完成令牌签发流程 return "forward:/oauth/authorize"; } }
2. Thymeleaf同意页视图(consent.html)
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <title>权限授权确认</title> </head> <body> <h2>应用 <span th:text="${clientId}"></span> 请求以下权限</h2> <form method="post" th:action="@{/oauth/confirm_access}"> <input type="hidden" name="state" th:value="${state}"> <div th:each="scope : ${scopes}"> <label> <input type="checkbox" name="scope" th:value="${scope}" checked> <span th:text="${scope}"></span> </label> </div> <button type="submit">确认授权</button> <a th:href="@{/oauth/authorize?error=access_denied&state=${state}}">取消授权</a> </form> </body> </html>
三、确保令牌包含所选作用域
默认情况下,授权服务器会使用更新后的授权请求中的作用域生成令牌。如果需要在令牌中额外携带角色信息,可自定义TokenEnhancer:
@Component public class CustomTokenEnhancer implements TokenEnhancer { @Override public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) { Map<String, Object> extraInfo = new HashMap<>(); UserDetails user = (UserDetails) authentication.getPrincipal(); extraInfo.put("roles", user.getAuthorities().stream() .map(GrantedAuthority::getAuthority) .collect(Collectors.toList())); ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(extraInfo); return accessToken; } }
在授权服务器配置中添加该增强器:
@Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { TokenEnhancerChain enhancerChain = new TokenEnhancerChain(); enhancerChain.setTokenEnhancers(Arrays.asList(customTokenEnhancer, jwtAccessTokenConverter)); endpoints.authorizationRequestResolver(dynamicScopeResolver) .tokenEnhancer(enhancerChain); // 其他配置 }
四、关键注意事项
- 确保登录流程优先级高于授权端点,在
SecurityFilterChain配置中先处理登录请求。 - 客户端注册时,需允许请求所有可能的作用域(
read/write/delete),保证初始请求的作用域能被正常过滤。 - 测试时验证不同角色用户登录后,同意页显示的作用域是否匹配,令牌的
scope字段是否与用户选择一致。
内容的提问来源于stack exchange,提问作者Harshad Prajapati
相关产品推荐
相关产品推荐

