单点登录(十八)----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过滤器
<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的更多相关文章
- 单点登录(十六)-----遇到问题-----cas4.2.x登录成功后报错No principal was found---cas中文乱码问题完美解决
情况 我们之前已经完成了cas4.2.x登录使用mongodb验证方式并且自定义了加密. 单点登录(十五)-----实战-----cas4.2.x登录mongodb验证方式实现自定义加密 但是悲剧的是 ...
- 单点登录(十五)-----实战-----cas4.2.x登录mongodb验证方式实现自定义加密
我们在前一篇文章中实现了cas4.2.x登录使用mongodb验证方式. 单点登录(十三)-----实战-----cas4.2.X登录启用mongodb验证方式完整流程 也学习参考了cas5.0.x版 ...
- 单点登录(十四)-----实战-----cas5.0.x登录mongodb验证方式常规的四种加密的思考和分析
我们在上一篇文章中已经讲解了cas4.2.X登录启用mongodb验证方式 单点登录(十三)-----实战-----cas4.2.X登录启用mongodb验证方式完整流程 但是密码是明文存储的,也就是 ...
- 单点登录(十三)-----实战-----cas4.2.X登录启用mongodb验证方式完整流程
我们在之前的文章中中已经讲到了正确部署运行cas server 和 在cas client中配置. 在此基础上 我们去掉了https的验证,启用了http访问的模式. 单点登录(七)-----实战-- ...
- 单点登录(十二)-----遇到问题-----cas启用mongodb验证方式登录后没反应-pac4j-mongo包中的MongoAuthenticatInvocationTargetException
cas启用mongodb验证方式登录后没反应 控制台输出 2017-02-09 20:27:15,766 INFO [org.jasig.cas.authentication.MongoAuthent ...
- CAS单点登录学习(二):客户端配置
下载jar包因为cas的源码修改变动很大,所以客户端引入的jar包根据服务端的war包而定.之前搭建的cas服务端用的版本是3.5.2,经过测试,可以使用cas-client-core的3.2.1版本 ...
- CAS单点登录源码解析之【客户端】
cas 3.5.3服务器搭建+spring boot集成+shiro模拟登录(不修改现有shiro认证架构).因为我们属于供应商,所以有些客户那里会需要接对方的CAS,所以没有使用shiro和cas的 ...
- 单点登录(十)-----遇到问题-----cas启用mongodb验证方式报错com.mongodb.CommandFailureException---Authentication failed
cas启用mongodb验证方式报错com.mongodb.CommandFailureException---Authentication failed. 完整报错信息: 二月 08, 2017 5 ...
- Dynamic CRM 2013学习笔记(十八)根据主表状态用JS控制子表自定义按钮
有时要根据主表的审批状态来控制子表上的按钮要不要显示,比如我们有一个需求审批通过后就不能再上传文件了. 首先打开Visual Ribbon Editor, 如下图,我们可以利用Enable Rules ...
随机推荐
- 013-- mysql常用的查询优化方法
浅谈MySQL中优化sql语句查询常用的30种方法 1.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引. 2.应尽量避免在 where 子句 ...
- java IO流 对文件操作的代码集合
Io流 按照分类 有两种分类 流向方向: 有输入流和输出流 按照操作类型有:字节流和字符流 按照流向方向 字节流的一些操作 //读文件 FileInputStream fis = new FileIn ...
- ajax 异步刷新
第一种方法,ajax实现:当然,ajax使用起来确实很简单就可以实现,但是里面的很多知识还是比较有点深的.我之前做页面时间自动刷新的功能就是用的ajax.完整代码是:1.getTime.php: 复制 ...
- Description Resource Path Location Type Cannot change version of project fac
http://www.cnblogs.com/eaysun/p/5661631.html
- map的运用
一.map是一种关联容器,支持高效的查找和访问 map中的元素是一些关键字-值(key-value)对: 关键字起索引作用: 值表示与索引相关联的数据. 关联容器中元素是根据关键字存储的,故其不支持位 ...
- OO第四阶段总结
一.测试与正确性论证的区别 从哲学的角度来说,正确性论证与测试的关系就像理论与实践的关系一样. 使用测试的方法检验程序正确性确实是一个非常方便可行且广泛运用的方法.可以通过几个简单或复杂的测试样例,迅 ...
- 结对项目:SudokuGame
1. Github项目地址:https://github.com/ZiJiaW/SudokuGame GUI在BIN目录下的SudokuGUI.rar中,解压后打开SudokuGame.exe即可.2 ...
- java实验2实验报告(20135232王玥)
实验二 Java面向对象程序设计 一.实验内容 1. 初步掌握单元测试和TDD 2. 理解并掌握面向对象三要素:封装.继承.多态 3. 初步掌握UML建模 4. 熟悉S.O.L.I.D原则 5. 了解 ...
- Express搭建NodeJS项目
1.安装Node.js: 2.安装npm; 3.安装Express; 在本例中默认全局安装express 安装express生成器 如果没有安装express-generator或安装路径不对,会报以 ...
- 项目复审-Bata阶段
项目复审-Bata阶段 小组的名字和链接 优点 缺点 名次 别看了你没救了 https://www.cnblogs.com/liaoyujun233/p/9148781.html 基本功能都已经实现, ...