我们在上面章节已经完成了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的使用。

Apache Shiro框架简介

springMVC与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了。

过程可参考

springMVC与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过滤器

<bean id= "casFilter" class= "org.apache.shiro.cas.CasFilter" >

<property name= "failureUrl" value= "${shiro.failureUrl}" />

</bean>

修改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);
	        }  

	}

最新文章

  1. 思科 vlan 相关操作
  2. Oracle存储过程中传入参数,传出字符串
  3. Chromium源码--视频播放流程分析(拨开云雾)
  4. struts2文件下载相关信息
  5. 7 libjpeg使用
  6. js实现加减乘除
  7. vm虚拟机里的桥接模式下“复制物理网络连接状态”作用
  8. Java中resourceBundle和Properties的区别
  9. linux下查看串口信息
  10. 基础排序算法,java实现(快速,冒泡,选择,堆排序,插入)
  11. 静态Web开发 DOM
  12. jquery.cycle.js简单用法实例
  13. python数据库做成邮箱的注册系统!
  14. (原+转)Eclipse中Android调用OpenCv
  15. 【BZOJ1835】【ZJOI2010】基站选址
  16. 第三方登录:新浪微博登录(OAuth2.0)
  17. Join 具体用法
  18. MVC与单元测试实践之健身网站(三)-角色与权限
  19. Hadoop vs Spark性能对比
  20. SSH(Struts 2.3.31 + Spring 4.1.6 + Hibernate 5.0.12 + Ajax)框架整合实现简单的增删改查(包含分页,Ajax 无刷新验证该用户是否存在)

热门文章

  1. 基于openvswitch+Docker构建SDN网络测试环境 (使用ovs-docker进行构建)
  2. 新手Python第一天(接触)
  3. 高可用OpenStack(Queen版)集群-9.Cinder控制节点集群
  4. Sentence | Never underestimate yourself.
  5. SDWebImage 错误汇总
  6. Python序列之列表 (list)
  7. 1.centos6.8安装docker
  8. Vue实现双向绑定的原理以及响应式数据
  9. 硬件设计原理图Checklist 参考案例二 【转载】
  10. Java里字符串split方法