Как защитить конечные точки Spring Data Rest с помощью Spring Security? - PullRequest
0 голосов
/ 25 января 2019

Я работаю с приложением Spring. Все хорошо в браузере. Я могу войти с существующими пользователями, мне нужно указать только мое имя пользователя и пароль. Я также могу зарегистрироваться с новым пользователем, а затем войти в систему с ним.

У меня также есть некоторые конечные точки REST, которые я могу вызвать. Я не определял эти конечные точки вручную. Они были созданы автоматически, потому что я использую зависимость spring-boot-starter-data-rest.

URL-адрес запроса REST будет выглядеть примерно так: http://localhost:8182/api/v1/recipes.

Я пытаюсь получить список рецептов, используя Почтальон. Я хотел бы получить сообщение об ошибке, например «403 Forbidden», или что-то подобное, потому что я не предоставил никаких учетных данных. Вместо этого я получаю HTML-код страницы входа и код состояния «200 OK».

Это применимо также после того, как я предоставил имя пользователя и пароль в качестве заголовков запроса (возможно, мне нужно использовать другой способ предоставления учетных данных)

user:user
password:password

Следующий список содержит несколько фрагментов кода, чтобы показать все, что я написал в проекте, касающееся бита безопасности приложения:

  1. Первый фрагмент кода представляет класс SecurityConfig из моего проекта:

    @Configuration
    @EnableWebSecurity
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class SecurityConfig extends WebSecurityConfigurerAdapter{
        @Autowired
        private UserService userService;
    
        @Autowired
        public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception{
            auth.userDetailsService(userService).passwordEncoder(User.PASSWORD_ENCODER);
        }
    
        @Override
        public void configure(WebSecurity web) throws Exception{
            web.ignoring().antMatchers("/css/**");
            web.ignoring().antMatchers("/images/**");
            web.ignoring().antMatchers("/js/**");
        }
    
        @Override
        protected void configure(HttpSecurity http) throws Exception{
            http.authorizeRequests()
                    .antMatchers("/sign-up").permitAll()
                    .anyRequest()
                    .hasRole("USER")
                    .and()
                    .formLogin()
                    .loginPage("/login")
                    .permitAll()
                    .successHandler(loginSuccessHandler())
                    .failureHandler(loginFailureHandler())
                    .and()
                    .logout()
                    .permitAll()
                    .logoutSuccessUrl("/login")
                    .and()
                    .csrf().disable();
        }
    
        public AuthenticationSuccessHandler loginSuccessHandler(){
            return (request, response, authentication) ->{
              response.sendRedirect("/recipes/");
            };
        }
    
        public AuthenticationFailureHandler loginFailureHandler(){
            return (request, response, exception) ->{
              request.getSession().setAttribute("flash",
                      new FlashMessage("Incorrect username and/or password. Try again.",
                              FlashMessage.Status.FAILURE));
                response.sendRedirect("/login");
            };
        }
    
        @Bean
        public EvaluationContextExtension securityExtension(){
            return new EvaluationContextExtensionSupport() {
                @Override
                public String getExtensionId() {
                    return "security";
                }
    
                @Override
                public Object getRootObject(){
                    Authentication authentication =
                            SecurityContextHolder.getContext().getAuthentication();
                    return new SecurityExpressionRoot(authentication) {
                    };
                }
            };
        }
    }
    
  2. Вторым является класс сущности User:

        @Entity
        public class User implements UserDetails{
            public static final PasswordEncoder PASSWORD_ENCODER =
                    new BCryptPasswordEncoder();
    
            @Id
            @GeneratedValue(strategy = GenerationType.IDENTITY)
            private Long id;
    
            @NotNull
            @Column(unique = true)
            @Size(min = 2, max = 20)
            private String username;
    
            @NotNull
            @Column(length = 100)
            @JsonIgnore
            private String password;
    
            @NotNull
            @Column(length = 100)
            @JsonIgnore
            private String matchingPassword;
    
            @Column(nullable = false)
            private boolean enabled;
    
            @OneToOne
            @JoinColumn(name = "role_id")
            @JsonIgnore
            private Role role;
    
            @ManyToMany(targetEntity = Recipe.class, fetch = FetchType.EAGER)
            @JoinTable(name = "users_favorite_recipes",
                    joinColumns = @JoinColumn(name="user_id"),
                    inverseJoinColumns = @JoinColumn(name = "recipe_id"))
            private List<Recipe> favoritedRecipes = new ArrayList<>();
    
            @JsonIgnore
            @OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
            private List<Recipe> ownedRecipes = new ArrayList<>();
    
            //constructor ...
            //getters and setters ...
    
            public void encryptPasswords(){
                password = PASSWORD_ENCODER.encode(password);
                matchingPassword = PASSWORD_ENCODER.encode(matchingPassword);
            }
    
            @Override
            public Collection<? extends GrantedAuthority> getAuthorities() {
                List<GrantedAuthority> authorities = new ArrayList<>();
                authorities.add(new SimpleGrantedAuthority(role.getName()));
                return authorities;
            }
    
            @Override
            public String getPassword() {
                return password;
            }
    
            @Override
            public String getUsername() {
                return username;
            }
    
            @Override
            public boolean isAccountNonExpired() {
                return true;
            }
    
            @Override
            public boolean isAccountNonLocked() {
                return true;
            }
    
            @Override
            public boolean isCredentialsNonExpired() {
                return true;
            }
    
            @Override
            public boolean isEnabled() {
                return enabled;
            }
        }
    
  3. Третий фрагмент представляет интерфейс, который расширяет UserDetailsService:

    public interface UserService extends UserDetailsService{
        UserDetails loadUserByUsername(String username);
        User findByUsername(String username);
        User registerNewUser(String username, boolean enabled, String password, String matchingPassword);
        void save(User user);
        List<User> findAll();
    }
    
  4. Четвертый и последний фрагмент является реализацией предыдущего интерфейс (UserService):

    @Component
    @ComponentScan
    public class UserServiceImpl implements UserService{
        @Autowired
        private UserDao userDao;
    
        @Autowired
        private RoleDao roleDao;
    
        @Override
        public User findByUsername(String username) {
            User user = userDao.findByUsername(username);
            Hibernate.initialize(user.getFavoritedRecipes());
            return user;
        }
    
        @Override
        public UserDetails loadUserByUsername(String username)
            throws UsernameNotFoundException{
            User user = userDao.findByUsername(username);
            if(user ==  null){
                throw new UsernameNotFoundException(
                  username + " was not found"
                );
            }
    
            return user;
        }
    
        @Override
        public void save(User user) {
            userDao.save(user);
        }
    
        @Override
        public User registerNewUser(String username, boolean enabled, String password, String matchingPassword) {
            return userDao.save(new User(username, enabled, password, matchingPassword));
        }
    
        @Override
        public List<User> findAll() {
            return userDao.findAll();
        }
    }
    

Что я должен изменить в этой ситуации, чтобы получить функциональную авторизацию REST API?

Ответы [ 2 ]

0 голосов
/ 25 января 2019

Вам необходимо настроить собственную точку входа для аутентификации, чтобы получить 403 сообщения, вы можете использовать Http403ForbiddenEntryPoint.

Пример:.

@RestController
public class Controller {

    @GetMapping("/test")
    public String test() {
        return "test";
    }
}

Добавление .exceptionHandling().authenticationEntryPoint(new Http403ForbiddenEntryPoint()):

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception{
        http.authorizeRequests()
                .antMatchers("/sign-up").permitAll()
                .anyRequest()
                .hasRole("USER")
                .and()
                .formLogin()
                .loginPage("/login")
                .permitAll()
                .and()
                .logout()
                .permitAll()
                .logoutSuccessUrl("/login")
                .and()
                .csrf().disable()
                .exceptionHandling().authenticationEntryPoint(new Http403ForbiddenEntryPoint());
    }
}

Теперь, когда я пытаюсь получить доступ к http://localhost:8080/test, я получаю сообщение 403 Access Denied.

0 голосов
/ 25 января 2019

Насколько я понимаю, у вас есть RESTful API (без UI ), если это правда, вы можете обновить SecurityConfig # configure(HttpSecurity http) метод заменит это:

@Override
protected void configure(HttpSecurity http) throws Exception{
    http.authorizeRequests()
            .antMatchers("/sign-up").permitAll()
            .anyRequest()
            .hasRole("USER")
            .and()
            .formLogin()
            .loginPage("/login")
            .permitAll()
            .successHandler(loginSuccessHandler())
            .failureHandler(loginFailureHandler())
            .and()
            .logout()
            .permitAll()
            .logoutSuccessUrl("/login")
            .and()
            .csrf().disable();
}

По этому:

@Override
    protected void configure(HttpSecurity http) throws Exception {
            http
                .cors()
                .and()
                .csrf()
                .disable()
                .exceptionHandling()
                .authenticationEntryPoint((request, response, exc) ->
                        response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "You are not authorized to access this resource."))
                .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                // Array of String that contain's all endpoints you want secure
                .antMatchers(ENDPOINTS_TO_SECURE).access("hasAnyRole('ROLE_USER')")
                // Array of String that contain's all endpoints you want to permit
                .antMatchers(WHITE_LIST).permitAll()
                .anyRequest()
                .authenticated();
        // disable page caching
        http.headers().cacheControl();
    }
...