CAS單點登錄(三)——多種認證方式

今天我們講解一下CAS的多種認證方式,在上一篇文章中我們講解了CAS基礎服務的搭建,完成了CAS認證服務中心的簡單部署,如果忘記了話,可以先去複習一下——CAS單點登錄(二)——搭建基礎服務


CAS認證方式有多種,我們可以根據自己的需求來實現。在前一篇文章中搭建服務中我們其實就把CAS的JDBC認證方式講解了,在讀取用戶名我們可以指定爲從數據庫的表中讀取數據。

一、JDBC認證

我們還是接着上次的代碼進行講解,你可以先下載先前的代碼——Chapter1。上次在CAS基本服務搭建的代碼中,我們只是簡單的使用了一下,今天我們將完善更多的配置。

在前面我們設計了一個user表,表的字段爲:
在這裏插入圖片描述
常用單向加密算法:MD5、SHA、HMAC。

一般我們常用的加密算法就這幾種。在JDBC認證中我們也可以選擇配置加密算法,加密算法一般爲上面的三種,MD5、SHA、HMAC,加密類型爲NONE|DEFAULT|STANDARD|BCRYPT|SCRYPT|PBKDF2這幾種,我們在配置文件中選擇加密類型,指定加密算法。

在這裏插入圖片描述
前面配置不變指定JDBC配置,後面的配置爲密碼加密策略,配置如下:

##
# JDBC配置
#
#查詢賬號密碼SQL,必須包含密碼字段
cas.authn.jdbc.query[0].sql=select * from user where username=?

#指定上面的SQL查詢字段名(必須)
cas.authn.jdbc.query[0].fieldPassword=password

#指定過期字段,1爲過期,若過期不可用
cas.authn.jdbc.query[0].fieldExpired=expired

#爲不可用字段段,1爲不可用,需要修改密碼
cas.authn.jdbc.query[0].fieldDisabled=disabled

#數據庫連接
cas.authn.jdbc.query[0].url=jdbc:mysql://127.0.0.1:3306/cas?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false

#數據庫dialect配置
cas.authn.jdbc.query[0].dialect=org.hibernate.dialect.MySQLDialect

#數據庫用戶名
cas.authn.jdbc.query[0].user=root

#數據庫用戶密碼
cas.authn.jdbc.query[0].password=123

#數據庫事務自動提交
cas.authn.jdbc.query[0].autocommit=false

#數據庫驅動
cas.authn.jdbc.query[0].driverClass=com.mysql.jdbc.Driver

#超時配置
cas.authn.jdbc.query[0].idleTimeout=5000

#默認加密策略,通過encodingAlgorithm來指定算法,默認NONE不加密
# NONE|DEFAULT|STANDARD|BCRYPT|SCRYPT|PBKDF2
cas.authn.jdbc.query[0].passwordEncoder.type=DEFAULT
# 字符類型
cas.authn.jdbc.query[0].passwordEncoder.characterEncoding=UTF-8
# 加密算法
cas.authn.jdbc.query[0].passwordEncoder.encodingAlgorithm=MD5
# 加密鹽
#cas.authn.jdbc.query[0].passwordEncoder.secret=
# 加密字符長度
#cas.authn.jdbc.query[0].passwordEncoder.strength=16

然後我們啓動應用。當我們運行起CAS,輸入原來的用戶名和密碼——anumbrella/anumbrella,並不能登錄,因爲我們更改密碼驗證爲MD5加密模式了。將anumbrella使用md5加密後,填入數據庫,再次登錄,可以發現登錄成功。由此驗證我們加密成功!

接着我們再新增用戶test、test2、test3,密碼分別爲用戶名md5加密,而test2的expired,test3的disabled都爲1。如下:

在這裏插入圖片描述
因此當我們登錄test2和test3用戶時,將會有需要更改密碼和禁用提示。
在這裏插入圖片描述
在這裏插入圖片描述
除此之外如果我們要自定義加密類型,就需要實現org.springframework.security.crypto.password.PasswordEncoder接口,並且把類名配置在passwordEncoder.type。

這裏配置爲自定義加密,新建類MyPasswordEncoder。

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爲輸入的用戶密碼
        return charSequence.toString();
    }

    @Override
    public boolean matches(CharSequence charSequence, String str) {
        // 當encode方法返回不爲null時,matches方法纔會調用,charSequence爲encode返回的字符串
        // str字符串爲數據庫中密碼字段返回的值
        String encodeStr = charSequence.toString() + "aa";
        if (encodeStr.equals(str)) {
            return true;
        }
        return false;
    }
}

更改配置爲:

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

更改anumbrella用戶密碼爲11aa,啓動應用,根據加密算法只要我們登錄輸入密碼11即可驗證成功。

如果要密碼無加密,調整passwordEncoder.type=NONE。
如果要加密策略爲SHA,調整passwordEncoder.encodingAlgorithm=SHA。

到此JDBC的認證方式我們就講解完畢了,關於更多的使用參考文檔:

密碼加密配置:

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

二、白名單(Whitelist)認證

CAS同時也支持白名單認證方式,主要是File和JSON格式。

File形式:

添加依賴包:

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

在配置文件中添加如下配置:

##
# 白名單——file配置
#
cas.authn.file.separator=::
cas.authn.file.filename=file:///Users/anumbrella/file
cas.authn.file.name=

在/Users/anumbrella目錄下,新建file文件,內容如下:

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

該文件配置對應的就是用戶名和密碼,重啓CAS,可以發現配置生效了。

同樣的如果我們要配置密碼加密,與上面JDBC配置加密一樣,更改配置文件如下:

#默認加密策略,通過encodingAlgorithm來指定算法,默認NONE不加密
# NONE|DEFAULT|STANDARD|BCRYPT|SCRYPT|PBKDF2
cas.authn.file.passwordEncoder.type=DEFAULT
# 字符類型
cas.authn.file.passwordEncoder.characterEncoding=UTF-8
# 加密算法
cas.authn.file.passwordEncoder.encodingAlgorithm=MD5

其他都完全一致,只是更改配置文件前部分更改爲對應的認證類型的即可。

JSON形式:

與File相似,一樣添加依賴包:

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

在配置文件中添加如下配置:

##
# 白名單——json配置
#
cas.authn.json.location=file:///Users/anumbrella/file.json
cas.authn.json.name=

在/Users/anumbrella目錄下,新建file.json文件,內容如下:

{
  "@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"
  }
}

用戶名和密碼仍然爲anumbrella/anumbrella。

同樣的如果我們要配置密碼加密,更改配置文件如下:

#默認加密策略,通過encodingAlgorithm來指定算法,默認NONE不加密
# NONE|DEFAULT|STANDARD|BCRYPT|SCRYPT|PBKDF2
cas.authn.json.passwordEncoder.type=DEFAULT
# 字符類型
cas.authn.json.passwordEncoder.characterEncoding=UTF-8
# 加密算法
cas.authn.json.passwordEncoder.encodingAlgorithm=MD5

三、黑名單(Blacklist)認證

在CAS中黑名單的配置就比較簡單,配置如下:

##
# 黑名單配置
#
cas.authn.reject.users=test,anumbrella
cas.authn.reject.name=

當不在黑名單中的用戶,就會全部接受,用戶甚至亂輸入密碼都可實現登錄。

同樣的如果我們要配置密碼加密,更改配置文件如下:

#默認加密策略,通過encodingAlgorithm來指定算法,默認NONE不加密
# NONE|DEFAULT|STANDARD|BCRYPT|SCRYPT|PBKDF2
cas.authn.reject.passwordEncoder.type=DEFAULT
# 字符類型
cas.authn.reject.passwordEncoder.characterEncoding=UTF-8
# 加密算法
cas.authn.reject.passwordEncoder.encodingAlgorithm=MD5

四、Shiro認證

我們知道Shiro是個一個強大且易用的Java安全框架,執行身份驗證、授權、密碼學和會話管理。通過使用Shiro可以快速搭建一套角色、權限控制的流程,因此Shiro使用的機會還是很大的。

添加依賴包如下:

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

在配置文件添加如下:

##
# Shiro配置
#
#允許登錄的用戶,必須要有以下權限,否則拒絕,多個逗號隔開
cas.authn.shiro.requiredPermissions=staff
#允許登錄的用戶,必須要有以下角色,否則拒絕,多個逗號隔開
cas.authn.shiro.requiredRoles=admin
#shir配置文件位置
cas.authn.shiro.location=classpath:shiro.ini
#shiro name 唯一
cas.authn.shiro.name=cas-shiro

在resources下新建shiro.ini文件,配置如下:

[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:*

這裏的shiro.ini文件,主要是看shiro的配置情況,可以根據具體的需求進行更改。

重啓CAS服務,根據我們的配置可以發現anumbrella用戶可以登錄,test用戶登錄失敗,沒有相應的權限。

同樣的如果我們要配置密碼加密,更改配置文件如下:

#默認加密策略,通過encodingAlgorithm來指定算法,默認NONE不加密
# NONE|DEFAULT|STANDARD|BCRYPT|SCRYPT|PBKDF2
cas.authn.shiro.passwordEncoder.type=DEFAULT
# 字符類型
cas.authn.shiro.passwordEncoder.characterEncoding=UTF-8
# 加密算法
cas.authn.shiro.passwordEncoder.encodingAlgorithm=MD5

五、Rest認證

這裏的Rest認證就是指通過數據接口對用戶進行認證,通過發起一個POST請求來進行認證。
當用戶點擊登錄後,CAS會發送POST請求到在請求頭中包含一個Authorization認證,裏面的值爲Basic XYZ,而這個XYZ就是通過Base64編碼後的用戶信息。

比如:
若輸入用戶名密碼爲:anumbrella/123

那麼請求頭包括:
authorization=Basic Base64(anumbrella:123)

同樣的添加依賴包:

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

然後再配置文件中添加配置:

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

如果登錄成功響應200,返回響應中包含id和attributes字段,如下:

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

如果失敗,返回的結果可能如下:
在這裏插入圖片描述
返回狀態碼:403用戶不可用;404賬號不存在;423賬戶被鎖定;412過期;428密碼需要更改;其他登錄失敗

我們新建一個Spring Boot服務,用來模擬Rest請求的地址的服務,新建SysUser類,用來定義返回約束的json。

public class SysUser {

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

    @JsonProperty("@class")
    //需要返回實現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
    //用戶是否不可用
    private boolean disable = false;


    @JsonIgnore
    //用戶是否過期
    private boolean expired = false;

    @JsonIgnore
    //是否鎖定
    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;
    }
}

然後再Controller層裏,定義路徑/login的POST方法。

@RestController
public class RestAuthController {

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

    /**
     * 1. cas 服務端會通過post請求,並且把用戶信息以"用戶名:密碼"進行Base64編碼放在authorization請求頭中
     * 2. 返回200狀態碼並且格式爲{"@class":"org.apereo.cas.authentication.principal.SimplePrincipal","id":"casuser","attributes":{}} 是成功的
     * 2. 返回狀態碼403用戶不可用;404賬號不存在;423賬戶被鎖定;428過期;其他登錄失敗
     *
     * @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);
            //嘗試查找用戶庫是否存在
            user = new SysUser();
            user.setUsername("anumbrella");
            user.setPassword("123");
            if (user != null) {
                if (!user.getPassword().equals(userTemp.password)) {
                    //密碼不匹配
                    return new ResponseEntity(HttpStatus.BAD_REQUEST);
                }
                if (user.isDisable()) {
                    //禁用 403
                    return new ResponseEntity(HttpStatus.FORBIDDEN);
                }
                if (user.isLocked()) {
                    //鎖定 423
                    return new ResponseEntity(HttpStatus.LOCKED);
                }
                if (user.isExpired()) {
                    //過期 428
                    return new ResponseEntity(HttpStatus.PRECONDITION_REQUIRED);
                }
            } else {
                //不存在 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
        return user;
    }

    /**
     * 根據請求頭獲取用戶名及密碼
     *
     * @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.
         */
        //當請求過來時,會通過把用戶信息放在請求頭authorization中,並且通過Basic認證方式加密
        String authorization = httpHeaders.getFirst("authorization");//將得到 Basic Base64(用戶名:密碼)
        String baseCredentials = authorization.split(" ")[1];
        String usernamePassword = Base64Utils.decoder(baseCredentials);//用戶名:密碼
        LOGGER.debug("login user: {}", usernamePassword);
        String credentials[] = usernamePassword.split(":");
        return new UserTemp(credentials[0], credentials[1]);
    }

    /**
     * 解析請求過來的用戶
     */
    private class UserTemp {
        private String username;
        private String password;

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

我們使用PostMan模擬CAS服務登錄,向http://localhost:8088/login發起一個POST請求,請求中包含用戶名和密碼的認證信息,在Rest服務中,我將用戶名和密碼寫死了,定爲anumbrella/123,當然這裏可以按具體需求連接數據庫來實現。如下:
在這裏插入圖片描述
最後返回我們期望的結果,現在我們更改CAS配置rest地址爲http://localhost:8088/login,重啓服務,然後輸入密碼登錄測試。

在這裏插入圖片描述
可以發現登錄成功,我們實現了Rest服務認證。

同樣的,這裏我們密碼沒有進行加密配置。如果我們要配置密碼加密,更改配置文件如下:

#默認加密策略,通過encodingAlgorithm來指定算法,默認NONE不加密
# NONE|DEFAULT|STANDARD|BCRYPT|SCRYPT|PBKDF2
cas.authn.rest.passwordEncoder.type=DEFAULT
# 字符類型
cas.authn.rest.passwordEncoder.characterEncoding=UTF-8
# 加密算法
cas.authn.rest.passwordEncoder.encodingAlgorithm=MD5
#默認加密策略,通過encodingAlgorithm來指定算法,默認NONE不加密
# NONE|DEFAULT|STANDARD|BCRYPT|SCRYPT|PBKDF2
cas.authn.rest.passwordEncoder.type=DEFAULT
# 字符類型
cas.authn.rest.passwordEncoder.characterEncoding=UTF-8
# 加密算法
cas.authn.rest.passwordEncoder.encodingAlgorithm=MD5

到這裏CAS的多種認證方式就講完了,當然CAS的認證還有很多種,可以去查看官方的文檔——配置文檔

代碼實例:Chapter2

參考

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章