CAS单点登录-自定义认证之Shiro、Rest(六)

CAS单点登录-自定义认证之Shiro、Rest(六)

注意:单点登录版本为cas-5.1.3
若需要上个版本代码,可以点击下载:GitHub 码云

这章即将讲解cas服务端集成shiro认证、Rest认证

上一章讲了自定认证之jdbc 已经解决了很多需求上的问题,但这远远不够,因为需求总是变态的,所以我们要时刻准备着

犹如:

  • 公司老程序员多,用目前使用成熟的技术吧
  • 公司数据安全性有要求,cas不允许连到我的账号库,哦,给我写个接口总可以吧

Shiro认证

接下来不得不分析一下shiro的需求量:

  • 框架使用shiro鉴权 ★★★★☆
  • 熟悉shiro的比较多 ★★★★★
  • 相对于来说轻量级 ★★☆☆☆

当然了使用shiro的公司还是非常的多,听说包括spring的官网也是用shiro的,所以在cas中集成shiro进行鉴权,把老系统的配置直接拿来用,那再好不过了。

pom.xml

添加maven依赖

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

加了依赖即将支持三种校验方式,包括文件存储用户校验器、拒绝用户校验器、shiro校验器

这里写图片描述

若对Whitelist(文件校验白名单)、Blacklist(黑名单)机制、配置了解或者有需求疑问感兴趣可以联系博主,这些将不讲解,但附上配置图

这里写图片描述

cas系统配置

# Shiro Authentication 开始
#允许登录的用户,必须要有以下权限,否则拒绝,多个逗号隔开
cas.authn.shiro.requiredPermissions=staff
#允许登录的用户,必须要有以下权限,否则拒绝,多个逗号隔开
cas.authn.shiro.requiredRoles=admin
#shir配置文件位置
cas.authn.shiro.config.location=classpath:shiro.ini
#shiro name 唯一
cas.authn.shiro.name=cas-shiro
# 与Query Authentication一致的加密策略
cas.authn.shiro.passwordEncoder.type=DEFAULT
# cas.authn.shiro.passwordEncoder.characterEncoding=UTF-8
cas.authn.shiro.passwordEncoder.encodingAlgorithm=MD5
# Shiro Authentication 结束

shiro.ini:

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

[users]
#密码123
admin = 202cb962ac59075b964b07152d234b70, admin
#不可登录,因为配置了需要角色admin
#密码123456
test = e10adc3949ba59abbe56e057f20f883e, developer

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

这个文件就不多说了,主要是看shiro的配置情况,包括可以自定义用户存储策略,校验realm,从侧面来说只需要配置鉴权部分即可

注意: cas-shiro只做Subject.login();另外一个角度说,只做鉴权,不做其他退出之类的,所以在这里filter之类的鉴权器是无作用的跟不用说urls选项的匹配。

测试

尝试登录:

  • admin/123 成功
  • test/123456 失败(没有权限)

Rest 认证

由于架构或公司政策等原因,不得不使用接口来打通数据的问题。

那问题来了:

问:什么是Rest认证?
答:通过数据接口对用户进行认证
问:cas又是怎么做的?
答:通过请求接口,返回固定格式,进行对密码匹配,判断用户是否合法
问:什么场景下用rest认证?用户数据存在远端、不允许cas直接访问帐号数据、cas不希望你知道帐号数据的表结构、存储方式可能目前不满足

配置

application.properties

#REST 认证开始
#请求远程调用接口
cas.authn.rest.uri=http://localhost:8881/login
#加密策略
cas.authn.rest.passwordEncoder.type=DEFAULT
cas.authn.rest.passwordEncoder.characterEncoding=UTF-8
#加密算法
cas.authn.rest.passwordEncoder.encodingAlgorithm=MD5
#REST 结束

当用户点击登录后,cas会发送post请求到http://localhost:8881/login并且把用户信息以”用户名:密码”进行Base64编码放在authorization请求头中

若输入用户名密码为:admin/123

那么请求头包括:
authorization=Basic Base64(admin+MD5(123))

那么发送后客户端必须响应一下数据,cas明确规定如下:

  1. cas 服务端会通过post请求,并且把用户信息以”用户名:密码”进行Base64编码放在authorization请求头中
  2. 返回200状态码并且格式为{“@class”:”org.apereo.cas.authentication.principal.SimplePrincipal”,”id”:”casuser”,”attributes”:{}} 是成功的; 返回状态码403用户不可用;404账号不存在;423账户被锁定;428过期;其他登录失败

这里写图片描述

客户端校验Demo

SysUser.java

/*
 * 版权所有.(c)2008-2017. 卡尔科技工作室
 */

package com.carl.auth.sso.rest.client.bean;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;

import javax.validation.constraints.NotNull;
import java.util.HashMap;
import java.util.Map;

/**
 * @author Carl
 * @date 2017/9/14
 * @since JDK1.7
 */
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<>();

    @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;
    }
}

而上面的属性,必须跟@class的实现一一对应,如attributes 就是返回属性给对接的客户端,有必要的信息必须返回给cas,cas会进行二次过滤,而二次过滤是属于多属性返回的内容,后面的章节会说明白哦


AuthUserController.java

/*
 * 版权所有.(c)2008-2017. 卡尔科技工作室
 */

package com.carl.auth.sso.rest.client.controller;

import com.carl.auth.sso.rest.client.bean.SysUser;
import com.carl.auth.sso.rest.client.service.UserRepertory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.Base64Utils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;

import java.io.UnsupportedEncodingException;

/**
 * @author Carl
 * @date 2017/9/14
 * @since JDK1.7
 */
@RestController
public class AuthUserController {
    private static final Logger LOGGER = LoggerFactory.getLogger(AuthUserController.class);

    @Autowired
    private UserRepertory userRepertory;

    /**
     * 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 = userRepertory.getUser(userTemp.username);
            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 = new String(Base64Utils.decodeFromString(baseCredentials), "UTF-8");//用户名:密码
        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;
        }
    }
}

这段代码核心是实现cas明确要求的返回值,如果敢兴趣,请仔细看注解

若尝试用户rest-locked/admin结果如下
这里写图片描述

总结

  1. shiro的使用率及集成
  2. rest的应用场景及集成

cas的认证方式中,最常见三种校验方式:

  • 数据库查询(上一章)
  • shiro集成验证(本章)
  • 远程rest认证(本章)

相信很多人对自定义校验器非常感兴趣,例如验证码登录、扫码登录等等,那么这些再后面再继续说
因为验证码登录必须先把界面调整,这是主题的范畴,回过头再讲自定义。


下载本章代码:GitHub

注意:

由于版本5.1.3是shiro.ini配置在classpath下会从临时文件中加载,有bug,希望在5.1.4或者5.1.5进行解决,那么如果在5.1.3希望用shiro,配置在其他系统盘绝对路径即可

下载代码后注意查看README.md

模块名模块介绍备注端口情况必须httpspath启动循序
sso-servercas服务接入鉴权8443cas3
sso-config配置中心管理各个服务配置8888×config1
sso-rest-clientrest验证应用rest验证应用8883×/2

用户:

用户名密码是否可登录备注验证器
admin123可登录shiro
rest-admin123可登录shiro
rest-test123可登录rest
rest-locked123×锁定rest
rest-disable123×不可用rest
rest-expired123×过期,修改密码rest

作者联系方式

如果技术的交流或者疑问可以联系或者提出issue。

邮箱:huang.wenbin@foxmail.com

QQ: 756884434 (请注明:SSO-CSDN)

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值