具有 Office 365 身份验证的 Spring Boot 应用程序

标签 spring azure spring-boot azure-active-directory azure-ad-msal

我正在开发一个使用 Office 365 进行身份验证的应用程序(后端为 Spring Boot,前端为 React)。但我想使用我自己的用户组和权限。例如,当用户第一次访问/api/auth 时,我想从 microsoft graph 检索信息并将其保存到我的数据库,然后使用我自己的角色/权限保护我的端点。

到目前为止我成功做到了:

  1. 当我转到 localhost:8080(返回)时,我将重定向到 Azure 门户进行身份验证。然后,我可以访问我的端点
  2. 我可以通过自己的角色保护我的端点
  3. 当我转到 localhost:3000(react 应用程序)时,我有一个按钮可以将我重定向到门户并为我提供一个 azure 访问 token (感谢 MSAL.js)

所以我的问题是我无法在后端验证这个 azure token 并从后到前发送新 token 来发送请求(例如 GET/api/users 或 POST/api/todos)。我认为我的后端配置和实现是错误的,但我没有找到验证 token 并为后端返回 token 的方法...

我希望我说清楚了,英语不是我的母语

这是我的 WebSecurityConfig

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests().antMatchers("/**")
                .authenticated()
                .and()
                .csrf().csrfTokenRepository(csrfTokenRepository()).and()
                .addFilterAfter(csrfHeaderFilter(), CsrfFilter.class)
                .oauth2Login();

    }

    private Filter csrfHeaderFilter() {
        return new OncePerRequestFilter() {
            @Override
            protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
                CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
                if (csrf != null) {
                    Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN");
                    String token = csrf.getToken();
                    if (cookie == null || token != null && !token.equals(cookie.getValue())) {
                        cookie = new Cookie("XSRF-TOKEN", token);
                        cookie.setPath("/");
                        response.addCookie(cookie);
                    }
                }
                filterChain.doFilter(request, response);
            }
        };
    }

    @Bean
    public HttpFirewall allowUrlEncodedSlashHttpFirewall() {
        StrictHttpFirewall firewall = new StrictHttpFirewall();
        firewall.setAllowUrlEncodedSlash(true);
        return firewall;
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        super.configure(web);
        web.httpFirewall(allowUrlEncodedSlashHttpFirewall());
    }

    private CsrfTokenRepository csrfTokenRepository() {
        HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
        repository.setHeaderName("X-XSRF-TOKEN");
        return repository;
    }
}

以下是我保护端点的方法:

@PreAuthorize("hasPermission(#foo, 'write')")

session :

@Configuration
public class AclPermissionEvaluator implements PermissionEvaluator {
    @Autowired
    private RoleRepository roleRepository;

    @Autowired
    private SecurityService securityService;

    @Override
    public boolean hasPermission(final Authentication authentication, final Object privilegeName, final Object privilegeType) {
        DefaultOidcUser principal = (DefaultOidcUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        Map<String, Object> userAttributes = principal.getAttributes();
        UserInfo userInfo = securityService.getUserInfoByLogin((String) userAttributes.get("unique_name"));
        if (userInfo == null || StringUtils.isBlank((CharSequence) privilegeName) || StringUtils.isBlank((CharSequence) privilegeType)) {
            return false;
        }

        for (Permission permission : roleRepository.getByName(userInfo.getRoleName()).getPermissions()) {
            if (permission.getPermission().startsWith((String) privilegeName) || permission.getPermission().equals("*")) {
                return true;
            }
        }

        return false;
    }

    //We don't need an implementation of this function for now
    @Override
    public boolean hasPermission(final Authentication authentication, final Serializable serializable, final String s, final Object o) {
        return false;
    }

}

安全服务:

@Service
@Transactional
public class SecurityServiceImpl implements SecurityService {
  @Autowired private UserRepository userRepo;

  @Autowired
  private RoleRepository roleRepository;

  @Override
  public UserInfo getUserInfoByLogin(String username) {
    User user = userRepo.getUserByUsername(username);
    ModelMapper modelMapper = new ModelMapper();
    return modelMapper.map(user, UserInfo.class);
  }
}

角色存储库:

@Repository
public interface RoleRepository extends JpaRepository<Role, Long> {
    public Role getByName(String name);
}

在我的 application.yml 上:

spring:
  security:
    oauth2:
      client:
        registration:
          azure:
            clientId: <my-clientId>
            clientSecret: <my-secret>

azure:
  activedirectory:
    tenant-id: <my-tenant-id>
    user-group:
      allowed-groups: all
    active-directory-groups: all

最佳答案

您可以使用类似于下面的代码

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
@Import(SecurityProblemSupport.class)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    private final CorsFilter corsFilter;
    private final SecurityProblemSupport problemSupport;
    private final OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService;

    public SecurityConfiguration(CorsFilter corsFilter, SecurityProblemSupport problemSupport, OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService) {
        this.corsFilter = corsFilter;
        this.problemSupport = problemSupport;
        this.oidcUserService = oidcUserService;
    }

    @Override
    public void configure(WebSecurity web) {
        web.ignoring()
            .antMatchers(HttpMethod.OPTIONS, "/**")
            .antMatchers("/app/**/*.{js,html}")
            .antMatchers("/i18n/**")
            .antMatchers("/content/**")
            .antMatchers("/swagger-ui/index.html")
            .antMatchers("/test/**");
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        // @formatter:off
        http
            .csrf()
            .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
            .and()
            .addFilterBefore(corsFilter, CsrfFilter.class)
            .exceptionHandling()
            .accessDeniedHandler(problemSupport)
            .and()
            .headers()
            .contentSecurityPolicy("default-src 'self'; frame-src 'self' data:; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://storage.googleapis.com; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:")
            .and()
            .referrerPolicy(ReferrerPolicyHeaderWriter.ReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN)
            .and()
            .featurePolicy("geolocation 'none'; midi 'none'; sync-xhr 'none'; microphone 'none'; camera 'none'; magnetometer 'none'; gyroscope 'none'; speaker 'none'; fullscreen 'self'; payment 'none'")
            .and()
            .frameOptions()
            .deny()
            .and()
            .authorizeRequests()
            .antMatchers("/api/auth-info").permitAll()
            .antMatchers("/api/**").authenticated()
            .antMatchers("/management/health").permitAll()
            .antMatchers("/management/info").permitAll()
            .antMatchers("/management/prometheus").permitAll()
            .antMatchers("/management/**").hasAuthority(AuthoritiesConstants.ADMIN)
            .and()
            .oauth2Login()
            .userInfoEndpoint()
            .oidcUserService(oidcUserService);
        // @formatter:on
    }

    /**
     * Map authorities from "groups" or "roles" claim in ID Token.
     *
     * @return a {@link GrantedAuthoritiesMapper} that maps groups from
     * the IdP to Spring Security Authorities.
     */
    @Bean
    public GrantedAuthoritiesMapper userAuthoritiesMapper() {
        return (authorities) -> {
            Set<GrantedAuthority> mappedAuthorities = new HashSet<>();

            authorities.forEach(authority -> {
                mappedAuthorities.addAll(authorities);
            });
            return mappedAuthorities;
        };
    }

}

您需要更改userAuthoritiesMapper方法来实现您想要的

额外配置

Spring 安全配置

spring:
  security:
    oauth2:
      client:
        registration:
          azure:
            client-id: <<CLIENT_ID>>
            client-secret: <<CLIENT_SECRET>>

Azur Active Directory 配置

azure:
  activedirectory:
    tenant-id: <<YOUR_TENANT_ID>>
    active-directory-groups: Users
  b2c:
    reply-url: http://localhost:9000 # should be absolute url.
    logout-success-url: http://localhost:9000

每当你陷入任何 Spring Boot 配置时,我都会告诉你一个 secret ,去寻找如何 jhipster做到这一点

您可以访问此 article 找到更多信息由 jhipster 创建者发布

注意:SecurityProblemSupport 是 zalando 实现的库 最后发布的代码是从这个repo复制的

关于具有 Office 365 身份验证的 Spring Boot 应用程序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62095515/

相关文章:

azure - 使用持久函数中的重试选项,最后一次尝试后会发生什么?

asp.net - Windows Azure 一般问题

ssl - Spring Boot 1.4.1 SSL trustAnchors 异常

java - 从另一个异步方法调用的 Spring 异步方法

java - Thread.sleep 是否可以减少读取和保存大文件时的内存问题

Azure数据工厂: StartDate in Default Parameters

java - 根据 en 值在 Spring Boot 中加载 Rabbit 或 IBM mq 配置

java - 什么用spring Restcontroller or RepositoryRestResource

java - 关闭 Spring ApplicationContext

java - Java中复杂的数据驱动的Web应用程序-技术决策