第四部分: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(); }}
- 第四部分:spring security使用cas单点登录配置
- 第三部分:shiro集成spring使用cas单点登录配置
- 第三部分:shiro集成spring使用cas单点登录配置
- spring security +cas单点登录
- CAS+Spring security实现单点登录之配置篇
- cas单点登录整合spring security
- Spring Security集成CAS实现单点登录
- Spring Security 4.0 CAS实现单点登录
- spring security 集成cas单点登录核心配置及相关java代码
- spring boot配置Cas单点登录
- Spring Security调研记录【五】--基于Cas实现单点登录
- asp.net 使用 Cas 单点登录 配置
- CAS单点登录配置
- CAS单点登录配置
- CAS单点登录配置
- Security 4.0 CAS实现单点登录
- Spring Security教程第四部分-自定义登录页面
- cas 单点登录配置速成
- 字符编码及转换
- Latex 公式颜色
- Android之史上最全最简单最有用的第三方开源库收集整理
- 利用Gson使json字符串与java bean/list/map之间的相互转换
- android 获取activity的根view
- 第四部分:spring security使用cas单点登录配置
- 真实文件被隐藏病毒的搞定方法
- FAQ12644]联系人接收短彩信时不读,手机重启后,通知栏中该条未读消息显示为号码,不显示已保存的联系人名称
- HTML5如何重塑O2O用户体验
- HTTP WebService服务和URL服务
- 字符串验证类
- 7、Java方法
- PHP房贷计算器代码,等额本息,等额本金
- curl 错误