该项目是在shiro(三)的基础上进行修改的,建议首先将shiro的部分了解一下。
CAS的知识点:
单点登录:简称SSO,SSO使得在多个应用系统中,用户只需要登录一次就可以访问所有互相信任的应用系统
CAS框架:CAS是实现单点登录的框架。
结构:cas分为两个部分,CAS SERVER 和 CAS Client 需要独立部署,主要负责对用户的认证工作,Cas Client 负责对客户端保护资源的请求,需要登录的时候,重定向到CAS Server
盗用一张图:(深深的吸引了我)
执行流程:
1:Web Browser去访问CAS Client 的时候,cas客户端会向Web Browser索要session和令牌,如果没有则执行第二步
2:当发现令牌和session的时候,会重定向到cas服务端(重点),在重定向的时候,地址需要按照一定的格式:
登录:认证中心的地址加"/login"+"service="+你本地的项目发布的地址+你shiro的地址(filterUrlPattern)
原因:service前面的地址是认证中心的地址+"/login",后面的是认证完成之后,需要跳会的地址,在调回的地址中需要设置
successUrl 和 failureUrl,登录成功和失败的路径。
登出:认证中心的地址加"/logout"+"service="+shiro的地址(你本地项目的地址)
3:如果认证中心发现当前还没有登录,此时会跳转到登录页面,进行登录
4:登录成功之后,认证中心会生成一个Ticket ,就是唯一的票据
格式:http://a:8080/?ticket=ST-XXXX-XXX
5:然后客户端会再去向服务器端去认证票据,
6:认证成功之后返回用户信息,认证完成之后,会建立一个全局的session
登出的流程:
1)、客户端向客户端发送登出Logout请求。
2)、客户端取消本地会话,同时通知认证中心,用户已登出。
3)、客户端返回客户端登出请求。
4)、认证中心通知所有用户登录访问的应用,用户已登出。
引用一张图:图片地址,https://blog.csdn.net/javaloveiphone/article/details/52439613?utm_source=blogxgwz0
写的小的案例:
shiro在1.2版本的时候就整合了cas,在使用cas的时候,就是将CasFilter添加到shiro的Filter中
一些常用常量
// logger
private static final Logger logger = LoggerFactory.getLogger(MyShiroCasConfig.class);
// cas 的server地址
public static final String casServerUrlPrefix = "http://127.0.0.1";
// cas 登录页面的地址
public static final String casLoginUrl = casServerUrlPrefix + "/login";
// cas 登出页面地址
public static final String casLogoutUrl = casServerUrlPrefix + "/logout";
// 当前功臣对外提供的服务地址
public static final String shiroServerUrlPrefix = "http://127.0.1.28:8080";
// casFilter cas 拦截的地址
public static final String casFilterUrlPattern = "/shiro-cas";
// 登录的地址
public static final String loginUrl = casLoginUrl + "?service=" + shiroServerUrlPrefix + casFilterUrlPattern;
// 退出的地址
public static final String logoutUrl = casLogoutUrl + "?service=" + casLogoutUrl;
// 登录成功的地址
public static final String loginSuccessUrl = "/index";
// 失败的地址
public static final String unauthorizedUrl = "/403";
设置 SecurityManager --- shiro的核心处理器
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager defaultWebSecurityManager =
new DefaultWebSecurityManager();
// 才采用缓存
defaultWebSecurityManager.setCacheManager(getEhCacheManager());
// 指定shiro
defaultWebSecurityManager.setRealm(myShiroCasRealm());
// 指定subjectFactory,如果实现的cas的remember me(免登录) 的功能,
defaultWebSecurityManager.setSubjectFactory(new CasSubjectFactory());
return defaultWebSecurityManager;
}
该类是shiro 的核心处理类,将Realm和用户的数据进行数据的比较,new CasSubjectFactory()的作用是如果要使用面登录的功能,需要添加该类。
设置Ehcache缓存的Bean
/**
* 【配置的ehcache进行数据的缓存
*
* @return
*/
@Bean
public EhCacheManager getEhCacheManager() {
EhCacheManager ehCacheManager = new EhCacheManager();
ehCacheManager.setCacheManagerConfigFile("classpath:ehcache-shiro.xml");
return ehCacheManager;
}
Ehcache的配置
<ehcache
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false">
<diskStore path="d:/ehcache"/>
<defaultCache
maxElementsInMemory="20000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
maxElementsOnDisk="10000000"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU"
/>
<cache name="eternalCache"
maxElementsInMemory="20000"
eternal="true"
overflowToDisk="true"
diskPersistent="false"
timeToLiveSeconds="0"
diskExpiryThreadIntervalSeconds="120"
/>
</ehcache>
设置自定义的CasRealm的类
@Bean
public MyShiroCasRealm myShiroCasRealm() {
MyShiroCasRealm myShiroCasRealm = new MyShiroCasRealm();
// 设置cas登录服务器地址的前缀
myShiroCasRealm.setCasServerUrlPrefix(MyShiroCasConfig.casServerUrlPrefix);
// 客户端回调地址,登录成功后的跳转的地址(自己的服务器)
myShiroCasRealm.setCasService(MyShiroCasConfig.shiroServerUrlPrefix + MyShiroCasConfig.casFilterUrlPattern);
return myShiroCasRealm;
}
setCasServerUrlPrefix:表示的你访问的认证中心的地址 ,setCasService:表示当你认真中心认证完成之后需要访问的service地址
MyShiroCasRealm的类
package com.sgqing.demo.config;
import com.sgqing.demo.entity.SysPermission;
import com.sgqing.demo.entity.SysRole;
import com.sgqing.demo.entity.UserInfo;
import com.sgqing.demo.sevice.UserInfoService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.cas.CasRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
/**
* shiro集成cas之后,进行的配置
*/
public class MyShiroCasRealm extends CasRealm {
@Autowired
private UserInfoService userInfoService;
private static final Logger logger = LoggerFactory.getLogger(MyShiroCasRealm.class);
/**
* 用于授权
*
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("进行授权 -------->");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
String name = (String) super.getAvailablePrincipal(principalCollection);
UserInfo byUsername = userInfoService.findByUsername(name);
for (SysRole role : byUsername.getRoleList()) {
info.addRole(role.getRole());
for (SysPermission permission : role.getPermissions()) {
info.addStringPermission(permission.getPermission());
}
}
return info;
}
/**
* 用于认证
*
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
// 调用父类的认证,父类认证已经完成了
AuthenticationInfo authenticationInfo = super.doGetAuthenticationInfo(authenticationToken);
String name = (String) authenticationInfo.getPrincipals().getPrimaryPrincipal();
SecurityUtils.getSubject().getSession().setAttribute("no", name);
return authenticationInfo;
}
}
上面的类作用:直接继承CasRealm类,然后CasRealm已经完成了数据的认证工作,我们直接调用父类的功能即可
设置单点退出的监听器
// 设置单点登出的监听器
/*
*SingleSignOutFilter需要配置在所有Filter之前,当Cas Client通过Cas Server登录成功,
* Cas Server会携带生成的Service Ticket回调Cas Client,
* 此时SingleSignOutFilter会将Service Ticket与当前的Session绑定在一起。
* 当Cas Server在进行logout后回调Cas Client应用时也会携带该Service Ticket,
* 此时Cas Client配置的SingleSignOutFilter将会使对应的Session失效,进而达到登出的目的。
*
* SingleSignOutHttpSessionListener用于在Cas Client应用中的Session过期时将其从对应的映射关系中移除。
* */
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public ServletListenerRegistrationBean servletListenerRegistrationBean() {
ServletListenerRegistrationBean bean = new ServletListenerRegistrationBean();
bean.setListener(new SingleSignOutHttpSessionListener());
bean.setEnabled(true);
return bean;
}
作用是将所有的过期的session将其从对应的映射关系中移除
设置单点退出的拦截器
// 设置单点登出的拦截器
// FilterRegistrationBean 表示的是设置拦截器的优先级
/*SingleSignOutFilter需要配置在所有Filter之前,当Cas Client通过Cas Server登录成功,
* Cas Server会携带生成的Service Ticket回调Cas Client,
* 此时SingleSignOutFilter会将Service Ticket与当前的Session绑定在一起。
* 当Cas Server在进行logout后回调Cas Client应用时也会携带该Service Ticket,
* 此时Cas Client配置的SingleSignOutFilter将会使对应的Session失效,进而达到登出的目的。
* */
@Bean
public FilterRegistrationBean registrationBean() {
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setName("registrationBean");
registrationBean.setFilter(new SingleSignOutFilter());
registrationBean.addUrlPatterns("/*");//拦截所有的请求
registrationBean.setEnabled(true);
registrationBean.setOrder(10);//设置优先级
return registrationBean;
}
上面的作用是说,在登录的时候,客户端会去服务端进行认证,此时认证成功之后,服务端会将地址和ST返回给客户端,而在此时该拦截器会将session跟ST绑定在一起,如果访问退出的时候,此时服务端也会将服务地址和ST返回,此时的监听器会将所有的session全部变为失效。
设置配置的触发的地方;
// 注册DelegatingFilterProxy(shiro) 是一个代理类,用于管理拦截器的生命周期, 所有的请求都会拦截
/*
* 先在filter中加入DelegatingFilterProxy类,"targetFilterLifecycle"指明作用于filter的所有生命周期。
* 原理是,DelegatingFilterProxy类是一个代理类,所有的请求都会首先发到这个filter代理,然后再按照"filter-name"委派到spring中的这个bean。
* 在Spring中配置的bean的name要和web.xml中的<filter-name>一样.
此外,spring bean实现了Filter接口,但默认情况下,是由spring容器来管理其生命周期的(不是由tomcat这种服务器容器来管理)。
如果设置"targetFilterLifecycle"为True,则spring来管理Filter.init()和Filter.destroy();若为false,则这两个方法失效!!
该步只是将当前的的生命周期交给了spring管理,具体的管理还是需要下面的LifecycleBeanPostProcessor的对象去进行操作
bean.addUrlPatterns("/*"); :表示的是拦截所有的请求
* */
@Bean
public FilterRegistrationBean filterRegistrationBean() {
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(new DelegatingFilterProxy("shiroFilter")); //设置的shiro的拦截器 ShiroFilterFactoryBean
bean.addInitParameter("targetFilterLifecycle", "true");
bean.setEnabled(true);
bean.addUrlPatterns("/*");
return bean;
}
作用:用于设置shiro的拦截器,和将每一个拦截器的生命周期交给spring去管理
上面设置了声明周期,下面进行设置生命周期的自动化:
// 设置方法的自动初始化和销毁,init和destory方法被自动调用。注意,如果使用了该类,则不需要手动初始化方法和销毁方法,否则出错
@Bean(name = "lifecycleBeanPostProcessor")
public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
开启注解声明
// 开启shiro aop 的注解支持,使用代理的方式,所以需要开启代码的支持
@Bean
public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator proxyCreator = new DefaultAdvisorAutoProxyCreator();
// 设置代理方式,true是cglib的代理方式,false是普通的jdk代理方式
proxyCreator.setProxyTargetClass(true);
return proxyCreator;
}
// 开启注解
@Bean
public AuthorizationAttributeSourceAdvisor attributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
设置shiro的拦截器工厂类
// 使用工厂模式,创建并初始化ShiroFilter
@Bean(name = "shiroFilter")
public ShiroFilterFactoryBean getShiroFilterFactoryBean(SecurityManager securityManager, CasFilter casFilter) {
ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
factoryBean.setSecurityManager(securityManager);
// 如果不设置,会自动寻找目录下的/login.jsp页面
factoryBean.setLoginUrl(loginUrl);
// 设置无权限访问页面
factoryBean.setUnauthorizedUrl(unauthorizedUrl);
// 添加casFilter中,注意,casFilter需要放到shiroFilter的前面
Map<String, Filter> linkedHashMap = new LinkedHashMap<>();
linkedHashMap.put("casFilter", casFilter);
factoryBean.setFilters(linkedHashMap);
loadShiroFilterChain(factoryBean);
return factoryBean;
}
说明:loginUrl表示的是:当没有登录的时候,客户端回去访问服务端,请求的参数格式是:认证中心的地址+"/login"+"service="+"当前项目的地址"+"shiro定义的返回的地址"
在设置拦截器的时候,需要先执行cas的拦截器,再执行shiro的拦截器
设置CasFilter
// 定义cas的拦截器
@Bean(name = "casFilter")
public CasFilter getCasFilter() {
CasFilter filter = new CasFilter();
// 自动注入拦截器的名称
filter.setName("casFilter");
// 是否自动的将当前的拦截器进行注入
filter.setEnabled(true);
// 在登录失败之后,也就是shiro执行CasRealm的doGetAuthenticationInfo 方法向CasServer验证tiker
filter.setFailureUrl(loginUrl);//认证失败之后,重新登录
filter.setLoginUrl(loginUrl);
return filter;
}
对shiroFilter所需要的拦截器
/**
* 加载shiroFilter权限控制规则(从数据库读取然后配置),角色/权限信息由MyShiroCasRealm对象提供doGetAuthorizationInfo实现获取来的
* 生产中会将这部分规则放到数据库中
*
* @param shiroFilterFactoryBean
*/
private void loadShiroFilterChain(ShiroFilterFactoryBean shiroFilterFactoryBean) {
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
filterChainDefinitionMap.put(casFilterUrlPattern, "casFilter");
//2.不拦截的请求
filterChainDefinitionMap.put("/css/**", "anon");
filterChainDefinitionMap.put("/js/**", "anon");
filterChainDefinitionMap.put("/login", "anon");
// 此处将logout页面设置为anon,而不是logout,因为logout被单点处理,而不需要再被shiro的logoutFilter进行拦截
filterChainDefinitionMap.put("/logout", "anon");
filterChainDefinitionMap.put("/error", "anon");
//3.拦截的请求(从本地数据库获取或者从casserver获取(webservice,http等远程方式),看你的角色权限配置在哪里)
filterChainDefinitionMap.put("/user", "authc"); //需要登录
//4.登录过的不拦截
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
}
上面是根据Shiro和Cas整合之后操作的代码。
说一下,执行的流程:
首先在启动的时候会对所有的请求进行拦截 DelegatingFilterProxy,指定的是casFilter和shiroFilter两个拦截器,并同时将生命周期交给spring去管理,当然只是交给还不可以,需要设置一个@Bena,LifecycleBeanPostProcessor这个类用于管理声明周期,
在shiroFilter中首先设置的是 securityManager,在securityManager设置 EhCacheManager 缓存,在缓存中就只是读取缓存的一些配置 -> 同时设置自定义的CasRealm类,在CasRealm类中需要指定远程认证的地址和认证成功之后返回的地址,CasRealm类中去继承CasRealm类,然后覆盖认证和授权这两个类 ,(当设置完成登录的地址的时候,同时设置登出的拦截器(FilterRegistrationBean中有SingleSignOutFilter对象,该对象用于清除过时的session,不过该拦截器要优先于所有的拦截器进行执行)和监听器(ServletListenerRegistrationBean-->监听器中有SingleSignOutHttpSessionListener对象,用于清除session的映射(所谓的映射就是cas客户端和访问端)))- -> 并同时设置参数,修改登录的页面,需要修改成客户端去访问服务端的地址,如果不修改,会自动的去访问当前项目下面的login页面,就不会走cas认证中心的地址,还修改当没有权限访问的时候,请求的路径,同时还设置casFilter拦截器(该拦截器中设置的是如果认证失败之后跳转的位置(一般是登录的页面)和登录的页面)同时设置之前设置的shiro拦截器的一些设置(主要是对一些地址进行权限的设置),设置完成之后,再开启shiro的注解