Spring Boot not fetching Keycloak Roles - spring-boot

I'm trying to implement role based authentication in my Spring Boot app which uses Keycloak. I defined a realm role and I annotated my controller with a #RolesAllowed, it didn't use the roles defined in Keycloak. Attached is SecurityConfiguration to show how I attempted to define authorization and authentication and a snippet from the WorkQueue controller class.
SecurityConfiguration.java:
package com.mycompany.myapp.configurations;
import org.keycloak.adapters.springboot.KeycloakSpringBootConfigResolver;
import org.keycloak.adapters.springsecurity.KeycloakConfiguration;
import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider;
import org.keycloak.adapters.springsecurity.config.KeycloakWebSecurityConfigurerAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
// configure in extends WebSecurityConfigurerAdapter
#EnableWebSecurity
#EnableGlobalMethodSecurity(jsr250Enabled = true)
#KeycloakConfiguration
#Import(KeycloakSpringBootConfigResolver.class)
public class SecurityConfiguration extends KeycloakWebSecurityConfigurerAdapter {
/**
* Registers the KeycloakAuthenticationProvider with the authentication manager.
*/
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
KeycloakAuthenticationProvider keycloakAuthenticationProvider = new KeycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
auth.authenticationProvider(keycloakAuthenticationProvider);
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers(
// Client-side JS
// TODO
// "/**",
// the standard favicon URI
"/favicon.ico",
// the robots exclusion standard
"/robots.txt",
// web application manifest
"/manifest.webmanifest", "/sw.js", "/offline.html",
// icons and images
"/icons/**", "/images/**", "/styles/**", "/css/**", "/js/**",
"/sw-runtime-resources-precache.js");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/work_queue/**").permitAll()//hasRole("myapp")
.antMatchers("/actuator/**").permitAll()
.anyRequest().authenticated().and().oauth2Login()
.and()
.logout()
.invalidateHttpSession(true)
.clearAuthentication(true)
.logoutSuccessUrl("/login?logout")
.deleteCookies("JSESSIONID")
.permitAll()
.and()
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
// .permitAll().and().authorizeRequests().anyRequest().authenticated().and().oauth2Login();
// .formLogin()
// .loginPage("/login")
// .permitAll();
}
#Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(buildSessionRegistry());
}
#Bean
protected SessionRegistry buildSessionRegistry() {
return new SessionRegistryImpl();
}
}
Snippet from WorkQueueController.java
#Controller
#RequestMapping("/work_queue")
public class WorkQueueController
#GetMapping
#RolesAllowed("my-role") // Line A
public String work_queue(Principal principal, Model model) {
log.debug("Principal is a "+principal.getClass().getName());
OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) principal;
Object principal2 = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
Collection<? extends GrantedAuthority> authorities = SecurityContextHolder.getContext().getAuthentication().getAuthorities();
for(GrantedAuthority authority : authorities) {
log.debug("Authority: "+authority.getAuthority());
}
log.debug("Principal2 is a "+principal2.getClass().getName());
DefaultOidcUser user = null;
if(user!=null && user instanceof DefaultOidcUser) {
user = (DefaultOidcUser) principal2;
}
// log.debug("Roles "+simpleKeycloakAccount.getRoles());
Object principal3 = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if(principal3 != null && principal3 instanceof DefaultOidcUser) {
DefaultOidcUser user3 = (DefaultOidcUser)principal3;
// user = (DefaultOidcUser) principal;
for(String key : user3.getAttributes().keySet()) {
Object value = user3.getAttributes().get(key);
log.debug("user3 key={} value={}",key,value);
}
}
}
When Line A is present, I get a 403 (forbidden). When I comment line A, the method executes, and I'm able to see values I defined in Keycloak:
Principal is a org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken
Authority: ROLE_USER
Authority: SCOPE_email
Authority: SCOPE_openid
Authority: SCOPE_profile
Principal2 is a org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser
user3 key=at_hash value=<some-hash-value>
user3 key=sub value=<some-guid>
user3 key=email_verified value=true
user3 key=iss value=https://auth.mycompanytest.com/auth/realms/MyRealmk
user3 key=typ value=ID
user3 key=preferred_username value=ben.pracht#ruralsourcing.com
user3 key=given_name value=Woods
user3 key=nonce value=CZfW09_IKZbF5e0QhL-cDC4Uvk0QIJvmMqys0VmczlQ
user3 key=aud value=[myapp-app]
user3 key=acr value=0
user3 key=azp value=myapp-app
user3 key=auth_time value=2022-06-20T22:56:52Z
user3 key=name value=Woods Man
user3 key=exp value=2022-06-20T23:11:44Z
user3 key=session_state value=aa3e95e1-47e5-463b-a599-bf486a40fc00
user3 key=family_name value=Man
user3 key=iat value=2022-06-20T23:06:44Z
user3 key=email value=<my-email-address>
user3 key=jti value=d0f1b89f-ee20-424f-b12e-1ea869f5e712
So, I conclude that it reached Keycloak, but it decided to not use or fetch Keycloak roles. Why?
Bonus points for telling me if how a role defined in a client works differently than a role defined in a realm.
<note: for privacy reasons, various things above hidden to protect the innocent>

Related

Why am I redirected directly to login page instead of index.html in Spring?

I am learning to design a login page in Spring Boot. I have not created any file named login, instead I have created an index page but by default it is redirecting me to login page?
package com.main.medipp;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import javax.sql.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
#SuppressWarnings("deprecation")
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private DataSource dataSource;
#Bean
public UserDetailsService userDetailsService() {
return (UserDetailsService) new CustomUserDetailsService();
}
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService());
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/sign_up").permitAll()
.antMatchers("/users").authenticated()
.anyRequest().permitAll()
.and()
.formLogin()
.usernameParameter("email")
.defaultSuccessUrl("/users")
.permitAll()
.and()
.logout().logoutSuccessUrl("/").permitAll();
}
}
This is the code , i used , please help me with what changes should i make now!!
If you have secured your / endpoint, you will get redirected to the default login page. To fix that, add a rule to the / endpoint to disable authentication and redirect to the index page via a Controller.

Spring boot security - cannot add role and authorities together for inMemoryAuthentication

This is my Configuration class, where is register the users and the url matchers.
package com.example.security;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin")
.password(passwordEncoder().encode("admin"))
.authorities("API_TEST1","API_TEST2")
.roles("ADMIN")
.and().withUser("test")
.password(passwordEncoder().encode("test1234"))
.authorities("API_TEST1")
.roles("USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/api/test1").hasAuthority("API_TEST1")
.antMatchers("/api/test2").hasAuthority("API_TEST2")
.and().httpBasic();
}
#Bean
public PasswordEncoder passwordEncoder()
{
return new BCryptPasswordEncoder();
}
}
For some reason the roles and authorities do not work together, they work when I disable one of them:
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin")
.password(passwordEncoder().encode("admin"))
.authorities("API_TEST1","API_TEST2")
.roles("ADMIN")
.and().withUser("test")
.password(passwordEncoder().encode("test1234"))
.authorities("API_TEST1")
.roles("USER");
}
When I login with admin, admin I can enter the /admin section, but entering the api (api/test1 or api/test2) gives me a 403 error, while I should be able to enter both.
Here is my complete project:
https://github.com/douma/spring-security-exp

org.springframework.security.access.AccessDeniedException: Access is denied

I'm trying to implement OAuth in my spring boot rest server. Here's my configurations
configuration\AuthorizationServerConfig.java
package com.vcomm.server.configuration;
import com.vcomm.server.service.util.CustomAuthenticationKeyGenerator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.event.EventListener;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.event.AuthenticationSuccessEvent;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import javax.annotation.Resource;
import javax.sql.DataSource;
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Resource(name = "roomUserDetailsService")
UserDetailsService userDetailsService;
#Autowired
private DataSource dataSource;
#Bean
public TokenStore tokenStore() {
JdbcTokenStore tokenStore = new JdbcTokenStore(dataSource);
tokenStore.setAuthenticationKeyGenerator(new CustomAuthenticationKeyGenerator());
return tokenStore;
}
#Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("123");
return converter;
}
#Bean
#Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
defaultTokenServices.setSupportRefreshToken(true);
defaultTokenServices.setAuthenticationManager(authenticationManager);
return defaultTokenServices;
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource);
}
#Autowired
private AuthenticationManager authenticationManager;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
.pathMapping("/oauth/authorize", Constant.AUTH_V1 + "/oauth/authorize")
.pathMapping("/oauth/check_token", Constant.AUTH_V1 + "/oauth/check_token")
.pathMapping("/oauth/confirm_access", Constant.AUTH_V1 + "/auth/v1/oauth/confirm_access")
.pathMapping("/oauth/error", Constant.AUTH_V1 + "/oauth/error")
.pathMapping("/oauth/token", Constant.AUTH_V1 + "/oauth/token")
.pathMapping("/oauth/token_key", Constant.AUTH_V1 + "/oauth/token_key")
.tokenStore(tokenStore())
.userDetailsService(userDetailsService)
.authenticationManager(authenticationManager);
}
#EventListener
public void authSuccessEventListener(AuthenticationSuccessEvent authorizedEvent){
// write custom code here for login success audit
System.out.println("User Oauth2 login success");
System.out.println("This is success event : "+authorizedEvent.getSource());
}
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
oauthServer.allowFormAuthenticationForClients();
}
}
configuration\ResourceServerConfig.java
package com.vcomm.server.configuration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
#Configuration
#EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/api/**")
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.exceptionHandling()
.accessDeniedHandler(new OAuth2AccessDeniedHandler());
}
#Autowired
TokenStore tokenStore;
#Override
public void configure(ResourceServerSecurityConfigurer config) {
config.tokenServices(tokenServicesResourceServer());
}
#Autowired
private AuthenticationManager authenticationManager;
#Bean
public DefaultTokenServices tokenServicesResourceServer() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore);
defaultTokenServices.setAuthenticationManager(authenticationManager);
return defaultTokenServices;
}
}
configuration\SpringWebSecurityConfig.java
package com.vcomm.server.configuration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SpringWebSecurityConfig extends WebSecurityConfigurerAdapter {
#Configuration
#Order(1001)
public static class superAdminWebSecurityConfig extends WebSecurityConfigurerAdapter {
#Resource(name = "emUserDetailsService")
UserDetailsService emUserDetailsService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.maximumSessions(2)
.maxSessionsPreventsLogin(true);
http
.csrf().disable();
http
.httpBasic()
.disable();
http
.authorizeRequests()
.antMatchers("/superadmin/api/v1/login")
.permitAll();
http
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/superadmin/api/v1/logout", "POST"))
.deleteCookies("JSESSIONID")
.logoutSuccessHandler((httpServletRequest, httpServletResponse, authentication) -> httpServletResponse.setStatus(HttpServletResponse.SC_NO_CONTENT))
.invalidateHttpSession(true);
http
.antMatcher("/superadmin/**")
.authorizeRequests()
.antMatchers("/superadmin/**").hasAuthority("ADMIN_PRIVILEGE");
}
#Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
#Override
#Bean
#Primary
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean(name = "emAuthenticationProvider")
public AuthenticationProvider emAuthenticationProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setPasswordEncoder(passwordEncoder());
provider.setUserDetailsService(emUserDetailsService);
return provider;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(emUserDetailsService);
auth.authenticationProvider(emAuthenticationProvider());
}
}
#Configuration
#Order(1002)
public static class adminWebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.maximumSessions(2)
.maxSessionsPreventsLogin(true);
http
.csrf().disable();
http
.httpBasic()
.disable();
http
.authorizeRequests()
.antMatchers("/admin/api/v1/login")
.permitAll();
http
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/admin/api/v1/logout", "POST"))
.deleteCookies("JSESSIONID")
.logoutSuccessHandler((httpServletRequest, httpServletResponse, authentication) -> httpServletResponse.setStatus(HttpServletResponse.SC_NO_CONTENT))
.invalidateHttpSession(true);
http
.antMatcher("/admin/**")
.authorizeRequests()
.antMatchers("/admin/**").hasAuthority("ORGANISATION_PRIVILEGE");
}
}
#Configuration
#Order(1003)
public static class appWebSecurityConfig extends WebSecurityConfigurerAdapter{
#Resource(name = "roomUserDetailsService")
UserDetailsService roomUserDetailsService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.maximumSessions(2)
.maxSessionsPreventsLogin(true);
http
.csrf().disable();
http
.httpBasic()
.disable();
http
.authorizeRequests()
.antMatchers("/api/v1/*/login")
.permitAll();
http
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/api/v1/logout", "POST"))
.deleteCookies("JSESSIONID")
.logoutSuccessHandler((httpServletRequest, httpServletResponse, authentication) -> httpServletResponse.setStatus(HttpServletResponse.SC_NO_CONTENT))
.invalidateHttpSession(true);
http
.antMatcher("/api/**")
.authorizeRequests()
.antMatchers("/api/**").hasAuthority("ROOM_PRIVILEGE");
}
#Bean(name = "roomPasswordEncoder")
public PasswordEncoder roomPasswordEncoder(){
return new BCryptPasswordEncoder();
}
#Override
#Bean(name = "roomAuthenticationManager")
public AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManagerBean();
}
#Bean(name = "roomAuthenticationProvider")
public AuthenticationProvider roomAuthenticationProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setPasswordEncoder(roomPasswordEncoder());
provider.setUserDetailsService(roomUserDetailsService);
return provider;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(roomUserDetailsService);
auth.authenticationProvider(roomAuthenticationProvider());
}
}
}
While calling http://localhost:5524/auth/v1/oauth/authorize?client_id=clientapp&response_type=code&scope=read
I got this response
{
"timestamp": 1582545217836,
"status": 401,
"error": "Unauthorized",
"message": "Unauthorized",
"path": "/auth/v1/oauth/authorize"
}
I'm using jdbc to manage state of oauth here's my oauth table data
+-----------+--------------+---------------+-------+-------------------------------------------+-------------------------+-------------+-----------------------+------------------------+
| client_id | resource_ids | client_secret | scope | authorized_grant_types | web_server_redirect_uri | authorities | access_token_validity | refresh_token_validity |
+-----------+--------------+---------------+-------+-------------------------------------------+-------------------------+-------------+-----------------------+------------------------+
| clientapp | NULL | secret | read | password,authorization_code,refresh_token | http://localhost:8081/ | room | 36000 | 36000 |
+-----------+--------------+---------------+-------+-------------------------------------------+-------------------------+-------------+-----------------------+------------------------+
I think this error log is enough to answer this question because I'm new to spring boot.
If need additional information please ask through commants
It was my misunderstanding OAuth workflow. While calling this URL it's trying to authenticate me before grant. But postman showed just 401 instead of giving a redirect response. Calling it via a browser will redirect me to the login page for authentication.
Posting this answer to help guys new to OAuth and spring boot :)

Spring Security Always returning 403 forbidden, Access denied

I want to enable admin to access admin page and do admin stuff, but when I try to do that by setting that the url with /admin/** can only be accessed by user with role admin, it returns 403 Forbidden, access denied. But the user has authorities set to ROLE_ADMIN I checked. What am I doing wrong?
My Controller for user login
#RestController
public class UserController {
#Autowired
AuthenticationManager authenticationManager;
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private AuthorityService authorityService;
#Autowired
private UserAuthorityService userAuthorityService;
#Autowired
TokenUtils tokenUtils;
#Autowired
private UserService userService;
#RequestMapping(value = "/api/login", method = RequestMethod.POST, produces = "text/html")
public ResponseEntity<String> login(#RequestBody LoginDTO loginDTO) {
try {
// System.out.println(loginDTO.getUsername() + " " + loginDTO.getPassword());
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
loginDTO.getUsername(), loginDTO.getPassword());
Authentication authentication = authenticationManager.authenticate(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
UserDetails details = userDetailsService.loadUserByUsername(loginDTO.getUsername());
return new ResponseEntity<String>(tokenUtils.generateToken(details), HttpStatus.OK);
} catch (Exception ex) {
return new ResponseEntity<String>("Invalid login", HttpStatus.BAD_REQUEST);
}
}
#RequestMapping(value = "/api/register", method = RequestMethod.POST, produces = "text/html")
public ResponseEntity<String> register(#RequestBody RegisterDTO registerDTO) {
try {
System.out.println(registerDTO);
User user = userService.findUserByUsername(registerDTO.getUsername());
// // Check if user with that username exists
if(user != null){
// User with that username is found
return new ResponseEntity<String>("User with that username exists", HttpStatus.BAD_REQUEST);
}
// We need to save the user so his ID is generated
User newUser = userService.saveUser(new User(registerDTO));
UserAuthority userAuthority = userAuthorityService.save(new UserAuthority(newUser, authorityService.findOneByName("User")));
Set<UserAuthority> authorities = new HashSet<>();
authorities.add(userAuthority);
newUser.setUserAuthorities(authorities);
User savedUser = userService.save(newUser);
return new ResponseEntity<String>("You have registered successfully with username " + savedUser.getUsername(), HttpStatus.OK);
} catch (Exception ex) {
return new ResponseEntity<String>("Invalid register", HttpStatus.BAD_REQUEST);
}
}
}
I can say that I test my app with postman and login and registration are working fine. When the user is logged in I can the token with the correct data and users authorities, but why when I try to access /admin/building/add url it is returning 403 error?
My Controller for adding building for admin page:
#RestController
public class BuildingController {
#Autowired
private BuildingService buildingService;
#RequestMapping(value = "/admin/building/add", method = RequestMethod.POST, produces = "text/html")
public ResponseEntity<String> addBuilding(#RequestBody BuildingDTO buildingDTO) {
try{
Building newBuilding = new Building(buildingDTO);
return new ResponseEntity<String>(newBuilding.getName(), HttpStatus.OK);
}catch (Exception ex) {
return new ResponseEntity<String>("Data was not valid", HttpStatus.BAD_REQUEST);
}
}
}
My SecurityConfiguration.java
#Configuration
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Autowired
public void configureAuthentication(
AuthenticationManagerBuilder authenticationManagerBuilder)
throws Exception {
authenticationManagerBuilder
.userDetailsService(this.userDetailsService).passwordEncoder(
passwordEncoder());
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public AuthenticationTokenFilter authenticationTokenFilterBean()
throws Exception {
AuthenticationTokenFilter authenticationTokenFilter = new AuthenticationTokenFilter();
authenticationTokenFilter
.setAuthenticationManager(authenticationManagerBean());
return authenticationTokenFilter;
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/index.html", "/view/**", "/app/**", "/", "/api/login", "/api/register").permitAll()
// defined Admin only API area
.antMatchers("/admin/**").hasRole("ADMIN")
.anyRequest()
.authenticated()
.and().csrf().disable();
//if we use AngularJS on client side
// .and().csrf().csrfTokenRepository(csrfTokenRepository());
//add filter for adding CSRF token in the request
httpSecurity.addFilterAfter(new CsrfHeaderFilter(), CsrfFilter.class);
// Custom JWT based authentication
httpSecurity.addFilterBefore(authenticationTokenFilterBean(),
UsernamePasswordAuthenticationFilter.class);
}
/**
* If we use AngularJS as a client application, it will send CSRF token using
* name X-XSRF token. We have to tell Spring to expect this name instead of
* X-CSRF-TOKEN (which is default one)
* #return
*/
private CsrfTokenRepository csrfTokenRepository() {
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
repository.setHeaderName("X-XSRF-TOKEN");
return repository;
}
}
I should mention that I am using Angularjs for frontend, but even so I can login and the correct authorities are displayed for that user. But for some reason I can not access the admin page, even if I login as admin.
Also I tried .hasAuthority("ROLE_ADMIN") and .hasRole("ROLE_ADMIN")(which displays an error for ROLE_) and so I changed it to .hasRole("ADMIN") but it is still not working.
In the database the role for admin is saved as ROLE_ADMIN.
Try like this :
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private static String REALM="MY_TEST_REALM";
#Autowired
public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("bill").password("abc123").roles("ADMIN");
auth.inMemoryAuthentication().withUser("tom").password("abc123").roles("USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/user/**").hasRole("ADMIN")
.and().httpBasic().realmName(REALM).authenticationEntryPoint(getBasicAuthEntryPoint())
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);//We don't need sessions to be created.
}
#Bean
public CustomBasicAuthenticationEntryPoint getBasicAuthEntryPoint(){
return new CustomBasicAuthenticationEntryPoint();
}
/* To allow Pre-flight [OPTIONS] request from browser */
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers(HttpMethod.OPTIONS, "/**");
}
}
For a complet configuration example : Secure Spring REST API using Basic Authentication
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();// We don't need sessions to be created.
}
}
This did it for me. Now I am able to submit my post requests successfully
Try this in SecurityConfig:
.antMatchers("/api/admin").access("hasRole('ADMIN')")
.antMatchers("/api/user").access("hasRole('ADMIN') or hasRole('USER')")

Spring Security + REST + postgreSQL

I did a tutorial https://auth0.com/blog/securing-spring-boot-with-jwts/ about authorization using Spring Security, but this example use hardcoded user data. I would like to authorize using database PostgreSQL. How can I do that? Or do you know some examples on github using Spring REST Security and PostgreSQL?
package com.example.security;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers(HttpMethod.POST, "/login").permitAll()
.anyRequest().authenticated()
.and()
// We filter the api/login requests
.addFilterBefore(new JWTLoginFilter("/login", authenticationManager()),
UsernamePasswordAuthenticationFilter.class)
// And filter other requests to check the presence of JWT in header
.addFilterBefore(new JWTAuthenticationFilter(),
UsernamePasswordAuthenticationFilter.class);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// Create a default account
auth.inMemoryAuthentication()
.withUser("admin")
.password("password")
.roles("ADMIN");
}
}
You can use it with your custom userdetailservice like this:
#Autowired
private CustomUserDetailService userDetailsService;
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsService)
;
}
and add customuserdetail service :
#Service
public class CustomUserDetailService implements UserDetailsService {
#Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
User user = getUserFromDatabase();
UserItem userItem = new UserItem(user.getUsername(),user.getPassword(),true,true,true,true, new ArrayList<GrantedAuthority>());;
userItem.setAuthorities(AuthorityUtils.createAuthorityList("ROLE_ADMIN", "ROLE_USER"));
return userItem;
}
}
You need to create a bean for dataSource like this
#Bean
public DriverManagerDataSource dataSource() {
DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();
driverManagerDataSource.setDriverClassName("org.postgresql.Driver");
driverManagerDataSource.setUrl("jdbc:postgresql://127.0.0.1:5432/mydb");
driverManagerDataSource.setUsername("postgres");
driverManagerDataSource.setPassword("root");
return driverManagerDataSource;
}
And then autowire javax.sql.DataSource inside your WebSecurityConfig class:
#Autowired
DataSource dataSource;
if your password is Bcrypt encoded then create a bean for passwordEncoder
#Bean(name="passwordEncoder")
public PasswordEncoder passwordencoder(){
return new BCryptPasswordEncoder();
}
Configure authentication like this:
public void configAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().dataSource(dataSource)
.usersByUsernameQuery(
"select email,password from users where email=?").passwordEncoder(passwordencoder());
}
and finally hit the /login route.

Resources