我们在上面章节已经完成了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. CHAPTER 40 Science in Our Digital Age 第40章 我们数字时代的科学

    CHAPTER 40 Science in Our Digital Age 第40章 我们数字时代的科学 The next time you switch on your computer, you ...

  2. 《The Mythical Man-Month(人月神话)》读后感(1)

    临近考试周,这里我通过平时阅读的<人月神话>十九个章节和知乎.简书等网页中网友们对<人月神话>的读后感,对书中各个章节进行简单的总结,以下均为个人手打观点的思考与整合,仅供大家 ...

  3. 1、Ansible安装配置

    ansible介绍: Ansible是一款基于Python开发的自动化运维工具,主要是实现批量系统配置.批量程序部署.批量运行命令.批量执行任务等等诸多功能.Ansible是一款灵活的开源工具,能够很 ...

  4. 使用vbox构建局域网络

    update: 也可以启用DHCP自动分配IP地址.(看到过的某一篇博文写过要使用这个服务还得自己搭--就没有动手去实践一下直接手动分配了静态的IP.偶然尝试了一下发现动态IP分配和手动静态IP分配都 ...

  5. 关于java内存泄露的总结--引用的类型:强引用,弱引用,软引用

    今天面试了一家公司的java开发方面的实习生,被问到一个问题:如何处理java中的内存泄露问题,保证java的虚拟机内存不会被爆掉,当时其实觉得面试官的问题有点泛,所以也没有很好领会他的意思,答案也不 ...

  6. Ubuntu16.04Server版离线安装Nginx1.8.1+Mysql5.7.23+Python3.6.2

    nginx1.8.1 1.安装前准备工作 1.1.检查系统版本,确认源码编译所依赖的环境,提前下载好压缩包. 整个环境都是使用root权限安装,系统版本为server版的ubuntu16.04.4 r ...

  7. 编程之法section II: 2.1 求最小的k个数

    ====数组篇==== 2.1 求最小的k个数: 题目描述:有n个整数,请找出其中最小的k个数,要求时间复杂度尽可能低. 解法一: 思路:快排后输出前k个元素,O(nlogn). writer: zz ...

  8. 第一次spring冲刺第3、4天

    11月14至15日 因为忙于项目的谈论与探究,以及周末的活动变动,使得博客没有时间提交上去,这里补交. 这两天,主要是应对于市场的探究做谈论,我们主要面对什么类型的客户,以及他们最需要的是什么等话题做 ...

  9. 团队作业4 Alpha冲刺《嗨!你的快递》

    仓库地址:https://git.coding.net/day_light/ourexpressmaster1.git   张新宇 1第一天日期:2018/6/13 1.1今日任务 进行核心功能数据匹 ...

  10. IDE调研之二

    Eclipse和Jetbrains的IntelliJ IDEA对比: Eclipse工具 在Eclipse中,可以最大化控制台.通过双击标签或者Ctrl+M快捷键就可以实现,但是在IntelliJ I ...