Apereo CAS: Step by Step Guide To Implement a Custom Authentication Handler
In this article, we are going to look into the details of implementing a custom authentication handler in an Apereo CAS application.
Join the DZone community and get the full member experience.
Join For FreeAs described in a previous article about getting an Apereo CAS application up and running, we used the default username and password to log in to the CAS. But in a real-world scenario, we need to implement some sort of mechanism to authenticate a user. It is more common to use an authentication handler for this purpose. In this article, we are going to look into the details of implementing a custom authentication handler in an Apereo CAS (hereafter it will be simply referred to as CAS) application.
Note: Additional implementational details are available inside the following code blocks as Java comments.
Following are steps for implementing a custom authentication handler:
- Import the CAS overlay project into the eclipse IDE.
- Before we start any coding, you need to make sure you have the following dependencies in your build.gradle file:
dependencies {
.....
// Other CAS dependencies/modules may be listed here...
compile"org.apereo.cas:cas-server-support-json-service-registry:${casServerVersion}"
compile"org.apereo.cas:cas-server-core-authentication-api:${casServerVersion}"
compile"org.apereo.cas:cas-server-core-web-api:${casServerVersion}"
compile"org.apereo.cas:cas-server-core-util-api:${casServerVersion}"
}
The next step is to define the authentication handler class. The role of this class is to validate credentials and send back the result to the client. Create a package in the "src/main/java" folder and create a new class inside this package. This class should extend “AbstractUsernamePasswordAuthenticationHandler” available in the "org.apereo.cas.authentication.handler.support " package. The following code snippets should depict to you what a custom authentication handler looks like.
Public class MyAuthHandler extends AbstractUsernamePasswordAuthenticationHandler{
/* If you want to make availble custom properties defined in
"cas.properties" file in to this class*/
@Autowired
private CasConfigurationProperties casProperties;
Public MyAuthHandler(String name, ServicesManager servicesManager, PrincipalFactory principalFactory, Integer order) {
super(name, servicesManager, principalFactory, order);
}
@Override
Protected AuthenticationHandlerExecutionResult authenticateUsernamePasswordInternal(finalUsernamePasswordCredentialcredential,
final String originalPassword) throws GeneralSecurityException {
//If login failed throw an exception
if (!isSuccesfulLogin())) {
throw new FailedLoginException();
}
//If login is successful, then create and return back the resolved user //details. If there are additional parameter such as warning about the //password Can be passed inside the “ArrayList()”
AuthenticationHandlerExecutionResult principle = createHandlerResult(credential,
this.getPrincipalFactory( ).createPrincipal( id, user), new ArrayList<>(0) );
return principle;
}
}
- Next, we will look into how to implement the “authenticateUsernamePasswordInternal” method in detail. The following code snippets illustrate these details:
@Override
protected AuthenticationHandlerExecutionResult authenticateUsernamePasswordInternal(finalUsernamePasswordCredentialcredential,
final String originalPassword) throwsGeneralSecurityException {
//username and password can be extracted from the credential parameter
String username = credential.getUsername();
String password = credential.getPassword();
//If there is any other additional parameter, if you want pass from the login
//page, then you can retrieve them in HTTP request object
HttpServletRequest requestObj = ((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest();
String otherparam = (String)requestObj.getParameter("otherparam");
//If you want to pass any properties to authenticator class, pass "casProperties" here
MyAuthenticator authenticator = new MyAuthenticator(casProperties);
/*Authenticate the user credentials using some mechanism like
verifying them against the data available in a database */
User userObj=authenticator.login(username, password);
final Map<String,List<Object>> attributes= new HashMap<String,List<Object>>();
final Map<String, Object>user = new HashMap<>();
final List<Object> userInfo = new ArrayList<>();
if (userObj != null && userObj.isSuccesfulLogin())) {
user.put("id", userObj.id());
user.put("firstname", userObj.getFirstname());
user.put("lastname",userObj.getSurname());
user.put("username", userObj.getUsername());
user.put("email", userObj.getEmail());
user.put("roles", StringUtils.join(userObj.listRoles(), ","));
}else{
throw new FailedLoginException();
}
userInfo.add(user);
attributes.put("attributes", userInfo);
/* In the last parameter, you can pass back set of warnings related to the credentials
and show them user if needed as ArrayList<> object. In this example we don't send any.
*/
AuthenticationHandlerExecutionResult principal = createHandlerResult(credential,
this.getPrincipalFactory().createPrincipal( id, attributes), new ArrayList<>(0) );
// return back the resolved principal
return principal;
}
- Once the handler class is ready, it needed to be registered with CAS. CAS is built on the Java Spring framework. Therefore this can be done through a configuration class. All we do in this class is create a bean of the MyAuthHandler class, which we create in the previous step, and register it as a CAS Authentication plan. Create another package inside the "src/main/java" folder and create a class in it. Following is how this configuration class looks:
@Configuration("MyAuthenticationEventExecutionPlanConfiguration")
@EnableConfigurationProperties(CasConfigurationProperties.class)
Public class MyAuthenticationEventExecutionPlanConfiguration implements AuthenticationEventExecutionPlanConfigurer{
@Autowired
private CasConfigurationProperties casProperties;
/*here in 4th parameter, we can set the order of this handler is executed by CAS,
if there are multiple authentication stratagies are avaible */
@Bean
Public AuthenticationHandler myAuthHandler() {
final MyAuthHandler handler = new MyAuthHandler("authHandler", null,null ,1, casProperties);
return handler;
}
@Override
Public void configureAuthenticationExecutionPlan(AuthenticationEventExecutionPlan plan) {
plan.registerAuthenticationHandler(myAuthHandler());
}
}
- As the final step, we need to make sure our handler is available to the CAS. For this, create the “META-INF” folder inside the "src\main\resources" folder of your project. Add a file with the name “spring.factories” inside the above-created folder. Add the following line to that file:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.myorg.config.AuthenticationEventExecutionPlanConfigurer
- Now go to the root folder of the project in the command prompt and give the command to start the server. On the startup console, there should be a log message you can see our handle is binding to CAS runtime.
Now if you are going to log in through the login page, our authentication handler will be executed and our custom authentication will be used. In another article, we are going to see how to customize the user interface of the CAS application.
Opinions expressed by DZone contributors are their own.
Comments