I am trying to use keycloak for authentification by username and password. But at the same time I need to check, if the user from local database with the same name as user in keycloak has some roles in local database, PostgreSQL for example. The problem, I don't need keycloak roles, but need to check roles in local database.
Now my SecurityConfig which extends from KeycloakWebSecurityConfigurerAdapter looks like this:
#KeycloakConfiguration
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
#Autowired
RealmConfig realmConfig;
#Autowired
AuthConfig authConfig;
#Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new NullAuthenticatedSessionStrategy();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
SimpleAuthorityMapper grantedAuthorityMapper = new SimpleAuthorityMapper();
grantedAuthorityMapper.setPrefix("ROLE_");
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
auth.authenticationProvider(keycloakAuthenticationProvider);
}
#Bean
public KeycloakConfigResolver keycloakConfigResolver() {
return new KeycloakSpringBootConfigResolver();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
//roles from local database
String roles = String.join(",", authConfig.roles());
http
.csrf().disable()
.authorizeRequests()
.antMatchers( "/api/gui*")
.hasAnyRole(roles)
.anyRequest()
.permitAll()
.and()
.exceptionHandling().accessDeniedHandler(accessDeniedHandler())
.and().logout().logoutUrl("/api/logout").logoutSuccessUrl("/api/").deleteCookies("OAuth_Token_Request_State", "JSESSIONID").invalidateHttpSession(true);
}
}
But hasAnyRoles check roles from keycloak users. Is there any way to make hasAnyRoles checking roles from local database user?
Related
hello I'm a novice in google api.
I already inserted youtube api authorities in this page https://console.cloud.google.com/apis/credentials/consent.
and then when I check google oauth page, it doesn't show any authorities what I need.
when I click any account, then just go to main page.
and I checked the issue in the intelliJ debug console.
here is only three authorities.
how can I solve this issue?
thank you in advance
here is the code below.
#Service
#RequiredArgsConstructor
public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
private final UserRepository userRepository;
private final HttpSession httpSession;
#Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
OAuth2UserService<OAuth2UserRequest, OAuth2User> delegate = new DefaultOAuth2UserService();
OAuth2User oAuth2User = delegate.loadUser(userRequest);
String registrationId = userRequest.getClientRegistration().getRegistrationId();
String userNameAttributeName = userRequest.getClientRegistration()
.getProviderDetails()
.getUserInfoEndpoint()
.getUserNameAttributeName();
OAuthAttribute attributes = OAuthAttribute.of(registrationId, userNameAttributeName, oAuth2User.getAttributes());
User user = saveOrUpdate(attributes);
httpSession.setAttribute("user", new SessionUser(user));
return new DefaultOAuth2User(
Collections.singleton(new SimpleGrantedAuthority(user.getRoleKey())),
attributes.getAttributes(),
attributes.getNameAttributeKey());
}
private User saveOrUpdate(OAuthAttribute attribute) {
User user = userRepository.findByEmail(attribute.getEmail())
.map(it -> it.update(attribute.getName(), attribute.getPicture()))
.orElse(attribute.toEntity());
return userRepository.save(user);
}
}
#RequiredArgsConstructor
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final CustomOAuth2UserService customOAuth2UserService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.headers().frameOptions().disable()
.and()
.authorizeRequests()
.antMatchers("/", "/css/**", "/images/**", "/js/**", "/h2-console/**").permitAll()
.antMatchers("/api/v1/**").hasRole(RoleType.USER.name())
.antMatchers("/welcome").permitAll()
.anyRequest().authenticated()
.and()
.logout()
.logoutSuccessUrl("/")
.and()
.oauth2Login()
.userInfoEndpoint()
.userService(customOAuth2UserService);
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.requestMatchers(PathRequest.toStaticResources().atCommonLocations());
}
}
Youtube Data api is channel based not not user based. You need to pick which channel you want to grant access the application access to. After that you will see the consent screen where it tells you what scopes of access the application is requesting.
I am trying to implement Single Sign On with Spring Security OAuth2 and JWT.
I use two separate applications:
An Authorization Server – which is the central authentication mechanism
Client Application: the applications using SSO
When a user tries to access a secured page in the client app, they’ll be redirected to authenticate first, via the Authentication Server.
And I am using the Authorization Code grant type out of OAuth2 to drive the delegation of authentication.
Authorization server:
#Configuration
#EnableAuthorizationServer
public class OAuth2AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
public static final Logger LOGGER = LoggerFactory.getLogger(AuthorizationServerConfigurerAdapter.class);
#Autowired
private AuthenticationManager authenticationManager;
#Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
#Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("abcd");
return converter;
}
#Bean
#Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
defaultTokenServices.setSupportRefreshToken(true);
defaultTokenServices.setTokenEnhancer(accessTokenConverter());
return defaultTokenServices;
}
#Override
public void configure(ClientDetailsServiceConfigurer clientDetailsServiceConfigurer) throws Exception {
clientDetailsServiceConfigurer
.inMemory()
.withClient("webapp")
.secret("Pass")
.authorizedGrantTypes("implicit", "refresh_token", "password", "authorization_code")
.scopes("user_info")
.autoApprove(true);
}
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.authenticationManager(authenticationManager);
}
}
Security Configuration on Authorization Server
#Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Value("${ldap.url}")
private String ldapUrl;
#Value("${ldap.userDnPatterns}")
private String ldapUserDnPatterns;
#Autowired
private PersonService personService;
#Autowired
private RoleService roleService;
#Override
protected void configure(HttpSecurity http) throws Exception { // #formatter:off
http.requestMatchers()
.antMatchers("/login", "/oauth/authorize")
.and()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.formLogin()
.permitAll();
} // #formatter:on
#Bean(name = "authenticationManager")
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.authenticationProvider(this.ldapAndDatabaseAuthenticationProvider());
}
#Bean(name="ldapAuthenticationProvider")
public AuthenticationProvider ldapAndDatabaseAuthenticationProvider(){
LdapUserDetailsMapper userDetailsMapper = new LdapUserDetailsMapper();
userDetailsMapper.setRoleAttributes(new String[]{"groupMembership"});
LdapAndDatabaseAuthenticationProvider provider =
new LdapAndDatabaseAuthenticationProvider(
this.ldapAuthenticator(),
this.ldapAuthoritiesPopulator(),
this.personService);
provider.setUserDetailsContextMapper(userDetailsMapper);
return provider;
}
#Bean( name = "ldapAuthoritiesPopulator" )
public LdapAndDatabaseAuthoritiesPopulator ldapAuthoritiesPopulator(){
return new LdapAndDatabaseAuthoritiesPopulator(this.contextSource(), "", roleService);
}
#Bean( name = "ldapAuthenticator" )
public LdapAuthenticator ldapAuthenticator() {
BindAuthenticator authenticator = new BindAuthenticator( this.contextSource() );
authenticator.setUserDnPatterns(new String[]{"cn={0},ou=prod,o=TEMP"});
return authenticator;
}
#Bean( name = "contextSource" )
public DefaultSpringSecurityContextSource contextSource() {
DefaultSpringSecurityContextSource contextSource =
new DefaultSpringSecurityContextSource( ldapUrl );
return contextSource;
}
}
application.properties:
server.port=8888
server.context-path=/auth
security.basic.enabled=false
When I login the client application, It correctly forwards to Authorization Server for Single Sign On.
I enter the user credentials. User successfully get authenticated, but then I see the below error on browser:
OAuth Error
error="invalid_grant", error_description="A redirect_uri can only be
used by implicit or authorization_code grant types."
URL Shows:
http://localhost:8888/auth/oauth/authorize?client_id=webapp&redirect_uri=http://localhost:8080/jwt/webapp&response_type=code&state=LGvAzj
I also see the below at the log:
02:14:43.610 [http-nio-8888-exec-6] DEBUG o.s.s.o.p.e.FrameworkEndpointHandlerMapping/getHandlerInternal Looking up handler method for path /oauth/authorize
02:14:43.614 [http-nio-8888-exec-6] DEBUG o.s.s.o.p.e.FrameworkEndpointHandlerMapping/getHandlerInternal Returning handler method [public org.springframework.web.servlet.ModelAndView org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint.authorize(java.util.Map<java.lang.String, java.lang.Object>,java.util.Map<java.lang.String, java.lang.Str ing>,org.springframework.web.bind.support.SessionStatus,java.security.Principal)]
02:14:43.849 [http-nio-8888-exec-6] INFO o.s.s.o.p.e.AuthorizationEndpoint/handleOAuth2Exception Handling OAuth2 error: error="invalid_grant", error_description="A redirect_uri can only be used by implicit or authorization_code grant types."
Can you please help me to find the problem?
UPDATE
Actually, Dur is right. This configuration is correct and works fine. I had another configuration file which configures JdbcClientDetails and it was overwriting the clientDetailsService created with inmemory in this configuration.
I have added "spring-boot-starter-actuator" dependency to my spring-boot project.
The project already has form based security.
The root context for the application is "/".
I have added the actuator at the context root "/actuators" by adding to application.yaml:
management:
context-path: /actuators
The non-sensitive actuators are working, such as "health".
When I try to access the sensitive actuators, the popup appears for username/password. The authentication takes place, but then I see "403" Access is Denied.
Here is the configuration for Web security:
#Configuration
#EnableWebSecurity
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private AuthenticationLookupService authenticationLookupService;
private AuthenticationManagerBuilder authenticationManagerBuilder;
private UrlSuccessAuthenticationHandler successHandler;
#Autowired
public void setAuthenticationLookupService(AuthenticationLookupService authenticationLookupService) {
this.authenticationLookupService = authenticationLookupService;
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
this.authenticationManagerBuilder = auth;
}
#Autowired
public void setSuccessHandler(UrlSuccessAuthenticationHandler successHandler) {
this.successHandler = successHandler;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/", "/index.html", "/auth/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/index.html").successHandler(successHandler)
.permitAll()
.and()
.logout()
.permitAll();
http.csrf().disable(); // todo: fix later
}
#PostConstruct
public void process() throws Exception {
this.authenticationManagerBuilder.userDetailsService(this.authenticationLookupService).passwordEncoder(new ShaPasswordEncoder());
}
}
Add below values in application.properties or application.yml and then when popup asks for username and password provide this credentials
security.user.name=admin
security.user.password=secret
If you are providing your values check if that user has ADMIN role, because actuator needs ADMIN role user to access sensitive end points.
Update
If you are using spring-boot 1.5.*+ then user should have ACTUATOR role
We have a dedicated authorization server extending AuthorizationServerConfigurerAdapter, where we have set authorities overriding void configure(ClientDetailsServiceConfigurer clients) method.
#Configuration
#EnableAuthorizationServer
protected static class OAuth2Config extends AuthorizationServerConfigurerAdapter {
#Value('${oauth.clientId}')
private String clientId
#Value('${oauth.secret:}')
private String secret
#Value('${oauth.resourceId}')
private String resourceId
#Autowired
#Qualifier('authenticationManagerBean')
private AuthenticationManager authenticationManager
#Bean
public JwtAccessTokenConverter accessTokenConverter() {
return new JwtAccessTokenConverter();
}
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.checkTokenAccess("permitAll()")
oauthServer.allowFormAuthenticationForClients()
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager)
.accessTokenConverter(accessTokenConverter())
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient(clientId)
.secret(secret)
.authorizedGrantTypes("password", "authorization_code", "refresh_token", "implicit")
.authorities("USER", "ADMIN")
.scopes("read", "write", "trust")
.resourceIds(resourceId)
}
Now how to use the authorities in the resource server for role based authorization.
We are able to authenticate via authorization server generated token.
Need help.
In the resource server you should extend the ResourceServerConfigurerAdapter to configure the requestMatchers and set the role for each resource.
#Configuration
#EnableResourceServer
public class OAuth2Config extends ResourceServerConfigurerAdapter {
#Value("${keys.public}")
private String publicKey;
#Override
public void configure(HttpSecurity http) throws Exception {
http
.requestMatchers()
.antMatchers("/**")
.and()
.authorizeRequests()
.antMatchers("/service1/**").access("#oauth2.hasScope('ADMIN')")
.antMatchers("/service2/**").access("#oauth2.hasScope('USER')");
}
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.tokenStore(tokenStore());
}
#Bean
public TokenStore tokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}
#Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter tokenConverter = new JwtAccessTokenConverter();
tokenConverter.setVerifierKey(publicKey);
return tokenConverter;
}
}
You have received a token from the auth server. You can now use that token to make another request to the auth server to retrieve the user object. This json object would contain roles(authority).
The request would look like as follows.
curl -H "Authorization: Bearer 2a953581-e9c9-4278-b42e-8af925f49a99"
http://localhost:9999/uaa/user
In order to do this, you need to create user service endpoint and implement UserDetailsService also.
#RequestMapping("/user")
public Principal user(Principal user) {
return user;
}
#Bean
UserDetailsService userDetailsService.....
The role list is created and set in the org.springframework.security.core.userdetailsin the UserDetailsService.User as follows.
AuthorityUtils.createAuthorityList("ROLE_USER", "ROLE_ADMIN"));
I'm in the process of building separate resource server and authhorization server. For now i'm using the user-info-uri in resource server to extract the Principal from the authorization server matching the access-token, with config:
spring:
oauth2:
resource:
userInfoUri: http://localhost:9999/uaa/user
In the resource server I have protected endpoints, based on role, as follows:
http
.authorizeRequests()
.antMatchers("/invoices/**").hasRole("END_USER")
.anyRequest().authenticated();
When I manually access the user-info-uri, I can see that the the authorities contain:
"authority": "ROLE_END_USER"
But when I try to access the /invoices resource I get an Access-Denied exception, and in the log I see:
OAuth2Authentication#bc5074a8: Principal: my-login; Credentials: [PROTECTED]; Authenticated: true; Details: remoteAddress=0:0:0:0:0:0:0:1, tokenType=BearertokenValue=<TOKEN>; Granted Authorities: ROLE_USER
Authoriteis = "ROLE_USER". Where does that come from, should'nt it be "ROLE_END_USER" at this point also?
I've seen implementations using a shared database for the storage of tokens, is that really necessary for what I want to achive?
Resource Server:
#SpringBootApplication
#EnableOAuth2Resource
public class EndUserResourceServiceApplication extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/invoices/**").hasRole("END_USER")
.anyRequest().authenticated();
}
public static void main(String[] args) {
SpringApplication.run(EndUserResourceServiceApplication.class, args);
}
}
Auth Server:
#SpringBootApplication
#RestController
#EnableResourceServer
public class ApiAuthServerApplication extends ResourceServerConfigurerAdapter {
#Configuration
#EnableWebSecurity
#Order(-10)
protected static class LoginConfig extends WebSecurityConfigurerAdapter {
#Autowired
private CustomUserDetailsService userDetailsService;
#Bean(name = "authenticationManagerBean")
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
#Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin().permitAll()
.and()
.requestMatchers().antMatchers("/login", "/oauth/authorize", "/oauth/confirm_access")
.and()
.authorizeRequests().anyRequest().authenticated();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
#Configuration
#EnableAuthorizationServer
protected static class OAuth2Config extends AuthorizationServerConfigurerAdapter {
#Autowired
private TokenStore tokenStore;
#Autowired
#Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
#Autowired
private CustomUserDetailsService userDetailsService;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.authenticationManager(this.authenticationManager)
.userDetailsService(this.userDetailsService)
.tokenStore(this.tokenStore);
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("my-client")
.secret("our_s3cret")
.authorities("ROLE_CLIENT")
.authorizedGrantTypes("implicit", "password", "refresh_token")
.redirectUris("http://anywhere")
.scopes("read")
.autoApprove(true);
}
#Bean
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
}
public static void main(String[] args) {
SpringApplication.run(ApiAuthServerApplication.class, args);
}
#RequestMapping("/user")
public Principal user(Principal user) {
return user;
}
}
Summary:
Can I use user-info-uri for validating access-tokens and to use "hasRole"
Is it necessary to use shared database for token storage when using separate resource server and authorization server?
I ended up using a shared token store, which works well at the moment