Spring 拒绝访问数据由 oauth2 授权
Access Denied with Spring data rest with oauth2 authorization
我卡在这个授权问题上了,怎么办都无法转发。
如果我使用@PreAuthorize 保护我的资源,我会收到 access_denied 响应。
其余控制器:
@RestController
@RequestMapping("/api/users")
public class UserRestController {
private UserService userService;
@GetMapping
@PreAuthorize("hasRole('USER')")
public ResponseEntity<Collection<User>> findAll() {
return ResponseEntity.status(HttpStatus.OK).body(userService.findAll());
}
}
网络安全:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin().disable() // disable form authentication
.anonymous().disable() // disable anonymous user
.authorizeRequests().anyRequest().denyAll(); // denying all access
}
}
授权服务器配置:
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
@Autowired
private ApplicationConfigurationProperties configuration;
@Autowired
@Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
@Autowired
private UserDetailOath2Service userDetailsService;
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
.tokenStore(tokenStore())
.tokenServices(tokenServices())
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService);
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients
.inMemory()
.withClient(configuration.getClientId())
.secret(configuration.getClientSecret())
.scopes("read", "write")
.authorizedGrantTypes("client_credentials", "password", "refresh_token")
.resourceIds(RestApiResourceServerConfiguration.RESOURCE_ID);
}
@Bean
@Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
defaultTokenServices.setSupportRefreshToken(true);
defaultTokenServices.setTokenEnhancer(accessTokenConverter());
return defaultTokenServices;
}
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
final JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("abcd");
return converter;
}
}
资源服务器:
@Configuration
@EnableResourceServer
public class RestApiResourceServerConfiguration extends ResourceServerConfigurerAdapter {
public static final String RESOURCE_ID = "restservice";
@Autowired
private DefaultTokenServices tokenServices;
@Autowired
private TokenStore tokenStore;
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId(RESOURCE_ID)
.tokenServices(tokenServices)
.tokenStore(tokenStore);
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/api/**").authenticated()
.anyRequest().permitAll().and().csrf().disable();
}
}
用户详情服务:
@Service
public class UserDetailOath2Service implements UserDetailsService {
private final Logger LOGGER = Logger.getLogger(UserDetailOath2Service.class);
@Autowired
private UserRepository repository;
@Override
@Transactional(readOnly = true)
public UserDetails loadUserByUsername(String username) {
LOGGER.info("Entering in loadUserByUsername " + username);
final User user = repository.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("User not found");
}
final List<SimpleGrantedAuthority> authorities = user.getAuthorities().stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());
return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), authorities);
}
当我请求 http://localhost:8090/oauth/token 时,我收到了令牌:
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicmVzdHNlcnZpY2UiXSwidXNlcl9uYW1lIjoiYWxlcyIsInNjb3BlIjpbInJlYWQiLCJ3cml0ZSJdLCJleHAiOjE1NDgxMTExNjcsImF1dGhvcml0aWVzIjpbIlVTRVIiXSwianRpIjoiNGZjODNiOTktMjZiNC00NWZkLWIxMGQtZDgxMzAzZDM2MjM4IiwiY2xpZW50X2lkIjoiZGF0YXJlc3RjbGllbnQifQ.rZAB_LmKuAN6R7i-7dUvYv4Q6vr8LhTNKgPMDVufFTc",
"token_type": "bearer",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicmVzdHNlcnZpY2UiXSwidXNlcl9uYW1lIjoiYWxlcyIsInNjb3BlIjpbInJlYWQiLCJ3cml0ZSJdLCJhdGkiOiI0ZmM4M2I5OS0yNmI0LTQ1ZmQtYjEwZC1kODEzMDNkMzYyMzgiLCJleHAiOjE1NTA2NTk5NjcsImF1dGhvcml0aWVzIjpbIlVTRVIiXSwianRpIjoiNGNiYWYyZWUtOTFhOC00N2Q2LTllZmEtYzA4ODI1NTI5MmQ3IiwiY2xpZW50X2lkIjoiZGF0YXJlc3RjbGllbnQifQ.41tdJ3Qc4nodc4ZAOr6dhYOa8XTqBOFQc9X1yM7NrGE",
"expires_in": 43199,
"scope": "read write",
"jti": "4fc83b99-26b4-45fd-b10d-d81303d36238"
}
所以我拿到令牌并尝试调用受保护的资源:
GET /api/users HTTP/1.1
Host: localhost:8090
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicmVzdHNlcnZpY2UiXSwidXNlcl9uYW1lIjoiYWxlcyIsInNjb3BlIjpbInJlYWQiLCJ3cml0ZSJdLCJleHAiOjE1NDgxMTExNjcsImF1dGhvcml0aWVzIjpbIlVTRVIiXSwianRpIjoiNGZjODNiOTktMjZiNC00NWZkLWIxMGQtZDgxMzAzZDM2MjM4IiwiY2xpZW50X2lkIjoiZGF0YXJlc3RjbGllbnQifQ.rZAB_LmKuAN6R7i-7dUvYv4Q6vr8LhTNKgPMDVufFTc
结果是这样的:
{
"error": "access_denied",
"error_description": "Access is denied"
}
我生成令牌的用户具有 USER 角色。
拜托,有人可以帮我找出我做错了什么吗?
谢谢。
生成的令牌具有 USER authority,而不是 USER role。
角色和权限之间存在细微但重要的区别:https://www.baeldung.com/spring-security-granted-authority-vs-role
将@PreAuthorize("hasRole('USER')")
更改为@PreAuthorize("hasAuthority('USER')")
或授予权限ROLE_USER
以获得角色USER
您似乎没有为该角色提供资源访问权限。
public void configure(final HttpSecurity http) throws Exception {
// @formatter:off
http.csrf().disable().authorizeRequests()
// This is needed to enable swagger-ui interface.
.antMatchers("/swagger-ui.html","/swagger-resources/**","/webjars/**", "/v2/api-docs/**").permitAll()
.antMatchers("/api/v1/**").hasAuthority("ROLE_TRUSTED_CLIENT")
// @formatter:on
}
确保您没有覆盖其他 role/permission 的权限。所以配置像
public void configure(final HttpSecurity http) throws Exception {
// @formatter:off
http.csrf().disable().authorizeRequests()
// This is needed to enable swagger-ui interface.
.antMatchers("/swagger-ui.html","/swagger-resources/**","/webjars/**", "/v2/api-docs/**").permitAll()
.antMatchers("/api/v1/**").hasAuthority("ROLE_TRUSTED_CLIENT")
.antMatchers("/api/v1/**").hasAuthority("ROLE_USER");
// @formatter:on
}
会有问题。该权限现在仅授予 ROLE_USER 和 ROLE_TRUSTED_CLIENT。
要提供多个角色访问权限,请使用以下内容
http.csrf().disable().authorizeRequests()
.antMatchers("/swagger-ui.html","/swagger-resources/**","/webjars/**", "/v2/api-docs/**").permitAll()
.antMatchers("/api/v1/**").hasAnyAuthority("ROLE_TRUSTED_CLIENT", "ROLE_USER")
.anyRequest().authenticated();
我卡在这个授权问题上了,怎么办都无法转发。
如果我使用@PreAuthorize 保护我的资源,我会收到 access_denied 响应。
其余控制器:
@RestController
@RequestMapping("/api/users")
public class UserRestController {
private UserService userService;
@GetMapping
@PreAuthorize("hasRole('USER')")
public ResponseEntity<Collection<User>> findAll() {
return ResponseEntity.status(HttpStatus.OK).body(userService.findAll());
}
}
网络安全:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin().disable() // disable form authentication
.anonymous().disable() // disable anonymous user
.authorizeRequests().anyRequest().denyAll(); // denying all access
}
}
授权服务器配置:
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
@Autowired
private ApplicationConfigurationProperties configuration;
@Autowired
@Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
@Autowired
private UserDetailOath2Service userDetailsService;
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
.tokenStore(tokenStore())
.tokenServices(tokenServices())
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService);
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients
.inMemory()
.withClient(configuration.getClientId())
.secret(configuration.getClientSecret())
.scopes("read", "write")
.authorizedGrantTypes("client_credentials", "password", "refresh_token")
.resourceIds(RestApiResourceServerConfiguration.RESOURCE_ID);
}
@Bean
@Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
defaultTokenServices.setSupportRefreshToken(true);
defaultTokenServices.setTokenEnhancer(accessTokenConverter());
return defaultTokenServices;
}
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
final JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("abcd");
return converter;
}
}
资源服务器:
@Configuration
@EnableResourceServer
public class RestApiResourceServerConfiguration extends ResourceServerConfigurerAdapter {
public static final String RESOURCE_ID = "restservice";
@Autowired
private DefaultTokenServices tokenServices;
@Autowired
private TokenStore tokenStore;
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId(RESOURCE_ID)
.tokenServices(tokenServices)
.tokenStore(tokenStore);
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/api/**").authenticated()
.anyRequest().permitAll().and().csrf().disable();
}
}
用户详情服务:
@Service
public class UserDetailOath2Service implements UserDetailsService {
private final Logger LOGGER = Logger.getLogger(UserDetailOath2Service.class);
@Autowired
private UserRepository repository;
@Override
@Transactional(readOnly = true)
public UserDetails loadUserByUsername(String username) {
LOGGER.info("Entering in loadUserByUsername " + username);
final User user = repository.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("User not found");
}
final List<SimpleGrantedAuthority> authorities = user.getAuthorities().stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());
return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), authorities);
}
当我请求 http://localhost:8090/oauth/token 时,我收到了令牌:
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicmVzdHNlcnZpY2UiXSwidXNlcl9uYW1lIjoiYWxlcyIsInNjb3BlIjpbInJlYWQiLCJ3cml0ZSJdLCJleHAiOjE1NDgxMTExNjcsImF1dGhvcml0aWVzIjpbIlVTRVIiXSwianRpIjoiNGZjODNiOTktMjZiNC00NWZkLWIxMGQtZDgxMzAzZDM2MjM4IiwiY2xpZW50X2lkIjoiZGF0YXJlc3RjbGllbnQifQ.rZAB_LmKuAN6R7i-7dUvYv4Q6vr8LhTNKgPMDVufFTc",
"token_type": "bearer",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicmVzdHNlcnZpY2UiXSwidXNlcl9uYW1lIjoiYWxlcyIsInNjb3BlIjpbInJlYWQiLCJ3cml0ZSJdLCJhdGkiOiI0ZmM4M2I5OS0yNmI0LTQ1ZmQtYjEwZC1kODEzMDNkMzYyMzgiLCJleHAiOjE1NTA2NTk5NjcsImF1dGhvcml0aWVzIjpbIlVTRVIiXSwianRpIjoiNGNiYWYyZWUtOTFhOC00N2Q2LTllZmEtYzA4ODI1NTI5MmQ3IiwiY2xpZW50X2lkIjoiZGF0YXJlc3RjbGllbnQifQ.41tdJ3Qc4nodc4ZAOr6dhYOa8XTqBOFQc9X1yM7NrGE",
"expires_in": 43199,
"scope": "read write",
"jti": "4fc83b99-26b4-45fd-b10d-d81303d36238"
}
所以我拿到令牌并尝试调用受保护的资源:
GET /api/users HTTP/1.1
Host: localhost:8090
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicmVzdHNlcnZpY2UiXSwidXNlcl9uYW1lIjoiYWxlcyIsInNjb3BlIjpbInJlYWQiLCJ3cml0ZSJdLCJleHAiOjE1NDgxMTExNjcsImF1dGhvcml0aWVzIjpbIlVTRVIiXSwianRpIjoiNGZjODNiOTktMjZiNC00NWZkLWIxMGQtZDgxMzAzZDM2MjM4IiwiY2xpZW50X2lkIjoiZGF0YXJlc3RjbGllbnQifQ.rZAB_LmKuAN6R7i-7dUvYv4Q6vr8LhTNKgPMDVufFTc
结果是这样的:
{
"error": "access_denied",
"error_description": "Access is denied"
}
我生成令牌的用户具有 USER 角色。
拜托,有人可以帮我找出我做错了什么吗?
谢谢。
生成的令牌具有 USER authority,而不是 USER role。 角色和权限之间存在细微但重要的区别:https://www.baeldung.com/spring-security-granted-authority-vs-role
将@PreAuthorize("hasRole('USER')")
更改为@PreAuthorize("hasAuthority('USER')")
或授予权限ROLE_USER
以获得角色USER
您似乎没有为该角色提供资源访问权限。
public void configure(final HttpSecurity http) throws Exception {
// @formatter:off
http.csrf().disable().authorizeRequests()
// This is needed to enable swagger-ui interface.
.antMatchers("/swagger-ui.html","/swagger-resources/**","/webjars/**", "/v2/api-docs/**").permitAll()
.antMatchers("/api/v1/**").hasAuthority("ROLE_TRUSTED_CLIENT")
// @formatter:on
}
确保您没有覆盖其他 role/permission 的权限。所以配置像
public void configure(final HttpSecurity http) throws Exception {
// @formatter:off
http.csrf().disable().authorizeRequests()
// This is needed to enable swagger-ui interface.
.antMatchers("/swagger-ui.html","/swagger-resources/**","/webjars/**", "/v2/api-docs/**").permitAll()
.antMatchers("/api/v1/**").hasAuthority("ROLE_TRUSTED_CLIENT")
.antMatchers("/api/v1/**").hasAuthority("ROLE_USER");
// @formatter:on
}
会有问题。该权限现在仅授予 ROLE_USER 和 ROLE_TRUSTED_CLIENT。
要提供多个角色访问权限,请使用以下内容
http.csrf().disable().authorizeRequests()
.antMatchers("/swagger-ui.html","/swagger-resources/**","/webjars/**", "/v2/api-docs/**").permitAll()
.antMatchers("/api/v1/**").hasAnyAuthority("ROLE_TRUSTED_CLIENT", "ROLE_USER")
.anyRequest().authenticated();