CAS single sign on - multiple authentication methods

Today, we will talk about the multiple authentication methods of CAS. In the previous article, we explained the construction of CAS basic services and completed the simple deployment of CAS authentication service center. If you forget, you can review it first—— CAS single sign on (2) - building basic services.

There are many ways of CAS authentication. We can implement it according to our own needs. In the previous article, we explained the JDBC authentication method of CAS in building the service. In reading the user name, we can specify to read the data from the database table.

First, JDBC authentication

Let's continue with the last code. You can download the previous code first—— Chapter1 . Last time in the code of CAS basic service building, we just used it briefly. Today we will improve more configurations.

In the previous section, we designed a user table with the following fields:

Common one-way encryption algorithms: MD5, SHA, HMAC.

Generally, we use these encryption algorithms. In JDBC authentication, we can also choose to configure the encryption algorithm. The encryption algorithm is generally the above three, MD5, SHA, HMAC. The encryption type is none | default | standard | bcrypt | crypt | pbkdf2. We select the encryption type in the configuration file and specify the encryption algorithm.


The previous configuration does not change to specify JDBC configuration, and the later configuration is password encryption policy. The configuration is as follows:

##
# JDBC configuration
#
#Query account password SQL, which must contain password field
cas.authn.jdbc.query[0].sql=select * from user where username=?

#Specify the SQL query field name above (required)
cas.authn.jdbc.query[0].fieldPassword=password

#Specify expiration field, 1 is expiration, if expiration is not available
cas.authn.jdbc.query[0].fieldExpired=expired

#Is not available field segment, 1 is not available, need to change password
cas.authn.jdbc.query[0].fieldDisabled=disabled

#Database connection
cas.authn.jdbc.query[0].url=jdbc:mysql://127.0.0.1:3306/cas?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false

#Database dialect configuration
cas.authn.jdbc.query[0].dialect=org.hibernate.dialect.MySQLDialect

#Database user name
cas.authn.jdbc.query[0].user=root

#Database user password
cas.authn.jdbc.query[0].password=123

#Database transaction auto commit
cas.authn.jdbc.query[0].autocommit=false

#Database driven
cas.authn.jdbc.query[0].driverClass=com.mysql.jdbc.Driver

#Overtime configuration
cas.authn.jdbc.query[0].idleTimeout=5000

#The default encryption policy specifies the algorithm through encodingAlgorithm. NONE is not encrypted by default
# NONE|DEFAULT|STANDARD|BCRYPT|SCRYPT|PBKDF2
cas.authn.jdbc.query[0].passwordEncoder.type=DEFAULT
# Character type
cas.authn.jdbc.query[0].passwordEncoder.characterEncoding=UTF-8
# encryption algorithm
cas.authn.jdbc.query[0].passwordEncoder.encodingAlgorithm=MD5
# Encrypted salt
#cas.authn.jdbc.query[0].passwordEncoder.secret=
# Encrypted character length
#cas.authn.jdbc.query[0].passwordEncoder.strength=16

Then we launch the app. When we run CAS and enter the original user name and password - anumbrella/anumbrella, we can't log in because we changed the password validation to MD5 encryption mode. After encrypting anumbrella with MD5, fill in the database and log in again. You can find that the login is successful. So we can verify the success of encryption!

Then we add users test, test2 and test3. The password is md5 encryption for user name, while the expired and disabled of test2 are all 1. As follows:

So when we log in to test2 and test3 users, we will need to change the password and disable the prompt.


In addition, if we want to customize the encryption type, we need to implement the org.springframework.security.crypto.password.PasswordEncoder interface and configure the class name in passwordEncoder.type.

This is configured as custom encryption, and a new class MyPasswordEncoder is created.

package net.anumbrella.sso;

import org.springframework.security.crypto.password.PasswordEncoder;

/**
 * @author anumbrella
 */
public class MyPasswordEncoder implements PasswordEncoder {

    @Override
    public String encode(CharSequence charSequence) {
        // charSequence is the entered user password
        return charSequence.toString();
    }

    @Override
    public boolean matches(CharSequence charSequence, String str) {
        // When encode method returns non null, matches method will be called, and charSequence is the string returned by encode
        // str string is the value returned by the password field in the database
        String encodeStr = charSequence.toString() + "aa";
        if (encodeStr.equals(str)) {
            return true;
        }
        return false;
    }
}

Change configuration to:

cas.authn.jdbc.query[0].passwordEncoder.type=net.anumbrella.sso.MyPasswordEncoder

Change the anumbrella user password to 11aa, start the application, according to the encryption algorithm, as long as we log in and enter password 11, we can verify the success.

If you want the password to be unencrypted, adjust passwordEncoder.type=NONE.
If the encryption policy is SHA, adjust passwordEncoder.encodingAlgorithm=SHA.

We have finished explaining the authentication method of JDBC. For more use, please refer to the following documents:

Password encryption configuration: https://apereo.github.io/cas/5.3.x/installation/Configuration-Properties-Common.html#password-encoding

2, Whitelist certification

CAS also supports white list authentication, mainly in File and JSON formats.

File form:

Add dependency package:

<dependency>
  <groupId>org.apereo.cas</groupId>
  <artifactId>cas-server-support-generic</artifactId>
  <version>${cas.version}</version>
</dependency>

Add the following configuration to the configuration file:

##
# White list - file configuration
#
cas.authn.file.separator=::
cas.authn.file.filename=file:///Users/anumbrella/file
cas.authn.file.name=

In the / Users/anumbrella directory, create a new file file with the following contents:

anumbrella::anumbrella
test::test
test2::test2

This file configuration corresponds to the user name and password. Restart CAS and you can see that the configuration is effective.

In the same way, if we want to configure password encryption, just like the above JDBC configuration encryption, change the configuration file as follows:

#The default encryption policy specifies the algorithm through encodingAlgorithm. NONE is not encrypted by default
# NONE|DEFAULT|STANDARD|BCRYPT|SCRYPT|PBKDF2
cas.authn.file.passwordEncoder.type=DEFAULT
# Character type
cas.authn.file.passwordEncoder.characterEncoding=UTF-8
# encryption algorithm
cas.authn.file.passwordEncoder.encodingAlgorithm=MD5

All others are completely consistent, except that the first part of the configuration file is changed to the corresponding authentication type.

JSON form:

Similar to File, add dependency package:

<dependency>
  <groupId>org.apereo.cas</groupId>
  <artifactId>cas-server-support-generic</artifactId>
  <version>${cas.version}</version>
</dependency>

Add the following configuration to the configuration file:

##
# White list - json configuration
#
cas.authn.json.location=file:///Users/anumbrella/file.json
cas.authn.json.name=

In the / Users/anumbrella directory, create a new file.json, as follows:

{
  "@class" : "java.util.LinkedHashMap",
  "anumbrella" : {
    "@class" : "org.apereo.cas.adaptors.generic.CasUserAccount",
    "password" : "anumbrella",
    "attributes" : {
      "@class" : "java.util.LinkedHashMap",
      "firstName" : "shu",
      "lastName" : "yun"
    },
    "status" : "OK",
    "expirationDate" : "2018-10-19"
  }
}

The username and password are still anumbrella/anumbrella.

Similarly, if we want to configure password encryption, change the configuration file as follows:

#The default encryption policy specifies the algorithm through encodingAlgorithm. NONE is not encrypted by default
# NONE|DEFAULT|STANDARD|BCRYPT|SCRYPT|PBKDF2
cas.authn.json.passwordEncoder.type=DEFAULT
# Character type
cas.authn.json.passwordEncoder.characterEncoding=UTF-8
# encryption algorithm
cas.authn.json.passwordEncoder.encodingAlgorithm=MD5

3, Blacklist certification

The configuration of blacklist in CAS is relatively simple, as follows:

##
# Blacklist configuration
#
cas.authn.reject.users=test,anumbrella
cas.authn.reject.name=

When users are not in the blacklist, they will accept all of them. Users can log in even if they enter their passwords randomly.

Similarly, if we want to configure password encryption, change the configuration file as follows:

#The default encryption policy specifies the algorithm through encodingAlgorithm. NONE is not encrypted by default
# NONE|DEFAULT|STANDARD|BCRYPT|SCRYPT|PBKDF2
cas.authn.reject.passwordEncoder.type=DEFAULT
# Character type
cas.authn.reject.passwordEncoder.characterEncoding=UTF-8
# encryption algorithm
cas.authn.reject.passwordEncoder.encodingAlgorithm=MD5

4, Shiro certification

We know Shiro is a powerful and easy-to-use Java security framework that performs authentication, authorization, cryptography, and session management. By using Shiro, you can quickly set up a set of role and permission control processes, so the chance of using Shiro is still great.

Add dependency package as follows:

<dependency>
  <groupId>org.apereo.cas</groupId>
  <artifactId>cas-server-support-shiro-authentication</artifactId>
  <version>${cas.version}</version>
</dependency>

Add the following to the configuration file:

##
# Shiro configuration
#
#Users allowed to log in must have the following permissions, otherwise they are denied, separated by commas
cas.authn.shiro.requiredPermissions=staff
#Users who are allowed to log in must have the following roles, otherwise they will be rejected and separated by commas
cas.authn.shiro.requiredRoles=admin
#shir profile location
cas.authn.shiro.location=classpath:shiro.ini
#shiro name unique
cas.authn.shiro.name=cas-shiro

Create a new shiro.ini file under resources. The configuration is as follows:

[main]
cacheManager = org.apache.shiro.cache.MemoryConstrainedCacheManager
securityManager.cacheManager = $cacheManager

[users]
anumbrella = 123, admin
test = test, developer

[roles]
admin = system,admin,staff,superuser:*
developer = commit:*

The shiro.ini file here is mainly about the configuration of Shiro, which can be changed according to specific requirements.

Restart CAS service. According to our configuration, we can find that the anumbrella user can log in, and the test user fails to log in without corresponding permission.

Similarly, if we want to configure password encryption, change the configuration file as follows:

#The default encryption policy specifies the algorithm through encodingAlgorithm. NONE is not encrypted by default
# NONE|DEFAULT|STANDARD|BCRYPT|SCRYPT|PBKDF2
cas.authn.shiro.passwordEncoder.type=DEFAULT
# Character type
cas.authn.shiro.passwordEncoder.characterEncoding=UTF-8
# encryption algorithm
cas.authn.shiro.passwordEncoder.encodingAlgorithm=MD5

5, Rest certification

Here, Rest authentication refers to authenticating the user through the data interface, and authenticating by sending a POST request. When the user clicks log in, CAS will send a POST request to include an Authorization authentication in the request header. The value is Basic XYZ, which is the user information encoded by Base64.

For example: If the user name and password are: anumbrella/123

Then the request header includes: authorization=Basic Base64(anumbrella:123)

Also add dependency package:

<dependency>
    <groupId>org.apereo.cas</groupId>
    <artifactId>cas-server-support-rest-authentication</artifactId>
    <version>${cas.version}</version>
</dependency>

Then add the configuration in the configuration file:

##
# Rest configuration
#
cas.authn.rest.uri=http://localhost:8088/login
cas.authn.rest.name=

If the login succeeds in response 200, the return response contains the id and attributes fields, as follows:

{"@class":"org.apereo.cas.authentication.principal.SimplePrincipal","id":"casuser","attributes":{}}

If it fails, the result may be as follows:

Return status code: 403 user unavailable; 404 account does not exist; 423 account is locked; 412 expires; 428 password needs to be changed; other login failures

We create a new Spring Boot service to simulate the address service of the Rest request, and a new SysUser class to define the json of the return constraint.

public class SysUser {

    @JsonProperty("id")
    @NotNull
    private String username;

    @JsonProperty("@class")
    //You need to return the class name interface that implements org.apereo.cas.authentication.principal.Principal
    private String clazz = "org.apereo.cas.authentication.principal.SimplePrincipal";


    @JsonProperty("attributes")
    private Map<String, Object> attributes = new HashMap<String, Object>();

    @JsonIgnore
    @NotNull
    private String password;

    @JsonIgnore
    //Is the user unavailable
    private boolean disable = false;


    @JsonIgnore
    //Whether the user expires
    private boolean expired = false;

    @JsonIgnore
    //Is it locked?
    private boolean locked = false;

    public boolean isLocked() {
        return locked;
    }

    public SysUser setLocked(boolean locked) {
        this.locked = locked;
        return this;
    }

    public boolean isDisable() {
        return disable;
    }

    public SysUser setDisable(boolean disable) {
        this.disable = disable;
        return this;
    }

    public boolean isExpired() {
        return expired;
    }

    public SysUser setExpired(boolean expired) {
        this.expired = expired;
        return this;
    }

    public String getPassword() {
        return password;
    }

    public SysUser setPassword(String password) {
        this.password = password;
        return this;
    }

    public String getUsername() {
        return username;
    }

    public SysUser setUsername(String username) {
        this.username = username;
        return this;
    }

    public String getClazz() {
        return clazz;
    }

    public Map<String, Object> getAttributes() {
        return attributes;
    }

    public SysUser setAttributes(Map<String, Object> attributes) {
        this.attributes = attributes;
        return this;
    }

    @JsonIgnore
    public SysUser addAttribute(String key, Object val) {
        getAttributes().put(key, val);
        return this;
    }
}

Then in the Controller layer, define the POST method of path / login.

@RestController
public class RestAuthController {

    private static final Logger LOGGER = LoggerFactory.getLogger(RestAuthController.class);

    /**
     * 1. cas The server will request through post, and base 64 code the user information with "user name: password" in the authorization request header
     * 2. It is successful to return 200 status codes in the format of {"@ class": "org. Apereo. CAS. Authentication. Principle. Simpleprincipal", "Id": "casuser", "attributes": {}}}
     * 2. Return status code 403 user unavailable; 404 account does not exist; 423 account locked; 428 expired; other login failures
     *
     * @param httpHeaders
     * @return
     */
    @PostMapping("/login")
    public Object login(@RequestHeader HttpHeaders httpHeaders) {
        LOGGER.info("Rest api login.");
        LOGGER.debug("request headers: {}", httpHeaders);
        SysUser user = null;
        try {
            UserTemp userTemp = obtainUserFormHeader(httpHeaders);
            //Try to find out if the user library exists
            user = new SysUser();
            user.setUsername("anumbrella");
            user.setPassword("123");
            if (user != null) {
                if (!user.getPassword().equals(userTemp.password)) {
                    //Password mismatch
                    return new ResponseEntity(HttpStatus.BAD_REQUEST);
                }
                if (user.isDisable()) {
                    //Disable 403
                    return new ResponseEntity(HttpStatus.FORBIDDEN);
                }
                if (user.isLocked()) {
                    //Lock 423
                    return new ResponseEntity(HttpStatus.LOCKED);
                }
                if (user.isExpired()) {
                    //Expired 428
                    return new ResponseEntity(HttpStatus.PRECONDITION_REQUIRED);
                }
            } else {
                //No 404
                return new ResponseEntity(HttpStatus.NOT_FOUND);
            }
        } catch (UnsupportedEncodingException e) {
            LOGGER.error("", e);
            new ResponseEntity(HttpStatus.BAD_REQUEST);
        }
        LOGGER.info("[{}] login is ok", user.getUsername());
        //json returned successfully
        return user;
    }

    /**
     * Get the user name and password according to the request header
     *
     * @param httpHeaders
     * @return
     * @throws UnsupportedEncodingException
     */
    private UserTemp obtainUserFormHeader(HttpHeaders httpHeaders) throws UnsupportedEncodingException {
        /**
         *
         * This allows the CAS server to reach to a remote REST endpoint via a POST for verification of credentials.
         * Credentials are passed via an Authorization header whose value is Basic XYZ where XYZ is a Base64 encoded version of the credentials.
         */
        //When the request comes, it will be encrypted by putting the user information in the request header authorization and Basic authentication
        String authorization = httpHeaders.getFirst("authorization");//You will get basic Base64 (user name: password)
        String baseCredentials = authorization.split(" ")[1];
        String usernamePassword = Base64Utils.decoder(baseCredentials);//User name: password
        LOGGER.debug("login user: {}", usernamePassword);
        String credentials[] = usernamePassword.split(":");
        return new UserTemp(credentials[0], credentials[1]);
    }

    /**
     * Resolve the requested user
     */
    private class UserTemp {
        private String username;
        private String password;

        public UserTemp(String username, String password) {
            this.username = username;
            this.password = password;
        }
    }
}

We use PostMan to simulate CAS service login and log in to http://localhost:8088/login Start a POST request, which contains the authentication information of user name and password. In the Rest service, I have written the user name and password to anumbrella/123, which can be realized by connecting to the database according to the specific requirements. As follows:

Finally, we return the expected result. Now we change the CAS configuration rest address to http://localhost:8088/login , restart the service, and then enter the password to log in to the test.


It can be found that the login is successful. We have implemented the Rest service authentication.

Similarly, our password is not configured for encryption. If we want to configure password encryption, change the configuration file as follows:

#The default encryption policy specifies the algorithm through encodingAlgorithm. NONE is not encrypted by default
# NONE|DEFAULT|STANDARD|BCRYPT|SCRYPT|PBKDF2
cas.authn.rest.passwordEncoder.type=DEFAULT
# Character type
cas.authn.rest.passwordEncoder.characterEncoding=UTF-8
# encryption algorithm
cas.authn.rest.passwordEncoder.encodingAlgorithm=MD5
#The default encryption policy specifies the algorithm through encodingAlgorithm. NONE is not encrypted by default
# NONE|DEFAULT|STANDARD|BCRYPT|SCRYPT|PBKDF2
cas.authn.rest.passwordEncoder.type=DEFAULT
# Character type
cas.authn.rest.passwordEncoder.characterEncoding=UTF-8
# encryption algorithm
cas.authn.rest.passwordEncoder.encodingAlgorithm=MD5

So far, we have talked about the various authentication methods of CAS. Of course, there are many other authentication methods of CAS. You can check the official documents—— Configuration document.

Code instance: Chapter2

Reference resources

Tags: JDBC Shiro REST JSON

Posted on Wed, 18 Mar 2020 04:56:42 -0700 by rg_22uk