Spring Security 5 OAuth2 select a client programmatically - spring

I have an application with Spring Security 5 OAuth2 support. I have a number of OAuth2 clients to select from. I would like to execute programatic logic and, based on the incoming request, to select the target client. Now I got it to work using a custom login page and executing the logic in this custom page's controller.
In my configuration:
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
logger.info("SecurityConfig.filterChain(): Entry ...");
http.authorizeRequests(authorizeRequests -> {
try {
authorizeRequests.antMatchers("/oauth2/custom-login-page").permitAll().anyRequest().authenticated().and().
oauth2Login().loginPage("/oauth2/custom-login-page");
}
catch (Exception exception) {
exception.printStackTrace();
}
});
return http.build();
}
In the controller:
#GetMapping("/oauth2/custom-login-page")
public String getCustomLoginPage(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, HttpSession httpSession, Model model) throws Exception {
String targetClientRegistrationID = null;
// Custom logic to pick registrationId
...
return "redirect:/oauth2/authorization/" + targetClientRegistrationID;
}
I would like to confirm that this is the right way to do it. If not, what would be the right/alternative way(s) to wire in such selection logic.
Thank you!

You can specify multiple oauth2 providers in the config (fe: application.yml) file and it should be handled by the framework:
spring:
security:
oauth2:
client:
registration:
<custom-oauth-name>:
client-id: <client-id>
client-secret: <secret>
provider:
<custom-oauth-name>:
issuer-uri: https://accounts.google.com
More you can read here https://www.baeldung.com/spring-security-openid-connect#discovery

Related

Spring oauth2login oidc grant access based on user info

I'm trying to set up Authentication based on this tutorial: https://www.baeldung.com/spring-security-openid-connect part 7 specifically.
I have filled properties and configured filter chain like this:
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests(authorizeRequests -> authorizeRequests
.anyRequest().authenticated())
.oauth2Login(oauthLogin -> oauthLogin.permitAll());
return http.build();
}
which works, but now all users from oidc can connect log in. I want to restrict access based on userinfo. E.g. add some logic like:
if(principal.getName() == "admin") {
//allow authentication
}
are there any way to do it?
I tried to create customer provider like suggested here: Add Custom AuthenticationProvider to Spring Boot + oauth +oidc
but it fails with exception and says that principal is null.
You can retrieve user info when authentication is successful and do further checks based user info.
Here is sample code that clears security context and redirects the request:
#Component
public class OAuth2AuthenticationSuccessHandler implements AuthenticationSuccessHandler {
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
if(authentication instanceof OAuth2AuthenticationToken) {
OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) authentication;
// OidcUser or OAuth2User
// OidcUser user = (OidcUser) token.getPrincipal();
OAuth2User user = token.getPrincipal();
if(!user.getName().equals("admin")) {
SecurityContextHolder.getContext().setAuthentication(null);
SecurityContextHolder.clearContext();
redirectStrategy.sendRedirect(request, response, "login or error page url");
}
}
}
}
Are you sure that what you want to secure does not include #RestController or #Controller with #ResponseBody? If so, the client configuration you are referring to is not adapted: you need to setup resource-server configuration for this endpoints.
I wrote a tutorial to write apps with two filter-chains: one for resource-server and an other one for client endpoints.
The complete set of tutorials the one linked above belongs to explains how to achieve advanced access-control on resource-server. Thanks to the userAuthoritiesMapper configured in resource-server_with_ui, you can write the same security expressions based on roles on client controller methods as I do on resource-server ones.

OAuth2 with Google and Spring Boot - I can't log out

I've been trying to get a successful Oauth2 login with Google and Spring Boot for a while now. This only works partially. Why partly - because I can't manage the logout or when I pressed the logout button I see an empty, white browser page with my URL (http://localhost:8181/ben/"). After a refresh of the page I get error from google, but if I open a new tab, enter my url, I'm still logged in to google, because I can see my user, which I'm outputting to my react application.
#SpringBootApplication
#EnableOAuth2Sso
#RestController
#CrossOrigin
public class SocialApplication extends WebSecurityConfigurerAdapter {
public static void main(String[] args) {
SpringApplication.run(SocialApplication.class, args);
}
#RequestMapping("/user")
public Principal user(Principal principal) {
return principal;
}
#RequestMapping("/logout")
public String fetchSignoutSite(HttpServletRequest request, HttpServletResponse response) {
Cookie rememberMeCookie = new Cookie("JSESSIONID", "");
rememberMeCookie.setMaxAge(0);
response.addCookie(rememberMeCookie);
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null) {
new SecurityContextLogoutHandler().logout(request, response, auth);
}
auth.getPrincipal();
return "redirect:/ben/login";
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**").authorizeRequests().antMatchers("/ben/*").permitAll().anyRequest().authenticated().and()
.logout().logoutSuccessUrl("http://localhost:8181/ben/login").invalidateHttpSession(true)
.clearAuthentication(true).deleteCookies("JSESSIONID");
}
My application.yml file looks like this:
# Spring Boot configuration
spring:
profiles:
active: google
# Spring Security configuration
security:
oauth2:
client:
clientId: 415772070383-3sapp4flauo6iqsq8eag7knpcii50v9k.apps.googleusercontent.com
clientSecret: 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
accessTokenUri: https://www.googleapis.com/oauth2/v4/token
userAuthorizationUri: https://accounts.google.com/o/oauth2/v2/auth
clientAuthenticationScheme: form
scope:
- email
- profile
resource:
userInfoUri: https://www.googleapis.com/oauth2/v3/userinfo
preferTokenInfo: true
# Server configuration
server:
port: 8181
servlet:
context-path: /ben
That fetchSignoutSite only emptying the JsessionId and logging out from Spring Security context. So you would still need to add part where you go to google and sign out from there which I have no experience on implementation.

How to add Jwt Token based security In Microservices

In my microservices, I will try to implement Jwt spring-security, But I don't know how to apply it.
In my microservices, I have used the 2020.0.3 spring cloud version.
In user services, I have connected the department service using the Rest template.
I need help with how to add Jwt security in these microservices.
This is 4 microservices
Server = Eureka Server
service-API-gateway = Spring cloud Apigateway
service-department & services-user = These two microservices connect with Rest template
Microservices Project Structure
: https://i.stack.imgur.com/ajTiX.png
So at a higher level, Spring Security is applied on controller level when using jwt as authentication. First you need to add a Security config that will extend WebSecurityConfigurerAdapter (this is common for http based security) and in that class you need to define configure method like:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic().disable()
.csrf().disable() // IF your clients connect without a cookie based, this will be fine
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/register", "/login","/your_open_endpoints_etc").permitAll()
.and()
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
}
Then in the filter class which extends OncePerRequestFilter, you can define the do filter like this, you have to set the UsernamePasswordAuthenticationFilter instance inside the Spring authentication context:
#Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
logger.info("do filter...");
String token = jwtProvider.getTokenFromRequest((HttpServletRequest) httpServletRequest);
try{
if (token != null && jwtProvider.validateToken(token)) {
String username = jwtProvider.getUsernameFromToken(token);
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(username, null, jwtProvider.getAuthorities(token));
SecurityContextHolder.getContext().setAuthentication(auth);
}
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
catch (RuntimeException e)
{
// Some general Exception handling that will wrap and send as HTTP Response
}
}
Check on the extending filters further, they might change as per your requirement
finally in rest endpoints you can safe guard like:
#PreAuthorize("hasRole('ROLE_YOURROLE')")
#GetMapping(path = "/your_secured_endpoint", consumes = "application/json",
produces = "application/json")
public ResponseEntity<List<SomePOJOObject>> getAllAppointmentsForPatient()
{
return new ResponseEntity<>(thatSomePOJOObjectListYouWant, HttpStatus.OK);
}

External keycloak server and Spring boot app behind reverse proxy - redirect to root context after success login

My spring boot service is working behind reverse proxy and secured by external keycloak server.
After successful login at Keycloak server it redirects me to my service and then I get redirect to root of context path instead of initial url.
So request chain is looks like:
initial url: http://~HOSTNAME~/~SERVICE-NAME~/rest/info/654321
and redirects:
http://~HOSTNAME~/~SERVICE-NAME~/rest/sso/login
https://ext-keycloak.server/auth/realms/test/protocol/openid-connect/auth?response_type=code&client_id=dev&redirect_uri=http%3A%2F%2F~HOSTNAME~%2F~SERVICE-NAME~%2Frest%2Fsso%2Flogin&state=60ebad0d-8c68-43cd-9461&login=true&scope=openid
http://~HOSTNAME~/~SERVICE-NAME~/rest/sso/login?state=60ebad0d-8c68-43cd-9461&session_state=074aaa0d-4f72-440e&code=a8c92c50-70f8-438c-4fe311f0b3b6.074aaa0d-440e-8726.8166b689-bbdd-493a-8b8f
http://~HOSTNAME~/~SERVICE-NAME~/rest/ - I have no handlers here and getting error.
First problem was that application generated wrong redirect uri for keycloak. All services are in kubernetes cluster and have urls like: http://~HOSTNAME~/~SERVICE-NAME~/rest (where '/rest' is context path).
~SERVICE-NAME~ part is used to locate service in cluster and application gets request without this prefix. But proxy adds header X-Original-Request with original url and I decided to use it (unfortunately I can't change configuration of proxy and keycloak servers). I made filter to use header value to generate correct redirect uri by copy-pasting from Spring's org.springframework.web.filter.ForwardedHeaderFilter. Now it generates correct redirect_uri but I'm getting wrong redirect at the end as described above.
How can I get redirect to initial page in this case?
Spring security config:
#EnableWebSecurity
#ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
private final PermissionConfig permissionConfig;
#Autowired
public SecurityConfig(PermissionConfig permissionConfig) {
this.permissionConfig = permissionConfig;
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new NullAuthoritiesMapper());
auth.authenticationProvider(keycloakAuthenticationProvider);
}
#Bean
public KeycloakSpringBootConfigResolver KeycloakConfigResolver() {
return new KeycloakSpringBootConfigResolver();
}
#Bean
#Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
var urlRegistry = http.authorizeRequests()
.antMatchers("/actuator/**")
.permitAll()
.antMatchers("/info/**")
.hasAnyAuthority(permissionConfig.getRoles().toArray(new String[0]));
}
#Bean
public FilterRegistrationBean<OriginalUriHeaderFilter> originalUriHeaderFilter() {
OriginalUriHeaderFilter filter = new OriginalUriHeaderFilter();
FilterRegistrationBean<OriginalUriHeaderFilter> registration = new FilterRegistrationBean<>(filter);
registration.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ASYNC, DispatcherType.ERROR);
registration.setOrder(Ordered.HIGHEST_PRECEDENCE);
return registration;
}
}
spring keycloak config (yaml)
keycloak:
auth-server-url: 'https://ext-keycloak.server/auth/'
realm: test
ssl-required: NONE
resource: dev
credentials:
secret: 'hex-value'
confidential-port: 0
disable-trust-manager: true
For future searchs, an another approach is to use the KeycloakCookieBasedRedirect.createCookieFromRedirectUrl method, that includes a cookie for keycloak redirect.
Uh, the problem was with service prefix not with Keycloak.
When I tryed to get page Spring set JSESSIONID cookie with path=/rest, stored request to session and redirected me to Keycloak. After login Spring couldn't find session and redirected me to root context because browser didn't provide JSESSIONID cookie for path /~SERVICE-NAME~/rest !!
By default Spring sets cookie path=server.servlet.contextPath. All I've done just added cookie path to application.yaml:
server:
port: 8080
servlet:
contextPath: /rest
session:
cookie:
path: /
Spring Server Properties
Spring DebugFilter is quite useful thing

Service-to-service communication with feign in spring security 5

in fact the spring-oauth project turn into maintenance mode we trying migrate our application into pure spring security 5 which support resource server configuration as well.
Our actual resource server configuration looks like this:
#Configuration
#EnableResourceServer
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class OAuth2ResourceServerConfig extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests(
authorizeRequests -> {
authorizeRequests.antMatchers("/api/unsecured/**").permitAll();
authorizeRequests.anyRequest().authenticated();
}
);
}
#Bean
#ConfigurationProperties(prefix = "security.oauth2.client")
public ClientCredentialsResourceDetails clientCredentialsResourceDetails() {
return new ClientCredentialsResourceDetails();
}
#Bean
public TokenStore jwkTokenStore() {
return new JwkTokenStore("http://localhost:8080/...", new JwtAccessTokenConverter());
}
#Bean
public RequestInterceptor oauth2FeignRequestInterceptor(){
return new OAuth2FeignRequestInterceptor(new DefaultOAuth2ClientContext(), clientCredentialsResourceDetails());
}
#Bean
public OAuth2RestTemplate clientCredentialsRestTemplate() {
return new OAuth2RestTemplate(clientCredentialsResourceDetails());
}
}
and these properties:
security:
oauth2:
client:
client-id: service-id
client-secret: secret
access-token-uri: http://localhost:8081/oauth/token
This resource server is configured to work with jwt token. To verify token uses rsa public key from link passes to jwkstore. It is also able call another resource server with Feign.
And this is new configuration:
#Configuration
static class OAuth2ResourceServerConfig extends WebSecurityConfigurerAdapter {
private final JwtDecoder jwtDecoder;
ResourceServerConfiguration(JwtDecoder jwtDecoder) {
this.jwtDecoder = jwtDecoder;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests(authorizeRequests -> authorizeRequests
.antMatchers("/public/unsecured/**").permitAll()
.anyRequest().authenticated())
.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
.oauth2ResourceServer(oauth2ResourceServer -> oauth2ResourceServer
.jwt(jwtConfigurer -> {
jwtConfigurer.decoder(NimbusJwtDecoder.withJwkSetUri("http://localhost:8080/...").build());
jwtConfigurer.jwtAuthenticationConverter(tokenExtractor());
})
);
}
This configuration works fine to decode and verify tokens, but Feign doesn't work. Previous configuration with spring oauth2 supports Oauth2 feign interceptor which call authorization server to get its own access token. But I don't know how to configure this in spring security 5. This is flow which I need:
frontend client call spring resource server A with token
resource server A need data from resource server B
resource server A call authorization server to get access token with client_credentials grant type
resource server A call resource server B with its access token set to request header by feign
resource server A return all data to frontend client
Can you tell me how to configure 3. and 4. step in spring security 5 without spring's oauth project? Thank you.

Resources