java Spring Secuirty Oauth 2 - 多用户认证服务

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/27917004/
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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-02 12:37:09  来源:igfitidea点击:

Spring Secuirty Oauth 2 - multiple user authentication services

javaspringoauthspring-securityspring-security-oauth2

提问by Konrad

My application provides oauth2 token service identical to this one provided in the following github project: https://github.com/iainporter/oauth2-provider

我的应用程序提供的 oauth2 令牌服务与以下 github 项目中提供的服务相同:https: //github.com/iainporter/oauth2-provider

It is based on Spring Security OAuth2.

它基于 Spring Security OAuth2。

I provided my custom implementation of UserDetailsService:

我提供了 UserDetailsS​​ervice 的自定义实现:

<bean id="userService" class="org.example.core.service.DBUserServiceImpl" />

and the following user authentication manager:

以及以下用户身份验证管理器:

<sec:authentication-manager alias="userAuthenticationManager">
    <sec:authentication-provider user-service-ref="userService">
        <sec:password-encoder ref="passwordEncoder" />
    </sec:authentication-provider>
</sec:authentication-manager>

Now I would like to provide other method of user authentication (other UserDetailsService), for example:

现在我想提供其他的用户认证方法(other UserDetailsS​​ervice),例如:

<bean id="otherUserService" class="org.example.core.service.LDAPUserServiceImpl" />

Unfortunately I didn't find a way how to do it in documentation. On the request level I would like to distinguish which method (which user service) to use by either:

不幸的是,我没有找到如何在文档中做到这一点的方法。在请求级别,我想区分使用哪种方法(哪个用户服务):

  • query parameter
  • http header (e.g. RealmName)
  • 查询参数
  • http 标头(例如 RealmName)

回答by Mithun

You need to use DelegatingAuthenticationEntryPointto configure multiple entry points. Which means you can have multiple ways of authenticating. Following is the sample code:

您需要使用DelegatingAuthenticationEntryPoint来配置多个入口点。这意味着您可以通过多种方式进行身份验证。以下是示例代码:

DBUser entry point:

DBUser 入口点:

public class DBUserAuthencticationEntryPoint extends BasicAuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest request,
            HttpServletResponse response, AuthenticationException authException)
            throws IOException, ServletException {
        super.commence(request, response, authException);
    }
}

LDAP entry point:

LDAP 入口点:

public class LDAPAuthencticationEntryPoint extends BasicAuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest request,
            HttpServletResponse response, AuthenticationException authException)
            throws IOException, ServletException {
         super.commence(request, response, authException);
    }
}

Then you need to create RequestMatchers to pick the correct entry point (based on header/realm name):

然后你需要创建RequestMatchers 来选择正确的入口点(基于标题/领域名称):

DBUser request matcher:

DBUser 请求匹配器:

RequestMatcher dbUserMatcher = new RequestMatcher() {       
    @Override
    public boolean matches(HttpServletRequest request) {
        // Logic to identify a DBUser kind of reqeust
    }
};

LDAP user requset matcher:

LDAP 用户请求匹配器:

RequestMatcher ldapMatcher = new RequestMatcher() {     
    @Override
    public boolean matches(HttpServletRequest request) {
        // Logic to identify a LDAP kind of reqeust
    }
};

Now we need to add these matchers and entry points to DelegatingAuthenticationEntryPoint. In runtime DelegatingAuthenticationEntryPointpicks up the entry point and does the authentication based on the matcher which return true.

现在我们需要将这些匹配器和入口点添加到DelegatingAuthenticationEntryPoint. 在运行时DelegatingAuthenticationEntryPoint选取入口点并根据返回的匹配器进行身份验证true

DBUserAuthencticationEntryPoint dbUserEntryPoint = new DBUserAuthencticationEntryPoint();
LDAPAuthencticationEntryPoint ldapEntryPoint  = new LDAPAuthencticationEntryPoint();

LinkedHashMap<RequestMatcher,AuthenticationEntryPoint> entryPoints = new LinkedHashMap<RequestMatcher,AuthenticationEntryPoint>();
entryPoints.put(ldapMatcher, ldapEntryPoint);
entryPoints.put(dbUserMatcher, dbUserEntryPoint);

DelegatingAuthenticationEntryPoint delegatingAuthenticationEntryPoint  = new DelegatingAuthenticationEntryPoint(entryPoints);

Now map the DelegatingAuthenticationEntryPointto HttpSecurityin the configure()method:

现在映射DelegatingAuthenticationEntryPointHttpSecurityconfigure()方法:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.
            authorizeRequests().
                regexMatchers("/login.*").permitAll().
                regexMatchers("/api.*").fullyAuthenticated().        
        and().
            formLogin().loginPage("/login").
        and().
            exceptionHandling().authenticationEntryPoint(delegatingAuthenticationEntryPoint);
    }
}

Configure the provider manager:

配置提供者管理器:

@Bean
public AuthenticationManager authenticationManager() {
    return new ProviderManager(Arrays.asList(provider1, provider2);
}

回答by Konrad

I found different solution than solution provided by Mithun.

我找到了与 Mithun 提供的解决方案不同的解决方案。

Application context contains user authentication manager initiated with different authentication providers:

应用程序上下文包含使用不同身份验证提供程序启动的用户身份验证管理器:

<sec:authentication-manager alias="userAuthenticationManager">
    <sec:authentication-provider ref="customerAuthProvider" />
    <sec:authentication-provider ref="adminAuthProvider" />
</sec:authentication-manager>

where customerAuthProvider and adminAuthProvider are extensions of DaoAuthenticationProvider with different userDetails Service:

其中 customerAuthProvider 和 adminAuthProvider 是 DaoAuthenticationProvider 的扩展,具有不同的 userDetails 服务:

<bean id="customerAuthProvider" class="org.example.security.authentication.provider.CustomerAuthenticationProvider">
    <property name="userDetailsService" ref="userService" />
    <property name="passwordEncoder" ref="passwordEncoder" />
</bean>

<bean id="adminAuthProvider" class="org.example.security.authentication.provider.AdminAuthenticationProvider">
    <property name="userDetailsService" ref="otherUserService" />
</bean>

All you need to do is to override "supports" method that indicates whether current authentication provider is able to handle specific authentication:

您需要做的就是覆盖指示当前身份验证提供程序是否能够处理特定身份验证的“支持”方法:

public class CustomerAuthenticationProvider extends DaoAuthenticationProvider {

    @Override
    public boolean supports ( Class<?> authentication ) {
        return CustomerUsernamePasswordAuthenticationToken.isAssignableFrom(authentication);
    }
}

public class AdminAuthenticationProvider extends DaoAuthenticationProvider {

    @Override
    public boolean supports ( Class<?> authentication ) {
        return AdminUsernamePasswordAuthenticationToken.isAssignableFrom(authentication);
    }
}

At the end you need to extend token granter. In my case I extended ResourceOwnerPasswordTokenGranter which means that it supports "password" grant:

最后,您需要扩展令牌授予者。就我而言,我扩展了 ResourceOwnerPasswordTokenGranter,这意味着它支持“密码”授予:

<oauth:authorization-server client-details-service-ref="client-details-service" token-services-ref="tokenServices">
    <oauth:refresh-token/>
    <oauth:custom-grant token-granter-ref="customPasswordTokenGranter"/>
</oauth:authorization-server>

You can use TokenRequest object to distinguish which Authentication class to instantiate (AdminUsernamePasswordAuthenticationToken or CustomerUsernamePasswordAuthenticationToken)

您可以使用 TokenRequest 对象来区分要实例化的 Authentication 类(AdminUsernamePasswordAuthenticationToken 或 CustomerUsernamePasswordAuthenticationToken)

public class CustomResourceOwnerPasswordTokenGranter extends ResourceOwnerPasswordTokenGranter {

    protected OAuth2Authentication getOAuth2Authentication ( ClientDetails client, TokenRequest tokenRequest ) {
        Map parameters = tokenRequest.getRequestParameters();
        String username = (String) parameters.get("username");
        String password = (String) parameters.get("password");

        String realmName = (String) parameters.get("realm_name");

        Authentication userAuth = createAuthentication(username, password, realmName);
        try {
            userAuth = this.authenticationManager.authenticate(userAuth);
        } catch ( AccountStatusException ase ) {
            throw new InvalidGrantException(ase.getMessage());
        } catch ( BadCredentialsException e ) {
            throw new InvalidGrantException(e.getMessage());
        }
        if ( ( userAuth == null ) || ( ! ( userAuth.isAuthenticated() ) ) ) {
            throw new InvalidGrantException("Could not authenticate user: " + username);
        }

        OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);
        return new OAuth2Authentication(storedOAuth2Request, userAuth);
    }

    private Authentication createAuthentication ( String username, String password, String realmName ) throws InvalidGrantException {
       // TODO: decide basing on realm name
    }
}