Java 如何使用Spring安全性保护Spring数据Rest端点?

Java 如何使用Spring安全性保护Spring数据Rest端点?,java,spring,spring-boot,spring-security,spring-data-rest,Java,Spring,Spring Boot,Spring Security,Spring Data Rest,我正在使用Spring应用程序。在浏览器上一切正常。我可以使用现有用户登录,只需提供我的用户名和密码。我也可以注册一个新用户,然后用它登录 我还可以调用一些REST端点。我没有手动定义这些端点。它们是自动创建的,因为我使用的是SpringBootStarter数据rest依赖项 REST请求的URL类似于 我正试图通过邮递员获得一份食谱清单。我想得到一条类似“403禁止”之类的错误消息,因为我没有提供任何凭据。相反,我会收到登录页面的HTML代码和状态代码“200OK” 在我将用户名和密码作为请

我正在使用Spring应用程序。在浏览器上一切正常。我可以使用现有用户登录,只需提供我的用户名和密码。我也可以注册一个新用户,然后用它登录

我还可以调用一些REST端点。我没有手动定义这些端点。它们是自动创建的,因为我使用的是SpringBootStarter数据rest依赖项

REST请求的URL类似于

我正试图通过邮递员获得一份食谱清单。我想得到一条类似“403禁止”之类的错误消息,因为我没有提供任何凭据。相反,我会收到登录页面的HTML代码和状态代码“200OK”

在我将用户名和密码作为请求头提供后,这也适用(可能我需要使用另一种方式提供凭据)

下面的列表包含一些代码片段,以显示我在项目中编写的与应用程序的安全性相关的所有内容:

  • 第一段代码表示我的项目中的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) {
                    };
                }
            };
        }
    }
    
  • 第二个是用户实体类:

        @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;
            }
        }
    
    @实体
    公共类用户实现UserDetails{
    公共静态最终密码编码器密码\u编码器=
    新的BCryptPasswordEncoder();
    @身份证
    @GeneratedValue(策略=GenerationType.IDENTITY)
    私人长id;
    @NotNull
    @列(唯一=真)
    @尺寸(最小值=2,最大值=20)
    私有字符串用户名;
    @NotNull
    @列(长度=100)
    @杰索尼奥雷
    私有字符串密码;
    @NotNull
    @列(长度=100)
    @杰索尼奥雷
    私有字符串匹配密码;
    @列(nullable=false)
    启用私有布尔值;
    @奥内托内
    @JoinColumn(name=“role\u id”)
    @杰索尼奥雷
    私人角色;
    @ManyToMany(targetEntity=Recipe.class,fetch=FetchType.EAGER)
    @JoinTable(name=“users\u favorite\u recipes”,
    joinColumns=@JoinColumn(name=“user\u id”),
    inverseJoinColumns=@JoinColumn(name=“recipe\u id”))
    私有列表favoritedRecipes=新的ArrayList();
    @杰索尼奥雷
    @OneToMany(mappedBy=“user”,cascade=CascadeType.ALL)
    私有列表ownedRecipes=newArrayList();
    //构造器。。。
    //接球手和接球手。。。
    公共无效密码(){
    密码=密码\编码器。编码(密码);
    matchingPassword=密码\编码器编码(matchingPassword);
    }
    @凌驾
    
    公共收集据我所知,您有一个RESTful API(没有UI),如果这是真的,您可以更新SecurityConfig#
    配置(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();
        }
    

    您需要配置自己的身份验证入口点,要获取403条消息,可以使用http403禁止入口点

    示例:

    @RestController
    public class Controller {
    
        @GetMapping("/test")
        public String test() {
            return "test";
        }
    }
    
    添加
    .exceptionHandling().authenticationEntryPoint(新的Http403ForbiddenEntryPoint())


    现在当我尝试访问
    http://localhost:8080/test
    ,我收到
    403拒绝访问
    消息。

    我可以为antMatcher字符串使用通配符吗?我使用了您提供的代码,但不是,我可以获取所有配方实体,而不提供任何用户名或密码(使用REST)。此外,如果我尝试从浏览器访问根端点,登录页面将不再显示()啊,我看到了,所以您有了一个UI!正如我在回答中所说,这是为了RESTful API安全,是的,您可以使用通配符的路径(如
    “/recipes/**”
    )我添加了
    .exceptionHandling().authenticationEntryPoint(新的Http403ForbiddenEntryPoint())
    configure
    方法末尾的
    行。我收到
    403拒绝访问
    消息。但是如果我从浏览器访问根端点,它将给我相同的错误,而不是将我重定向到登录页面。但是,如果我使用端点,一切都很好,我可以使用任何可用的用户登录。我想我们已经接近了要解决此问题,只有根终结点问题。我必须如何在REST客户端(如Postman)中提供凭据?我需要指定我仍然没有添加任何自己的终结点。我仍然使用自动生成的REST终结点,这将是我在本例中所需的结果。我添加的REST终结点仅用于演示离子用途(你显然不需要)。您需要编写自定义入口点或重写Http403ForbiddenEntryPoint,因为您需要root用户重定向到登录,而不是获取403。您能否提供一些代码,说明如何创建该自定义入口,或重写Http403ForbiddenEntryPoint?另外,我需要如何在rest客户端(如Postman)中提供凭据,以便e授权,并获得所需数据?
    @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();
        }
    
    @RestController
    public class Controller {
    
        @GetMapping("/test")
        public String test() {
            return "test";
        }
    }
    
    @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());
        }
    }