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:
- Spring Boot 2.x
- Spring Security
- Angular 15.x
- Keycloak 20.x.x
- Spring Data JPA
- Mysql 5+
- Docker
Prerequisites:
- Follow instructions from official docker documentation and install docker on your local machine
- 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:
- Clone the Github repository and navigate to
/src/main/resources/docker
directory. - Execute the following command to bring up Keycloak and MySql containers
$ docker-compose -f keycloak-mysql.yml up
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
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/*
BaseU URL: http://localhost:4200
Web Origins: *
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(){}
}
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?
- 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 - 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','John','Doe','389-399-3893');
3. Go to src/main/webapp/spring-data-ui
directory and execute the following command to install dependencies
$ npm install
4. Execute the following command to start the Angular application and it will be available on http://localhost:4200
$ ng serve --watch
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
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
- 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());
}
}
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);
}
}
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);
}
}
EmployeeRepository.java
public interface EmployeeRepository extends JpaRepository<Employee,Long>{
}
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);
}
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();
}
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
.........
......
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);
}
}
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 { }
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);
}
}
All the code combined into one repository and uploaded to Github. Please let me know if you have any questions. Happy coding 🙂