第四部分:spring security使用cas单点登录配置

来源:互联网 发布:淘宝客好赚钱吗 编辑:程序博客网 时间:2024/05/22 15:09

第四部分:spring security使用cas单点登录配置

  spring security本身就提供了对cas的支持,只需要简单的配置就可以实现单点登录。
  由于客户端项目没有使用spring security自带的权限管理,采用了自定义的实现,配置起来比正常的spring security要复杂一些。

(一)spring security域cas集成配置

1、增加cas依赖

  需要为客户端项目增加spring-security-cas的依赖,它会自动把cas-client-core.jar的依赖加入到项目中。

<!-- cas --><dependency>    <groupId>org.springframework.security</groupId>    <artifactId>spring-security-cas</artifactId>    <version>3.1.0.RELEASE</version></dependency>

2、增加applicationContext-cas.xml

  加入了securityFilter来处理权限,额外注入了一个基础数据库操作类resourcesDao。在需要使用数据库的地方可以根据项目情况来进行注入。注入只需要为属性增加set方法即可。

<?xml version="1.0" encoding="UTF-8"?><beans:beans xmlns="http://www.springframework.org/schema/security" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"    xmlns:p="http://www.springframework.org/schema/p" xmlns:beans="http://www.springframework.org/schema/beans"    xsi:schemaLocation="http://www.springframework.org/schema/context  http://www.springframework.org/schema/context/spring-context.xsd             http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd               http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd"    default-lazy-init="true">    <!-- 不要过滤图片等静态资源 -->    <http pattern="/**/*.jpg" security="none" />    <http pattern="/**/*.png" security="none" />    <http pattern="/**/*.gif" security="none" />    <http pattern="/**/*.css" security="none" />    <http pattern="/**/*.js" security="none" />    <!--SSO -->    <http entry-point-ref="casEntryPoint" auto-config="true">        <intercept-url pattern="/" access="IS_AUTHENTICATED_ANONYMOUSLY" />        <custom-filter ref="casFilter" after="CAS_FILTER"/>        <!-- 登出过滤器 -->        <custom-filter ref="requestSingleLogoutFilter" before="LOGOUT_FILTER" />        <custom-filter ref="singleLogoutFilter" before="CAS_FILTER" />        <!-- 权限过滤器 -->        <custom-filter ref="securityFilter" before="FILTER_SECURITY_INTERCEPTOR" />    </http>    <beans:bean id="casEntryPoint" class="org.springframework.security.cas.web.CasAuthenticationEntryPoint">        <beans:property name="loginUrl" value="${cas.server}/login" />        <beans:property name="serviceProperties" ref="serviceProperties" />    </beans:bean>    <beans:bean id="serviceProperties" class="org.springframework.security.cas.ServiceProperties">        <beans:property name="service" value="${cas.client}/j_spring_cas_security_check" />        <beans:property name="sendRenew" value="false" />    </beans:bean>    <beans:bean id="casFilter" class="org.springframework.security.cas.web.CasAuthenticationFilter">        <beans:property name="authenticationManager" ref="authenticationManager" />        <beans:property name="authenticationFailureHandler">            <beans:bean class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">                <!-- cas登录失败跳转页面,跳转首页 -->                <beans:property name="defaultFailureUrl" value="${cas.client}"/>            </beans:bean>        </beans:property>        <!-- 登录成功处理,将用户信息存入session -->        <beans:property name="authenticationSuccessHandler">            <beans:bean class="com.whty.bwjf.framework.core.cas.CasAuthenticationSuccessHandler">                <!-- cas登录成功后跳转页面 -->                <beans:property name="defaultTargetUrl" value="/admin.jsp"/>                <beans:property name="usersDao" ref="resourcesDao"></beans:property>                <beans:property name="resDao" ref="resourcesDao"></beans:property>            </beans:bean>        </beans:property>    </beans:bean>    <authentication-manager alias="authenticationManager">        <authentication-provider ref="casAuthenticationProvider" />    </authentication-manager>    <beans:bean id="casAuthenticationProvider" class="org.springframework.security.cas.authentication.CasAuthenticationProvider">        <!-- 注入获取tsp用户的service -->        <beans:property name="userDetailsService" ref="tspUserDetailServiceImpl" />        <beans:property name="serviceProperties" ref="serviceProperties" />        <beans:property name="ticketValidator">            <beans:bean class="org.jasig.cas.client.validation.Cas20ServiceTicketValidator">                <beans:constructor-arg index="0" value="${cas.server}" />            </beans:bean>        </beans:property>        <beans:property name="key" value="an_id_for_this_auth_provider_only" />    </beans:bean>    <!-- 注销客户端 -->    <beans:bean id="singleLogoutFilter" class="org.jasig.cas.client.session.SingleSignOutFilter" />    <!-- 注销服务器端 -->    <beans:bean id="requestSingleLogoutFilter" class="org.springframework.security.web.authentication.logout.LogoutFilter">        <beans:constructor-arg value="${cas.server}/logout" />        <beans:constructor-arg>            <beans:bean class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler" />        </beans:constructor-arg>        <beans:property name="filterProcessesUrl" value="/j_spring_cas_security_logout" />    </beans:bean>    <!-- 认证过滤器 -->    <beans:bean id="securityFilter"        class="com.whty.bwjf.tsp.core.security.TspSecurityFilter">        <!-- 用户拥有的权限 -->        <beans:property name="authenticationManager" ref="authenticationManager" />        <!-- 用户是否拥有所请求资源的权限 -->        <beans:property name="accessDecisionManager" ref="tspAccessDecisionManager" />        <!-- 资源与权限对应关系 -->        <beans:property name="securityMetadataSource" ref="tspSecurityMetadataSource" />    </beans:bean>    <!-- 判断是否有访问权限 -->    <beans:bean id="tspAccessDecisionManager"        class="com.whty.bwjf.tsp.core.security.TspAccessDecisionManager"></beans:bean>    <!-- 从数据库提取权限和资源,装配到HashMap中,供Spring Security使用,用于权限校验 -->    <beans:bean id="tspSecurityMetadataSource"        class="com.whty.bwjf.tsp.core.security.TspSecurityMetadataSource">        <!-- 菜单表DAO -->        <beans:constructor-arg name="resourcesDao" ref="resourcesDao"></beans:constructor-arg>    </beans:bean>    <!-- 基础数据库操作类 -->    <beans:bean id="resourcesDao"        class="com.whty.bwjf.framework.core.dao.imp.BaseDao">        <beans:property name="sessionFactory" ref="sessionFactory"></beans:property>    </beans:bean>    <!-- 为Spring Security提供一个经过用户认证后的UserDetails -->    <beans:bean id="tspUserDetailServiceImpl"        class="com.whty.bwjf.tsp.core.security.TspUserDetailServiceImpl">        <!-- 用户表DAO -->        <beans:property name="usersDao" ref="resourcesDao"></beans:property>        <!-- 菜单表DAO -->        <beans:property name="resDao" ref="resourcesDao"></beans:property>    </beans:bean></beans:beans> 

2、cas属性文件

cas.server=http://192.168.5.129:8080/cascas.client=http://192.168.4.184:8091/ucs

3、userDetailsService

  CasAuthenticationProvider中需要自行配置一个继承UserDetailsService接口的实现类,来保存用户信息和权限信息。

/**  *该类的主要作用是为Spring Security提供一个经过用户认证后的UserDetails。 *该UserDetails包括用户名、密码、是否可用、是否过期等信息。 */@SuppressWarnings("deprecation")public class TspUserDetailServiceImpl implements UserDetailsService {    @Resource    private IBaseDao<SysUser> usersDao;    @Resource    private IBaseDao<SysResource> resDao;    public void setUsersDao(IBaseDao<SysUser> usersDao) {        this.usersDao = usersDao;    }    public void setResDao(IBaseDao<SysResource> resDao) {        this.resDao = resDao;    }    /**     * 登入默认会调到这里     */    @Transactional    @Override    public UserDetails loadUserByUsername(String username)            throws UsernameNotFoundException {        //获取用户信息        String hql = "from SysUser t where t.account = :username";        Map<String, Object> params = new HashMap<String, Object>();        params.put("username", username);        List<SysUser> userList = null;        try {            userList = this.usersDao.find(hql,params);        } catch (Exception e) {            e.printStackTrace();        }        SysUser users = null;        if(userList == null || userList.size() < 1){            users = null;        }else{            users = userList.get(0);        }        //取得用户的权限        Collection<GrantedAuthority> grantedAuths = obtionGrantedAuthorities(users);        boolean enables = true;        boolean accountNonExpired = true;        boolean credentialsNonExpired = true;        boolean accountNonLocked = true;        //封装成spring security的user        User userdetail = new User(users.getAccount(), users.getPassword(), enables, accountNonExpired, credentialsNonExpired, accountNonLocked, grantedAuths);        return userdetail;    }    //取得用户的权限    private Set<GrantedAuthority> obtionGrantedAuthorities(SysUser user) {        Set<GrantedAuthority> authSet = new HashSet<GrantedAuthority>();          //获取用户所属组          Set<SysRole> sysRoles = user.getSysRoles();          for(SysRole sysRole : sysRoles ){                //获取用户组对应 角色集合              Set<SysResource> sysResources = sysRole.getSysResources();            for(SysResource sysResource : sysResources){                authSet.add(                          new GrantedAuthorityImpl(sysResource.getText()));            }        }        return authSet;    }}

4、登出配置

  web.xml新增单点登出过滤器。
  web.xml中需要加入cas的SingleSignOutFilter实现单点登出功能,该过滤器需要放在shiroFilter之前,spring字符集过滤器之后。在实际使用时发现,SingleSignOutFilter如果放在了spring字符集过滤器之前,数据在传输过程中就会出现乱码。

<!-- 用于单点退出,该过滤器用于实现单点登出功能--><listener>    <listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class></listener><!-- 该过滤器用于实现单点登出功能。 --><filter>    <filter-name>CAS Single Sign Out Filter</filter-name>    <filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class></filter><filter-mapping>    <filter-name>CAS Single Sign Out Filter</filter-name>    <url-pattern>/*</url-pattern></filter-mapping>

(二)自定义权限与cas集成配置

  如果使用spring security自带的验证框架,只需要前面三步,并且去掉自定义过滤器和相关bean就可以实现cas单点登录了。

1、登录成功后将用户信息放入Session

  其实spring security已经将UserDetails对象保存在了Session中,但是公司的项目并没有使用Session中的UserDetails,而是自己使用了自己保存的session对象,因此需要将用户信息在登陆时保存到Session中。

/** * 登录成功处理Handler,保存用户信息到session中 */public class CasAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler{    final Logger logger = LoggerFactory.getLogger(getClass());     @Resource    private IBaseDao<SysUser> usersDao;    @Resource    private IBaseDao<SysResource> resDao;    public void setUsersDao(IBaseDao<SysUser> usersDao) {        this.usersDao = usersDao;    }    public void setResDao(IBaseDao<SysResource> resDao) {        this.resDao = resDao;    }    @Override    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException,            ServletException {        logger.info("cas验证成功");        String username = ((UserDetails) authentication.getPrincipal()).getUsername();        SysUser user = getUser(username);        request.getSession().setAttribute(Constans.SESSION_USER, user);        if (user != null) {            request.getSession().setAttribute(Constans.SESSION_AUTH, loadAut(user.getId()));        }        super.onAuthenticationSuccess(request, response, authentication);    }    //获取用户信息    private SysUser getUser(String username){        String hql = "from SysUser t where t.account = :username";        Map<String, Object> params = new HashMap<String, Object>();        params.put("username", username);        List<SysUser> userList = null;        try {            userList = this.usersDao.find(hql,params);        } catch (Exception e) {            logger.error("读取菜单失败");            logger.error(e.getMessage());        }        if(userList == null || userList.size() < 1){            return null;        }else{            return userList.get(0);        }    }    //获取权限信息    private Set<String> loadAut(String uid) {        Set<String> set = new HashSet<String>();        List<SysResource> resources = new ArrayList<SysResource>();        Map<String, Object> params = new HashMap<String, Object>();        String hql = "select res from SysResource res  join res.sysRoles r join r.sysUsers u where 1 = 1 and u.id = :userId order by res.seq asc ";        params.put("userId", uid);        try {            resources = resDao.find(hql, params);        } catch (Exception e) {            logger.error("读取菜单失败");            logger.error(e.getMessage());        }        if (resources != null && resources.size() > 0) {            for(SysResource resource : resources){                set.add(resource.getUrl());            }        }        return set;    }}

2、accessDecisionManager和securityMetadataSource

  自定义具体如何实现可以参考spring security相关文章,本系统在实现cas集成时没有修改原系统的这两个实现类,现在仅放出来提供参考。

/** *AccessdecisionManager在Spring security中是很重要的。 * *在验证部分简略提过了,所有的Authentication实现需要保存在一个GrantedAuthority对象数组中。  *这就是赋予给主体的权限。 GrantedAuthority对象通过AuthenticationManager *保存到 Authentication对象里,然后从AccessDecisionManager读出来,进行授权判断。  * *Spring Security提供了一些拦截器,来控制对安全对象的访问权限,例如方法调用或web请求。  *一个是否允许执行调用的预调用决定,是由AccessDecisionManager实现的。  *这个 AccessDecisionManager 被AbstractSecurityInterceptor调用, *它用来作最终访问控制的决定。 这个AccessDecisionManager接口包含三个方法:  * void decide(Authentication authentication, Object secureObject,    List<ConfigAttributeDefinition> config) throws AccessDeniedException; boolean supports(ConfigAttribute attribute); boolean supports(Class clazz);  从第一个方法可以看出来,AccessDecisionManager使用方法参数传递所有信息,这好像在认证评估时进行决定。   特别是,在真实的安全方法期望调用的时候,传递安全Object启用那些参数。   比如,让我们假设安全对象是一个MethodInvocation。   很容易为任何Customer参数查询MethodInvocation,  然后在AccessDecisionManager里实现一些有序的安全逻辑,来确认主体是否允许在那个客户上操作。   如果访问被拒绝,实现将抛出一个AccessDeniedException异常。  这个 supports(ConfigAttribute) 方法在启动的时候被  AbstractSecurityInterceptor调用,来决定AccessDecisionManager  是否可以执行传递ConfigAttribute。   supports(Class)方法被安全拦截器实现调用,  包含安全拦截器将显示的AccessDecisionManager支持安全对象的类型。 */public class TspAccessDecisionManager implements AccessDecisionManager {    @Override    public void decide(Authentication authentication, Object object,            Collection<ConfigAttribute> configAttributes)            throws AccessDeniedException, InsufficientAuthenticationException {        if(configAttributes == null) {            return;        }        //所请求的资源拥有的权限(一个资源对多个权限)        Iterator<ConfigAttribute> iterator = configAttributes.iterator();        while(iterator.hasNext()) {            ConfigAttribute configAttribute = iterator.next();            //访问所请求资源所需要的权限            String needPermission = configAttribute.getAttribute();            System.out.println("needPermission is " + needPermission);            //用户所拥有的权限authentication            for(GrantedAuthority ga : authentication.getAuthorities()) {                if(needPermission.equals(ga.getAuthority())) {                    return;                }            }        }        //没有权限让我们去捕捉        throw new AccessDeniedException(" 没有权限访问!");    }    @Override    public boolean supports(ConfigAttribute attribute) {        // TODO Auto-generated method stub        return true;    }    @Override    public boolean supports(Class<?> clazz) {        // TODO Auto-generated method stub        return true;    }}
/** * 加载资源与权限的对应关系 该过滤器的主要作用就是通过spring著名的IoC生成securityMetadataSource。 * securityMetadataSource相当于本包中自定义的MyInvocationSecurityMetadataSourceService。 * 该MyInvocationSecurityMetadataSourceService的作用是从数据库提取权限和资源,装配到HashMap中, * 供Spring Security使用,用于权限校验。 *  * @author sparta 11/3/29 */public class TspSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {    private IBaseDao<SysResource> resourcesDao;    // 由spring调用    public TspSecurityMetadataSource(IBaseDao<SysResource> resourcesDao) throws Exception {        this.resourcesDao = resourcesDao;        loadResourceDefine();    }    private static Map<String, Collection<ConfigAttribute>> resourceMap = null;    private RequestMatcher pathMatcher;    // 返回所请求资源所需要的权限    @Override    public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {        Iterator<String> it = resourceMap.keySet().iterator();        while (it.hasNext()) {            String resURL = it.next();            Iterator<String> ite = resourceMap.keySet().iterator();            if (resURL != null && !"".equals(resURL)) {                pathMatcher = new AntPathRequestMatcher(resURL);            } else {                pathMatcher = new AntPathRequestMatcher("/other.do");            }            if (pathMatcher.matches(((FilterInvocation) object).getRequest())) {                Collection<ConfigAttribute> returnCollection = resourceMap.get(resURL);                return returnCollection;            }        }        return null;    }    // 加载所有资源与权限的关系    @Transactional    private void loadResourceDefine() throws Exception {        if (resourceMap == null) {            resourceMap = new HashMap<String, Collection<ConfigAttribute>>();            // Session session = this.resourcesDao.get            BaseDao<SysResource> dao = (BaseDao) resourcesDao;            Session session = dao.getSessionFactory().openSession();            Query query = session.createQuery("from SysResource");            List<SysResource> resources = query.list();            // List<SysResource> resources =            // this.resourcesDao.findAll(SysResource.class);            for (SysResource resource : resources) {                Collection<ConfigAttribute> configAttributes = new ArrayList<ConfigAttribute>();                // 以权限名封装为Spring的security Object                // resource.getRoleName() 角色名称 可随意 role_admin 或者 admin                ConfigAttribute configAttribute = new SecurityConfig(resource.getText());                configAttributes.add(configAttribute);                // resource.getInterceptUrl() 格式必须是 拦截的包路径                // 或者是 比如 /manager/**/*.jh 或者 /system/manager/**/*.jsp                resourceMap.put(resource.getUrl(), configAttributes);            }        }        Set<Entry<String, Collection<ConfigAttribute>>> resourceSet = resourceMap.entrySet();        Iterator<Entry<String, Collection<ConfigAttribute>>> iterator = resourceSet.iterator();    }    @Override    public Collection<ConfigAttribute> getAllConfigAttributes() {        // TODO Auto-generated method stub        return null;    }    @Override    public boolean supports(Class<?> clazz) {        // TODO Auto-generated method stub        return true;    }}

3、session失效处理

  公司项目使用了自定义权限处理,在session失效后casEntryPoint的 access-denied-page没有生效,因此自定义了一个session过滤器来处理session失效的问题。
  这个过滤器应该放在单点登录过滤器之前,CharacterEncodingFilter之后。

<!-- session过滤器,检查是否有session --><filter>    <filter-name>onlineFilter</filter-name>    <filter-class>com.whty.bwjf.framework.core.util.OnlineFilter</filter-class></filter><filter-mapping>    <filter-name>onlineFilter</filter-name>    <url-pattern>*.do</url-pattern></filter-mapping><filter-mapping>    <filter-name>onlineFilter</filter-name>    <url-pattern>*.htm</url-pattern></filter-mapping><filter-mapping>    <filter-name>onlineFilter</filter-name>    <url-pattern>*.jsp</url-pattern></filter-mapping>
/** * 登陆验证过滤 */public class OnlineFilter extends HttpServlet implements Filter {     private static final long serialVersionUID = 1L;    private String redirect_url;    public void doFilter(ServletRequest request, ServletResponse response,   FilterChain chain) throws IOException, ServletException {        // 这里设置如果没有登陆将要转发到的页面          HttpServletRequest req = (HttpServletRequest) request;         HttpServletResponse res = (HttpServletResponse) response;         HttpSession session = req.getSession(true);          String requestUri = req.getRequestURI();        String contextPath = req.getContextPath();        String url = requestUri.substring(contextPath.length());        // 从session里取的用户名信息          SysUser user = (SysUser) session.getAttribute(Constans.SESSION_USER);        // 这里获取session,为了检查session里有没有保存用户信息,没有的话回转发到登陆页面        // 判断如果没有取到用户信息,就跳转到登陆页面          if(user == null && StringUtils.isEmpty(req.getParameter("ticket"))) {            // 跳转到登陆页面               if(isAjaxRequest(req)){                try {                    response.getWriter().print("login");                } catch (IOException e) {                    e.printStackTrace();                }            }            else{                res.sendRedirect(redirect_url);            }        } else {            chain.doFilter(request, response);           }     }      private boolean isAjaxRequest(HttpServletRequest request) {          String header = request.getHeader("X-Requested-With");          if (header != null && "XMLHttpRequest".equals(header))              return true;          else              return false;      }    public void init(FilterConfig filterConfig) throws ServletException {        StringBuilder url = new StringBuilder();        url.append(SysConfig.getConfiguration().getProperty(Constans.SYSCONFIG_CAS_SERVER));        url.append("/login?service=");        url.append(SysConfig.getConfiguration().getProperty(Constans.SYSCONFIG_CAS_CLIENT));        url.append("/j_spring_cas_security_check");        redirect_url = url.toString();    }}
1 0
原创粉丝点击