Java 如何为需要验证由第三方和用户生成的JWT令牌的rest api配置spring http安全性;你没有密码吗?
我想保护一个由Angular应用程序使用的Spring Boot REST API。任何用户和其他应用都不应访问此API 我无法控制这个angular应用程序,但有人告诉我,用户的实际身份验证正在angular应用程序上进行,该应用程序在对其用户进行身份验证后设置并发送JWT。由于angular应用程序和我的API部署在同一个域上,因此我确实能够访问JWT,该请求作为cookie发送。我还能够成功地从中提取用户名 我现在必须使用JWT用户名和与之关联的角色对请求进行身份验证和授权,以限制对某些端点的访问。为了做到这一点,我已经获得了访问几个包含所需信息的数据库视图的权限,但是我没有获得任何类型的密码信息。我无法控制该数据库中的视图或任何其他内容。显然,如果在这些视图中找不到令牌中的用户名,则该用户是未经授权的。角色也是如此 我的问题是如何配置Java 如何为需要验证由第三方和用户生成的JWT令牌的rest api配置spring http安全性;你没有密码吗?,java,angular,spring,security,jwt,Java,Angular,Spring,Security,Jwt,我想保护一个由Angular应用程序使用的Spring Boot REST API。任何用户和其他应用都不应访问此API 我无法控制这个angular应用程序,但有人告诉我,用户的实际身份验证正在angular应用程序上进行,该应用程序在对其用户进行身份验证后设置并发送JWT。由于angular应用程序和我的API部署在同一个域上,因此我确实能够访问JWT,该请求作为cookie发送。我还能够成功地从中提取用户名 我现在必须使用JWT用户名和与之关联的角色对请求进行身份验证和授权,以限制对某些端
websecurityConfigureAdapter
和UserDetails
无论我尝试什么,当从控制器代码调用时,我要么立即得到401 unauthorized,要么返回SecurityContextHolder.getContext().getAuthentication().getPrincipal()
,而不是UserDetails
对象,即使它设置正确
这只是一个猜测,但它可能返回401,因为在我的UserDetails
类中被重写的getPassword()
方法返回一个空字符串(return”“;
),但它应该返回什么呢,因为我在任何时候都不能访问任何类似于用户密码的内容
Web安全配置适配器:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter
{
@Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
@Autowired
private JwtUserDetailsService jwtUserDetailsService;
@Autowired
private JwtRequestFilter jwtRequestFilter;
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception
{
auth.userDetailsService(jwtUserDetailsService).passwordEncoder(passwordEncoder());
}
@Bean
public PasswordEncoder passwordEncoder()
{
return new BCryptPasswordEncoder();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception
{
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception
{
httpSecurity.cors().and().csrf().disable()
// I will eventually filter multiple endpoints by roles
// but at this point I just want it to work
.authorizeRequests().anyRequest().authenticated().and() // I am guessing it fails here because of the password issue?
.exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
}
用户详细信息:
public class MyUserDetails implements UserDetails
{
private static final long serialVersionUID = 7371936004280236913L;
private final MyUserDto MyUser;
public MyUserDetails(MyUserDto MyUser)
{
this.MyUser = MyUser;
}
public MyUserDto getMyUser()
{ return MyUser; }
@Override
public Collection<? extends GrantedAuthority> getAuthorities()
{
List<MyUserAuthority> authorities = new ArrayList<MyUserAuthority>();
for(MyRoleDto userRole : MyUser.getRoles())
authorities.add(new MyUserAuthority(userRole));
return authorities;
}
@Override
public String getPassword()
{ return ""; } // What should this return?
@Override
public String getUsername()
{ return this.MyUser.getUserId(); }
@Override
public boolean isAccountNonExpired()
{ return true; }
@Override
public boolean isAccountNonLocked()
{ return true; }
@Override
public boolean isCredentialsNonExpired()
{ return true; }
@Override
public boolean isEnabled()
{ return true; }
}
公共类MyUserDetails实现UserDetails
{
私有静态最终长serialVersionUID=7371936004280236913L;
私有最终MyUserdToMyUser;
公共MyUserDetails(MyUserDto MyUser)
{
this.MyUser=MyUser;
}
public MyUserDto getMyUser()
{返回MyUser;}
@凌驾
公共集合好吧,既然有这么多的答案,就像java/spring boot一样,这是一种流行的、有文档记录的编程语言和框架,我决定只使用用户名作为密码
@Override
public String getPassword()
{
// I am using BCryptPasswordEncoder but you may
// have to change to whatever you're using.
return new BCryptPasswordEncoder().encode(this.getUsername());
}
请记住,您必须对其进行编码,因为密码提供程序希望对其进行编码。否则,提供程序会说密码不匹配
我不知道这是否是“最佳实践”,但它确实有效。我认为user1969903
已经回答了问题的解决方案。但是,我建议您在解决方案中添加一小撮盐
@覆盖
公共字符串getPassword()
{
//我正在使用BCryptPasswordEncoder,但您可以
//你必须改变你正在使用的东西。
返回新的BCryptPasswordEncoder().encode(this.getUsername()+someString);
}
someString
可以是您可以在您的环境中配置的内容,也可以是用户配置文件数据的一部分,如姓氏、电子邮件、zipcode等。这样,如果有人碰巧获得令牌,他们就不容易对其进行解码。您的问题与设置或用户密码无关
@Override
public String getPassword()
{
// I am using BCryptPasswordEncoder but you may
// have to change to whatever you're using.
return new BCryptPasswordEncoder().encode(this.getUsername());
}
正如您所指出的,用户已经在前端通过身份验证,可能是通过与OAuth2客户端交互。因此,OAuth2客户端将为您的前端提供JWT令牌
在每次HTTP交互中,前端都会将此JWT令牌发送到后端
后端有两个职责:
- 一方面,验证接收到的令牌是否有效(过期,如果它是数字签名的,则带有有效签名,来自可信来源,具有正确的访问群体,等等)
- 另一方面,您需要识别与接收到的JWT令牌相对应的用户
对于第二个职责,您需要一些在JWT令牌中定义的所有属性中唯一标识用户的属性
此属性必须是用于从数据库中获取用户信息的属性
对于Spring安全性,您需要从UserDetailsService
实现中的loadByUserName
方法返回UserDetails
接口的一些实现
如您所述,如果您能够获得一个用户名
,只需创建一个用户详细信息服务
实现,该实现使用该信息查询数据库中的视图以获取用户信息,并定义实现用户详细信息
的某个类,而不注意密码,您就不需要了首先,您不需要再次对用户进行身份验证
此UserDetailsService
实现将与Spring安全配置相关
您的用户已预验证,您需要预验证资料。请在WebSecurityConfig
类中包含以下内容:
@覆盖
受保护的无效配置(AuthenticationManagerBuilder auth)引发异常{
UserDetailsByNameServiceWrapper=新的UserDetailsByNameServiceWrapper(userDetailsService);
PreAuthenticateAuthenticationProvider PreAuthenticationProvider=新的PreAuthenticateAuthenticationProvider();
setPreAuthenticatedUserDetailsService(包装器);
身份验证提供者(预授权提供者);
}
如果您的JwtRequestFilter
如您所述是OncePerRequestFilter
,则在验证JWT令牌并获得唯一用户标识符后,您需要将此信息提供给Spring SecurityAuthenticationManager
,以获得对适当用户的引用<