单点登录(十八)----cas4.2.x客户端增加权限控制shiro
我们在上面章节已经完成了cas4.2.x登录启用mongodb的验证方式。
单点登录(十三)-----实战-----cas4.2.X登录启用mongodb验证方式完整流程
也完成了获取管理员身份属性
单点登录(十七)----cas4.2.x登录mongodb验证方式成功后返回更多信息更多属性到客户端
现在需要做的就是给客户端 cas client加上 权限控制。
权限控制可以使用spring Security或者shiro。
安全框架Shiro和Spring Security比较
Shiro
首先Shiro较之 Spring Security,Shiro在保持强大功能的同时,还在简单性和灵活性方面拥有巨大优势。
Java官方推荐Shiro,新人果断选shiro,学习成本低,易用。
Shiro是一个强大而灵活的开源安全框架,能够非常清晰的处理认证、授权、管理会话以及密码加密。如下是它所具有的特点:
易于理解的 Java Security API;
简单的身份认证(登录),支持多种数据源(LDAP,JDBC,Kerberos,ActiveDirectory 等);
对角色的简单的签权(访问控制),支持细粒度的签权;
支持一级缓存,以提升应用程序的性能;
内置的基于 POJO 企业会话管理,适用于 Web 以及非 Web 的环境;
异构客户端会话访问;
非常简单的加密 API;
不跟任何的框架或者容器捆绑,可以独立运行。
Spring Security
除了不能脱离Spring,shiro的功能它都有。而且Spring Security对Oauth、OpenID也有支持,Shiro则需要自己手动实现。Spring Security的权限细粒度更高。
shiro更简单一些,Spring Security包含的范围更广一些。
注:
OAuth在"客户端"与"服务提供商"之间,设置了一个授权层(authorization layer)。"客户端"不能直接登录"服务提供商",只能登录授权层,以此将用户与客户端区分开来。"客户端"登录授权层所用的令牌(token),与用户的密码不同。用户可以在登录的时候,指定授权层令牌的权限范围和有效期。
"客户端"登录授权层以后,"服务提供商"根据令牌的权限范围和有效期,向"客户端"开放用户储存的资料。
OpenID 系统的第一部分是身份验证,即如何通过 URI 来认证用户身份。目前的网站都是依靠用户名和密码来登录认证,这就意味着大家在每个网站都需要注册用户名和密码,即便你使用的是同样的密码。如果使用 OpenID ,你的网站地址(URI)就是你的用户名,而你的密码安全的存储在一个 OpenID 服务网站上(你可以自己建立一个 OpenID 服务网站,也可以选择一个可信任的 OpenID 服务网站来完成注册)。
与OpenID同属性的身份识别服务商还有ⅥeID,ClaimID,CardSpace,Rapleaf,Trufina ID Card等,其中ⅥeID通用账户的应用最为广泛。
我们之前也学习过shiro的使用。
那么我们这次还是使用shiro来进行权限控制。
整体思路
shiro是权限管理框架,现在已经会利用它如何控制权限。为了能够为多个系统提供统一认证入口,又研究了单点登录框架cas。因为二者都会涉及到对session的管理,所以需要进行集成。
Shiro在1.2.0的时候提供了对cas的集成。因此在项目中添加shiro-cas的依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-cas</artifactId>
<version>${shiro.version}</version>
</dependency>
Shiro对cas集成后,cas client的配置更加简单了。原理就是将casFilter添加到到shiroFilter的filterChain中。 shiroFilter是在web.xml中定义的,前文已经讲过。
在没有单点登录情况下的话,shiro登录认证和授权认证默认在AuthorizingRealm的doGetAuthorizationInfo和doGetAuthenticationInfo中进行,可以自定义shiroDbRealm(继承AuthorizingRealm的自定义类)覆写doGetAuthorizationInfo和doGetAuthenticationInfo,实现自定义登录认证和授权认证。
有单点登录情况下,登录认证是在casserver进行的,那么执行流程是这样的:用户从 cas server登录成功后,跳到cas client的CasRealm执行默认的doGetAuthorizationInfo和doGetAuthenticationInfo,此时doGetAuthenticationInfo做的工作是把登录用户信息传递给shiro,保持默认即可,而对于授权的处理,可以通过MyCasRealm(继承CasRealm的自定义类)覆写doGetAuthorizationInfo进行自定义授权认证。
项目现状
涉及到我们之后的操作步骤,所以说明下目前项目的状况从
单点登录(十七)----cas4.2.x登录mongodb验证方式成功后返回更多信息更多属性到客户端
之后开始继续配置,也就是说我们已经配置好了 cas client的基础拦截的基础上添加shiro控制。
目前的web.xml为:
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0"> <!-- ****************** 单点登录开始 ********************--> <!-- 用于实现单点登出功能 可选 --> <listener> <listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class> </listener> <!-- 该过滤器用于实现单点登出功能,单点退出配置,一定要放在其他filter之前 可选 --> <filter> <filter-name>CAS Single Sign Out Filter</filter-name> <filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class> <init-param> <param-name>casServerUrlPrefix</param-name> <param-value>http://127.0.0.1:8080/cas/</param-value> </init-param> </filter> <filter-mapping> <filter-name>CAS Single Sign Out Filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- 该过滤器负责用户的认证工作,必须 --> <filter> <filter-name>CASFilter</filter-name> <filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class> <init-param> <!--casServerLoginUrl:cas服务的登陆url --> <param-name>casServerLoginUrl</param-name> <param-value>http://127.0.0.1:8080/cas/login</param-value> </init-param> <init-param> <!--serverName:本项目的ip+port --> <param-name>serverName</param-name> <param-value>http://127.0.0.1:8080</param-value> </init-param> <init-param> <param-name>useSession</param-name> <param-value>true</param-value> </init-param> <init-param> <param-name>redirectAfterValidation</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>CASFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- 该过滤器负责对Ticket的校验工作,必须--> <filter> <filter-name>CAS Validation Filter</filter-name> <filter-class> org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter </filter-class> <init-param> <param-name>casServerUrlPrefix</param-name> <param-value>http://127.0.0.1:8080/cas/</param-value> </init-param> <init-param> <param-name>serverName</param-name> <param-value>http://127.0.0.1:8080</param-value> </init-param> <init-param> <param-name>encoding</param-name> <param-value>utf-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>CAS Validation Filter</filter-name> <!-- 对项目中的哪些路径做登录拦截--> <url-pattern>/*</url-pattern> </filter-mapping> <!-- 该过滤器对HttpServletRequest请求包装, 可通过HttpServletRequest的getRemoteUser()方法获得登录用户的登录名,可选 --> <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> <!-- ****************** 单点登录结束 ********************--> <filter> <filter-name>encodingFilter</filter-name> <filter-class>com.test.web.servlet.filter.EncodingFilter</filter-class> </filter> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <servlet> <servlet-name>springMVC</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-web.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springMVC</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <context-param> <param-name>contextConfigLocation</param-name> <param-value> classpath:spring-web.xml </param-value> </context-param> <welcome-file-list> <welcome-file></welcome-file> </welcome-file-list> <session-config> <cookie-config> <name>_sid</name> </cookie-config> <tracking-mode>COOKIE</tracking-mode> </session-config> <error-page> <error-code>404</error-code> <location>/404</location> </error-page> <error-page> <exception-type>javax.servlet.ServletException</exception-type> <location>/error</location> </error-page> <jsp-config> <jsp-property-group> <url-pattern>*.jsp</url-pattern> <page-encoding>UTF-8</page-encoding> <scripting-invalid>false</scripting-invalid> </jsp-property-group> </jsp-config> </web-app>
配置shiro
那么我们就开始配置shiro了。
过程可参考
导入jar包
因为我们的是maven工程,所以把shiro相关的包写入pom.xml文件。
在pom.xml中添加
<!--Apache Shiro所需的jar包--> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.2.2</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> <version>1.2.2</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.2.2</version> </dependency>
配置web.xml
在web.xml里添加shiro 的配置
shiro的filter应该放在其他的filter的上面
<!-- Shiro配置 --> <filter> <filter-name>shiroFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>shiroFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
建立casRelm
主要是扩展AuthorizingRealm, 因为登录验证我们在casserver已经做验证了,所以这里主要实现接受用户信息到shiro框架即可,把登录用户信息传递给shiro。
我新建一个MyShiro的class
MyShiro.java
package com.test.web.support.shiro; import java.util.List; import java.util.Map; 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.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.jasig.cas.client.authentication.AttributePrincipal; import org.jasig.cas.client.util.AssertionHolder; import com.test.util.CommonUtils; public class MyShiro extends AuthorizingRealm{ @Override protected AuthorizationInfo doGetAuthorizationInfo( PrincipalCollection principals) { AttributePrincipal principal=AssertionHolder.getAssertion().getPrincipal(); if (principal != null) { Map<String, Object> attributes = principal.getAttributes(); if (attributes.size() > 0) { List<String> roles =CommonUtils.arrayStringtoArrayList((String)attributes.get("roles")); //权限信息对象info,用来存放查出的用户的所有的角色(role)及权限(permission) SimpleAuthorizationInfo info=new SimpleAuthorizationInfo(); //用户的角色集合 info.addRoles(roles); //用户的角色对应的所有权限,如果只使用角色定义访问权限,下面的一行可以不要 //info.addStringPermissions(user.getPermissionList()); } } return null; } @Override protected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken token) throws AuthenticationException { AttributePrincipal principal=AssertionHolder.getAssertion().getPrincipal(); String username = principal.getName(); if (principal != null) { Map<String, Object> attributes = principal.getAttributes(); if (attributes.size() > 0) { String name = (String) attributes.get("name"); //若存在,将此用户存放到登录认证info中 return new SimpleAuthenticationInfo(username,null,name); } } return null; } }
spring中配置spring-shiro.xml
主要是新增
<bean id="shiroDbRealm" class="com.test.web.support.shiro.MyShiro">
</bean>
然后在securityManager中启用它
<property name="realm" ref="shiroDbRealm" />
其他保持默认即可。
<?xml version="1.0" encoding="UTF-8"?> <!-- ~ Licensed to the Apache Software Foundation (ASF) under one ~ or more contributor license agreements. See the NOTICE file ~ distributed with this work for additional information ~ regarding copyright ownership. The ASF licenses this file ~ to you under the Apache License, Version 2.0 (the ~ "License"); you may not use this file except in compliance ~ with the License. You may obtain a copy of the License at ~ ~ http://www.apache.org/licenses/LICENSE-2.0 ~ ~ Unless required by applicable law or agreed to in writing, ~ software distributed under the License is distributed on an ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY ~ KIND, either express or implied. See the License for the ~ specific language governing permissions and limitations ~ under the License. --> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> <!-- <bean id="iniRealm" class="org.apache.shiro.realm.text.IniRealm"> <constructor-arg name="resourcePath" value="classpath:com/test/web/conf/shiro/shiro.ini"></constructor-arg> </bean> --> <!-- <bean id="mongoRealm" class="com.test.web.support.shiro.MongoRealm"> <property name="credentialsMatcher"> <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher"> <property name="hashAlgorithmName" value="MD5"></property> </bean> </property> <property name="mongoTemplate" ref="mongoTemplate" /> </bean> --> <!-- <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/> --> <bean id="shiroDbRealm" class="com.test.web.support.shiro.MyShiro"> </bean> <!-- securityManager --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <!-- <property name="cacheManager" ref="cacheManager" /> --> <!-- <property name="sessionManager" ref="sessionManager" /> --> <!-- Single realm app. If you have multiple realms, use the 'realms' property instead. --> <property name="rememberMeManager"> <bean class="org.apache.shiro.web.mgt.CookieRememberMeManager"> <property name="cookie"> <bean class="org.apache.shiro.web.servlet.SimpleCookie"> <constructor-arg name="name" value="RememberMe" /> <property name="maxAge" value="604800" /> </bean> </property> </bean> </property> <property name="realm" ref="shiroDbRealm" /> </bean> <!-- shiroFilter --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager" /> <property name="loginUrl" value="/login" /> <property name="successUrl" value="/" /> <property name="unauthorizedUrl" value="/login" /> <property name="filters"> <map> <entry key="authc"> <bean class="com.test.web.support.shiro.AjaxCompatibleAuthenticationFilter"></bean> </entry> </map> </property> <property name="filterChainDefinitions"> <value> /login = anon <!-- roles[admin]表示访问此连接需要用户的角色为admin --> /showcontentjson = roles[admin] /showcontentjson = roles[normal] </value> </property> </bean> </beans>
并在xml中添加引用
<!-- <import resource="classpath:spring-datasource.xml" /> -->
<import resource="classpath:com/test/web/conf/shiro/spring-shiro.xml" />
cas与shiro搭配
基本流程
CAS协议的一个基本理解:
1. 如果你想访问一个被CAS客户端保护的应用,而你还没有进行认证。你讲被重定向到CAS服务端的登录页面。在应用中你需要配置CAS的登录url地址。
http://application.examples.com/protected/index.jsp → HTTP 302
→ https://server.cas.com/login?service=http://application.examples.com/shiro-cas
2. 当你填上登录名和密码在CAS服务端进行认证后,你就被重定向到一个带有服务端票据的应用URL。服务端票据是一次性使用的令牌,可在CAS服务端标识用户的唯一性(或用户属性)。
https://server.cas.com/login?service=http://application.examples.com/shiro-cas → HTTP 302
→ http://application.examples.com/shiro-cas?ticket=ST-4545454542121-cas
3. 应用去CAS服务端询问票据的有效性,CAS服务端响应经过认证的用户唯一标识。CAS客户端将页面转发到受保护的页面。
http://application.examples.com/shiro-cas?ticket=ST-4545454542121-cas → HTTP 302
→ http://application.examples.com/protected/index.jsp
如何配置shiro与CAS服务器工作?
需要定义一个casFilter来处理cas的过滤以及验证成功后传递到哪一个链接。
https://server.cas.com/login?service=http://application.examples.com/shiro-cas
例如我们这里的传到/shiro-cas链接。
在你的应用中配置url,这个url被用来接收CAS服务端票据。
定义过滤器对应的url:
/shiro-cas = casFilter
这样一来,当用户经过CAS服务端的有效票据认证后被重定向到应用的服务地址(/shiro-cas),这个过滤器接收服务端票据并创建一个可以被CasRealm使用的CasToken。
CasRealm使用被CasFilter创建的CasToken来验证用户的合法性。
导入jar包
Shiro在1.2.0的时候提供了对cas的集成。因此在项目中添加shiro-cas的依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-cas</artifactId>
<version>${shiro.version}</version>
</dependency>
创建并导入shiro配置文件
shiro.properties内容如下:
cas.loginUrl=http://127.0.0.1:8080/cas/login?service=http://127.0.0.1:8080/client1/shiro-cas cas.logoutUrl=http://127.0.0.1:8080/cas/logout?service=http://127.0.0.1:8080/client1/shiro-cas cas.serverUrlPrefix=http://127.0.0.1:8080/cas/ shiro.cas.service=http://127.0.0.1:8080/client1/shiro-cas shiro.failureUrl=/error shiro.successUrl=/
shiro.cas.service是客户端接受cas传递信息的链接。
要跟cas.loginUrl带的service参数对应。
service=http://127.0.0.1:8080/client1/shiro-cas
shiro-cas则需要跟shiro配置的CasFilter的链接对应。
任意xml中导入配置文件。
<context:property-placeholder location="classpath:shiro.properties" ignore-unresolvable="true"/>
xml头部需要
<?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:webflow="http://www.springframework.org/schema/webflow-config"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/webflow-config http://www.springframework.org/schema/webflow-config/spring-webflow-config-2.3.xsd">
修改shiro配置文件spring-shiro.xml
需要新增配置CAS过滤器
修改realm配置
之前的real为
<bean id="shiroDbRealm" class="com.test.web.support.shiro.MyShiro">
</bean>
修改为
<bean id="shiroDbRealm" class="com.test.web.support.shiro.MyShiro">
<property name="casServerUrlPrefix" value="${cas.serverUrlPrefix}"/> <!-- 一定是ip+port+context path -->
<property name="casService" value="${shiro.cas.service}"/> <!-- 没有这句,认证不会通过,casfilter失败 -->
</bean>
修改realm获取信息方式
realm需要修改一下为集成的realm由AuthorizingRealm变为CasRealm。
之前为
package com.test.web.support.shiro; import java.util.List; import java.util.Map; 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.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.jasig.cas.client.authentication.AttributePrincipal; import org.jasig.cas.client.util.AssertionHolder; import com.test.util.CommonUtils; public class MyShiro extends AuthorizingRealm{ @Override protected AuthorizationInfo doGetAuthorizationInfo( PrincipalCollection principals) { AttributePrincipal principal=AssertionHolder.getAssertion().getPrincipal(); if (principal != null) { Map<String, Object> attributes = principal.getAttributes(); if (attributes.size() > 0) { List<String> roles =CommonUtils.arrayStringtoArrayList((String)attributes.get("roles")); //权限信息对象info,用来存放查出的用户的所有的角色(role)及权限(permission) SimpleAuthorizationInfo info=new SimpleAuthorizationInfo(); //用户的角色集合 info.addRoles(roles); //用户的角色对应的所有权限,如果只使用角色定义访问权限,下面的一行可以不要 //info.addStringPermissions(user.getPermissionList()); } } return null; } @Override protected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken token) throws AuthenticationException { AttributePrincipal principal=AssertionHolder.getAssertion().getPrincipal(); String username = principal.getName(); if (principal != null) { Map<String, Object> attributes = principal.getAttributes(); if (attributes.size() > 0) { String name = (String) attributes.get("name"); //若存在,将此用户存放到登录认证info中 return new SimpleAuthenticationInfo(username,null,name); } } return null; } }
修改为
package com.test.web.support.shiro; import java.util.ArrayList; import java.util.List; import java.util.Map; 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.subject.PrincipalCollection; import org.apache.shiro.subject.SimplePrincipalCollection; import org.jasig.cas.client.authentication.AttributePrincipal; import org.jasig.cas.client.util.AssertionHolder; import org.jasig.cas.client.validation.Assertion; import org.jasig.cas.client.validation.TicketValidationException; import org.jasig.cas.client.validation.TicketValidator; import org.springframework.util.StringUtils; import com.test.util.CommonUtils; public class MyShiro extends CasRealm { private String casServerUrlPrefix; private String casService; private TicketValidator ticketValidator; private String defaultRoles; protected AuthorizationInfo doGetAuthorizationInfo( PrincipalCollection principals) { SimplePrincipalCollection principalCollection = (SimplePrincipalCollection)principals; List listPrincipals = principalCollection.asList(); Map attributes = (Map)listPrincipals.get(1); if (attributes.size() > 0) { List<String> roles = CommonUtils .arrayStringtoArrayList((String) attributes .get("roles")); // 权限信息对象info,用来存放查出的用户的所有的角色(role)及权限(permission) SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); // 用户的角色集合 info.addRoles(roles); // 用户的角色对应的所有权限,如果只使用角色定义访问权限,下面的一行可以不要 // info.addStringPermissions(user.getPermissionList()); return info; } return null; }@Override 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; Cas20ServiceTicketValidator cas20ServiceTicketValidator=new Cas20ServiceTicketValidator(casServerUrlPrefix); cas20ServiceTicketValidator.setEncoding("utf-8"); TicketValidator ticketValidator = cas20ServiceTicketValidator; try { Assertion casAssertion = ticketValidator.validate(ticket, getCasService()); AttributePrincipal casPrincipal = casAssertion.getPrincipal(); String userId = casPrincipal.getName(); List principals=new ArrayList<String>(); if (casPrincipal != null) { Map<String, Object> attributes = casPrincipal.getAttributes(); principals.add(userId); principals.add(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); } }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 TicketValidator getTicketValidator() {
return ticketValidator;
}public void setTicketValidator(TicketValidator ticketValidator) {
this.ticketValidator = ticketValidator;
}public String getDefaultRoles() {
return defaultRoles;
}public void setDefaultRoles(String defaultRoles) {
this.defaultRoles = defaultRoles;
}}
修改shiroFilter配置
shiroFilter需要引入casFilter,loginUrl需要修改为cas.loginUrl。拦截的地址
/shiro-cas需要与casFilter对应(shiro-cas来源于我们的shiro.cas.service属性,也需要与cas.loginUrl属性中serivce的参数对应)
原shiroFilter配置为
<!-- shiroFilter --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager" /> <property name="loginUrl" value="/login" /> <property name="successUrl" value="/" /> <property name="unauthorizedUrl" value="/404" /> <property name="filters"> <map> <entry key="authc"> <bean class="com.test.web.support.shiro.AjaxCompatibleAuthenticationFilter"></bean> </entry> </map> </property> <property name="filterChainDefinitions"> <value> /login = anon <!-- roles[admin]表示访问此连接需要用户的角色为admin --> /showcontent = roles[admin] /showcontentjson = roles[normal] </value> </property> </bean>
修改为
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"/> <!-- 设定角色的登录链接,这里为cas登录页面的链接可配置回调地址 --> <property name="loginUrl" value="${cas.loginUrl}" /> <property name="successUrl" value="${shiro.successUrl}" /> <property name="filters"> <map> <entry key="casFilter" value-ref="casFilter"/> </map> </property> <property name="filterChainDefinitions"> <value> /shiro-cas = casFilter /channel/** = authc /shipment/** = authc /** = authc </value> </property> </bean>
最终的spring-shiro.xml为:
<?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:webflow="http://www.springframework.org/schema/webflow-config" xmlns:p="http://www.springframework.org/schema/p" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:c="http://www.springframework.org/schema/c" xmlns:util="http://www.springframework.org/schema/util" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/webflow-config http://www.springframework.org/schema/webflow-config/spring-webflow-config-2.3.xsd"> <context:property-placeholder location="classpath:shiro.properties" ignore-unresolvable="true"/> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"/> <!-- 设定角色的登录链接,这里为cas登录页面的链接可配置回调地址 --> <property name="loginUrl" value="${cas.loginUrl}" /> <property name="successUrl" value="${shiro.successUrl}" /> <property name="filters"> <map> <entry key="casFilter" value-ref="casFilter"/> </map> </property> <property name="filterChainDefinitions"> <value> /shiro-cas = casFilter /channel/** = authc /shipment/** = authc /** = authc </value> </property> </bean> <bean id="casFilter" class="org.apache.shiro.cas.CasFilter"> <property name="failureUrl" value="${shiro.failureUrl}"/> </bean> <bean id="shiroDbRealm" class="com.test.web.support.shiro.MyShiro"> <property name="casServerUrlPrefix" value="${cas.serverUrlPrefix}"/> <!-- 一定是ip+port+context path --> <property name="casService" value="${shiro.cas.service}"/> <!-- 没有这句,认证不会通过,casfilter失败 --> </bean> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="shiroDbRealm"/> <property name="subjectFactory" ref="casSubjectFactory"/> </bean> <bean id="casSubjectFactory" class="org.apache.shiro.cas.CasSubjectFactory"/> <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/> <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean"> <property name="staticMethod" value="org.apache.shiro.SecurityUtils.setSecurityManager"/> <property name="arguments" ref="securityManager"/> </bean> </beans>
这里的shiro-cas不需要新建为路由或者jsp页面的,随意起名,只要前面几个地方对应起来即可。
如果没有拦截进入我们自定义的Realm,说明spring-shiro.xml或者web.xml有问题,建议直接使用我这里的这两个文件,只微调参数即可。
修改web.xml
我们之前的web.xml没有shiro拦截的cas版本的web.xml,现在很多属性需要去掉。
最终的xml为
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0"> <!-- Shiro配置 --> <filter> <filter-name>shiroFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <init-param> <param-name>targetFilterLifecycle</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>shiroFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- ****************** 单点登录开始 ********************--> <!-- 用于实现单点登出功能 可选 --> <listener> <listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class> </listener> <!-- 该过滤器用于实现单点登出功能,单点退出配置,一定要放在其他filter之前 可选 --> <filter> <filter-name>CAS Single Sign Out Filter</filter-name> <filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class> <init-param> <param-name>casServerUrlPrefix</param-name> <param-value>http://127.0.0.1:8080/cas/</param-value> </init-param> </filter> <filter-mapping> <filter-name>CAS Single Sign Out Filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- 该过滤器对HttpServletRequest请求包装, 可通过HttpServletRequest的getRemoteUser()方法获得登录用户的登录名,可选 --> <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> <!-- ****************** 单点登录结束 ********************--> <servlet> <servlet-name>springMVC</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-web.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springMVC</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <context-param> <param-name>contextConfigLocation</param-name> <param-value> classpath:spring-web.xml </param-value> </context-param> <welcome-file-list> <welcome-file></welcome-file> </welcome-file-list> <session-config> <cookie-config> <name>_sid</name> </cookie-config> <tracking-mode>COOKIE</tracking-mode> </session-config> <error-page> <error-code>404</error-code> <location>/404</location> </error-page> <error-page> <exception-type>javax.servlet.ServletException</exception-type> <location>/error</location> </error-page> <jsp-config> <jsp-property-group> <url-pattern>*.jsp</url-pattern> <page-encoding>UTF-8</page-encoding> <scripting-invalid>false</scripting-invalid> </jsp-property-group> </jsp-config> </web-app>
页面上测试权限
在index页面上新增
<shiro:hasRole name="admin">admin角色登录显示此内容<br></shiro:hasRole>
<shiro:hasRole name="normal">normal角色登录显示此内容<br></shiro:hasRole>
index页面头部加上标签
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
然后数据库中分别新增有admin权限和normal权限的账户
其他
其他路由获取用户信息
其他路由中获取获取cas返回过来的对象信息
Subject subject = SecurityUtils.getSubject(); Object principal = subject.getPrincipal(); PrincipalCollection principals = subject.getPrincipals();
注销logout
需要写一个登出路由
调用当前的shiro的subject.logout();注销当前系统的对象,然后返回到页面
@RequestMapping("/logout") public ModelAndView casLogout(HttpServletRequest request, HttpServletResponse response, UserDetailsVo vo) { SimpleUtils.getSubject().logout(); return "/logout"; }
logout.jsp页面再重定向到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="http://127.0.0.1:8080/cas/logout?service=http://127.0.0.1:8080/client1/shiro-cas"; </script> </head> <body> </body> </html>
遇到问题
cas shiro页面重定向
根据配置好自动发现拦截到之后跳转到了cas登录界面,输入帐号密码之后就一直页面重定向了。
这种情况要检查
<property name="successUrl" value="${shiro.successUrl}" />
设置的
shiro.successUrl
在
拦截器中的权限
<property name="filterChainDefinitions">
<value>
/shiro-cas = casFilter
/channel/** = authc,perms["ppt"]
/shipment/** = authc
/** = authc
</value>
</property>
如果是需要登录或者某种角色的话 需要确定是否在shiro中登录成功,以及有没有这个角色。
一般来说没有正确把cas中的登录信息 赋值给 shiro登录管理器,
但是又设置了拦截会导致这种情况。
没进入Realm
shiro-cas没有拦截进入我们自定义的Realm,说明spring-shiro.xml或者web.xml配置得有问题,建议直接使用我这里的这两个文件,只微调参数即可。
shiro接受用户信息乱码
我们在只启用cas时是处理过乱码问题的,需要在cas拦截器上加编码。
但是这里发现用shiro来拦截之后,
在接受cas证书里的信息时,中文又乱码了。
我们跟踪进代码发现是有encoding参数的,只是没有 可以设置的地方。
解决方法是
修改doGetAuthenticationInfo方法中设置编码utf-8
@Override 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; Cas20ServiceTicketValidator cas20ServiceTicketValidator=new Cas20ServiceTicketValidator(casServerUrlPrefix); cas20ServiceTicketValidator.setEncoding("utf-8"); TicketValidator ticketValidator = cas20ServiceTicketValidator; try { Assertion casAssertion = ticketValidator.validate(ticket, getCasService()); AttributePrincipal casPrincipal = casAssertion.getPrincipal(); String userId = casPrincipal.getName(); List principals=new ArrayList<String>(); if (casPrincipal != null) { Map<String, Object> attributes = casPrincipal.getAttributes(); principals.add(userId); principals.add(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); } }
最新文章
- 思科 vlan 相关操作
- Oracle存储过程中传入参数,传出字符串
- Chromium源码--视频播放流程分析(拨开云雾)
- struts2文件下载相关信息
- 7 libjpeg使用
- js实现加减乘除
- vm虚拟机里的桥接模式下“复制物理网络连接状态”作用
- Java中resourceBundle和Properties的区别
- linux下查看串口信息
- 基础排序算法,java实现(快速,冒泡,选择,堆排序,插入)
- 静态Web开发 DOM
- jquery.cycle.js简单用法实例
- python数据库做成邮箱的注册系统!
- (原+转)Eclipse中Android调用OpenCv
- 【BZOJ1835】【ZJOI2010】基站选址
- 第三方登录:新浪微博登录(OAuth2.0)
- Join 具体用法
- MVC与单元测试实践之健身网站(三)-角色与权限
- Hadoop vs Spark性能对比
- SSH(Struts 2.3.31 + Spring 4.1.6 + Hibernate 5.0.12 + Ajax)框架整合实现简单的增删改查(包含分页,Ajax 无刷新验证该用户是否存在)
热门文章
- 基于openvswitch+Docker构建SDN网络测试环境 (使用ovs-docker进行构建)
- 新手Python第一天(接触)
- 高可用OpenStack(Queen版)集群-9.Cinder控制节点集群
- Sentence | Never underestimate yourself.
- SDWebImage 错误汇总
- Python序列之列表 (list)
- 1.centos6.8安装docker
- Vue实现双向绑定的原理以及响应式数据
- 硬件设计原理图Checklist 参考案例二 【转载】
- Java里字符串split方法