通过扩展WebSecurityConfigurerAdapter,如何构造带有自定义认证逻辑的configure
By extending WebSecurityConfigurerAdapter, How to construct configure with custom authentication logic
我正在使用 okta 进行身份验证。我们公司的 okta 禁用了 'default' 授权服务器。所以现在我不能使用 'okta-spring-security-starter' 来简单地执行此操作来验证从 url headers:
传递的令牌
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
public class OktaOAuth2WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/health").permitAll()
.anyRequest().authenticated()
.and()
.oauth2ResourceServer().jwt();
http.cors();
Okta.configureResourceServer401ResponseBody(http);
}
}
所以我需要点击 okta introspect 端点 (https://developer.okta.com/docs/reference/api/oidc/#introspect) 来验证。所以我想知道我能否将此过程集成到 WebSecurityConfigurerAdapter
的配置中。也许是这样的???:
import com.okta.spring.boot.oauth.Okta;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
public class OktaOAuth2WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/health").permitAll()
.anyRequest().authenticated()
.and()
/*add something there*/
http.cors();
}
}
我看到类似覆盖 AuthenticationProvider(Custom Authentication provider with Spring Security and Java Config) 的内容,并使用 httpbasic 身份验证。如果我使用 .oauth2ResourceServer().jwt().
我可以做类似的事情吗
我的想法是覆盖身份验证提供程序并在提供程序中点击 okta 内省端点,这行得通吗???
我不使用 Okta,因此我不知道它究竟是如何工作的。但我有两个假设:
- 每个请求在授权中包含一个accessToken header
- 您向 ${baseUrl}/v1/introspect 发出 POST 请求,它会回答您 true 或 false 以表明 accessToken 是否有效
考虑到这两个假设,如果我必须手动实施自定义安全逻辑身份验证,我将执行以下步骤:
- 注册并实施
CustomAuthenticationProvider
- 添加过滤器以从请求中提取访问令牌
正在注册自定义身份验证提供程序:
// In OktaOAuth2WebSecurityConfig.java
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(customAuthenticationProvider());
}
@Bean
CustomAuthenticationProvider customAuthenticationProvider(){
return new CustomAuthenticationProvider();
}
CustomAuthenticationProvider:
public class CustomAuthenticationProvider implements AuthenticationProvider {
private static final Logger logger = LoggerFactory.getLogger(CustomAuthenticationProvider.class);
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
logger.debug("Authenticating authenticationToken");
OktaTokenAuthenticationToken auth = (OktaTokenAuthenticationToken) authentication;
String accessToken = auth.getToken();
// You should make a POST request to ${oktaBaseUrl}/v1/introspect
// to determine if the access token is good or bad
// I just put a dummy if here
if ("ThanhLoyal".equals(accessToken)){
List<GrantedAuthority> authorities = Collections.singletonList(new SimpleGrantedAuthority("USER"));
logger.debug("Good access token");
return new UsernamePasswordAuthenticationToken(auth.getPrincipal(), "[ProtectedPassword]", authorities);
}
logger.debug("Bad access token");
return null;
}
@Override
public boolean supports(Class<?> clazz) {
return clazz == OktaTokenAuthenticationToken.class;
}
}
注册过滤器以从请求中提取 accessToken:
// Still in OktaOAuth2WebSecurityConfig.java
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterAfter(accessTokenExtractorFilter(), UsernamePasswordAuthenticationFilter.class)
.authorizeRequests().anyRequest().authenticated();
// And other configurations
}
@Bean
AccessTokenExtractorFilter accessTokenExtractorFilter(){
return new AccessTokenExtractorFilter();
}
过滤器本身:
public class AccessTokenExtractorFilter extends OncePerRequestFilter {
private static final Logger logger = LoggerFactory.getLogger(AccessTokenExtractorFilter.class);
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
logger.debug("Filtering request");
Authentication authentication = getAuthentication(request);
if (authentication == null){
logger.debug("Continuing filtering process without an authentication");
filterChain.doFilter(request, response);
} else {
logger.debug("Now set authentication on the request");
SecurityContextHolder.getContext().setAuthentication(authentication);
filterChain.doFilter(request, response);
}
}
private Authentication getAuthentication(HttpServletRequest request) {
String accessToken = request.getHeader("Authorization");
if (accessToken != null){
logger.debug("An access token found in request header");
List<GrantedAuthority> authorities = Collections.singletonList(new SimpleGrantedAuthority("USER"));
return new OktaTokenAuthenticationToken(accessToken, authorities);
}
logger.debug("No access token found in request header");
return null;
}
}
我在这里上传了一个简单的项目供大家参考:https://github.com/MrLoyal/spring-security-custom-authentication
工作原理:
- AccessTokenExtractorFilter 紧跟在 UsernamePasswordAuthenticationFilter 之后,这是 Spring Security
的默认过滤器
- 一个请求到达,上面的过滤器从中提取accessToken并放在SecurityContext中
- 稍后,AuthenticationManager 调用 AuthenticationProvider(s) 来验证请求。这种情况下,CustomAuthenticationProvider 被调用
顺便说一句,你的问题应该包含 spring-security
标签。
更新 1:关于 AuthenticationEntryPoint
AuthenticationEntryPoint
声明当未经身份验证的请求到达时要做什么(在我们的例子中,当请求不包含有效的 "Authorization" header 时要做什么)。
在我的 REST API 中,我只是向客户端响应 401 HTTP 状态代码。
// CustomAuthenticationEntryPoint
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
response.reset();
response.setStatus(401);
// A utility method to add CORS headers to the response
SecUtil.writeCorsHeaders(request, response);
}
Spring 的 LoginUrlAuthenticationEntryPoint
将用户重定向到登录页面(如果已配置)。
因此,如果您想将未经身份验证的请求重定向到 Okta 的登录页面,您可以使用 AuthenticationEntryPoint。
Spring Security 5.2 附带了对内省端点的支持。请查看 GitHub 存储库中的 Opaque Token sample。
不过,要在这里简要回答,您可以这样做:
http
.authorizeRequests(authz -> authz
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2
.opaqueToken(opaque -> opaque
.introspectionUri("the-endpoint")
.introspectionClientCredentials("client-id", "client-password")
)
);
如果你用的是SpringBoot,那就简单一些。您可以在 application.yml
:
中提供这些属性
spring:
security:
oauth2:
resourceserver:
opaquetoken:
introspection-uri: ...
client-id: ...
client-secret: ...
然后你的 DSL 可以指定 opaqueToken
:
http
.authorizeRequests(authz -> authz
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2
.opaqueToken(opaque -> {})
);
我正在使用 okta 进行身份验证。我们公司的 okta 禁用了 'default' 授权服务器。所以现在我不能使用 'okta-spring-security-starter' 来简单地执行此操作来验证从 url headers:
传递的令牌import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
public class OktaOAuth2WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/health").permitAll()
.anyRequest().authenticated()
.and()
.oauth2ResourceServer().jwt();
http.cors();
Okta.configureResourceServer401ResponseBody(http);
}
}
所以我需要点击 okta introspect 端点 (https://developer.okta.com/docs/reference/api/oidc/#introspect) 来验证。所以我想知道我能否将此过程集成到 WebSecurityConfigurerAdapter
的配置中。也许是这样的???:
import com.okta.spring.boot.oauth.Okta;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
public class OktaOAuth2WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/health").permitAll()
.anyRequest().authenticated()
.and()
/*add something there*/
http.cors();
}
}
我看到类似覆盖 AuthenticationProvider(Custom Authentication provider with Spring Security and Java Config) 的内容,并使用 httpbasic 身份验证。如果我使用 .oauth2ResourceServer().jwt().
我可以做类似的事情吗我的想法是覆盖身份验证提供程序并在提供程序中点击 okta 内省端点,这行得通吗???
我不使用 Okta,因此我不知道它究竟是如何工作的。但我有两个假设:
- 每个请求在授权中包含一个accessToken header
- 您向 ${baseUrl}/v1/introspect 发出 POST 请求,它会回答您 true 或 false 以表明 accessToken 是否有效
考虑到这两个假设,如果我必须手动实施自定义安全逻辑身份验证,我将执行以下步骤:
- 注册并实施
CustomAuthenticationProvider
- 添加过滤器以从请求中提取访问令牌
正在注册自定义身份验证提供程序:
// In OktaOAuth2WebSecurityConfig.java
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(customAuthenticationProvider());
}
@Bean
CustomAuthenticationProvider customAuthenticationProvider(){
return new CustomAuthenticationProvider();
}
CustomAuthenticationProvider:
public class CustomAuthenticationProvider implements AuthenticationProvider {
private static final Logger logger = LoggerFactory.getLogger(CustomAuthenticationProvider.class);
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
logger.debug("Authenticating authenticationToken");
OktaTokenAuthenticationToken auth = (OktaTokenAuthenticationToken) authentication;
String accessToken = auth.getToken();
// You should make a POST request to ${oktaBaseUrl}/v1/introspect
// to determine if the access token is good or bad
// I just put a dummy if here
if ("ThanhLoyal".equals(accessToken)){
List<GrantedAuthority> authorities = Collections.singletonList(new SimpleGrantedAuthority("USER"));
logger.debug("Good access token");
return new UsernamePasswordAuthenticationToken(auth.getPrincipal(), "[ProtectedPassword]", authorities);
}
logger.debug("Bad access token");
return null;
}
@Override
public boolean supports(Class<?> clazz) {
return clazz == OktaTokenAuthenticationToken.class;
}
}
注册过滤器以从请求中提取 accessToken:
// Still in OktaOAuth2WebSecurityConfig.java
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterAfter(accessTokenExtractorFilter(), UsernamePasswordAuthenticationFilter.class)
.authorizeRequests().anyRequest().authenticated();
// And other configurations
}
@Bean
AccessTokenExtractorFilter accessTokenExtractorFilter(){
return new AccessTokenExtractorFilter();
}
过滤器本身:
public class AccessTokenExtractorFilter extends OncePerRequestFilter {
private static final Logger logger = LoggerFactory.getLogger(AccessTokenExtractorFilter.class);
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
logger.debug("Filtering request");
Authentication authentication = getAuthentication(request);
if (authentication == null){
logger.debug("Continuing filtering process without an authentication");
filterChain.doFilter(request, response);
} else {
logger.debug("Now set authentication on the request");
SecurityContextHolder.getContext().setAuthentication(authentication);
filterChain.doFilter(request, response);
}
}
private Authentication getAuthentication(HttpServletRequest request) {
String accessToken = request.getHeader("Authorization");
if (accessToken != null){
logger.debug("An access token found in request header");
List<GrantedAuthority> authorities = Collections.singletonList(new SimpleGrantedAuthority("USER"));
return new OktaTokenAuthenticationToken(accessToken, authorities);
}
logger.debug("No access token found in request header");
return null;
}
}
我在这里上传了一个简单的项目供大家参考:https://github.com/MrLoyal/spring-security-custom-authentication
工作原理:
- AccessTokenExtractorFilter 紧跟在 UsernamePasswordAuthenticationFilter 之后,这是 Spring Security 的默认过滤器
- 一个请求到达,上面的过滤器从中提取accessToken并放在SecurityContext中
- 稍后,AuthenticationManager 调用 AuthenticationProvider(s) 来验证请求。这种情况下,CustomAuthenticationProvider 被调用
顺便说一句,你的问题应该包含 spring-security
标签。
更新 1:关于 AuthenticationEntryPoint
AuthenticationEntryPoint
声明当未经身份验证的请求到达时要做什么(在我们的例子中,当请求不包含有效的 "Authorization" header 时要做什么)。
在我的 REST API 中,我只是向客户端响应 401 HTTP 状态代码。
// CustomAuthenticationEntryPoint
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
response.reset();
response.setStatus(401);
// A utility method to add CORS headers to the response
SecUtil.writeCorsHeaders(request, response);
}
Spring 的 LoginUrlAuthenticationEntryPoint
将用户重定向到登录页面(如果已配置)。
因此,如果您想将未经身份验证的请求重定向到 Okta 的登录页面,您可以使用 AuthenticationEntryPoint。
Spring Security 5.2 附带了对内省端点的支持。请查看 GitHub 存储库中的 Opaque Token sample。
不过,要在这里简要回答,您可以这样做:
http
.authorizeRequests(authz -> authz
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2
.opaqueToken(opaque -> opaque
.introspectionUri("the-endpoint")
.introspectionClientCredentials("client-id", "client-password")
)
);
如果你用的是SpringBoot,那就简单一些。您可以在 application.yml
:
spring:
security:
oauth2:
resourceserver:
opaquetoken:
introspection-uri: ...
client-id: ...
client-secret: ...
然后你的 DSL 可以指定 opaqueToken
:
http
.authorizeRequests(authz -> authz
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2
.opaqueToken(opaque -> {})
);