CAS单点登录-自定义认证之Shiro、Rest(六)
注意:单点登录版本为cas-5.1.3
若需要上个版本代码,可以点击下载:
这章即将讲解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明确规定如下:
- cas 服务端会通过post请求,并且把用户信息以”用户名:密码”进行Base64编码放在authorization请求头中
- 返回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
结果如下
总结
- shiro的使用率及集成
- rest的应用场景及集成
cas的认证方式中,最常见三种校验方式:
- 数据库查询(上一章)
- shiro集成验证(本章)
- 远程rest认证(本章)
相信很多人对自定义校验器
非常感兴趣,例如验证码登录、扫码登录等等,那么这些再后面再继续说
因为验证码登录必须先把界面调整,这是主题的范畴,回过头再讲自定义。
注意:
由于版本5.1.3是shiro.ini配置在classpath下会从临时文件中加载,有bug,希望在5.1.4或者5.1.5进行解决,那么如果在5.1.3希望用shiro,配置在其他系统盘绝对路径即可
下载代码后注意查看README.md
模块名 | 模块介绍 | 备注 | 端口情况 | 必须https | path | 启动循序 |
---|---|---|---|---|---|---|
sso-server | cas服务 | 接入鉴权 | 8443 | √ | cas | 3 |
sso-config | 配置中心 | 管理各个服务配置 | 8888 | × | config | 1 |
sso-rest-client | rest验证应用 | rest验证应用 | 8883 | × | / | 2 |
用户:
用户名 | 密码 | 是否可登录 | 备注 | 验证器 |
---|---|---|---|---|
admin | 123 | √ | 可登录 | shiro |
rest-admin | 123 | √ | 可登录 | shiro |
rest-test | 123 | √ | 可登录 | rest |
rest-locked | 123 | × | 锁定 | rest |
rest-disable | 123 | × | 不可用 | rest |
rest-expired | 123 | × | 过期,修改密码 | rest |
作者联系方式
如果技术的交流或者疑问可以联系或者提出issue。
邮箱:huang.wenbin@foxmail.com
QQ: 756884434 (请注明:SSO-CSDN)