单点登录:Single Sign On,简称SSO,SSO使得在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。
CAS框架:CAS(Central Authentication Service)是实现SSO单点登录的框架。
逻辑关系图:(注:图为转载)
分析:
1.图中用户访问cas客户端;
2.需要登录时,重定向到Cas-Server(Cas服务),其中service为Cas-Client路径
(用于Cas-Server执行完后返回到指定路径);
3.cas-server认证用户信息,并生成一个ticket返回给用户;
4.用户使用此ticket访问Cas-Client(连接了Cas-Server具体应用);
5.Cas-Client使用ticket再次访问Cas-Server进行认证;
6.认证成功后返回Server指定路径到Cas-Client,并返回具体登录用户信息,流程结束。
这是原生Cas的一套流程,那么我们需要集成到已经使用Shiro框架的应用中,如何进行无缝衔接呢?
具体流程:
对于Cas-Server就不多说了,官网下载下来后,修改验证用户信息的配置(从数据库中读取数据进行
身份认证),修改配置文件-deployerConfigContext:
(其中deployerConfigContext.xml文件是CAS专门提出来的供用户修改的配置,其他配置不建议修改)
- <bean id="primaryAuthenticationHandler"
- class="org.jasig.cas.adaptors.jdbc.QueryDatabaseAuthenticationHandler">
- <property name="dataSource" ref="dataSource" />
- <property name="sql" value="select password from userwhere userName=?" />
- </bean>
- <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
- <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
- <property name="url" value="jdbc:mysql://127.0.0.1:3306/**?characterEncoding=utf8" />
- <property name="username" value="**" />
- <property name="password" value="**" />
- </bean>
还需要引入mysql-connector-java-5.1.34.jar(连接mysql)
连接Cas-Server的使用了Shiro的应用项目连接的整体操作流程,如下:
1.首先需要引入包shiro-cas这个包是shiro和cas连接的通道;
2.修改项目中shiroRealm(自定义的登录验证类,里面包含登录认证、权限认证)
- /**
- * shiro登录实现类
- *
- */
- //重点是集成CasRealm
- public class ShiroRealm extends CasRealm {
- private Logger log = LoggerFactory.getLogger(ShiroRealm.class);
- private TicketValidator ticketValidator;
- protected TicketValidator ensureTicketValidator()
- {
- if(ticketValidator == null)
- ticketValidator = createTicketValidator();
- return ticketValidator;
- }
- @Override
- protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {
- CasToken casToken = (CasToken) authcToken;
- if (authcToken == null)
- return null;
- String ticket = (String) casToken.getCredentials();
- TicketValidator ticketValidator = ensureTicketValidator();
- try
- {
- Assertion casAssertion = ticketValidator.validate(ticket, getCasService());
- AttributePrincipal casPrincipal = casAssertion.getPrincipal();
- String userId = casPrincipal.getName();
- log.debug("Validate ticket : {} in CAS server : {} to retrieve user : {}", new Object[] {
- ticket, getCasServerUrlPrefix(), userId
- });
- Map<String, Object> attributes = casPrincipal.getAttributes();
- casToken.setUserId(userId);
- String rememberMeAttributeName = getRememberMeAttributeName();
- String rememberMeStringValue = (String)attributes.get(rememberMeAttributeName);
- boolean isRemembered = rememberMeStringValue != null && Boolean.parseBoolean(rememberMeStringValue);
- if(isRemembered)
- casToken.setRememberMe(true);
- /** 此处是封装用户信息
- sUsr su = new sUsr();
- su.setUsrCde(userId);
- sUsr susr = isUsrService.findByCode(su);
- AccessTokenInfo atInfo = new AccessTokenInfo();
- atInfo.setUsrCde(userId);
- //获取apikey
- AccessTokenInfo ati = accessTokenInfoService.selectOneByObject(atInfo);
- //构建ShiroUserAccount
- ShiroUserAccount sua = new ShiroUserAccount(susr,ati);
- */
- List<Object> principals = CollectionUtils.asList(new Object[] {
- sua, attributes
- });
- PrincipalCollection principalCollection = new SimplePrincipalCollection(principals, getName());
- return new SimpleAuthenticationInfo(principalCollection, ticket);
- }
- catch(TicketValidationException e)
- {
- throw new CasAuthenticationException((new StringBuilder()).append("Unable to validate ticket [").append(ticket).append("]").toString(), e);
- }
- }
- @Override
- protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {
- SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
- //获取登录用户的Shiro对象 ---主体身份信息(验权)
- ShiroUserAccount shiroUser = (ShiroUserAccount)principal.getPrimaryPrincipal();
- //断言,若对象为空则直接抛出异常
- Assert.notNull(shiroUser,"找不到principal中的SessionVariable---shiroUser");
- //添加用户拥有的role
- addRoles(info,shiroUser);
- addPermissions(info,shiroUser);
- return info;
- }
- }
3.配置shiro.xml文件:
shiroRealm:
- <bean id="shiroRealm" class="com.**.ShiroRealm">
- <!-- cas服务端地址前缀 -->
- <property name="casServerUrlPrefix" value="http://127.0.0.1:8080/SSO" />
- <!-- 应用服务地址,用来接收cas服务端票据,客户端的cas入口 -->
- <property name="casService" value="http://127.0.0.1:8585/**/shiro-cas" />
- </bean>
shiroFilter:
casFilter:
- <!--shiro过滤器配置,bean的id值须与web中的filter-name的值相同-->
- <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
- <property name="securityManager" ref="securityManager" />
- <span style="white-space:pre;"> </span><!-- 验证用户未登录时跳转的登录地址 -->
- <property name="loginUrl" value="http://127.0.0.1:8080/SSO/login?service=http://127.0.0.1:8585/**/shiro-cas" />
- <property name="successUrl" value="" />
- <!-- 验证用户权限的跳转地址 -->
- <property name="unauthorizedUrl" value="/" />
- <property name="filters">
- <span style="white-space:pre;"> </span> <map>
- <!--添加cas的过滤器到shiro -->
- <span style="white-space:pre;"> </span><entry key="casFilter" value-ref="casFilter"/>
- <span style="white-space:pre;"> </span><!--添加登出过滤 -->
- <span style="white-space:pre;"> </span><entry key="logoutFilter" value-ref="logoutFilter" />
- <span style="white-space:pre;"> </span> </map>
- </property>
- <property name="filterChainDefinitions">
- <value>
- /shiro-cas = casFilter
- /person/**=authc
- </value>
- </property>
- </bean>
- <bean id="casFilter" class="org.apache.shiro.cas.CasFilter">
- <!-- 配置验证错误时的失败页面 -->
- <property name="failureUrl" value="http://127.0.0.1:8080/SSO/login?service=http://127.0.0.1:8585/themis_front/shiro-cas" />
- <property name="successUrl" value="/themis/ReviewQuery" />
- </bean>
logoutFilter:
cas针对subject工厂配置:
如下是shiro剩余的基本配置:
- <bean id="logoutFilter" class="org.apache.shiro.web.filter.authc.LogoutFilter">
- <!-- 配置验证错误时的失败页面 -->
- <property name="redirectUrl" value="http://127.0.0.1:8080/SSO/logout?service=http://127.0.0.1:8585/themis_front/shiro-cas" />
- </bean>
- <bean id="casSubjectFactory" class="org.apache.shiro.cas.CasSubjectFactory"></bean>
- <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
- <!--单个realm使用realm,如果有多个realm,使用realms属性代替-->
- <property name="realm" ref="shiroRealm" />
- <!-- session 管理器 -->
- <property name="sessionManager" ref="sessionManager" />
- <!-- 缓存管理器 -->
- <property name="cacheManager" ref="shiroCacheManager" />
- <property name="subjectFactory" ref="casSubjectFactory"></property>
- </bean>
- <!-- session管理器 -->
- <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
- <!-- 超时时间 -->
- <property name="globalSessionTimeout" value="1800000"/>
- <!-- session存储的实现 -->
- <property name="sessionDAO" ref="shiroSessionDao"/>
- <!-- sessionIdCookie的实现,用于重写覆盖容器默认的JSESSIONID -->
- <property name="sessionIdCookie" ref="sharesession"/>
- <!-- 定时检查失效的session -->
- <property name="sessionValidationSchedulerEnabled" value="true" />
- </bean>
- <!-- sessionIdCookie的实现,用于重写覆盖容器默认的JSESSIONID -->
- <bean id="sharesession" class="org.apache.shiro.web.servlet.SimpleCookie">
- <!-- cookie的name,对应的默认是 JSESSIONID -->
- <constructor-arg name="name" value="SHAREJSESSIONID"/>
- <!-- 记住我cookie生效时间30天 -->
- <property name="maxAge" value="2592000" />
- </bean>
- <!-- session存储的实现 -->
- <bean id="shiroSessionDao" class="org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO" />
- <!-- 缓存管理实现 -->
- <bean id="shiroCacheManager" class="org.apache.shiro.cache.MemoryConstrainedCacheManager" />
- <!-- 保证实现了Shiro内部lifecycle函数的bean执行 -->
- <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
- <!-- AOP式方法级权限检查 -->
- <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor">
- <property name="proxyTargetClass" value="true" />
- </bean>
- <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
- <property name="securityManager" ref="securityManager"/>
- </bean>