I'm trying to make security using oauth2 resource server with google as authentication provider and add custom roles from database by getting email from JWT token and searching for it in database.
This is my configuration
#Bean
public SecurityFilterChain filterChain(final HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/csrf").permitAll()
.anyRequest().authenticated()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.oauth2ResourceServer().jwt().decoder(jwtDecoder()).jwtAuthenticationConverter(jwtAuthenticationConverter())
.and()
.and()
.cors().and()
.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
return http.build();
}
JWT decoder and authentication converter
#Bean
JwtDecoder jwtDecoder() {
NimbusJwtDecoder jwtDecoder = JwtDecoders.fromOidcIssuerLocation(issuerUri);
OAuth2TokenValidator<Jwt> withIssuer = JwtValidators.createDefaultWithIssuer(issuerUri);
MappedJwtClaimSetConverter converter = MappedJwtClaimSetConverter.withDefaults(Collections.singletonMap("roles", customClaim -> getUserRolesFromDatabase()));
jwtDecoder.setClaimSetConverter(converter);
jwtDecoder.setJwtValidator(withIssuer);
return jwtDecoder;
}
#Bean
public JwtAuthenticationConverter jwtAuthenticationConverter() {
JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
grantedAuthoritiesConverter.setAuthorityPrefix("ROLE_");
grantedAuthoritiesConverter.setAuthoritiesClaimName("roles");
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter);
return jwtAuthenticationConverter;
}
public List<String> getUserRolesFromDatabase() {
return Collections.singletonList("USER");
}
How do I get email claim from bearer token to use it in getUserRolesFromDatabase() and search for roles in database?
Related
I want to login to Spring Webflux Security through the changed authentication URL(/api/authenticate). So I made this code.
#Configuration
#EnableWebFluxSecurity
#EnableReactiveMethodSecurity
public class WebFluxSecurityConfig {
#Bean
public MapReactiveUserDetailsService userDetailsService() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("user")
.password("user")
.roles("USER")
.build();
UserDetails admin = User.withUsername("admin")
.password(passwordEncoder().encode("admin"))
.roles("ADMIN")
.build();
return new MapReactiveUserDetailsService(user, admin);
}
#Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
return http.csrf().disable()
.authorizeExchange()
.anyExchange().permitAll()
.and()
.formLogin()
.requiresAuthenticationMatcher(new PathPatternParserServerWebExchangeMatcher("/api/authenticate"))
.authenticationSuccessHandler((webFilterExchange, authentication) -> {
webFilterExchange.getExchange().getResponse().setStatusCode(HttpStatus.OK);
return Mono.empty();
})
.and()
.build();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
And then, I tried to login with user/user and admin/admin to localhost:8080/api/authenticate but it gives me response status code 302 and login?error.
How can I login with user/user and admin/admin to custom url localhost:8080/api/authenticate?
I have a Spring Cloud Gateway and one Microservice
I am using OAuth2 with Keycloak
API Gateway is my OAuth2 Client and it serves secure pages and redirect request to microservice
I need to authorize access to renderer one page "/backoffice", but when i use hasRole method to authorize the user a receive Access Denied.
It occurs because keycloak generates access token with the pattern:
Access Token from Keycloak
It is my Gateway SecurityWebFilterChain
#Configuration
#EnableWebFluxSecurity
public class SecurityConfig {
#Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http.csrf().disable();
http.
authorizeExchange()
.pathMatchers("/backoffice/**").hasRole("approver")
.anyExchange().permitAll()
.and()
.oauth2Login()
.and()
.oauth2Client();
return http.build();
}
}
I tried to use the same approach used by Microservice but I receive 401 Unauthorized... I tried to use #Order to have two filter but not works
// Microservice's SecurityWebFilterChain
#Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
http
.csrf().disable()
.authorizeExchange(exchanges ->
exchanges
.pathMatchers("/{id}/decision").hasRole("ROLE_user")
.anyExchange().permitAll())
.oauth2ResourceServer(oauth2 ->
oauth2.jwt(jwt -> jwt.jwtAuthenticationConverter(grantedAuthoritiesExtractor())));
return http.build();
}
Converter<Jwt, Mono<AbstractAuthenticationToken>> grantedAuthoritiesExtractor() {
JwtAuthenticationConverter jwtAuthenticationConverter =
new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(new GrantedAuthoritiesExtractor());
return new ReactiveJwtAuthenticationConverterAdapter(jwtAuthenticationConverter);
}
static class GrantedAuthoritiesExtractor implements Converter<Jwt, Collection<GrantedAuthority>> {
#Override
public Collection<GrantedAuthority> convert(Jwt jwt) {
var realm_access = (Map<String, List<String>>) jwt.getClaims().getOrDefault("realm_access", Collections.emptyMap());
return realm_access.get("roles").stream()
.map(role -> "ROLE_".concat(role))
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
}
}
Can I have both in same application, is there conflict?
I'm using a default Spring login form and REST API for other data. Sessions are enabled and used.
All urls (except /login form ) are protected.
So how to test the protected #RestController methods using TestRestTemplate? (I could make an additional request to /api/login to get Cookie and then generate and add Headers, but there is no REST endpoint for login, only a form-base authencation).
Also, is the #WithMockUser annotation only for MockMvc (and can't be used with TestRestTemplate)?
Steps
Clone spring security example repo git clone https://github.com/spring-guides/gs-securing-web.git
Added RestControllerIT
Added csrf().disable() to WebSecurityConfig. This test will not pass if csrf enabled
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment= SpringBootTest.WebEnvironment.RANDOM_PORT)
public class RestControllerIT {
#Autowired
TestRestTemplate testRestTemplate;
#LocalServerPort
int localPort;
#Test
public void test(){
String securedUrl = "http://localhost:" + localPort + "/hello";
String loginUrl = "http://localhost:" + localPort + "/login";
String username = "user";
String password = "password";
MultiValueMap<String, String> form = new LinkedMultiValueMap<>();
form.set("username", username);
form.set("password", password);
ResponseEntity<String> loginResponse = testRestTemplate.postForEntity(
loginUrl,
new HttpEntity<>(form, new HttpHeaders()),
String.class);
String cookie = loginResponse.getHeaders().get("Set-Cookie").get(0);
HttpHeaders headers = new HttpHeaders();
headers.add("Cookie", cookie);
ResponseEntity<String> responseFromSecuredEndPoint = testRestTemplate.exchange(securedUrl, HttpMethod.GET, new HttpEntity<>(headers), String.class);
assertEquals(responseFromSecuredEndPoint.getStatusCode(), HttpStatus.OK);
assertTrue(responseFromSecuredEndPoint.getBody().contains("Hello World!"));
}
}
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/", "/home").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
#Bean
#Override
public UserDetailsService userDetailsService() {
UserDetails user =
User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
}
I would like to generate a JWT token after a kerberos authentication.
My kerberos authentication works very well.
Here my spring security kerberos configuration :
#Configuration
#EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Value("${app.service-principal}")
private String servicePrincipal;
#Value("${app.keytab-location}")
private String keytabLocation;
#Bean
public AuthenticationManager customAuthenticationManager() throws Exception {
return authenticationManager();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.exceptionHandling()
.authenticationEntryPoint(spnegoEntryPoint())
.and()
.authorizeRequests()
.antMatchers("/", "/home").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login").permitAll()
.and()
.logout()
.permitAll()
.and()
.addFilterBefore(
spnegoAuthenticationProcessingFilter(authenticationManagerBean()),
BasicAuthenticationFilter.class);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.authenticationProvider(kerberosAuthenticationProvider())
.authenticationProvider(kerberosServiceAuthenticationProvider());
}
#Bean
public KerberosAuthenticationProvider kerberosAuthenticationProvider() {
KerberosAuthenticationProvider provider = new KerberosAuthenticationProvider();
SunJaasKerberosClient client = new SunJaasKerberosClient();
client.setDebug(true);
provider.setKerberosClient(client);
provider.setUserDetailsService(dummyUserDetailsService());
return provider;
}
#Bean
public SpnegoEntryPoint spnegoEntryPoint() {
return new SpnegoEntryPoint("/login");
}
#Bean
public SpnegoAuthenticationProcessingFilter spnegoAuthenticationProcessingFilter(
AuthenticationManager authenticationManager) {
SpnegoAuthenticationProcessingFilter filter = new SpnegoAuthenticationProcessingFilter();
filter.setAuthenticationManager(authenticationManager);
return filter;
}
#Bean
public KerberosServiceAuthenticationProvider kerberosServiceAuthenticationProvider() {
KerberosServiceAuthenticationProvider provider = new KerberosServiceAuthenticationProvider();
provider.setTicketValidator(sunJaasKerberosTicketValidator());
provider.setUserDetailsService(dummyUserDetailsService());
return provider;
}
#Bean
public SunJaasKerberosTicketValidator sunJaasKerberosTicketValidator() {
SunJaasKerberosTicketValidator ticketValidator = new SunJaasKerberosTicketValidator();
ticketValidator.setServicePrincipal(servicePrincipal);
ticketValidator.setKeyTabLocation(new FileSystemResource(keytabLocation));
ticketValidator.setDebug(true);
//ticketValidator.setHoldOnToGSSContext(true);
return ticketValidator;
}
#Bean
public DummyUserDetailsService dummyUserDetailsService() {
return new DummyUserDetailsService();
}
}
Is there a simple solution to generate a jwt token after this authentication?
Following my internet search, I found nothing interesting.
Thanks for your answers !
I have a SpringBoot based app, with multiple endpoints. Because of different clients that will be accessing the endpoints I would like to have different Authentication providers protecting them. Some endpoints would be protected by Kerberos (KerberosServiceAuthenticationProvider -- http://docs.spring.io/autorepo/docs/spring-security-kerberos/1.0.0.RC1/reference/htmlsingle/). Some endpoints would be protected by AD/LDAP (ActiveDirectoryLdapAuthenticationProvider).
I currently have it working with Kerberos OR LDAP, but not both:
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
protected class ApplicationSecurity extends WebSecurityConfigurerAdapter {
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//For Kerberos
auth.authenticationProvider(kerberosAuthenticationProvider())
.authenticationProvider(kerberosServiceAuthenticationProvider());
//For LDAP
//auth.authenticationProvider(customAuthenticationProvider());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers(HttpMethod.GET, APPLICATION_ADMIN_ENDPOINTS)
.permitAll()
.and()
.authorizeRequests()
.antMatchers(HttpMethod.PUT, APPLICATION_ADMIN_ENDPOINTS)
.hasAnyAuthority(AUTHENTICATED_APPLICATION_ADMIN_AUTHORITIES)
.and()
.authorizeRequests()
.antMatchers(HttpMethod.DELETE, APPLICATION_ADMIN_ENDPOINTS)
.hasAnyAuthority(AUTHENTICATED_APPLICATION_ADMIN_AUTHORITIES)
.and()
.authorizeRequests()
.antMatchers(CLIENT_ENDPOINTS)
.permitAll()
.and()
.authorizeRequests()
.antMatchers(SWAGGER_ENDPOINTS)
.permitAll()
.and()
.authorizeRequests()
.antMatchers(MANAGER_ENDPOINTS)
.hasAnyAuthority(AUTHENTICATED_MANAGER_AUTHORITIES)
.and()
.authorizeRequests()
.antMatchers(TRUSTED_AGENT_ENDPOINTS)
.hasAnyAuthority(AUTHENTICATED_TRUSTED_AGENT_AUTHORITIES)
.and()
.authorizeRequests()
.antMatchers("/kerb/**")
.hasAnyAuthority(AUTHENTICATED_APPLICATION_ADMIN_AUTHORITIES)
.and()
.addFilterBefore(spnegoAuthenticationProcessingFilter(authenticationManagerBean()), BasicAuthenticationFilter.class)
.httpBasic()
.and()
.csrf()
.disable();
}
}
#Bean
public AuthenticationProvider customAuthenticationProvider() {
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider(
ldapDomain, ldapUrl);
SimpleCaseAndWhitespaceMitigatingAuthoritiesMapper authoritiesMapper = new SimpleCaseAndWhitespaceMitigatingAuthoritiesMapper();
provider.setAuthoritiesMapper(authoritiesMapper);
provider.setConvertSubErrorCodesToExceptions(true);
return provider;
}
#Bean
public KerberosAuthenticationProvider kerberosAuthenticationProvider() {
KerberosAuthenticationProvider provider = new KerberosAuthenticationProvider();
SunJaasKerberosClient client = new SunJaasKerberosClient();
client.setDebug(true);
provider.setKerberosClient(client);
provider.setUserDetailsService(kerberosUserService());
return provider;
}
#Bean
public KerberosServiceAuthenticationProvider kerberosServiceAuthenticationProvider() {
KerberosServiceAuthenticationProvider provider = new KerberosServiceAuthenticationProvider();
provider.setTicketValidator(sunJaasKerberosTicketValidator());
provider.setUserDetailsService(kerberosUserService());
return provider;
}
#Bean
public SunJaasKerberosTicketValidator sunJaasKerberosTicketValidator() {
SunJaasKerberosTicketValidator ticketValidator = new SunJaasKerberosTicketValidator();
ticketValidator.setServicePrincipal(kerberosPrincipal);
File f = new File(keytabFile);
try {
LOG.info(String.format("Absolute: %s, Canonical: %s", f.getAbsolutePath(), f.getCanonicalPath()));
if(f.exists()){
LOG.info("File exists.");
}
else{
LOG.info("File DOES NOT exist.");
}
} catch (IOException e) {
e.printStackTrace();
}
ticketValidator.setKeyTabLocation(new FileSystemResource(f));
ticketValidator.setDebug(true);
return ticketValidator;
}
#Bean
public SpnegoAuthenticationProcessingFilter spnegoAuthenticationProcessingFilter(AuthenticationManager authenticationManager) {
SpnegoAuthenticationProcessingFilter filter = new SpnegoAuthenticationProcessingFilter();
filter.setAuthenticationManager(authenticationManager);
return filter;
}
#Bean
public KerberosUserDetailsService kerberosUserService() {
return new KerberosUserDetailsService();
}
Anyway to get this to work for both? I was thinking about making a custom authentication provider that would handle the requests, but wasn't sure if that would work.
Easiest think to do is have to SpringDistpatcherServlets in your Web.xml based on url mapping. Each url mapping is then in a different spring context. Each spring context can then have its own security.
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(kerberosAuthenticationProvider());
auth.authenticationProvider(kerberosServiceAuthenticationProvider());
auth.authenticationProvider(customAuthenticationProvider());
}
What you are doing is adding both authenticationProviders for Kerberos and your custom one to the AuthenticationManagerBuilder. This should register all of them.
What happens during runtime:
There is a ProviderManager handling all of your registered AuthenticationProvider and the build in one.
First it tries to authenticate as anonymous user. If the requested URL is set to permitAll this is the end of the story
Then the ProviderManager iterates over all of your AuthenticationProvider in the order you provided them. It checks if they support the authentication and tries to authenticate with them. If it fails it moves to the next one (saving the exception if there was one)
Finally there is a DaoAuthenticationProvider handling normal username-password-credentials
If any provider succeeds, the user is logged in, otherwise the saved exception is thrown.
Conclusion:
What you did should be quite close if not exactly what you want. For your Kerberos protected endpoint it would use the kerberos AuthenticationProvider. For the other endpoints it would try and fail Kerberos and move on to your custom provider.
If something is still not working I would recommend setting a breakpoint within the class org.springframework.security.authentication.ProviderManager and see how it is handling your provider.