我正在开发一个使用 Office 365 进行身份验证的应用程序(后端为 Spring Boot,前端为 React)。但我想使用我自己的用户组和权限。例如,当用户第一次访问/api/auth 时,我想从 microsoft graph 检索信息并将其保存到我的数据库,然后使用我自己的角色/权限保护我的端点。
到目前为止我成功做到了:
- 当我转到 localhost:8080(返回)时,我将重定向到 Azure 门户进行身份验证。然后,我可以访问我的端点
- 我可以通过自己的角色保护我的端点
- 当我转到 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/