Java 对同一资源使用 Oauth2 或 Http-Basic 身份验证的 Spring 安全性
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/23526644/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Spring security with Oauth2 or Http-Basic authentication for the same resource
提问by user3613594
I'm attempting to implement an API with resources that are protected by either Oauth2 OR Http-Basic authentication.
我正在尝试使用受 Oauth2 或 Http-Basic 身份验证保护的资源来实现 API。
When I load the WebSecurityConfigurerAdapter which applies http-basic authentication to the resource first, Oauth2 token authentication is not accepted. And vice-versa.
当我加载首先将 http-basic 身份验证应用于资源的 WebSecurityConfigurerAdapter 时,不接受 Oauth2 令牌身份验证。反之亦然。
Example configurations:This applies http-basic authentication to all /user/** resources
示例配置:这将 http-basic 身份验证应用于所有 /user/** 资源
@Configuration
@EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private LoginApi loginApi;
@Autowired
public void setLoginApi(LoginApi loginApi) {
this.loginApi = loginApi;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(new PortalUserAuthenticationProvider(loginApi));
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/users/**").authenticated()
.and()
.httpBasic();
}
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
This applies oauth token protection to the /user/** resource
这将 oauth 令牌保护应用于 /user/** 资源
@Configuration
@EnableResourceServer
protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http
.requestMatchers().antMatchers("/users/**")
.and()
.authorizeRequests()
.antMatchers("/users/**").access("#oauth2.clientHasRole('ROLE_CLIENT') and #oauth2.hasScope('read')");
}
}
I'm sure there is some piece of magic code I'm missing which tells spring to attempt both if the first has failed?
我确定我遗漏了一些神奇的代码,它告诉 spring 如果第一个失败则尝试两者?
Any assistance would be most appreciated.
任何帮助将不胜感激。
回答by raonirenosto
I believe that is not possible to have both authentications. You can have basic authentication and oauth2 authentication, but for distinct endpoints. The way as you did, the first configuration will overcome the second, in this case, http basic will be used.
我相信不可能同时进行两种身份验证。您可以进行基本身份验证和 oauth2 身份验证,但适用于不同的端点。按照您的方式,第一个配置将克服第二个配置,在这种情况下,将使用 http basic。
回答by zeldigas
Can't provide you with complete example, but here's a hints to dig:
无法为您提供完整的示例,但这里有一个挖掘提示:
Roughly, spring auth is just a combination of request filter that extract auth data from request (headers) and authentication manager that provides authentication object for that auth.
粗略地说,spring auth 只是从请求(标头)中提取身份验证数据的请求过滤器和为该身份验证提供身份验证对象的身份验证管理器的组合。
So to get basic and oauth at the same url, you need 2 filters installed in filter chain BasicAuthenticationFilter and OAuth2AuthenticationProcessingFilter.
因此,要在同一 url 中获取基本和 oauth,您需要在过滤器链 BasicAuthenticationFilter 和 OAuth2AuthenticationProcessingFilter 中安装 2 个过滤器。
I think the problem is that ConfiguringAdapters good for more simple confs as they tend to override each other. So as a first step try to move
我认为问题在于,ConfiguringAdapters 适用于更简单的 confs,因为它们往往会相互覆盖。所以作为第一步尝试移动
.httpBasic();
call to ResourceServerConfiguration
Note that you also need to provide 2 different auth managers: one for basic auth and one for oauth
请ResourceServerConfiguration
注意,您还需要提供 2 个不同的身份验证管理器:一个用于基本身份验证,一个用于 oauth
回答by Michael Ressler
This may be close to what you were looking for:
这可能与您要查找的内容相近:
@Override
public void configure(HttpSecurity http) throws Exception {
http.requestMatcher(new OAuthRequestedMatcher())
.authorizeRequests()
.anyRequest().authenticated();
}
private static class OAuthRequestedMatcher implements RequestMatcher {
@Override
public boolean matches(HttpServletRequest request) {
String auth = request.getHeader("Authorization");
// Determine if the client request contained an OAuth Authorization
return (auth != null) && auth.startsWith("Bearer");
}
}
The only thing this doesn't provide is a way to "fall back" if the authentication isn't successful.
如果身份验证不成功,它唯一没有提供的是一种“回退”的方法。
To me, this approach makes sense. If a User is directly providing authentication to the request via Basic auth, then OAuth is not necessary. If the Client is the one acting, then we need this filter to step in and make sure the request is properly authenticated.
对我来说,这种方法是有道理的。如果用户通过基本身份验证直接向请求提供身份验证,则不需要 OAuth。如果客户端是执行者,那么我们需要这个过滤器介入并确保请求得到正确的身份验证。
回答by kca2ply
I managed to get this work based on the hints by Michael Ressler's answer but with some tweaks.
我设法根据 Michael Ressler 的回答的提示完成了这项工作,但做了一些调整。
My goal was to allow both Basic Auth and Oauth on the same resource endpoints, e.g., /leafcase/123. I was trapped for quite some time due to the ordering of the filterChains (can be inspected in FilterChainProxy.filterChains); the default order is as follows:
我的目标是在相同的资源端点上允许 Basic Auth 和 Oauth,例如 /leafcase/123。由于 filterChains 的排序,我被困了很长一段时间(可以在 FilterChainProxy.filterChains 中检查);默认顺序如下:
- Oauth authentication server (if enabled in the same project)'s filterChains. default order 0 (see AuthorizationServerSecurityConfiguration)
- Oauth resource server's filterChains. default order 3 (see ResourceServerConfiguration). It has a request matcher logic that matches anything other than the Oauth authentication endpoints (e.g., /oauth/token, /oauth/authorize, etc. See ResourceServerConfiguration$NotOauthRequestMatcher.matches()).
- The filterChains that corresponds to config(HttpSecurity http) - default order 100, see WebSecurityConfigurerAdapter.
- Oauth 认证服务器(如果在同一个项目中启用)的 filterChains。默认顺序 0(请参阅 AuthorizationServerSecurityConfiguration)
- Oauth 资源服务器的 filterChains。默认顺序 3(请参阅 ResourceServerConfiguration)。它有一个请求匹配器逻辑,可以匹配 Oauth 身份验证端点以外的任何内容(例如,/oauth/token、/oauth/authorize 等。请参阅 ResourceServerConfiguration$NotOauthRequestMatcher.matches())。
- 与 config(HttpSecurity http) 对应的 filterChains - 默认顺序为 100,参见 WebSecurityConfigurerAdapter。
Since resource server's filterChains ranks higher than the one by WebSecurityConfigurerAdapter configured filterchain, and the former matches practically every resource endpoint, then Oauth resource server logic always kick in for any request to resource endpoints (even if the request uses the Authorization:Basic header). The error you would get is:
由于资源服务器的过滤器链排名高于 WebSecurityConfigurerAdapter 配置的过滤器链,而前者几乎匹配每个资源端点,因此 Oauth 资源服务器逻辑始终会启动对资源端点的任何请求(即使请求使用 Authorization:Basic 标头)。你会得到的错误是:
{
"error": "unauthorized",
"error_description": "Full authentication is required to access this resource"
}
I made 2 changes to get this work:
我进行了 2 处更改以完成这项工作:
Firstly, order the WebSecurityConfigurerAdapter higher than the resource server (order 2 is higher than order 3).
首先,将 WebSecurityConfigurerAdapter 排序高于资源服务器(2 阶高于 3 阶)。
@Configuration
@Order(2)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
Secondly, let configure(HttpSecurity) use a customer RequestMatcher that only matches "Authorization: Basic".
其次,让 configure(HttpSecurity) 使用只匹配“Authorization: Basic”的客户 RequestMatcher。
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.anonymous().disable()
.requestMatcher(new BasicRequestMatcher())
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic()
.authenticationEntryPoint(oAuth2AuthenticationEntryPoint())
.and()
// ... other stuff
}
...
private static class BasicRequestMatcher implements RequestMatcher {
@Override
public boolean matches(HttpServletRequest request) {
String auth = request.getHeader("Authorization");
return (auth != null && auth.startsWith("Basic"));
}
}
As a result it matches and handles a Basic Auth resource request before the resource server's filterChain has a chance to match it. It also ONLY handles Authorizaiton:Basic resource request, thus any requests with Authorization:Bearer will fall through, and then handled by resource server's filterChain (i.e., Oauth's filter kicks in). Also, it ranks lower than the AuthenticationServer (in case AuthenticationServer is enabled on the same project), so it doesn't prevent AuthenticaitonServer's filterchain from handling the request to /oauth/token, etc.
因此,它会在资源服务器的 filterChain 有机会匹配它之前匹配并处理 Basic Auth 资源请求。它还只处理 Authorizaiton:Basic 资源请求,因此任何带有 Authorization:Bearer 的请求都会失败,然后由资源服务器的 filterChain 处理(即 Oauth 的过滤器启动)。此外,它的排名低于 AuthenticationServer(如果 AuthenticationServer 在同一项目上启用),因此它不会阻止 AuthenticaitonServer 的过滤器链处理对 /oauth/token 等的请求。
回答by Ryan J. McDonough
The solution @kca2ply provided works very well. I noticed the browser wasn't issuing a challenge so I tweaked the code a little to the following:
@kca2ply 提供的解决方案效果很好。我注意到浏览器没有发出挑战,因此我将代码稍微调整为以下内容:
@Configuration
@Order(2)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http.anonymous().disable()
.requestMatcher(request -> {
String auth = request.getHeader(HttpHeaders.AUTHORIZATION);
return (auth != null && auth.startsWith("Basic"));
})
.antMatcher("/**")
.authorizeRequests().anyRequest().authenticated()
.and()
.httpBasic();
// @formatter:on
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user").password("password").roles("USER");
}
}
Using both requestMatcher()
and antMatcher()
made things work perfectly. Browsers and HTTP clients will now challenge for basic creds first if not provided already. If no credentials are provided, it falls through to OAuth2.
同时使用requestMatcher()
和antMatcher()
制造的东西很好地工作。如果尚未提供,浏览器和 HTTP 客户端现在将首先挑战基本凭据。如果未提供凭据,则属于 OAuth2。
回答by httPants
You can add a BasicAuthenticationFilter to the security filter chain to get OAuth2 OR Basic authentication security on a protected resource. Example config is below...
您可以向安全过滤器链添加 BasicAuthenticationFilter 以在受保护资源上获得 OAuth2 或基本身份验证安全性。示例配置如下...
@Configuration
@EnableResourceServer
public class OAuth2ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManagerBean;
@Override
public void configure(HttpSecurity http) throws Exception {
// @formatter:off
final String[] userEndpoints = {
"/v1/api/airline"
};
final String[] adminEndpoints = {
"/v1/api/jobs**"
};
http
.requestMatchers()
.antMatchers(userEndpoints)
.antMatchers(adminEndpoints)
.antMatchers("/secure/**")
.and()
.authorizeRequests()
.antMatchers("/secure/**").authenticated()
.antMatchers(userEndpoints).hasRole("USER")
.antMatchers(adminEndpoints).hasRole("ADMIN");
// @formatter:on
http.addFilterBefore(new BasicAuthenticationFilter(authenticationManagerBean),
UsernamePasswordAuthenticationFilter.class);
}
}
回答by kboom
And why not doing this the other way round? Just bypass resource server if there is no token attached, then fallback to normal security filter chain. This is by the way what resource server filter is stopping on.
为什么不反过来呢?如果没有附加令牌,只需绕过资源服务器,然后回退到正常的安全过滤器链。这是资源服务器过滤器停止的方式。
@Configuration
@EnableResourceServer
class ResourceServerConfig : ResourceServerConfigurerAdapter() {
@Throws(Exception::class)
override fun configure(resources: ResourceServerSecurityConfigurer) {
resources.resourceId("aaa")
}
/**
* Resources exposed via oauth. As we are providing also local user interface they are also accessible from within.
*/
@Throws(Exception::class)
override fun configure(http: HttpSecurity) {
http.requestMatcher(BearerAuthorizationHeaderMatcher())
.authorizeRequests()
.anyRequest()
.authenticated()
}
private class BearerAuthorizationHeaderMatcher : RequestMatcher {
override fun matches(request: HttpServletRequest): Boolean {
val auth = request.getHeader("Authorization")
return auth != null && auth.startsWith("Bearer")
}
}
}