简体   繁体   English

如何限制到特定域以使用 Spring-Boot 和 OAuth2 登录

[英]How to restrict to a specific domain to login with Spring-Boot and OAuth2

I have successfully done a OAuth2 login with spring boot and Google, but I'd like to restrict logins to a specific domain (we're using Google Apps for Work).我已经使用 spring boot 和 Google 成功完成了 OAuth2 登录,但我想将登录限制在特定域(我们使用的是 Google Apps for Work)。

I think that I should handle by extending class OAuth2ClientAuthenticationProcessingFilter (as specified in this thread ), but I'm not sure how to do that.我认为我应该通过扩展类 OAuth2ClientAuthenticationProcessingFilter (如本线程中指定的那样)来处理,但我不确定如何做到这一点。

Basically, I'd like to use Google OAuth 2.0 as the identity provider, but only company users (@company.com) must be accepted.基本上,我想使用 Google OAuth 2.0 作为身份提供者,但必须只接受公司用户 (@company.com)。

According to Stéphane suggestion, I came to this tutorial , and finally implemented this, which works for me with a Google+ profile:根据 Stéphane 的建议,我来到了本教程,并最终实现了这一点,这对我来说适用于 Google+ 个人资料:

@Configuration
@EnableOAuth2Sso
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    private static final String GOOGLE_PLUS_DOMAIN_ATTRIBUTE = "domain";
    private static final String CSRF_COOKIE_NAME = "XSRF-TOKEN";
    private static final String CSRF_HEADER_NAME = "X-XSRF-TOKEN";

    @Bean
    public AuthoritiesExtractor authoritiesExtractor(
            @Value("#{'${security.allowed-domains}'.split(',')}") final List<String> allowedDomains) {

        return new AuthoritiesExtractor() {
            @Override
            public List<GrantedAuthority> extractAuthorities(final Map<String, Object> map) {
                if (map != null && map.containsKey(GOOGLE_PLUS_DOMAIN_ATTRIBUTE)) {
                    final String domain = (String) map.get(GOOGLE_PLUS_DOMAIN_ATTRIBUTE);
                    if (!allowedDomains.contains(domain)) {
                        throw new BadCredentialsException("Not an allowed domain");
                    }
                    return AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER");
                }
                return null;
            }
        };
    }

    @Override
    protected void configure(final HttpSecurity http) throws Exception {
        // @formatter:off
        http.antMatcher("/**")
        .authorizeRequests()
        .antMatchers("/logout", "/api/mappings/**", "/public/**").permitAll()
        .anyRequest().hasAuthority("ROLE_USER")
        .and().logout().logoutUrl("/api/logout").logoutSuccessUrl("/logout")
        .and().csrf().csrfTokenRepository(csrfTokenRepository()).ignoringAntMatchers("/api/mappings/**")
        .and().addFilterAfter(csrfHeaderFilter(), CsrfFilter.class);
        // @formatter:on
    }

    private Filter csrfHeaderFilter() {
        return new OncePerRequestFilter() {
            @Override
            protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response,
                    final FilterChain filterChain) throws ServletException, IOException {

                final CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
                if (csrf != null) {
                    Cookie cookie = WebUtils.getCookie(request, CSRF_COOKIE_NAME);
                    final String token = csrf.getToken();
                    if (cookie == null || token != null && !token.equals(cookie.getValue())) {
                        cookie = new Cookie(CSRF_COOKIE_NAME, token);
                        cookie.setPath("/");
                        response.addCookie(cookie);
                    }
                }
                filterChain.doFilter(request, response);
            }
        };
    }

    private CsrfTokenRepository csrfTokenRepository() {
        final HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
        repository.setHeaderName(CSRF_HEADER_NAME);
        return repository;
    }
}

My application.yml file contains the following entries regarding oauth:我的 application.yml 文件包含以下有关 oauth 的条目:

security:
     oauth2:
         client:
                access-token-uri: https://www.googleapis.com/oauth2/v3/token
                user-authorization-uri: https://accounts.google.com/o/oauth2/auth
                client-authentication-scheme: form
                scope: profile,email
         resource:
                user-info-uri: https://www.googleapis.com/plus/v1/people/me
                prefer-token-info: false

When working with a Google+ profile, the resource server response provided in the map, contains an entry for domain.使用 Google+ 个人资料时,地图中提供的资源服务器响应包含域条目。 I just compared this value with configured allowed domains.我只是将这个值与配置的允许域进行了比较。

Hope this helps.希望这会有所帮助。

Update: On March 7th 2019, Google is deprecating Google+ APIs.更新:2019 年 3 月 7 日,Google 将弃用 Google+ API。 If you're like me, you'll have received an email from Google suggesting to update your software.如果您像我一样,就会收到一封来自 Google 的电子邮件,建议您更新您的软件。 In our case, the url https://www.googleapis.com/plus/v1/people/me , will be deprecated.在我们的例子中,网址https://www.googleapis.com/plus/v1/people/me将被弃用。 So, I'm posting here my updated configuration (build with Spring Boot 1.3.5).所以,我在这里发布我更新的配置(使用 Spring Boot 1.3.5 构建)。

security:
 oauth2:
     client:
            clientId: *your client id from Google*
            clientSecret: *your client secret from Google*                
            accessTokenUri: https://www.googleapis.com/oauth2/v4/token
            userAuthorizationUri: https://accounts.google.com/o/oauth2/v2/auth
            clientAuthenticationScheme: form
            scope: 
              - email
              - profile                
     resource:
            userInfoUri: https://www.googleapis.com/oauth2/v3/userinfo
            preferTokenInfo: false

 # Comma-separated list of domains                
 allowed-domains: *your allowed domains*

Please note that minor change must be done in you WebSecurityConfigurerAdapter as the attribute domain has changed its name.请注意,必须在您的 WebSecurityConfigurerAdapter 中进行细微更改,因为属性域已更改其名称。 So you'll need to replace the line:因此,您需要替换该行:

private static final String GOOGLE_PLUS_DOMAIN_ATTRIBUTE = "domain";私有静态最终字符串GOOGLE_PLUS_DOMAIN_ATTRIBUTE =“域”;

with

private static final String HOSTED_DOMAIN_ATTRIBUTE = "hd"; private static final String HOSTED_DOMAIN_ATTRIBUTE = "hd";

I found new solution!我找到了新的解决方案! You should choose one: non-reactive or reactive case it's important don't forget:您应该选择一个:非反应式反应式情况,重要的是不要忘记:

non-reactive:非反应性:

@Bean
fun oauth2UserService(rest: WebClient): OAuth2UserService<OAuth2UserRequest, OAuth2User> {
    val delegate = DefaultOAuth2UserService()
    return OAuth2UserService<OAuth2UserRequest, OAuth2User> { request ->
        val user = delegate.loadUser(request)
        val hd = user.getAttribute<String>("hd")
        if (hd == "your.domain.name")
            user
        else
            throw OAuth2AuthenticationException(OAuth2Error("invalid_token", "Not in the Team", ""))
    }
}

reactive:反应式:

@Bean
fun oauth2UserService(rest: WebClient): ReactiveOAuth2UserService<OAuth2UserRequest, OAuth2User> {
    val delegate = DefaultOAuth2UserService()
    return ReactiveOAuth2UserService<OAuth2UserRequest, OAuth2User> { request ->
        val user = delegate.loadUser(request)
        val hd = user.getAttribute<String>("hd")
        if (hd == "your.domain.name")
            Mono.just(user)
        else
            throw OAuth2AuthenticationException(OAuth2Error("invalid_token", "Not in the Team", ""))
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM