Java 如何使用Spring安全性保护Spring数据Rest端点?
我正在使用Spring应用程序。在浏览器上一切正常。我可以使用现有用户登录,只需提供我的用户名和密码。我也可以注册一个新用户,然后用它登录 我还可以调用一些REST端点。我没有手动定义这些端点。它们是自动创建的,因为我使用的是SpringBootStarter数据rest依赖项 REST请求的URL类似于 我正试图通过邮递员获得一份食谱清单。我想得到一条类似“403禁止”之类的错误消息,因为我没有提供任何凭据。相反,我会收到登录页面的HTML代码和状态代码“200OK” 在我将用户名和密码作为请求头提供后,这也适用(可能我需要使用另一种方式提供凭据) 下面的列表包含一些代码片段,以显示我在项目中编写的与应用程序的安全性相关的所有内容: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” 在我将用户名和密码作为请
@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());
}
}