DEV Community

Pavan K Jadda
Pavan K Jadda

Posted on

Keycloak with Angular and Spring Security

Keycloak is one of the widely used Open Source Identity and Access Management application. This article demonstrates usage of Keycloak as authentication server with Angular, Spring Boot and Spring Security. As usual, code uploaded to Github repository

Technologies:

  1. Spring Boot 2.x
  2. Spring Security
  3. Angular 11.x
  4. Keycloak 6.0.1
  5. Spring Data JPA
  6. Mysql 5.7
  7. Docker

Prerequisites:

  1. Follow instructions from official docker documentation and install docker on your local machine
  2. This article uses Keycloak and Mysql docker containers for the setup. But if you prefer standalone versions, follow instructions from official Keycloak docs to install Keycloak and create a database in your local Mysql server

Setup:

  1. Clone the Github repository and navigate to /src/main/resources/docker directory.
  2. Execute the following command to bring up Keycloak and MySql containers
$ docker-compose -f keycloak-mysql.yml up
Enter fullscreen mode Exit fullscreen mode

3. Go to http://localhost:8080 to login into Keycloak admin console and use the following credentials. Same can be configured in keycloak-mysql.yml file before starting containers

username: admin  
password: Test@2020
Enter fullscreen mode Exit fullscreen mode

and home page should look like this

4. In Keycloak realms are similar to domains in Active directory. To add a new group or domain, point your mouse on Master and click on Add Realm and name it as keycloakdemo

5. In Login tab enable everything and select Require SSL to external requests

6. Click on clients to see a list of existing clients and click create button to create a new client named angular-app

7. Select angular-app from clients list and modify the following values in the Settings tab then save the page

Valid Redirect URIs: [http://localhost:4200/\*](http://localhost:4200/*)  
BaseU URL: [http://localhost:4200](http://localhost:4200)  
Web Origins: \*
Enter fullscreen mode Exit fullscreen mode

8. Go to configure → Roles → Add Role and give RoleName as Admin and if you want to use a different name, make sure to change the value in src/main/java/com/pj/keycloak/security/Roles.java class

Roles.java

class Roles  
{  
    static final String _ADMIN_\="Admin";  

    private Roles()  
    {  

    }  
}
Enter fullscreen mode Exit fullscreen mode

9. Go to Manage → Users → Add User and enter the values as shown below

10. Select the created user from the Users list and navigate to the Credentials tab. Set the password to Test@2020

11. Click on Role Mappings and bring Admin role from Available Roles to Assigned Roles

12. Go to local Mysql server and create a database named keycloak-springsecurity. This database holds our application data. Note that this different from Keycloak database.

How to Run?

  1. Go to KeyCloakSpringSecurityApplication class and run it. If everything works as expected, you should be able to browse the REST API through http://localhost:8081
  2. Go to the database server and execute the following command to insert sample employee data
INSERT INTO \`keycloak-springsecurity\`.\`employee\`  
(\`id\`,\`email\`,\`first\_name\`,\`last\_name\`,\`phone\`) VALUES ([1001,'jdoe@example.com](mailto:1001,'jdoe@example.com)','John','Doe','389-399-3893');
Enter fullscreen mode Exit fullscreen mode

3. Go to src/main/webapp/spring-data-ui directory and execute the following command to install dependencies

$ npm install
Enter fullscreen mode Exit fullscreen mode

4. Execute the following command to start the Angular application and it will be available on http://localhost:4200

$  ng serve --watch
Enter fullscreen mode Exit fullscreen mode

5. Go to http://localhost:4200 to see Angular home page and it should automatically redirect you to KeyCloak login page. Enter credentials for John Doe you created earlier. And you should see the following screen

Angular home page

Click on Employees to see list of employees. That’s it, Spring Boot REST API and Angular app protected through Keycloak Server

Deep dive into Code

  1. Let’s look at the Spring code first. We have an Employee class with some basic fields and controller, service and repository classes defined as below

Employee.java

@Entity  
@Table(name = "employee")  
@Data  
public class Employee implements Serializable  
{  
    private static final long _serialVersionUID_ \= -2482579485413606056L;  

    @Id  
    @GeneratedValue(strategy = GenerationType._IDENTITY_)  
    private Long id;  

    @Column(name = "first\_name")  
    private String firstName;  

    @Column(name = "last\_name")  
    private String lastName;  

    @Column(name = "email")  
    private String email;  

    @Column(name = "phone")  
    private String phone;  


    @Override  
    public boolean equals(Object o)  
    {  
        if (this \== o)  
            return true;  
        if (o == null || getClass() != o.getClass())  
            return false;  
        Employee employee = (Employee) o;  
        return getId().equals(employee.getId());  
    }  

    @Override  
    public int hashCode()  
    {  
        return Objects._hash_(getId());  
    }  
}
Enter fullscreen mode Exit fullscreen mode

EmployeeController.java

@RestController  
@RequestMapping("/api/v1/employee")  
public class EmployeeController  
{  
    private final EmployeeService employeeService;  

    public EmployeeController(EmployeeService employeeService)  
    {  
        this.employeeService \= employeeService;  
    }  

    @GetMapping(path = "/list")  
    public List<Employee> findAll()  
    {  
        return employeeService.findAll();  
    }  

    @GetMapping(path = "/find/{id}")  
    public Optional<Employee> findById(@PathVariable Long id)  
    {  
        return employeeService.findById(id);  
    }  
}
Enter fullscreen mode Exit fullscreen mode

EmployeeServiceImpl.java

@Service  
public class EmployeeServiceImpl implements EmployeeService  
{  
    private final EmployeeRepository employeeRepository;  

    public EmployeeServiceImpl(EmployeeRepository employeeRepository)  
    {  
        this.employeeRepository \= employeeRepository;  
    }  

    @Override  
    public List<Employee> findAll()  
    {  
        return employeeRepository.findAll();  
    }  

    @Override  
    public Optional<Employee> findById(Long id)  
    {  
        return employeeRepository.findById(id);  
    }  
}
Enter fullscreen mode Exit fullscreen mode

EmployeeRepository.java

public interface EmployeeRepository extends JpaRepository<Employee,Long>  
{  
}
Enter fullscreen mode Exit fullscreen mode

2. SecurityConfig class takes care of securing the application with KeycloakWebSecurityConfigurerAdapter class, which provides a convenient base class for creating a WebSecurityConfigurer instance secured by Keycloak. This implementation allows customization by overriding methods.

3. Method configureGlobal() gets KeyCloakAuthenticationAdapter instance and sets the authentication provider to keycloakAuthenticationProvider. Spring Security uses keycloakAuthenticationProvider to perform authentication

@Autowired  
    public void configureGlobal(AuthenticationManagerBuilder authenticationManagerBuilder)  
    {  
        KeycloakAuthenticationProvider keycloakAuthenticationProvider=keycloakAuthenticationProvider();  
        keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());  
        authenticationManagerBuilder.authenticationProvider(keycloakAuthenticationProvider);  
    }
Enter fullscreen mode Exit fullscreen mode

4. Bean KeycloakSpringBootConfigResolver helps to connect to Keycloak based on config properties from application.yml file

5. configure() method is key to the whole application, which secures the Endpoints with required roles

@Override  
protected void configure(HttpSecurity http) throws Exception  
{  
    super.configure(http);  

    http.authorizeRequests()  
            .antMatchers("/api/\*\*")  
            .hasAnyRole(Roles._ADMIN_)  
            .anyRequest()  
            .permitAll();  

    http.cors();  
}
Enter fullscreen mode Exit fullscreen mode

6. application.yml file contains Keycloak server properties, realm and client details.

_\# Server properties  
_server:  
  port: 8081  

_#Keycloak Properties  
_keycloak:  
  auth-server-url: http://localhost:8080/auth  
  realm: keycloakdemo  
  resource: angular-app  
  public-client: true  
  principal-attribute: preferred\_username  
.........  
......
Enter fullscreen mode Exit fullscreen mode

7. Now, in Angular application, keycloak-js library helps to config and connect to Keycloak server and perform authentication. The service KeycloakService class uses the keycloak-js library classes to achieve this and loads it when initializing the application

keycloak.service.ts

import {Injectable} from "@angular/core";  
import \* as _Keycloak_ from "keycloak-js";  
import {KeycloakInstance} from "keycloak-js";  

@Injectable({  
  providedIn: 'root'  
})  
export class KeycloakService  
{  
  private keycloakAuth: KeycloakInstance;  

  constructor()  
  {  
  }  

  init(): Promise<any\>  
  {  
    return new _Promise_((resolve, reject) =>  
    {  
      const config = {  
        'url': 'http://localhost:8080/auth',  
        'realm': 'keycloakdemo',  
        'clientId': 'angular-app' };  
      _// @ts-ignore_ this.keycloakAuth \= new _Keycloak_(config);  
      this.keycloakAuth.init({onLoad: 'login-required'})  
          .success(() =>  
          {  
            resolve();  
          })  
          .error(() =>  
          {  
            reject();  
          });  
    });  
  }  

  getToken(): string {  
    return this.keycloakAuth.token;  
  }  

  logout()  
  {  
    const options = {  
      'redirectUri': 'http://localhost:4200',  
      'realm': 'keycloakdemo',  
      'clientId': 'angular-app' };  
    this.keycloakAuth.logout(options);  
  }  
}
Enter fullscreen mode Exit fullscreen mode

8. In app.module.ts file we inject this class and call init() method when the application initializes

export function _kcFactory_(keycloakService: KeycloakService) {  
  return () => keycloakService.init();  
}  
@NgModule({  
  declarations: \[  
    AppComponent,  
    EmployeeListComponent,  
    LogoutComponent  
  \],  
  imports: \[  
    BrowserModule,  
    AppRoutingModule,  
    HttpClientModule  
  \],  
  providers: \[KeycloakService,  
    {  
      provide: APP\_INITIALIZER,  
      useFactory: _kcFactory_,  
      deps: \[KeycloakService\],  
      multi: true },  
    {  
      provide: HTTP\_INTERCEPTORS,  
      useClass: TokenInterceptor,  
      multi: true }\],  
  bootstrap: \[AppComponent\]  
})  
export class AppModule { }
Enter fullscreen mode Exit fullscreen mode

9. Class TokenInterceptor implements HttpInterceptor interface, which helps to intercept every HTTP request and set the Bearer authentication token.

@Injectable()  
export class TokenInterceptor implements HttpInterceptor  
{  
  constructor(private kcService: KeycloakService)  
  {  
  }  

  intercept(request: HttpRequest<any\>, next: HttpHandler): Observable<HttpEvent<any\>>  
  {  
    const authToken = this.kcService.getToken() || "";  
    request = request.clone({  
      setHeaders: {  
        "Authorization": "Bearer " \+ authToken  
      }  
    });  
    return next.handle(request);  
  }  
}
Enter fullscreen mode Exit fullscreen mode

All the code combined into one repository and uploaded to Github. Please let me know if you have any questions. Happy coding :)

Discussion (2)

Collapse
caroldev profile image
Carol Avila

Thanks a lot for you contribution!
It really helped me to connect this three components

Collapse
pavankjadda profile image
Pavan K Jadda Author

Awesome