How to add a custom AuthenticationDetailsSource to BearerTokenAuthenticationFilter? - spring

The BearerTokenAuthenticationFilter uses an AuthenticationDetailsSource to build the details of an authentication request:
authenticationRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
I am implicitly using the OAuth2ResourceServerConfigurer, provided by spring-security-config-5.7.2, which sadly doesn't consider a developer-defined AuthenticationDetailsSource:
BearerTokenAuthenticationFilter filter = new BearerTokenAuthenticationFilter(resolver);
filter.setBearerTokenResolver(bearerTokenResolver);
filter.setAuthenticationEntryPoint(this.authenticationEntryPoint);
filter = postProcess(filter);
http.addFilter(filter);
I confirm that the BearerTokenAuthenticationFilter has the setter I need:
setAuthenticationDetailsSource()
But I am unable to find a proper and simple way of using the setter (or any other way) to use a custom AuthenticationDetailsSource for that specific filter. I am trying to avoid creating a new filter or a new configuration.
What I have tried:
Went to github to see if there are any new versions - there are none unfortunately.
Tried to autowire the spring security filter chain and directly set the AuthenticationDetailsSource for the filter, but with no success so far.
Is there someone who managed to easily set the AuthenticationDetailsSource for a BearerTokenAuthenticationFilter?
Later edit
I have posted this question as a github issue for the Spring Security team:
https://github.com/spring-projects/spring-security/issues/11655
According to jzheaux#GitHub and as pointed in the accepted answer, I successfully used an ObjectPostProcessor:
http
.oauth2ResourceServer((oauth2) -> oauth2
.jwt(withDefaults())
.withObjectPostProcessor(new ObjectPostProcessor<BearerTokenAuthenticationFilter>() {
#Override
public BearerTokenAuthenticationFilter postProcess(BearerTokenAuthenticationFilter object) {
object.setAuthenticationDetailsSource(myAuthenticationDetailsSource);
return object;
}
});

To set your own AuthenticationDetailsSource, create ObjectPostProcessor class, where you can use setAuthenticationDetailsSource:
public class MyObjectPostProcessor implements ObjectPostProcessor<BearerTokenAuthenticationFilter> {
#Override
public <O extends BearerTokenAuthenticationFilter> O postProcess(O filter) {
filter.setAuthenticationDetailsSource(new MyAuthenticationDetailsSource());
return filter;
}
}
Then you can set MyObjectPostProcessor when creating SecurityFilterChain configuration:
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.oauth2ResourceServer()
.withObjectPostProcessor(new MyObjectPostProcessor());
return http.build();
}
}

Related

Camel REST and Spring Security Java Configuration

I'm trying to set up Camel REST to use basic auth with a simple username/password from my application.properties and can't for the life of me seem to configure Camel Spring Security to do that. I'm trying to follow the Spring Security component documentation which seems to be missing the example of configuring the required beans. I found the missing example here under 'Controlling access to Camel routes' but this only shows the xml configuration.
How do I set up the required SpringSecurityAuthorizationPolicy bean? It needs an AuthenticationManager and an AccessDecisionManager and it also seems to require that I set its SpringSecurityAccessPolicy which I have no idea how to do.
I haven't gotten to test these yet, because I can't get my beans set up, but my rest route looks like:
rest("/ingest")
.post("/json").consumes("application/json")
.route()
.process(authProcessor)
.policy(authPolicy) // this is the bean I don't know how to configure
.to("direct:ingest")
.endRest();
and my AuthProcessor (taken from the camel component doc) looks like:
#Component
public class AuthProcessor implements Processor {
public void process(Exchange exchange) {
String userpass = new String(Base64.decodeBase64(exchange.getIn().getHeader("Authorization", String.class)));
String[] tokens = userpass.split(":");
// create an Authentication object
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(tokens[0], tokens[1]);
// wrap it in a Subject
Subject subject = new Subject();
subject.getPrincipals().add(authToken);
// place the Subject in the In message
exchange.getIn().setHeader(Exchange.AUTHENTICATION, subject);
}
}
and here's my broken bean configuration for what it's worth:
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
public SpringSecurityAuthorizationPolicy springSecurityAuthorizationPolicy(
AuthenticationManager authenticationManager, AccessDecisionManager accessDecisionManager) {
SpringSecurityAuthorizationPolicy policy = new SpringSecurityAuthorizationPolicy();
SpringSecurityAccessPolicy springSecurityAccessPolicy = new SpringSecurityAccessPolicy();
policy.setAuthenticationManager(authenticationManager);
policy.setAccessDecisionManager(accessDecisionManager);
policy.setSpringSecurityAccessPolicy(????);
return policy;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("user").password("pass").roles("USER");
}
#Bean(name = BeanIds.AUTHENTICATION_MANAGER)
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public AccessDecisionManager accessDecisionManager() {
AffirmativeBased affirmativeBased = new AffirmativeBased(ImmutableList.of(
new RoleVoter()
));
affirmativeBased.setAllowIfAllAbstainDecisions(true);
return affirmativeBased;
}
}
I've been banging my head against the wall trying to understand this so an example of how to do this would be amazing. It looks like the xml configuration for what I want to do (in the second link) is simple enough but I can't seem to replicate it in Java configuration.
I know it's an old topic, but I ran into similar questions. I managed to get it working. Not by overriding the accessDecisionManager() method within the WebSecurityConfigurerAdapter class, but by constructing a new instance while building my SpringSecurityAuthorizationPolicy:
#Bean
public Policy adminPolicy(AuthenticationManager authenticationManager) {
RoleVoter roleVoter = new RoleVoter();
SpringSecurityAuthorizationPolicy policy = new SpringSecurityAuthorizationPolicy();
policy.setAuthenticationManager(authenticationManager);
policy.setAccessDecisionManager(new UnanimousBased(List.of(roleVoter)));
policy.setSpringSecurityAccessPolicy(new SpringSecurityAccessPolicy(roleVoter.getRolePrefix() + "<ROLE_NAME>");
return policy;
}

Integrating Spring Security Global Method Security with Keycloak

I have issues with using the Pre/Post Authorization Annotations from Spring Security and the Servlet API with Keycloak integration. I investigated a lot of articles, tutorials and the following questions without further luck:
Obtaining user roles in servlet application using keycloak
Spring Boot Keycloak - How to get a list of roles assigned to a user?
Using spring security annotations with keycloak
Spring Boot + Spring Security + Hierarchical Roles
How do I add method based security to a Spring Boot project?
Configure DefaultMethodSecurityExpressionHandler using Spring Security Java Config
SpringBoot + method based hierarchical roles security: ServletContext is required
All I want is removing the ROLES_ prefix, use hierarchical roles and a comfortable way to retrieve the users' roles.
As of now, I am able to retrieve a hierarchical role like this in a Controller but cannot use the annotations:
#Controller
class HomeController {
#Autowired
AccessToken token
#GetMapping('/')
def home(Authentication auth, HttpServletRequest request) {
// Role 'admin' is defined in Keycloak for this application
assert token.getResourceAccess('my-app').roles == ['admin']
// All effective roles are mapped
assert auth.authorities.collect { it.authority }.containsAll(['admin', 'author', 'user'])
// (!) But this won't work:
assert request.isUserInRole('admin')
}
// (!) Leads to a 403: Forbidden
#GetMapping('/sec')
#PreAuthorize("hasRole('admin')") {
return "Hello World"
}
}
I am guessing that the #PreAuthorize annotation does not work, because that Servlet method is not successful.
There are only three roles - admin, author, user - defined in Keycloak and Spring:
enum Role {
USER('user'),
AUTHOR('author'),
ADMIN('admin')
final String id
Role(String id) {
this.id = id
}
#Override
String toString() {
id
}
}
Keycloak Configuration
Upon removing the #EnableGlobalMethodSecurity annotation from this Web Security reveals an Error creating bean with name 'resourceHandlerMapping' caused by a No ServletContext set error - no clue, where that comes from!
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
/**
* Registers the KeycloakAuthenticationProvider with the authentication manager.
*/
#Autowired
void configureGlobal(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(keycloakAuthenticationProvider().tap { provider ->
// Assigns the Roles via Keycloaks role mapping
provider.grantedAuthoritiesMapper = userAuthoritiesMapper
})
}
#Bean
RoleHierarchyImpl getRoleHierarchy() {
new RoleHierarchyImpl().tap {
hierarchy = "$Role.ADMIN > $Role.AUTHOR > $Role.USER"
}
}
#Bean
GrantedAuthoritiesMapper getUserAuthoritiesMapper() {
new RoleHierarchyAuthoritiesMapper(roleHierarchy)
}
SecurityExpressionHandler<FilterInvocation> expressionHandler() {
// Removes the prefix
new DefaultWebSecurityExpressionHandler().tap {
roleHierarchy = roleHierarchy
defaultRolePrefix = null
}
}
// ...
#Bean
#Scope(scopeName = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
AccessToken accessToken() {
def request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest()
def authToken = (KeycloakAuthenticationToken) request.userPrincipal
def securityContext = (KeycloakSecurityContext) authToken.credentials
return securityContext.token
}
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http)
http
.authorizeRequests()
.expressionHandler(expressionHandler())
// ...
}
}
Global Method Security Configuration
I needed to explicitly allow allow-bean-definition-overriding, because otherwise I got a bean with that name already defined error, which reveals that I completely lost control over this whole situation and don't know what's goin on.
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
class GlobalMethodSecurityConfig extends GlobalMethodSecurityConfiguration {
#Autowired
RoleHierarchy roleHierarchy
#Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
((DefaultMethodSecurityExpressionHandler)super.createExpressionHandler()).tap {
roleHierarchy = roleHierarchy
defaultRolePrefix = null
}
}
}
Any further configurations that could be important? Thanks a lot for your help!
As M. Deinum pointed out, one must remove the defaultRolePrefix in multiple places with a BeanPostProcessor, which is explained in (docs.spring.io) Disable ROLE_ Prefixing.
This approach seemed not very clean to me and so I wrote a custom AuthoritiesMapper to achieve mapping hierarchical roles from Keycloak without the need to rename them to the ROLE_ Spring standard. First of all, the Roles enumeration was modified to conform that standard inside the application scope:
enum Role {
USER('ROLE_USER'),
AUTHOR('ROLE_AUTHOR'),
ADMIN('ROLE_ADMIN')
// ...
}
Secondly, I replaced the RoleHierarchyAuthoritiesMapper with a prefixing hierarchical implementation:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
// ..
// Replaces the RoleHierarchyAuthoritiesMapper
#Bean
GrantedAuthoritiesMapper getUserAuthoritiesMapper() {
new PrefixingRoleHierarchyAuthoritiesMapper(roleHierarchy)
}
}
class PrefixingRoleHierarchyAuthoritiesMapper extends RoleHierarchyAuthoritiesMapper {
String prefix = 'ROLE_'
PrefixingRoleHierarchyAuthoritiesMapper(RoleHierarchy roleHierarchy) {
super(roleHierarchy)
}
#Override
Collection<? extends GrantedAuthority> mapAuthorities(Collection<? extends GrantedAuthority> authorities) {
def prefixedAuthorities = authorities.collect { GrantedAuthority originalAuthority ->
new GrantedAuthority() {
String authority = "${prefix}${originalAuthority.authority}".toUpperCase()
}
}
super.mapAuthorities(prefixedAuthorities)
}
}
And lastly, I got rid of the GlobalMethodSecurityConfig.
Apart from suggestions provided in (docs.spring.io) Disable ROLE_ Prefixing, and suggestion provided by M. Deinum, one more modification is needed while using KeycloakWebSecurityConfigurerAdapter.
In configureGlobal method, grantedAuthoritiesMapper bean is set in the bean keycloakAuthenticationProvider. And in grantedAuthoritiesMapper, prefix can be set to anything you want, where the default value is "ROLE_".
The code goes as follows:
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
SimpleAuthorityMapper grantedAuthoritiesMapper = new SimpleAuthorityMapper();
grantedAuthoritiesMapper.setPrefix("");
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(grantedAuthoritiesMapper);
auth.authenticationProvider(keycloakAuthenticationProvider);
}
This solution works for me.

spring jwt decoder openid token

External OAuth2 Provider doesn't have public JwkUri, so I tried too override default behavior using following code snippet:
#EnableWebSecurity
public class DirectlyConfiguredJwkSetUri extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("**/oauth2/code/esia**", "**/code/esia**", "**esia**").permitAll()
.antMatchers("/user").fullyAuthenticated()
.anyRequest().authenticated()
.and()
.csrf().disable()
.cors().disable()
.oauth2Client()
.clientRegistrationRepository(this.clientRegistrationRepository)
.authorizationCodeGrant()
.authorizationRequestResolver(new CustomAuthorizationRequestResolver(
this.clientRegistrationRepository, esiaConfig, signatureUtil, timeUtil))
.accessTokenResponseClient(customAccessTokenResponseClient())
.and().and().oauth2Login().tokenEndpoint().accessTokenResponseClient(customAccessTokenResponseClient())
.and().and().oauth2ResourceServer().jwt();
}
#Bean
JwtDecoder jwtDecoder() {
return new CustomJwtDecoder();
}
}
class CustomJwtDecoder implements JwtDecoder {
#Override
public Jwt decode(String token) throws JwtException {
System.out.println(token);
return null;
}
}
However Spring Security somehow still uses default realization and I am getting the following error...
[missing_signature_verifier] Failed to find a Signature Verifier for Client Registration: 'esia'. Check to ensure you have configured the JwkSet URI.
Also, I tried to set custom AuthenticationProvider but spring ignores it.
I guess the catch is that spring`s OAuth2LoginConfigurer method init(B http) calls new OidcAuthorizationCodeAuthenticationProvider(accessTokenResponseClient, oidcUserService)
I was facing the same issue even with 5.2.x release. In my case, the real problem was not in the JwtDecoder. I have fixed the issue by setting the jwk-set-uri property (you can change the provider name by the provider which you are using e.g okta, google etc.) :
security.oauth2.client.provider.azure.jwk-set-uri: https://login.microsoftonline.com/{tenant}/discovery/keys
For 5.1.3.RELEASE it looks like you cannot get around this problem easily.
It stems from the OidcAuthorizationCodeAuthenticationProvider.getJwtDecoder
This happens in line 156 which is a call to a private method
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
...
OidcIdToken idToken = createOidcToken(clientRegistration, accessTokenResponse);
...
}
The one option I see is if you make a copy of this code, and modify it yourself. Extending the class itself is not meaningful because all of the logic happens pretty much in the authenticate method. So you're still overriding it. then you add your provider using the http.authenticationProvider method
Another option is to override the SecurityConfigurerAdapter.postProcess method of the OAuth2LoginConfigurer class and do something clever there. Like populating the JWT decoder map through reflection.
Neither are admittedly preferred solutions. I believe that's why the refactoring happened for the 5.2 release.
Given the latest 5.2.x release then
You're almost there, but you must override the correct bean
#Bean
public JwtDecoderFactory<ClientRegistration> jwtDecoderFactory() {
final JwtDecoder decoder = jwtDecoder();
return context -> decoder;
}
and if you don't want to use lambdas
#Bean
public JwtDecoderFactory<ClientRegistration> jwtDecoderFactory() {
final JwtDecoder decoder = jwtDecoder();
return new JwtDecoderFactory<ClientRegistration>() {
#Override
public JwtDecoder createDecoder(ClientRegistration context) {
return decoder;
}
};
}
How did I figure this out, well I took a look at the OAuth2LoginConfigurer.java class which does
JwtDecoderFactory<ClientRegistration> jwtDecoderFactory = getJwtDecoderFactoryBean();
and the private method that fetches the bean look like this
private JwtDecoderFactory<ClientRegistration> getJwtDecoderFactoryBean() {
ResolvableType type = ResolvableType.forClassWithGenerics(JwtDecoderFactory.class, ClientRegistration.class);
String[] names = this.getBuilder().getSharedObject(ApplicationContext.class).getBeanNamesForType(type);
if (names.length > 1) {
throw new NoUniqueBeanDefinitionException(type, names);
}
if (names.length == 1) {
return (JwtDecoderFactory<ClientRegistration>) this.getBuilder().getSharedObject(ApplicationContext.class).getBean(names[0]);
}
return null;
}
(Found this while looking for a solution to overriding the Jwt and Oidc Token validation. Filip's answer helped me get to the solution so I figured I'd add this to help anyone who follows the same search.)
For a time-travel testing scenario, our jvm clock was set months in the future. Login was failing due to the validations done on Jwt and Oidc token timestamp.
This addition worked for our app on Spring Security 5.2.1
#Bean
public JwtDecoderFactory<ClientRegistration> getJWTDecoder() {
OidcIdTokenDecoderFactory factory = new OidcIdTokenDecoderFactory();
factory.setJwtValidatorFactory(new Function<ClientRegistration, OAuth2TokenValidator<Jwt>>() {
#Override
public OAuth2TokenValidator<Jwt> apply(ClientRegistration clientRegistration) {
return new CustomTimestampIgnoringOidcTokenValidator(clientRegistration);
}
});
}
This just replaces the Default validators with a custom one which only validates the other claims.

SpringBoot Spring Security can't have a given filter applied to only one path [duplicate]

I want to use #Autowire with a Filter. So I define my filter in the SecurityConfig as below:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(getA(), BasicAuthenticationFilter.class);
http.csrf().disable();
}
#Bean
public A getA(){
return new A();
}
This filter A extends Spring's GenericFilterBean.
I get below output when I invoke the controller, which shows the filter hits twice.
filter A before
filter A before
mycontroller invoke
filter A after
filter A after
My observation is, this extra invocation invoke with Spring container because if filter is not register as bean, it only get hits once. What is the reason and how can I fix it?
As you have observed, Spring Boot will automatically register any bean that is a Filter with the servlet container. One option is to not expose your filter as a bean and only register it with Spring Security.
If you want to be able to autowire dependencies into your Filter then it needs to be a bean. That means you need to tell Spring Boot not to register it as a filter. As described in the documentation, you do that using a FilterRegistrationBean:
#Bean
public FilterRegistrationBean registration(MyFilter filter) {
FilterRegistrationBean registration = new FilterRegistrationBean(filter);
registration.setEnabled(false);
return registration;
}
If you are on Spring 6.0.2 or plus version and if using OncePerRequestFilter,
Overriding shouldNotFilter method as follows will work.
#Override
protected boolean shouldNotFilter(HttpServletRequest request) {
return request.getServletPath().contains("/api/path/which/needs/to/exclude");
}
Removing #Component from the filter class helped me.
public class AuthTokenFilter extends OncePerRequestFilter {
}

Is #Order really needed in Spring Boot 2.0.4 OAuth server?

I have this little OAuth server class and I am using Spring Boot 2.0.4 and the spring-security-oauth2-autoconfigure 2.0.0.RELEASE dependency :
#RestController
#SpringBootApplication
#EnableAuthorizationServer
#Order(200) // really needed ?
public class MyOAuthServerApplication extends WebSecurityConfigurerAdapter {
#RequestMapping({ "/me" })
public Map<String, String> user(Principal principal) {
Map<String, String> map = new LinkedHashMap<>();
map.put("name", principal.getName());
return map;
}
#Configuration
#EnableResourceServer
protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
// #formatter:off
http.antMatcher("/me").authorizeRequests().anyRequest().authenticated();
// #formatter:on
}
}
#Bean
#Override
public UserDetailsService userDetailsService() {
UserDetails mary =
User.withUsername("mary")
.password("{bcrypt}$2a$10$B3NUb0x.MYnSfx7WJItrvO/ymEQwLCKQNehmCuA8keL1uTyHizI0i")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(mary);
}
public static void main(String[] args) {
SpringApplication.run(MyOAuthServerApplication.class, args);
}
}
This seems to work well with and without the #Order(200) annotation.
So is this annotation really needed ?
The Order annotation is used to define the injection precedence.
Read more her: https://www.baeldung.com/spring-order
In your case it's because of the EnableResourceServer annotation. And you must keep the annotation.
From the doc:
The #EnableResourceServer annotation creates a security filter with
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER-1) by default, so by
moving the main application security to
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER) we ensure that the
rule for "/me" takes precedence.
Please find the tutorial here: https://spring.io/guides/tutorials/spring-boot-oauth2/
You need it if you have another WebSecurityConfigurerAdapter configuration.
For example if you allow users to login via login form with a different UserDetailsService and so on. Then this should be tried before your oauth authentification and thus needs a lower order, for example #Order(199).
Another example would be different configuration for your API access.
If you don't have any other configuration, then you don't need to set the order.
Setting the order to 200 also seems to be an arbitrary value, that should simply be higher then the others and thus executed last.

Resources