Авторизация на основе входа в систему Spring Boot Oauth

Я создаю загрузочное приложение Spring с использованием oauth, прямо сейчас у меня работает вход в систему github, но я хочу иметь приложение на основе ролей, поэтому доступ к некоторым маршрутам может быть возможен только с помощью role_admin, а к другим - с помощью role_user, я Я пробовал кое-что, и мой код выглядит немного беспорядочным, поэтому я ценю любую помощь! Прямо сейчас в классе CustomOAuth2User в методе getAuthorities отображается Set roles = getUserRoles(); как пустое, поэтому по умолчанию для всех пользователей используется роль role_user, поэтому я могу получить доступ к маршрутам, которым нужна такая роль, но не к администраторским, даже если я создаю пользователя с ролью_admin.

Конфигурация безопасности

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    
    @Autowired
    private CustomOAuth2UserService oauthUserService;

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private UserService userService;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http
        .authorizeHttpRequests(auth -> {
            auth.requestMatchers(HttpMethod.GET, "/userinfo").hasRole("ADMIN");
            auth.requestMatchers(HttpMethod.GET, "/").hasRole("USER");
            auth.anyRequest().authenticated();
        })
         .oauth2Login(oauth2 -> oauth2
                        .userInfoEndpoint(userInfo -> userInfo.userService(oauthUserService)).successHandler(new AuthenticationSuccessHandler() {

                            @Override
                            public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                                                Authentication authentication) throws IOException, ServletException {

                                CustomOAuth2User oauthUser = (CustomOAuth2User) authentication.getPrincipal();

                                String github_id = oauthUser.getAttributes().get("id").toString();

                                Optional<User> userExistOp = userRepository.findByIdentifier(github_id);

                                if (!userExistOp.isPresent()) {
                                    //all user are saves with role User
                                    UserDTO userDTO = new UserDTO(oauthUser.getName(),"username", oauthUser.getAttributes().get("id").toString(), oauthUser.getAttributes().get("avatar_url").toString(), Role.ADMIN);

                                    userService.create(userDTO);
                                }

                                response.sendRedirect("/");
                            }
                        })
                )
        .build();
    }
}

CustomOAuth2UserService

@Service
public class CustomOAuth2UserService extends DefaultOAuth2UserService {

    @Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
        OAuth2User user =  super.loadUser(userRequest);
        return new CustomOAuth2User(user);
    }
}

CustomOauth2User

public class CustomOAuth2User implements OAuth2User {

    private OAuth2User oauth2User;

    public CustomOAuth2User(OAuth2User oauth2User) {
        this.oauth2User = oauth2User;
    }

    @Override
    public Map<String, Object> getAttributes() {
        return oauth2User.getAttributes();
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        Set<Role> roles = getUserRoles();

        System.out.println("User roles:");
        for (Role role : roles) {
            System.out.println(role.name());
        }

        if (roles != null && !roles.isEmpty()) {
            // Convert roles to GrantedAuthority objects
            return roles.stream()
                    .map(role -> new SimpleGrantedAuthority("ROLE_" + role.name()))
                    .collect(Collectors.toList());
        } else {
            // Provide a default role if user roles are not available
            return Collections.singleton(new SimpleGrantedAuthority("ROLE_USER"));
        }
    }

    @Override
    public String getName() {
        return oauth2User.getAttribute("name");
    }

    // Helper method to retrieve user roles from OAuth2User attributes
    private Set<Role> getUserRoles() {
        // Assuming roles are stored as an attribute with key "roles" in OAuth2User attributes
        Collection<String> roleStrings = (Collection<String>) oauth2User.getAttribute("roles");
        return roleStrings != null ?
                roleStrings.stream()
                        .map(Role::valueOf) // Convert role string to Role enum
                        .collect(Collectors.toSet()) :
                Collections.emptySet();
    }

    public String getEmail() {
        return oauth2User.<String>getAttribute("email");
    }
}

Модель пользователя

@Entity
@Table(name = "users")
@Data
@Setter
public class User implements UserDetails {

    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    private Long id;

    private String name;

    private String username;

    private String identifier;

    private String avatar;

    private Role role;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        if (this.role == Role.ADMIN) {
            return List.of(new SimpleGrantedAuthority("ROLE_ADMIN"), new SimpleGrantedAuthority("ROLE_USER"));
        } 
        return List.of(new SimpleGrantedAuthority("ROLE_USER"));
    }

    @Override
    public String getPassword() {
        return null;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

АутСервис

@Service
public class AuthService implements UserDetailsService{

    UserRepository userRepository;

    public AuthService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public UserDetails loadUserByUsername(String identifier) throws UsernameNotFoundException {
        Optional<User> userOp = userRepository.findByIdentifier(identifier);
        if (!userOp.isPresent()) {
            throw new UsernameNotFoundException("User not found with GitHub user ID: " + identifier);
        }

        User user = userOp.get();
        // Construct UserDetails object with user roles
        return org.springframework.security.core.userdetails.User
                .withUsername(String.valueOf(user.getIdentifier())) // GitHub user ID
                .password(user.getPassword())
                .authorities(user.getAuthorities())
                .accountExpired(true)
                .accountLocked(true)
                .credentialsExpired(true)
                .disabled(true)
                .build();
    }
}

Pom.xml

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.3</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.ecommerce</groupId>
    <artifactId>e-commerce</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>e-commerce</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>17</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-oauth2-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.flywaydb</groupId>
            <artifactId>flyway-core</artifactId>
        </dependency>

        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct</artifactId>
            <version>1.5.5.Final</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
             <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.11.0</version>
                <configuration>
                    <annotationProcessorPaths>
                        <path>
                            <groupId>org.mapstruct</groupId>
                            <artifactId>mapstruct-processor</artifactId>
                            <version>1.5.5.Final</version>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
        </plugins>
    </build>
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
3
0
206
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Что вам следует сделать, так это использовать собственный сервер авторизации (Keycloak? Auth0? Amazon cognito? Вариантов много). Этот новый сервер авторизации будет отвечать за сохранение ролей и добавление их в частные заявки токенов.

Тогда все, что вам нужно настроить вручную в клиентах и ​​серверах ресурсов Spring, — это сопоставление полномочий из частных утверждений, в которые ваш сервер авторизации помещает роли.

Спасибо за ваш ответ @ch4mp. Я подумывал сделать это вручную, чтобы понять, как все работает в Spring Security, поскольку я новичок в Spring Boot. Вы думаете, что в этом нет необходимости? стоит ли мне просто выбрать что-то вроде okta или keycloak?

PauloRamos 20.03.2024 11:55

Если вы хотите понять OAuth2 в Spring Security, начните со спецификации OAuth2 , а затем продолжите Руководство по Spring Security . Чтобы быстро начать, вы можете просмотреть мои уроки, но чтобы по-настоящему понять, вам придется усвоить первые две ссылки.

ch4mp 20.03.2024 15:30

Я прочитал немного вашего руководства, кажется, я совершаю ошибку, поскольку я создаю REST Api и использую oauth2-client для использования входа в систему oauth2. Я также прочитаю другой документ, на который вы ссылаетесь, этот сервер ресурсов и сервер авторизации все еще смущают меня, может быть, я буду использовать keycloak, чтобы не застрять в самой первой части проекта, чтобы я мог продолжить обучение больше вещей о весенней загрузке. Спасибо за помощь!

PauloRamos 20.03.2024 19:09

Keycloak — надежное и хорошо документированное решение, которым я пользуюсь каждый день. Не пропустите Основы OAuth2 во вступительном файле моих руководств, это должно помочь вам прояснить свои мысли об актерах OAuth2 и некоторых других (действительно) важных понятиях.

ch4mp 20.03.2024 22:26

Это мне очень помогло, спасибо, что помогли мне найти лучшее решение моей проблемы. Я также прочитал в вашем блоге сообщение об oauth2 BFF, оно действительно интересное, хотя сейчас оно для меня слишком продвинутое, но я сохранил его, чтобы прочитать, когда получу больше знаний об oauth и keycloak. Еще раз спасибо за ваши ответы @ch4mp, они были очень полезны. Если у вас есть какие-либо другие ресурсы, которые, по вашему мнению, могут быть полезны, я буду признателен.

PauloRamos 20.03.2024 23:33

Я не шутил в своем первом комментарии. После того, как вы немного попрактиковались в моих руководствах и получили общее представление об OAuth2, вернитесь к спецификации OAuth2 и разделу OAuth2 руководства Spring Security, это единственный источник истины.

ch4mp 21.03.2024 00:30

Другие вопросы по теме