如何在 Spring 引导中使用 RESTful 和基本身份验证
How to use RESTful with Basic Authentication in Spring Boot
你好,我正在使用 RESTful 进行基本身份验证,此代码是 RestController 的一部分:
@GetMapping("/jpa/users/{username}/goals")
public List<Goal> getAllGoals(@PathVariable String username) {
userId = getUserIdFromUsername(username);
return goalJpaRepository.findByUserId(userId);
}
public Long getUserIdFromUsername(String username) {
User user = userJpaRepository.findByUsername(username);
userId = user.getId();
return userId;
}
我有一个问题,例如我正在使用 Postman 来检索特定用户的目标,如下所示:
http://localhost:8080/jpa/users/john/goals 有 GET 请求
然后我使用用户名 john 的基本身份验证和此用户名的密码,我收到了 john 的目标。
之后,如果我为此 link http://localhost:8080/jpa/users/tom/goals 发出 GET 请求,我会收到 tom 的目标,但此时我与 john 一起登录,所以 john 可以看到他的目标,他也可以看到汤姆的目标。
问题是如何在 RestController 中访问登录用户名,因为我想做这样的事情:
if (loginUsername == username) {
return goalJpaRepository.findByUserId(userId);
}
return "Access denied!";
所以我想知道是否可以从 HTTP Header 访问登录用户名?
谢谢!
UPDATE - 是的,框架是 Spring Boot,我也在使用 Spring Security with Dao Authentication 因为我想从MySQL 数据库。无论如何,我不是 Spring 安全方面的专家。
现在我了解了如何在我的控制器方法中使用 Principal,但我不知道如何在这种特定情况下使用 Spring 安全性。我应该如何实施它?例如,用户 john 应该只查看和修改他的目标。
Spring 安全配置:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import com.dgs.restful.webservices.goaltrackerservice.user.MyUserDetailsService;
@Configuration
@EnableWebSecurity
public class SpringSecurityConfigurationBasicAuth extends WebSecurityConfigurerAdapter {
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
@Autowired
private MyUserDetailsService userDetailsService;
@Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider
= new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(bCryptPasswordEncoder());
return authProvider;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.antMatchers("/allusers").permitAll()
.anyRequest().authenticated()
.and()
// .formLogin().and()
.httpBasic();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider());
}
}
请注意,您此时没有进行任何安全保护。
如@Matt 所述"It depends which framework you are using"。但我猜你正在使用 spring。然后你应该看看 spring-security 模块文档。
基本上,您可以将经过身份验证的用户注入到您的方法参数中:
@GetMapping("/jpa/users/{username}/goals")
public List<Goal> getAllGoals(@PathVariable String username, Principal principal) {
if ( username.equals(principal.getName()) ) {
userId = getUserIdFromUsername(username);
return goalJpaRepository.findByUserId(userId);
} else {
throw new SomeExceptionThatWillBeMapped();
}
}
但是 spring-安全和许多框架提供了更好的模式来管理安全。
假设您使用 Spring 作为您的 Java 框架,您应该使用 Spring 安全来配置基本身份验证。许多在线教程(https://www.baeldung.com/spring-security-basic-authentication、
Spring 然后,安全性将在整个应用程序 (SecurityContextHolder.getContext()
) 中提供一个可用的安全上下文,您可以从中检索连接的用户信息(用户名,...)。
例如,要检索已连接用户的用户名,您应该这样做:
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String loginUsername = authentication.getName();
或者,如@gervais.b所述,Spring可以在您的控制器方法中注入Principal
(或Authentication
)。
正如@Glains 所说,更好的选择是使用 @PreAuthorize
和 @PostAuthorize
注释,它允许您基于 Spring 表达式语言定义简单规则。
您还可以使用 @PreAuthorize
解决此问题,这是 Spring 安全框架提供的注释,它使用 Spring 表达式语言。
@PreAuthorize("principal.name == #username")
@GetMapping("/jpa/users/{username}/goals")
public List<Goal> getAllGoals(@PathVariable String username) {
return goalJpaRepository.findByUserId(userId);
}
在幕后 Spring 将使用已经提到的 SecurityContextHolder
来获取当前经过身份验证的主体。如果表达式解析为 false,将返回响应代码 403
。
请注意,您必须启用全局方法安全性:
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
}
要回答关于 "Dao Authentication" 的新问题,答案是提供自定义 UserDetailsService
.
从您附加到问题的配置来看,您似乎已经有了 MyUserDetailsService
。
有很多文章解释了如何使用自定义 DetailsService
。这个似乎符合您的要求:https://www.baeldung.com/spring-security-authentication-with-a-database
编辑:关于如何确保只有 John 可以看到 John 的项目。
基本上,要确保只有 John 可以看到他的目标,您唯一可以做的就是将目标限制为只有 John 拥有的目标。但是有很多方法可以做到这一点。
正如您在最初的问题中所建议的那样,您可以 select 特定用户的目标。 spring-security 的强大之处在于它可以注入 Principal
但也可以注入某种身份验证对象。
您还可以使用 SecurityContextHolder
使 DAO/Repository 侧的过滤器更加隐式。当您的系统更以用户为中心或类似于多租户系统时,这种方法很好并且看起来更好。
使用一些特定的 @Annotations 或 Aspects 也是一种解决方案,但在这种情况下可能不太明显。
你好,我正在使用 RESTful 进行基本身份验证,此代码是 RestController 的一部分:
@GetMapping("/jpa/users/{username}/goals")
public List<Goal> getAllGoals(@PathVariable String username) {
userId = getUserIdFromUsername(username);
return goalJpaRepository.findByUserId(userId);
}
public Long getUserIdFromUsername(String username) {
User user = userJpaRepository.findByUsername(username);
userId = user.getId();
return userId;
}
我有一个问题,例如我正在使用 Postman 来检索特定用户的目标,如下所示:
http://localhost:8080/jpa/users/john/goals 有 GET 请求
然后我使用用户名 john 的基本身份验证和此用户名的密码,我收到了 john 的目标。
之后,如果我为此 link http://localhost:8080/jpa/users/tom/goals 发出 GET 请求,我会收到 tom 的目标,但此时我与 john 一起登录,所以 john 可以看到他的目标,他也可以看到汤姆的目标。
问题是如何在 RestController 中访问登录用户名,因为我想做这样的事情:
if (loginUsername == username) {
return goalJpaRepository.findByUserId(userId);
}
return "Access denied!";
所以我想知道是否可以从 HTTP Header 访问登录用户名?
谢谢!
UPDATE - 是的,框架是 Spring Boot,我也在使用 Spring Security with Dao Authentication 因为我想从MySQL 数据库。无论如何,我不是 Spring 安全方面的专家。
现在我了解了如何在我的控制器方法中使用 Principal,但我不知道如何在这种特定情况下使用 Spring 安全性。我应该如何实施它?例如,用户 john 应该只查看和修改他的目标。
Spring 安全配置:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import com.dgs.restful.webservices.goaltrackerservice.user.MyUserDetailsService;
@Configuration
@EnableWebSecurity
public class SpringSecurityConfigurationBasicAuth extends WebSecurityConfigurerAdapter {
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
@Autowired
private MyUserDetailsService userDetailsService;
@Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider
= new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(bCryptPasswordEncoder());
return authProvider;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.antMatchers("/allusers").permitAll()
.anyRequest().authenticated()
.and()
// .formLogin().and()
.httpBasic();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider());
}
}
请注意,您此时没有进行任何安全保护。
如@Matt 所述"It depends which framework you are using"。但我猜你正在使用 spring。然后你应该看看 spring-security 模块文档。
基本上,您可以将经过身份验证的用户注入到您的方法参数中:
@GetMapping("/jpa/users/{username}/goals")
public List<Goal> getAllGoals(@PathVariable String username, Principal principal) {
if ( username.equals(principal.getName()) ) {
userId = getUserIdFromUsername(username);
return goalJpaRepository.findByUserId(userId);
} else {
throw new SomeExceptionThatWillBeMapped();
}
}
但是 spring-安全和许多框架提供了更好的模式来管理安全。
假设您使用 Spring 作为您的 Java 框架,您应该使用 Spring 安全来配置基本身份验证。许多在线教程(https://www.baeldung.com/spring-security-basic-authentication、
Spring 然后,安全性将在整个应用程序 (SecurityContextHolder.getContext()
) 中提供一个可用的安全上下文,您可以从中检索连接的用户信息(用户名,...)。
例如,要检索已连接用户的用户名,您应该这样做:
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String loginUsername = authentication.getName();
或者,如@gervais.b所述,Spring可以在您的控制器方法中注入Principal
(或Authentication
)。
正如@Glains 所说,更好的选择是使用 @PreAuthorize
和 @PostAuthorize
注释,它允许您基于 Spring 表达式语言定义简单规则。
您还可以使用 @PreAuthorize
解决此问题,这是 Spring 安全框架提供的注释,它使用 Spring 表达式语言。
@PreAuthorize("principal.name == #username")
@GetMapping("/jpa/users/{username}/goals")
public List<Goal> getAllGoals(@PathVariable String username) {
return goalJpaRepository.findByUserId(userId);
}
在幕后 Spring 将使用已经提到的 SecurityContextHolder
来获取当前经过身份验证的主体。如果表达式解析为 false,将返回响应代码 403
。
请注意,您必须启用全局方法安全性:
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
}
要回答关于 "Dao Authentication" 的新问题,答案是提供自定义 UserDetailsService
.
从您附加到问题的配置来看,您似乎已经有了 MyUserDetailsService
。
有很多文章解释了如何使用自定义 DetailsService
。这个似乎符合您的要求:https://www.baeldung.com/spring-security-authentication-with-a-database
编辑:关于如何确保只有 John 可以看到 John 的项目。
基本上,要确保只有 John 可以看到他的目标,您唯一可以做的就是将目标限制为只有 John 拥有的目标。但是有很多方法可以做到这一点。
正如您在最初的问题中所建议的那样,您可以 select 特定用户的目标。 spring-security 的强大之处在于它可以注入
Principal
但也可以注入某种身份验证对象。您还可以使用
SecurityContextHolder
使 DAO/Repository 侧的过滤器更加隐式。当您的系统更以用户为中心或类似于多租户系统时,这种方法很好并且看起来更好。使用一些特定的 @Annotations 或 Aspects 也是一种解决方案,但在这种情况下可能不太明显。