Spring Security 6 -在OAuth2 ResourceServer中访问UserDetails

t1qtbnec  于 2023-08-02  发布在  Spring
关注(0)|答案(1)|浏览(160)

我正在尝试使用Springboot 3和SpringSecurity 6来设置OAuth2 Resource Server。
我能够正确配置身份验证;我实现了一个自定义的jwt转换器,它从我的数据库中获取用户的权限,并将它们分配给安全上下文。

  1. class CustomAuthenticationConverter implements Converter<Jwt, AbstractAuthenticationToken> {
  2. AzureUserService azureUserService;
  3. public CustomAuthenticationConverter(AzureUserService azureUserService) {
  4. this.azureUserService = azureUserService;
  5. }
  6. public AbstractAuthenticationToken convert(@NonNull Jwt jwt) {
  7. // Currently, creates a user in the database, if they don't exist yet. Guaranteed to get a user.
  8. User user = azureUserService.retrieveOrCreateUserFromJwt(jwt);
  9. JwtAuthenticationToken authentication = new JwtAuthenticationToken(jwt, user.getAuthorities(), user.getId());
  10. SecurityContextHolder.getContext().setAuthentication(authentication);
  11. return authentication;
  12. }
  13. }

字符串
现在我可以用权威来保护我的路线了。下一步:我希望能够访问一些路由内部的有关当前用户的信息。然而,即使在注册了我的用户详细信息服务类之后:

  1. @Bean
  2. public DaoAuthenticationProvider authenticationProvider() {
  3. DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
  4. provider.setUserDetailsService(userDetailsService);
  5. return provider;
  6. }


我无法访问路由中的UserDetails对象:

  1. @GetMapping("/api/admin/you")
  2. public String showProfile(@AuthenticationPrincipal CustomUserDetails userDetails) {
  3. return "Details"+ userDetails;
  4. }


这将返回“Details null”。
我不确定UserDetails是否不可访问,因为我只是将此应用程序用作OAuthResource Server,或者我没有正确配置它。
我的UserDetails类的实现:

  1. public class CustomUserDetails implements UserDetails {
  2. private final User user; // This is your own User entity class
  3. public CustomUserDetails(User user) {
  4. this.user = user;
  5. }
  6. @Override
  7. public Collection<? extends GrantedAuthority> getAuthorities() {
  8. return user.getAuthorities();
  9. }
  10. // We use OAuth2, hence no need for password
  11. @Override
  12. public String getPassword() {
  13. return null;
  14. }
  15. @Override
  16. public String getUsername() {
  17. return user.getId();
  18. }
  19. @Override
  20. public boolean isAccountNonExpired() {
  21. return true;
  22. }
  23. @Override
  24. public boolean isAccountNonLocked() {
  25. return true;
  26. }
  27. @Override
  28. public boolean isCredentialsNonExpired() {
  29. return true;
  30. }
  31. @Override
  32. public boolean isEnabled() {
  33. return user.getEnabled();
  34. }
  35. public User getUser() {
  36. return user;
  37. }
  38. }


以及服务:

  1. @Transactional
  2. @Service
  3. public class CustomUserDetailsService implements UserDetailsService {
  4. private final UserRepository userRepository;
  5. public CustomUserDetailsService(UserRepository userRepository) {
  6. this.userRepository = userRepository;
  7. }
  8. // Note that Spring calls the unique user identifier "username", but we're working with a unique identifier.
  9. @Override
  10. public CustomUserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
  11. Optional<User> optionalUser = userRepository.findById(username);
  12. if (optionalUser.isPresent()) {
  13. User user = optionalUser.get();
  14. return new CustomUserDetails(user);
  15. }
  16. else {
  17. throw new UsernameNotFoundException("User not found with id: " + username);
  18. }
  19. }
  20. }


另外,这里是我的SecurityConfiguration

  1. @Bean
  2. public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
  3. // Create RequestMatchers out of the Strings
  4. List<RequestMatcher> publicMatchers = Arrays.stream(publicPaths)
  5. .map(AntPathRequestMatcher::new)
  6. .collect(Collectors.toList());
  7. // Apply configuration
  8. http
  9. .authenticationProvider(authenticationProvider())
  10. .authorizeHttpRequests(authorizeRequests -> {
  11. authorizeRequests
  12. // Allow public access to the routes specified in 'publicMatchers' list
  13. .requestMatchers(request ->
  14. publicMatchers.stream().anyMatch(matcher -> matcher.matches(request))
  15. ).permitAll()
  16. // Require authentication for all other routes.
  17. .anyRequest().authenticated();
  18. })
  19. // Configure the OAuth 2.0 resource server to use JWT tokens provided by Azure AD B2C
  20. .oauth2ResourceServer(oauth2ResourceServer ->
  21. oauth2ResourceServer
  22. .jwt(jwt ->
  23. // Set the location where the server can find the JSON Web Key Set (JWK Set).
  24. // The JWK Set contains public keys. The server uses these public keys to
  25. // validate the signatures of incoming JWTs, ensuring they were issued by a trusted authorization server.
  26. // In this case, the URI of the JWK Set is provided by the 'jwkSetUri' variable.
  27. jwt.jwkSetUri(this.jwkSetUri)
  28. .jwtAuthenticationConverter(customAuthenticationConverter())
  29. )
  30. )
  31. .userDetailsService(userDetailsService);
  32. return http.build();
  33. }

du7egjpx

du7egjpx1#

在认证转换器bean中连接JPA存储库是可行的(例如,我做的是here),但是这是一个不好的实践,应该保留到不能在授权服务器上设置访问令牌私有声明的情况下,并且不能在它前面使用另一个(更可配置的)授权服务器。在Keycloak中,您使用“mappers”来完成,在Auth0中,您使用“actions”来完成,在Azure中,您使用“API connectors”来完成。
每次令牌发布时从授权服务器上的DB获取角色一次效率要高得多,而不是针对资源服务器上的每个传入请求获取角色。您可以使用缓存来缓解这种情况,但是创建、维护和调试缓存(分布式环境中的共享缓存)总是一件痛苦的事情。
使用授权转换器从DB获取角色的工作解决方案的细节(但是,如果你这样做,我打赌你迟早会后悔的):

  1. static interface AuthoritiesConverter extends Converter<Jwt, Set<SimpleGrantedAuthority>> {
  2. }
  3. static interface AuthenticationConverter extends Converter<Jwt, JwtAuthenticationToken> {
  4. }
  5. @Component
  6. @RequiredArgsConstructor
  7. public static class PersistedGrantedAuthoritiesRetriever implements AuthoritiesConverter {
  8. private final UserAuthorityRepository authoritiesRepo;
  9. @Override
  10. @Transactional(readOnly = true)
  11. public Set<SimpleGrantedAuthority> convert(Jwt jwt) {
  12. final Collection<UserAuthority> authorities = authoritiesRepo.findBySubject(jwt.getSubject());
  13. return authorities.stream().map(UserAuthority::getAuthority).map(SimpleGrantedAuthority::new).collect(Collectors.toSet());
  14. }
  15. }
  16. @Component
  17. @RequiredArgsConstructor
  18. public static class JpaAuthenticationConverter implements AuthenticationConverter {
  19. private final Converter<String, ? extends Collection<? extends GrantedAuthority>> authoritiesConverter;
  20. @Override
  21. public JwtAuthenticationToken convert(Jwt jwt) {
  22. return new JwtAuthenticationToken(jwt, authoritiesConverter.convert(jwt));
  23. }
  24. }
  25. @EnableWebSecurity
  26. @EnableMethodSecurity
  27. @Configuration
  28. public static class WebSecurityConfig {
  29. @Bean
  30. SecurityFilterChain filterChain(
  31. HttpSecurity http,
  32. Converter<Jwt, ? extends AbstractAuthenticationToken> authenticationConverter) throws Exception {
  33. http.oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt -> jwt.jwtAuthenticationConverter(authenticationConverter)));
  34. ...
  35. }
  36. }

字符串

展开查看全部

相关问题