我们在上面章节已经完成了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);
	        }  

	}

单点登录(十八)----cas4.2.x客户端增加权限控制shiro的更多相关文章

  1. 单点登录(十六)-----遇到问题-----cas4.2.x登录成功后报错No principal was found---cas中文乱码问题完美解决

    情况 我们之前已经完成了cas4.2.x登录使用mongodb验证方式并且自定义了加密. 单点登录(十五)-----实战-----cas4.2.x登录mongodb验证方式实现自定义加密 但是悲剧的是 ...

  2. 单点登录(十五)-----实战-----cas4.2.x登录mongodb验证方式实现自定义加密

    我们在前一篇文章中实现了cas4.2.x登录使用mongodb验证方式. 单点登录(十三)-----实战-----cas4.2.X登录启用mongodb验证方式完整流程 也学习参考了cas5.0.x版 ...

  3. 单点登录(十四)-----实战-----cas5.0.x登录mongodb验证方式常规的四种加密的思考和分析

    我们在上一篇文章中已经讲解了cas4.2.X登录启用mongodb验证方式 单点登录(十三)-----实战-----cas4.2.X登录启用mongodb验证方式完整流程 但是密码是明文存储的,也就是 ...

  4. 单点登录(十三)-----实战-----cas4.2.X登录启用mongodb验证方式完整流程

    我们在之前的文章中中已经讲到了正确部署运行cas server 和 在cas client中配置. 在此基础上 我们去掉了https的验证,启用了http访问的模式. 单点登录(七)-----实战-- ...

  5. 单点登录(十二)-----遇到问题-----cas启用mongodb验证方式登录后没反应-pac4j-mongo包中的MongoAuthenticatInvocationTargetException

    cas启用mongodb验证方式登录后没反应 控制台输出 2017-02-09 20:27:15,766 INFO [org.jasig.cas.authentication.MongoAuthent ...

  6. CAS单点登录学习(二):客户端配置

    下载jar包因为cas的源码修改变动很大,所以客户端引入的jar包根据服务端的war包而定.之前搭建的cas服务端用的版本是3.5.2,经过测试,可以使用cas-client-core的3.2.1版本 ...

  7. CAS单点登录源码解析之【客户端】

    cas 3.5.3服务器搭建+spring boot集成+shiro模拟登录(不修改现有shiro认证架构).因为我们属于供应商,所以有些客户那里会需要接对方的CAS,所以没有使用shiro和cas的 ...

  8. 单点登录(十)-----遇到问题-----cas启用mongodb验证方式报错com.mongodb.CommandFailureException---Authentication failed

    cas启用mongodb验证方式报错com.mongodb.CommandFailureException---Authentication failed. 完整报错信息: 二月 08, 2017 5 ...

  9. Dynamic CRM 2013学习笔记(十八)根据主表状态用JS控制子表自定义按钮

    有时要根据主表的审批状态来控制子表上的按钮要不要显示,比如我们有一个需求审批通过后就不能再上传文件了. 首先打开Visual Ribbon Editor, 如下图,我们可以利用Enable Rules ...

随机推荐

  1. 关于java学习中的一些易错点(基础篇)

    由JVM来负责Java程序在该系统中的运行,不同的操作系统需要安装不同的JVM,这样Java程序只需要跟JVM打交道,底层的操作由JVM去执行. JRE(Java Runtime Environmen ...

  2. mysql以zip安装,解决the service already exists(转载)

    喵喵亲测可用: 转自:https://www.cnblogs.com/dichters/p/5929209.html mysql以zip安装, mysqld -install 报错:​The serv ...

  3. CHAPTER 24 History of Our Planet 第24章 我们行星的历史

    CHAPTER 24 History of Our Planet 第24章 我们行星的历史 Uncovering the bones of ancient beasts is only part of ...

  4. 阿里云ubuntu16.04安装beef

    0x0 前言 环境:阿里云轻量服务器ubuntu16.04 需要安装2.4以上版本的ruby:https://www.cnblogs.com/Rain99-/p/10666247.html 参考资料 ...

  5. Python函数初识二

    一.变量的作用域LEGB 1.1.变量的作用域 在Python中,程序的变量并不是在哪个位置都可以访问的,访问权限决定于这个变量是在哪里赋值的.变量的作用域决定了在哪一部分程序可以访问哪个特定的变量名 ...

  6. 时间戳使用的bug,你见过么

    博主本人前几天给公司项目里写了个禁言和解除禁言的功能,项目中涉及到对时间的处理,因为学得时候也没很注意,就按自己的思路去写了,运行起来发现了一个天大的bug,就是写的延后一年尽然,刚开始就执行了,而且 ...

  7. [ c++] cmake 编译时 undefined reference to `std::cout' 错误的解决方案

    cmake ..  和 make 之后,出现如下错误 Linking CXX executable ../../../bin/ModuleTest CMakeFiles/ModuleTest.dir/ ...

  8. 使用socket发送http请求(get/post)

    手动发送http请求 解释说明 https://blog.csdn.net/zhangliang_571/article/details/23508953 http://www.cnblogs.com ...

  9. Xcode中的Target

    Xcode中的Target,主要包含下面几点知识: Target依赖 Build Phase Build Rule Target依赖 Target的依赖关系表示一个Target要构建成功,必先依赖于其 ...

  10. “Hello World!团队”Final发布—视频链接+文案+美工

    视频发布:http://www.bilibili.com/video/av17022373/ 文案加美工:http://www.cnblogs.com/chjy/p/7990116.html SkyH ...