您的位置:首页 > 其它

shiro安全框架扩展教程--整合cas框架扩展自定义CasRealm

2014-12-27 21:51 543 查看
       这次我给大家讲讲如何在shiro中整合cas框架,以及扩展自定义的角色和资源体系,啰嗦话不多说了,直接上代码说明

第一步,搭建cas服务器,我也不说拉,这个大家用现有的cas服务就行了

第二步,先加入cas-client的包到我们的项目,然后再下载个shiro-cas.jar也放到项目里

第三步配置shiro中的cas设置

<description>shiro配置</description>

<!-- 安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="cacheManager" ref="shiroCacheManager" />
<property name="sessionManager" ref="sessionManager" />
<property name="realm" ref="casRealm" />
<property name="subjectFactory" ref="casSubjectFactory" />
<!-- <property name="realm" ref="simpleUserRealm" /> -->
</bean>

<!-- 会话管理器 -->
<bean id="sessionManager"
class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
<property name="sessionValidationSchedulerEnabled" value="false" />
<property name="sessionDAO" ref="sessionDAO" />
<property name="globalSessionTimeout" value="600000" />
</bean>

<!-- 缓存管理器 -->
<bean id="shiroCacheManager"
class="com.silvery.security.shiro.cache.SimpleShiroCacheManager">
<property name="cache" ref="shiroCache" />
</bean>

<!-- 缓存实现类,注入自定义缓存机制 -->
<bean id="shiroCache" class="com.silvery.security.shiro.cache.SimpleShiroCache">
<property name="cacheManager" ref="simpleCacheManager" />
</bean>

<!-- 会话读写实现类 -->
<bean id="sessionDAO" class="com.silvery.security.shiro.session.CacheSessionDAO" />

<!-- 用户认证实现 -->
<bean id="simpleUserRealm" class="com.silvery.security.shiro.realm.SimpleUserRealm" />

<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />

<bean id="casFilter" class="org.apache.shiro.cas.CasFilter">
<!-- 配置验证错误时的失败页面 -->
<property name="failureUrl"
value="https://cas.test.com:8443/login?service=http://test.com/mh/cas/login.do" />
</bean>

<bean id="casRealm" class="com.silvery.security.shiro.realm.SimpleCasRealm">
<property name="defaultRoles" value="ROLE_USER" />
<property name="casServerUrlPrefix" value="https://cas.test.com:8443" />
<!-- 客户端的回调地址设置,必须和下面的过滤器拦截的地址一致 -->
<property name="casService" value="http://test.com/mh/cas/login.do" />
</bean>

<!-- 如果要实现cas的remember me的功能,需要用到下面这个bean,并设置到securityManager的subjectFactory中 -->
<bean id="casSubjectFactory" class="org.apache.shiro.cas.CasSubjectFactory" />

<bean
class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="staticMethod"
value="org.apache.shiro.SecurityUtils.setSecurityManager" />
<property name="arguments" ref="securityManager" />
</bean>

<!-- 过滤链配置 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager" />
<property name="loginUrl"
value="https://cas.test.com:8443/login?service=http://test.com/mh/cas/login.do" />
<property name="filters">
<map>
<entry key="cas" value-ref="casFilter" />
<entry key="role">
<bean
class="com.silvery.security.shiro.filter.SimpleRoleAuthorizationFilter" />
</entry>
<entry key="authc">
<bean
class="com.silvery.security.shiro.filter.SimpleFormAuthenticationFilter" />
</entry>
<entry key="exec">
<bean class="com.silvery.security.shiro.filter.SimpleExecutiveFilter" />
</entry>
</map>
</property>
</bean>

<!-- 权限资源配置 -->
<bean id="filterChainDefinitionsService"
class="com.silvery.security.shiro.service.ini.impl.SimpleFilterChainDefinitionsService">
<property name="definitions">
<value>
/mh/cas/login.do = cas
/mh/casUrl.do = role[ROLE_USER]
/static/** = anon
/** = exec
</value>
</property>
</bean>

关于这一个步骤的配置里面注释写的比较清楚了,至于一些类是自己重写的,可以自己参考前面的文章,/mh/cas/login.do其实就是cas拦截器的指定路径,如果想登录就请求这个路径即可,如果没有登录他会跳转cas的login页面

第四步就是需要重写我们的casrealm,你可以看到上面的配置有SimpleCasRealm,这个类是我自己重写的,是为了方便分配自己本地系统的权限体系,因为shiro-cas提供的默认CasRealm功能比较有限,不能动态角色体系,下面可以看看这个原始的CasRealm源码

public class CasRealm extends AuthorizingRealm
{

public CasRealm()
{
validationProtocol = "CAS";
rememberMeAttributeName = "longTermAuthenticationRequestTokenUsed";
setAuthenticationTokenClass(org/apache/shiro/cas/CasToken);
}

protected void onInit()
{
super.onInit();
ensureTicketValidator();
}

protected TicketValidator ensureTicketValidator()
{
if(ticketValidator == null)
ticketValidator = createTicketValidator();
return ticketValidator;
}

protected TicketValidator createTicketValidator()
{
String urlPrefix = getCasServerUrlPrefix();
if("saml".equalsIgnoreCase(getValidationProtocol()))
return new Saml11TicketValidator(urlPrefix);
else
return new Cas20ServiceTicketValidator(urlPrefix);
}

protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
throws AuthenticationException
{
CasToken casToken = (CasToken)token;
if(token == null)
return null;
String ticket = (String)casToken.getCredentials();
if(!StringUtils.hasText(ticket))
return null;
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 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);
List principals = CollectionUtils.asList(new Object[] {
userId, 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);
}
}

protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals)
{
SimplePrincipalCollection principalCollection = (SimplePrincipalCollection)principals;
List listPrincipals = principalCollection.asList();
Map attributes = (Map)listPrincipals.get(1);
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
addRoles(simpleAuthorizationInfo, split(defaultRoles));
addPermissions(simpleAuthorizationInfo, split(defaultPermissions));
List attributeNames = split(roleAttributeNames);
String value;
for(Iterator i$ = attributeNames.iterator(); i$.hasNext(); addRoles(simpleAuthorizationInfo, split(value)))
{
String attributeName = (String)i$.next();
value = (String)attributes.get(attributeName);
}

attributeNames = split(permissionAttributeNames);
String value;
for(Iterator i$ = attributeNames.iterator(); i$.hasNext(); addPermissions(simpleAuthorizationInfo, split(value)))
{
String attributeName = (String)i$.next();
value = (String)attributes.get(attributeName);
}

return simpleAuthorizationInfo;
}

private List split(String s)
{
List list = new ArrayList();
String elements[] = StringUtils.split(s, ',');
if(elements != null && elements.length > 0)
{
String arr$[] = elements;
int len$ = arr$.length;
for(int i$ = 0; i$ < len$; i$++)
{
String element = arr$[i$];
if(StringUtils.hasText(element))
list.add(element.trim());
}

}
return list;
}

private void addRoles(SimpleAuthorizationInfo simpleAuthorizationInfo, List roles)
{
String role;
for(Iterator i$ = roles.iterator(); i$.hasNext(); simpleAuthorizationInfo.addRole(role))
role = (String)i$.next();

}

private void addPermissions(SimpleAuthorizationInfo simpleAuthorizationInfo, List permissions)
{
String permission;
for(Iterator i$ = permissions.iterator(); i$.hasNext(); simpleAuthorizationInfo.addStringPermission(permission))
permission = (String)i$.next();

}

public String getCasServerUrlPrefix()
{
return casServerUrlPrefix;
}

public void setCasServerUrlPrefix(String casServerUrlPrefix)
{
this.casServerUrlPrefix = casServerUrlPrefix;
}

public String getCasService()
{
return casService;
}

public void setCasService(String casService)
{
this.casService = casService;
}

public String getValidationProtocol()
{
return validationProtocol;
}

public void setValidationProtocol(String validationProtocol)
{
this.validationProtocol = validationProtocol;
}

public String getRememberMeAttributeName()
{
return rememberMeAttributeName;
}

public void setRememberMeAttributeName(String rememberMeAttributeName)
{
this.rememberMeAttributeName = rememberMeAttributeName;
}

public String getDefaultRoles()
{
return defaultRoles;
}

public void setDefaultRoles(String defaultRoles)
{
this.defaultRoles = defaultRoles;
}

public String getDefaultPermissions()
{
return defaultPermissions;
}

public void setDefaultPermissions(String defaultPermissions)
{
this.defaultPermissions = defaultPermissions;
}

public String getRoleAttributeNames()
{
return roleAttributeNames;
}

public void setRoleAttributeNames(String roleAttributeNames)
{
this.roleAttributeNames = roleAttributeNames;
}

public String getPermissionAttributeNames()
{
return permissionAttributeNames;
}

public void setPermissionAttributeNames(String permissionAttributeNames)
{
this.permissionAttributeNames = permissionAttributeNames;
}

public static final String DEFAULT_REMEMBER_ME_ATTRIBUTE_NAME = "longTermAuthenticationRequestTokenUsed";
public static final String DEFAULT_VALIDATION_PROTOCOL = "CAS";
private static Logger log = LoggerFactory.getLogger(org/apache/shiro/cas/CasRealm);
private String casServerUrlPrefix;
private String casService;
private String validationProtocol;
private String rememberMeAttributeName;
private TicketValidator ticketValidator;
private String defaultRoles;
private String defaultPermissions;
private String roleAttributeNames;
private String permissionAttributeNames;

}
其实跟我们普通用的UserRealm或者是JdbcRealm差别不大,但是里面增加了casToken的验证,所以我们应该直接拿过来用,下面再加载出我们的自己的逻辑即可,所以我们可以选择继承当前的CasRealm重载一下他的两个方法

/**
*
* 扩展CAS桥接器,订制角色体系和资源体系
*
* @author shadow
*
*/
public class SimpleCasRealm extends CasRealm {

@Autowired
private CacheManager cacheManager;

private final static Logger log = LoggerFactory.getLogger(SimpleCasRealm.class);

public SimpleCasRealm() {
super();
setCacheManager(cacheManager);
}

protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
CasToken casToken = (CasToken) token;
if (token == null)
return null;
String ticket = (String) casToken.getCredentials();
if (!StringUtils.hasText(ticket))
return null;
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 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);
List principals = CollectionUtils.asList(new Object[] { userId, attributes });
PrincipalCollection principalCollection = new SimplePrincipalCollection(principals, getName());

// 这里可以拿到Cas的登录账号信息,加载到对应权限体系信息放到缓存中...

  return new SimpleAuthenticationInfo(principalCollection, ticket);
} catch (TicketValidationException e) {
throw new CasAuthenticationException((new StringBuilder()).append("Unable to validate ticket [")
.append(ticket).append("]").toString(), e);
}
}

protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
SimplePrincipalCollection principalCollection = (SimplePrincipalCollection) principals;
List listPrincipals = principalCollection.asList();
Map attributes = (Map) listPrincipals.get(1);
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();

// 这里可以加载缓存的中的数据到认证实体...

 addRoles(simpleAuthorizationInfo, split(getDefaultRoles()));

return simpleAuthorizationInfo;
}

protected List split(String s) {
List list = new ArrayList();
String elements[] = StringUtils.split(s, ',');
if (elements != null && elements.length > 0) {
String arr$[] = elements;
int len$ = arr$.length;
for (int i$ = 0; i$ < len$; i$++) {
String element = arr$[i$];
if (StringUtils.hasText(element))
list.add(element.trim());
}

}
return list;
}

protected void addRoles(SimpleAuthorizationInfo simpleAuthorizationInfo, List roles) {
String role;
for (Iterator i$ = roles.iterator(); i$.hasNext(); simpleAuthorizationInfo.addRole(role))
role = (String) i$.next();

}

protected void addPermissions(SimpleAuthorizationInfo simpleAuthorizationInfo, List permissions) {
String permission;
for (Iterator i$ = permissions.iterator(); i$.hasNext(); simpleAuthorizationInfo
.addStringPermission(permission))
permission = (String) i$.next();

}

/** 重写退出时缓存处理方法 */
protected void doClearCache(PrincipalCollection principals) {
Object principal = principals.getPrimaryPrincipal();
try {
getCache().remove(principal);
log.debug(new StringBuffer().append(principal).append(" on logout to remove the cache [").append(principal)
.append("]").toString());
} catch (CacheException e) {
log.error(e.getMessage());
}
}

/** 获取缓存管理器的缓存堆实例 */
protected Cache<Object, Object> getCache() throws CacheException {
return cacheManager.getCache(CacheEmnu.MEMCACHED_DATA_CACHE);
}

public CacheManager getCacheManager() {
return cacheManager;
}

public void setCacheManager(CacheManager cacheManager) {
this.cacheManager = cacheManager;
}

}

值得提醒大家的一个关键点,如何获取cas返回过来的对象信息呢?

Subject subject = SecurityUtils.getSubject();
Object principal = subject.getPrincipal();
PrincipalCollection principals = subject.getPrincipals();
第一个对象是可以获取到当前登录账号

第二个对象是一个List集合其中0元素是当前登录账号,1元素是一个map集合,这里就存放了我们cas服务给我返回的用户信息

我们写的拦截器判断是否有登录就用第一个Object判断是否有null即可

第五步既然有登录了,那就必须有退出功能,那如何才能完整退出呢?流程应该是先执行当前系统的注销,然后再执行cas的logout,这样就比较完整了,不会出现莫名其妙的问题

调用当前的shiro的subject.logout();注销当前系统的对象,然后返回到页面

@RequestMapping("/mh/cas/logout.do")
public ModelAndView casLogout(HttpServletRequest request, HttpServletResponse response, UserDetailsVo vo) {
SimpleUtils.getSubject().logout();
return createModelAndView("/mh/logout");
}页面再重定向到cas的logout,这样就把cas的ticket也注销成功
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>正在注销...</title>
<script type="text/javascript" src="${staticHost}/static/plugin/jquery/core.js"></script>
<script type="text/javascript">
location.href="https://cas.test.com:8443/logout?service=http://test.com/mh/index.do";
</script>
</head>
<body>
</body>
</html>

我想改造大概很明白了,其实shiro-cas.jar已经大部分拦截处理已经帮我们做好了,所以我们很安心地按照以往的方式来操控shiro的登录方式,希望对还没爬过这个坑的同学有帮助
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐