jeesite集成cas认证[通俗易懂]

2022-07-02 15:47:47 浏览数 (1)

大家好,又见面了,我是你们的朋友全栈君。

cas和shiro集成,很好的解决了登录及权限问题。本人最近第一次使用,框架使用的是jeesite开源框架,本身已经集成了shiro,现在将cas集成到项目中。

折腾了三天,终于把cas集成到jeesite中。现将集成过程写下,供朋友参考。

本项目集成cas的同时还留有登录入口,此时需要多种认证方式,步骤6、7的设置就是针对这个功能的,如不需要可直接跳过。

不做技术好多年了,项目时间紧只能亲自上阵,写的不周全的请多包涵。有问题望指教。

1、添加cas的maven依赖。

代码语言:javascript复制
  <!-- CAS -->
  <dependency>
      <groupId>org.jasig.cas.client</groupId>
      <artifactId>cas-client-core</artifactId>
      <version>3.2.1</version>
  </dependency>

2、web.xml添加内容。

<!– 该过滤器对HttpServletRequest请求包装, 可通过HttpServletRequest的getRemoteUser()方法获得登录用户的登录名,可选 –>

代码语言:javascript复制
 <filter>    
        <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>    
        <filter-class>    
            org.jasig.cas.client.util.HttpServletRequestWrapperFilter    
        </filter-class>    
    </filter>    
    <filter-mapping>    
        <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>    
        <url-pattern>/*</url-pattern>    
    </filter-mapping>    
    <!-- 该过滤器使得可以通过org.jasig.cas.client.util.AssertionHolder来获取用户的登录名。    
         比如AssertionHolder.getAssertion().getPrincipal().getName()。     
         这个类把Assertion信息放在ThreadLocal变量中,这样应用程序不在web层也能够获取到当前登录信息 -->    
    <filter>    
        <filter-name>CAS Assertion Thread Local Filter</filter-name>    
        <filter-class>org.jasig.cas.client.util.AssertionThreadLocalFilter</filter-class>   
    </filter>    
    <filter-mapping>    
        <filter-name>CAS Assertion Thread Local Filter</filter-name>    
        <url-pattern>/*</url-pattern>    
    </filter-mapping> 

3、配置spring-content-shiro.xml。

整体xml内容如下:

代码语言:javascript复制
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="
  http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-4.1.xsd
  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd"
 default-lazy-init="true">
 <description>Shiro Configuration</description>
    <!-- 加载配置属性文件 -->
 <context:property-placeholder ignore-unresolvable="true" location="classpath:project.properties" />
 
 <!-- Shiro权限过滤过滤器定义 -->
 <bean name="shiroFilterChainDefinitions" class="java.lang.String">
  <constructor-arg>
   <value>    
    /static/** = anon
                /userfiles/** = anon
                ${adminPath}/login = authc
                ${adminPath}/logout = logout
                ${adminPath}/cas = cas
                ${adminPath}/** = user
   </value>
  </constructor-arg>
 </bean>
 
 <bean id="logout" class="org.apache.shiro.web.filter.authc.LogoutFilter">
          <property name="redirectUrl" 
        value="${cas.logout.url}"/>
 </bean>
 
 <!-- 安全认证过滤器 -->
 <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
  <property name="securityManager" ref="securityManager" /> 
  <property name="loginUrl" value="${cas.server.url}/login?service=${cas.project.url}${adminPath}/cas" />
  <!--<property name="loginUrl" value="${adminPath}/login" /> -->
  <property name="successUrl" value="${adminPath}" />
  <property name="filters">
            <map>
                <entry key="cas" value-ref="casFilter"/>
                <entry key="authc" value-ref="formAuthenticationFilter"/>
                <entry key="logout" value-ref="logout" />
            </map>
        </property>
  <property name="filterChainDefinitions">
   <ref bean="shiroFilterChainDefinitions"/>
  </property>
 </bean>
 
 <!-- cas和shiro结合  -->
 <bean id="casRealm" class="com.sinosoft.modules.sys.security.CasLoginRealm">
     <property name="casServerUrlPrefix" value="${cas.server.url}"></property>
     <property name="casService" value="${cas.project.url}${adminPath}/cas"></property>
   </bean>
   <!-- CAS认证过滤器 -->  
 <bean id="casFilter" class="org.apache.shiro.cas.CasFilter">  
  <property name="failureUrl" value="${adminPath}/login"/>
 </bean>
 <!-- 定义Shiro安全管理配置 -->
 <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
  <property name="realms">
   <list>
    <ref bean="casRealm"/>
    <ref bean="systemAuthorizingRealm"/>
   </list>
  </property>
  <property name="sessionManager" ref="sessionManager" />
  <property name="cacheManager" ref="shiroCacheManager" />
 </bean>
 
 <!-- 配置使用自定义认证器,可以实现多Realm认证,并且可以指定特定Realm处理特定类型的验证 -->
    <bean id="authenticator" class="com.sinosoft.modules.sys.security.CustomizedModularRealmAuthenticator">
        <!-- 配置认证策略,只要有一个Realm认证成功即可,并且返回所有认证成功信息 -->
        <property name="authenticationStrategy">
            <bean class="org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy"></bean>
        </property>
    </bean>
 
 <!-- 自定义会话管理配置 -->
 <bean id="sessionManager" class="com.sinosoft.common.security.shiro.session.SessionManager">
  <property name="sessionDAO" ref="sessionDAO"/>
  
  <!-- 会话超时时间,单位:毫秒  -->
  <property name="globalSessionTimeout" value="${session.sessionTimeout}"/>
  
  <!-- 定时清理失效会话, 清理用户直接关闭浏览器造成的孤立会话   -->
  <property name="sessionValidationInterval" value="${session.sessionTimeoutClean}"/>
<!--    <property name="sessionValidationSchedulerEnabled" value="false"/> -->
   <property name="sessionValidationSchedulerEnabled" value="true"/>
   
  <property name="sessionIdCookie" ref="sessionIdCookie"/>
  <property name="sessionIdCookieEnabled" value="true"/>
 </bean>
 
 <!-- 指定本系统SESSIONID, 默认为: JSESSIONID 问题: 与SERVLET容器名冲突, 如JETTY, TOMCAT 等默认JSESSIONID,
  当跳出SHIRO SERVLET时如ERROR-PAGE容器会为JSESSIONID重新分配值导致登录会话丢失! -->
 <bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
     <constructor-arg name="name" value="jeesite.session.id"/>
 </bean>
 <!-- 自定义Session存储容器 -->
<!--  <bean id="sessionDAO" class="com.sinosoft.common.security.shiro.session.JedisSessionDAO"> -->
<!--   <property name="sessionIdGenerator" ref="idGen" /> -->
<!--   <property name="sessionKeyPrefix" value="${redis.keyPrefix}_session_" /> -->
<!--  </bean> -->
 <bean id="sessionDAO" class="com.sinosoft.common.security.shiro.session.CacheSessionDAO">
  <property name="sessionIdGenerator" ref="idGen" />
  <property name="activeSessionsCacheName" value="activeSessionsCache" />
  <property name="cacheManager" ref="shiroCacheManager" />
 </bean>
 
 <!-- 自定义系统缓存管理器-->
<!--  <bean id="shiroCacheManager" class="com.sinosoft.common.security.shiro.cache.JedisCacheManager"> -->
<!--   <property name="cacheKeyPrefix" value="${redis.keyPrefix}_cache_" /> -->
<!--  </bean> -->
 <bean id="shiroCacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
  <property name="cacheManager" ref="cacheManager"/>
 </bean>
 
 <!-- 保证实现了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>
 
</beans>

4、project.properties中增加属性url。

cas.server.url=http://localhost:8080/cas cas.project.url=http://1ocalhost:8080/infoService cas.logout.url=http://localhost:8080/cas/logout?server=http://localhost:8080/infoService

5、创建CasLoginRealm类,继承CasRealm。

代码语言:javascript复制
package com.sinosoft.modules.sys.security;
import java.util.Collection;  
import java.util.List;
import org.apache.commons.lang3.StringUtils;  
import org.apache.shiro.authc.AuthenticationException;  
import org.apache.shiro.authc.AuthenticationInfo;  
import org.apache.shiro.authc.AuthenticationToken;  
import org.apache.shiro.authc.SimpleAuthenticationInfo;  
import org.apache.shiro.authz.AuthorizationInfo;  
import org.apache.shiro.authz.SimpleAuthorizationInfo;  
import org.apache.shiro.cas.CasAuthenticationException;  
import org.apache.shiro.cas.CasRealm;  
import org.apache.shiro.cas.CasToken;  
import org.apache.shiro.session.Session;  
import org.apache.shiro.subject.PrincipalCollection;  
import org.apache.shiro.subject.SimplePrincipalCollection;  
import org.jasig.cas.client.authentication.AttributePrincipal;  
import org.jasig.cas.client.validation.Assertion;  
import org.jasig.cas.client.validation.TicketValidationException;  
import org.jasig.cas.client.validation.TicketValidator;  
import org.slf4j.Logger;  
import org.slf4j.LoggerFactory;  
import com.sinosoft.common.config.Global;  
import com.sinosoft.common.utils.SpringContextHolder;  
import com.sinosoft.common.web.Servlets;  
import com.sinosoft.modules.sys.entity.Menu;  
import com.sinosoft.modules.sys.entity.Role;  
import com.sinosoft.modules.sys.entity.User;  
import com.sinosoft.modules.sys.security.SystemAuthorizingRealm.Principal;  
import com.sinosoft.modules.sys.service.SystemService;  
import com.sinosoft.modules.sys.utils.LogUtils;  
import com.sinosoft.modules.sys.utils.UserUtils;  
/**
 * <p>Title: cas认证类</p>
 * <p>Description: cas认证操作类</p>
 * <p>Copyright: Copyright (c) 2017</p>
 * <p>Company: </p>
 * <p>Date: 2017-7-5</p>
 * @author holyspirit
 * @version 1.0
 */
public class CasLoginRealm extends CasRealm {
 private Logger logger = LoggerFactory.getLogger(getClass());
 private SystemService systemService;
 @Override
 protected AuthenticationInfo doGetAuthenticationInfo(
   AuthenticationToken token) throws AuthenticationException {
  // return super.doGetAuthenticationInfo(token);
  CasToken casToken = (CasToken) token;
  if (token == null) {
   return null;
  }
  // 获取ticket
  String ticket = (String) casToken.getCredentials();
  if (!org.apache.shiro.util.StringUtils.hasText(ticket)) {
   return null;
  }
  TicketValidator ticketValidator = ensureTicketValidator();
  try {
   // 回传ticket到服务端验证,验证通过就进入下一行,可以获取登录后的相关信息,否则直接抛异常,即验证不通过
   Assertion casAssertion = ticketValidator.validate(ticket,
     getCasService());
   AttributePrincipal casPrincipal = casAssertion.getPrincipal();
   String userId = casPrincipal.getName();
   User user = getSystemService().getUserByLoginName(userId);
   if (user != null) {
    Principal p = new Principal(user, false);
    PrincipalCollection principalCollection = new SimplePrincipalCollection(
      p, getName());
    return new SimpleAuthenticationInfo(principalCollection, ticket);
   } else {
    return null;
   }
  } catch (TicketValidationException e) {
   logger.error("票据认证失败", e);
   throw new CasAuthenticationException("Unable to validate ticket ["
       ticket   "]", e);
  }
 }
 @Override
 protected AuthorizationInfo doGetAuthorizationInfo(
   PrincipalCollection principals) {
  Principal principal = (Principal) getAvailablePrincipal(principals);
  // 获取当前已登录的用户
  if (!Global.TRUE.equals(Global.getConfig("user.multiAccountLogin"))) {
   Collection<Session> sessions = getSystemService().getSessionDao()
     .getActiveSessions(true, principal, UserUtils.getSession());
   if (sessions.size() > 0) {
    // 如果是登录进来的,则踢出已在线用户
    if (UserUtils.getSubject().isAuthenticated()) {
     for (Session session : sessions) {
      getSystemService().getSessionDao().delete(session);
     }
    }
    // 记住我进来的,并且当前用户已登录,则退出当前用户提示信息。
    else {
     UserUtils.getSubject().logout();
     throw new AuthenticationException("msg:账号已在其它地方登录,请重新登录。");
    }
   }
  }
  User user = getSystemService().getUserByLoginName(
    principal.getLoginName());
  if (user != null) {
   SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
   List<Menu> list = UserUtils.getMenuList();
   for (Menu menu : list) {
    if (StringUtils.isNotBlank(menu.getPermission())) {
     // 添加基于Permission的权限信息
     for (String permission : StringUtils.split(
       menu.getPermission(), ",")) {
      info.addStringPermission(permission);
     }
    }
   }
   // 添加用户权限
   info.addStringPermission("user");
   // 添加用户角色信息
   for (Role role : user.getRoleList()) {
    info.addRole(role.getEnname());
   }
   // 更新登录IP和时间
   getSystemService().updateUserLoginInfo(user);
   // 记录登录日志
   LogUtils.saveLog(Servlets.getRequest(), "系统登录");
   return info;
  } else {
   return null;
  }
 }
    /**
     * 
     * Title:获取系统业务对象
     * Description:获取系统业务对象
     * @param 
     * @return 
     * @Author:holyspirit
     * @Create Date: 2017-7-6
     * @Modifier:
     * @Modify Date:
     */
 public SystemService getSystemService() {
  if (systemService == null) {
   systemService = SpringContextHolder.getBean(SystemService.class);
  }
  return systemService;
 }
}

6、FormAuthenticationFilter类增加代码片段,重写redirectToLogin方法。

代码语言:javascript复制
@Override
 protected void redirectToLogin(ServletRequest request,
   ServletResponse response) throws IOException {
  setLoginUrl("/info/login");
  WebUtils.issueRedirect(request, response, getLoginUrl());
 }

7、新增CustomizedModularRealmAuthenticator类。

代码语言:javascript复制
package com.sinosoft.modules.sys.security;
import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
import java.util.Collection;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.cas.CasToken;
import org.apache.shiro.realm.Realm;
/**
 * <p>Title: 自定义Authenticator</p>
 * <p>Description:分别定义处理不同登录方式验证的Realm</p>
 * <p>Copyright: Copyright (c) 2017</p>
 * <p>Company: </p>
 * <p>Date: 2017-7-6</p>
 * @author holyspirit
 * @version 1.0
 */
public class CustomizedModularRealmAuthenticator extends
  ModularRealmAuthenticator {
 @Override
 protected AuthenticationInfo doAuthenticate(
   AuthenticationToken authenticationToken)
   throws AuthenticationException {
  //获取所有Realm
  Collection<Realm> realms = getRealms();
  Realm realm = null;
  //判断当前token是castoken还是UsernamePasswordToken
  //强转对应类型token
  //根据token类型获得对应的Realm
  if(authenticationToken instanceof CasToken){
   authenticationToken = (CasToken)authenticationToken;
   realm = getRealm(realms, authenticationToken);
  }
  if(authenticationToken instanceof UsernamePasswordToken){
   authenticationToken = (UsernamePasswordToken)authenticationToken;
   realm = getRealm(realms, authenticationToken);
  }
  //执行响应Realm
  return doSingleRealmAuthentication(realm, authenticationToken);
 }
 
 //判断当前token对应的Realm
 public Realm getRealm(Collection<Realm> realms, AuthenticationToken token){
  for (Realm realm : realms) {
   if(realm.supports(token)){
    return realm;
   }
  }
  return null;
 }
}

8、设置注销事件,退出cas认证同时清除系统session

代码语言:javascript复制
<a href="${ctx}/logout" title="退出登录">退出</a>
代码语言:javascript复制
问题集合:
代码语言:javascript复制
1。中文乱码问题,所有提交的请求中文变成乱码,这是过滤器顺序所致。cas过滤器和shiro过滤器要放在编码过滤器后面。

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/148373.html原文链接:https://javaforall.cn

0 人点赞