我使用Spring Security对Active Directory进行身份验证/授权。如果我在Spring Embedded Tomcat中运行以下代码,它就可以正常工作。
但是当我切换到Open/WAS Liberty服务器时,我在身份验证(/auth endpoint)上得到403:
我的WebSecurityConfiguration类如下所示:
@Configuration
@EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Value("${active.dir.domain}")
private String domain;
@Value("${active.dir.url}")
private String url;
@Value("${active.dir.userDnPattern}")
private String userDnPattern;
private final Environment environment;
public WebSecurityConfiguration(Environment environment) {
this.environment = environment;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(activeDirectoryAuthenticationProvider()).eraseCredentials(false);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors(Customizer.withDefaults())
.csrf().disable()
.authorizeRequests()
.antMatchers("/auth").permitAll()
.anyRequest()
.authenticated()
.and()
.addFilter(getAuthenticationFilter())
.addFilter(new AuthorizationFilter(authenticationManager()))
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
@Bean
public AuthenticationProvider activeDirectoryAuthenticationProvider() {
String adSearchFilter = "(&(sAMAccountName={1})(objectClass=user))";
ActiveDirectoryLdapAuthenticationProvider ad = new ActiveDirectoryLdapAuthenticationProvider(domain, url, userDnPattern);
ad.setConvertSubErrorCodesToExceptions(true);
ad.setUseAuthenticationRequestCredentials(true);
ad.setSearchFilter(adSearchFilter);
return ad;
}
//CORS configuration source
@Bean
public CorsConfigurationSource corsConfigurationSource() {
final CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("http://some.url"));
configuration.setAllowedMethods(Arrays.asList("*"));
configuration.setAllowCredentials(true);
configuration.setAllowedHeaders(Arrays.asList("*"));
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
//Customize the Spring default /login url to overwrite it with /auth.
private AuthenticationFilter getAuthenticationFilter() throws Exception {
final AuthenticationFilter filter = new AuthenticationFilter(authenticationManager());
filter.setFilterProcessesUrl("/auth");
return filter;
}
}
下面是我的授权过滤器类:
public class AuthorizationFilter extends BasicAuthenticationFilter {
public AuthorizationFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
String authorizationHeader = request.getHeader("Authorization");
if (authorizationHeader == null || !authorizationHeader.startsWith("Bearer ")) {
chain.doFilter(request, response);
return;
}
UsernamePasswordAuthenticationToken authentication = getAuthentication(request);
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(request, response);
}
//Extracts username from Jwt token
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
String token = request.getHeader("Authorization");
if (token != null) {
token = token.replace("Bearer ", "");
String username = Jwts.parser()
.setSigningKey("somesecret")
.parseClaimsJws(token)
.getBody()
.getSubject();
if (username != null) {
return new UsernamePasswordAuthenticationToken(username, null, new ArrayList<>());
}
}
return null;
}
}
下面是我的身份验证过滤器类:
public class AuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final AuthenticationManager authenticationManager;
public AuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
UserLoginRequestModel userLoginRequestModel = extractCredentials(request);
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
userLoginRequestModel.getUsername()
, userLoginRequestModel.getPassword()
, new ArrayList<>());
return authenticationManager.authenticate(token);
}
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication auth) throws IOException, ServletException {
String userId = ((UserDetails)auth.getPrincipal()).getUsername();
Instant now = Instant.now();
String jwtToken = Jwts.builder()
.setSubject(userId)
.setIssuer("me")
.setAudience("myapp")
.setId(UUID.randomUUID().toString())
.setIssuedAt(Date.from(now))
.setExpiration(Date.from(now.plus(30000)))
.signWith(SignatureAlgorithm.HS512, SecurityConstants.getTokenSecret())
.compact();
response.addHeader("Authorization", "Bearer " + jwtToken);
response.addHeader("Access-Control-Expose-Headers", accessControlHeaders.toString());
}
private UserLoginRequestModel extractCredentials(HttpServletRequest request) {
UserLoginRequestModel userLoginRequestModel = new UserLoginRequestModel();
String authorizationHeader = request.getHeader("Authorization");
try {
if (authorizationHeader != null && authorizationHeader.toLowerCase().startsWith("basic")) {
String base64Credentials = authorizationHeader.substring("Basic".length()).trim();
byte[] decodedCredentials = Base64.getDecoder().decode(base64Credentials);
String headerCredentials = new String(decodedCredentials, StandardCharsets.UTF_8);
final String[] credentialsValues = headerCredentials.split(":", 2);
userLoginRequestModel.setUsername(credentialsValues[0]);
userLoginRequestModel.setPassword(credentialsValues[1]);
} else {
userLoginRequestModel = new ObjectMapper().readValue(request.getInputStream(), UserLoginRequestModel.class);
}
return userLoginRequestModel;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
在 Postman ,我叫:
POST: http://localhost/myapi/v1/auth
我把用户名和密码传给它BasicAuth。
如果我在Open/WAS Liberty上运行,我会得到403 Forbidden。同样的代码,没有任何变化,在Spring附带的嵌入式Tomcat中运行良好,我得到200 OK。
1条答案
按热度按时间jpfvwuh41#
我遇到这种情况的原因是在我的Liberty server.xml中,我缺少定义的
context-path
。看起来,Liberty并不考虑在application.properties文件中设置的context-path
。下面是我的application.properties文件中的
context-path
。不幸的是,Liberty没有读取(或考虑)它,只是使用应用程序名称作为context-path
,而不是使用application.properties或application.yml文件中的设置:因此,如果部署在Sping Boot 嵌入式Tomcat容器中,而不是部署在Liberty容器中,上面的
context-path
将工作得很好。当您将其部署到OpenLiberty/WASLiberty时,您可能会发现您的端点将停止工作,并且您会收到403和/或404错误。
在上面的例子中,我有getAuthenticationFilter()方法,在我的
WebSecurityConfiguration
类中。下面,我添加了一些注解来解释它:基于上面的
context-path
,在Tomcat上,它变成了/myapi/v1/auth
,而在Liberty上,它最终只是/myapi/auth
,这是错误的。我认为Liberty所做的,它只会取API的名称,并向其添加端点,因此忽略了版本控制。因此,
AntPathRequestMatcher
类的matches()
方法将导致一个不匹配的/auth
端点,您将得到403错误。而其他端点将导致404错误。解决方案
1.在您的应用程序.属性中,保留:
服务器.servlet.上下文路径=/myapi/v1
,这将被嵌入式Tomcat拾取,您的应用程序将继续按预期工作。
1.在Open/WAS Liberty的server.xml配置中,将匹配的context-root添加到如下部分:
,这将由Open/WASLiberty拾取,您的应用程序也将继续在Liberty容器上按预期工作。