shiro集成spring使用cas单点登录配置

xtiawxf 2018-08-27

shiro集成spring使用cas单点登录配置

(一)shiro单点登录

配置的主要目的在于将登录页面改为${cas.server}?service=${cas.client}/login的形式,service后面为本地的回调地址。在cas服务器端登录成功后,会生成ticket返回给客户端,客户端的shiro使用ticket最为凭据保存起来。

shiro配置单点登陆后,在注销时原始的cas-client只能删除HttpSession,不能删除shiro的Session,因此未使用shiro的session管理器。

如果想启用shiro的Session管理器,可以参考Shiro & CAS 实现单点登录。

原有的CasRealm在AuthenticationInfo中只保存了用户名作为principal,MyCasRealm中重写了此方法,改为保存用户信息类。

需要在pom增加依赖,shiro-cas会通过依赖传递自动增加cas-client-core-3.2.1的依赖。

<!-- shiro -->

<dependency>

<groupId>org.apache.shiro</groupId>

<artifactId>shiro-spring</artifactId>

<version>1.2.3</version>

</dependency>

<dependency>

<groupId>org.apache.shiro</groupId>

<artifactId>shiro-cas</artifactId>

<version>1.2.3</version>

</dependency>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

1、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:util="http://www.springframework.org/schema/util"

xmlns:aop="http://www.springframework.org/schema/aop"

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/aop http://www.springframework.org/schema/aop/spring-aop.xsd

http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd

" default-lazy-init="false">

<description>Shiro安全配置</description>

<!-- 缓存管理器 -->

<bean id="cacheManager" class="com.whty.framework.base.common.cache.SpringCacheManagerWrapper">

<property name="cacheManager" ref="springCacheManager"/>

</bean>

<!-- Realm实现 -->

<bean id="casRealm" class="com.whty.oim.base.shiro.MyCasRealm">

<property name="cachingEnabled" value="true"/>

<property name="authenticationCachingEnabled" value="true"/>

<property name="authenticationCacheName" value="authenticationCache"/>

<property name="authorizationCachingEnabled" value="true"/>

<property name="authorizationCacheName" value="authorizationCache"/>

<!-- CAS Server -->

<property name="casServerUrlPrefix" value="${cas.server}"/>

<!-- 客户端的回调地址设置,必须和下面的shiro-cas过滤器拦截的地址一致 -->

<property name="casService" value="${cas.client}/login"/>

</bean>

<bean id="casSubjectFactory" class="org.apache.shiro.cas.CasSubjectFactory"/>

<!-- 安全管理器 -->

<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">

<property name="realm" ref="casRealm"/>

<!-- <property name="sessionManager" ref="sessionManager"/> -->

<property name="cacheManager" ref="cacheManager"/>

<!-- sessionMode参数设置为native时,那么shrio就将用户的基本认证信息保存到缺省名称为shiro-activeSessionCache 的Cache中 -->

<!--<property name="sessionMode" value="native" />-->

<property name="subjectFactory" ref="casSubjectFactory"/>

</bean>

<!-- 相当于调用SecurityUtils.setSecurityManager(securityManager) -->

<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">

<property name="staticMethod" value="org.apache.shiro.SecurityUtils.setSecurityManager"/>

<property name="arguments" ref="securityManager"/>

</bean>

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

<!-- 配置验证错误时的失败页面 -->

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

</bean>

<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">

<property name="securityManager" ref="securityManager" />

<!-- 设定角色的登录链接,这里为cas登录页面的链接可配置回调地址 -->

<property name="loginUrl" value="${cas.server}?service=${cas.client}/login" />

<property name="successUrl" value="/index" />

<property name="unauthorizedUrl" value="/"/>

<property name="filters">

<util:map>

<!-- <entry key="authc" value-ref="authcFilter"/>

<entry key="captchaFilter" value-ref="captchaFilter"/> -->

<!-- 添加casFilter到shiroFilter -->

<entry key="cas" value-ref="casFilter"/>

</util:map>

</property>

<property name="filterChainDefinitions">

<value>

/logout = logout

/login = cas

/** = user

</value>

</property>

</bean>

<!--保证实现了Shiro内部lifecycle函数的bean执行 -->

<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />

</beans>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82

2、cas.properties

cas.server=http://192.168.5.129:8080/cas

cas.client=http://192.168.4.184:8091/ucs

3、自定义Realm实现

主要修改了CasRealm的doGetAuthenticationInfo()方法,CasRealm默认只保存了username,本系统改为保存ShiroUser对象。

public class MyCasRealm extends CasRealm {

Logger logger = LoggerFactory.getLogger(MyCasRealm.class);

@Autowired

private UcsUserService ucsUserService;

@Autowired

private UcsRoleService ucsRoleService;

@Autowired

private UcsPermissionService ucsPermissionService;

@Value("${domain}")

private String domain;

/**

* 授权查询回调函数, 进行鉴权但缓存中无用户的授权信息时调用.

*

* @see org.apache.shiro.realm.AuthorizingRealm#doGetAuthorizationInfo(org.apache.shiro.subject.PrincipalCollection)

*/

@Override

protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

ShiroUser shiroUser = (ShiroUser) principals.getPrimaryPrincipal();

UcsUser ucsUser = ucsUserService.selectByUsername(shiroUser.loginName);

//把principals放session中 key=userId value=principals

SecurityUtils.getSubject().getSession().setAttribute(String.valueOf(ucsUser.getId()),SecurityUtils.getSubject().getPrincipals());

SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

//赋予角色

List<UcsRole> ucsRoles = ucsRoleService.selectByUser(ucsUser.getId());

if (!CheckEmptyUtil.isEmpty(ucsRoles)){

for(UcsRole ucsRole:ucsRoles){

info.addRole(ucsRole.getId());

}

}

//赋予权限

// List<UcsPermission> ucsPermissions = ucsPermissionService.selectByUser(ucsUser.getId(), domain);

if (!CheckEmptyUtil.isEmpty(shiroUser.getMenus())){

for(EasyUIMenu permission:shiroUser.getMenus()){

if(UcsBaseConstant.PermissionType.BUTTON.equals(permission.getType()))

info.addStringPermission(permission.getUrl());

}

}

return info;

}

/**

* Authenticates a user and retrieves its information.

*

* @param token the authentication token

* @throws AuthenticationException if there is an error during authentication.

*/

@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;

}

TicketValidator ticketValidator = ensureTicketValidator();

try {

// contact CAS server to validate service ticket

Assertion casAssertion = ticketValidator.validate(ticket, getCasService());

// get principal, user id and attributes

AttributePrincipal casPrincipal = casAssertion.getPrincipal();

String username = casPrincipal.getName();

logger.debug("Validate ticket : {} in CAS server : {} to retrieve user : {}", new Object[]{

ticket, getCasServerUrlPrefix(), username

});

Map<String, Object> attributes = casPrincipal.getAttributes();

// refresh authentication token (user id + remember me)

casToken.setUserId(username);

String rememberMeAttributeName = getRememberMeAttributeName();

String rememberMeStringValue = (String)attributes.get(rememberMeAttributeName);

boolean isRemembered = rememberMeStringValue != null && Boolean.parseBoolean(rememberMeStringValue);

if (isRemembered) {

casToken.setRememberMe(true);

}

// create simple authentication info

// 根据用户名获取账号信息

UcsUser ucsUser = ucsUserService.selectByUsername(username);

List<UcsPermission> ucsPermissions = null;

if (ucsUser != null) {

ucsPermissions = ucsPermissionService.selectByUser(ucsUser.getId(), domain);

} else {

throw new UnknownAccountException();//登录失败

}

//给菜单排序

List<EasyUIMenu> menus = toEasyUIMenu(ucsPermissions);

if(!CheckEmptyUtil.isEmpty(menus)){

Collections.sort(menus);

}

ShiroUser shiroUser = new ShiroUser(ucsUser.getId(), ucsUser.getUsername(), ucsUser.getName(), menus);

return new SimpleAuthenticationInfo(shiroUser, ticket, getName());

} catch (TicketValidationException e) {

throw new CasAuthenticationException("Unable to validate ticket [" + ticket + "]", e);

}

}

private List<EasyUIMenu> toEasyUIMenu(List<UcsPermission> ucsPermissions){

List<EasyUIMenu> menus = new ArrayList<EasyUIMenu>(0);

for (UcsPermission ucsPermission : ucsPermissions) {

menus.add(toEasyUIMenu(ucsPermission));

}

return menus;

}

private EasyUIMenu toEasyUIMenu(UcsPermission ucsPermission){

return new EasyUIMenu(ucsPermission.getId(), ucsPermission.getPid(), ucsPermission.getDomain(), ucsPermission.getName(), ucsPermission.getType(), ucsPermission.getSort(), ucsPermission.getIcon(), ucsPermission.getUrl(), ucsPermission.getDescription(), ucsPermission.getStatus());

}

@Override

public void clearCachedAuthorizationInfo(PrincipalCollection principals) {

super.clearCachedAuthorizationInfo(principals);

}

@Override

public void clearCachedAuthenticationInfo(PrincipalCollection principals) {

super.clearCachedAuthenticationInfo(principals);

}

@Override

public void clearCache(PrincipalCollection principals) {

super.clearCache(principals);

clearAllCache();

}

public void clearAllCachedAuthorizationInfo() {

getAuthorizationCache().clear();

}

public void clearAllCachedAuthenticationInfo() {

getAuthenticationCache().clear();

}

public void clearAllCache() {

clearAllCachedAuthenticationInfo();

clearAllCachedAuthorizationInfo();

}

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148

(二)cas登出

在任意一个子系统登出后,cas服务器会向所有发过登录请求的子系统发送一个登出请求,使所有子系统的HttpSession失效。如果使用了shiro的session管理器,需要修改cas客户端代码,在移除HttpSession的时候也移除shiro的Session。

1、web.xml

web.xml中需要加入cas的SingleSignOutFilter实现单点登出功能,该过滤器需要放在shiroFilter之前,spring字符集过滤器之后。在实际使用时发现,SingleSignOutFilter如果放在了spring字符集过滤器之前,数据在传输过程中就会出现乱码。

<!-- 用于单点退出,该过滤器用于实现单点登出功能,可选配置。-->

<listener>

<listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class>

</listener>

<!-- 该过滤器用于实现单点登出功能,可选配置。 -->

<filter>

<filter-name>CAS Single Sign Out Filter</filter-name>

<filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class>

</filter>

<filter-mapping>

<filter-name>CAS Single Sign Out Filter</filter-name>

<url-pattern>/*</url-pattern>

</filter-mapping>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

2、登出请求

登出只需要将当前页面跳转到cas服务器的logout页面即可实现登出。如果需要登出后跳转页面,可以加入service参数,并参考第一部分中第四节进行配置。

子系统使用的方法是在后台拼装好登出链接,发送给前台页面,前台页面接受使用el表达式接收。具体实现需要根据每个子系统的不同来自行实现。

@Value("${cas.server}")

private String cas_server;

@Value("${cas.client}")

private String cas_client;

@RequestMapping(value = "/index")

public String index(Model model){

StringBuilder logoutUrl = new StringBuilder();

logoutUrl.append(cas_server);

logoutUrl.append("/logout?service=");

logoutUrl.append(cas_client);

model.addAttribute("logoutUrl", logoutUrl.toString());

return "system/index";

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

前台页面:

<script>

var logoutUrl = '${logoutUrl}';

</script>

  • 1
  • 2
  • 3

缓存配置(可选)

我使用的ehcache作为shiro的缓存,也可以使用其他的。

spring-cache.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:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context"

xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:tx="http://www.springframework.org/schema/tx"

xsi:schemaLocation="

http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd

http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd

http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd

http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd

http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"

default-lazy-init="false">

<bean id="springCacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager">

<property name="cacheManager" ref="ehcacheManager"/>

</bean>

<!--ehcache-->

<bean id="ehcacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">

<property name="configLocation" value="classpath:properties/ehcache.xml"/>

</bean>

</beans>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

ehcache.xml

<?xml version="1.0" encoding="UTF-8"?>

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:noNamespaceSchemaLocation="ehcache.xsd"

updateCheck="true" monitoring="autodetect"

dynamicConfig="true">

<diskStore path="C:\ehcache\cache"/>

<!--<diskStore path="/application/cache"/> -->

<!-- 登录记录缓存 锁定10分钟 -->

<cache name="passwordRetryEhcache"

maxEntriesLocalHeap="2000"

eternal="false"

timeToIdleSeconds="1800"

timeToLiveSeconds="0"

overflowToDisk="false"

statistics="true">

</cache>

<cache name="authorizationCache"

maxEntriesLocalHeap="2000"

eternal="false"

timeToIdleSeconds="3600"

timeToLiveSeconds="0"

overflowToDisk="false"

statistics="true">

</cache>

<cache name="authenticationCache"

maxEntriesLocalHeap="2000"

eternal="false"

timeToIdleSeconds="3600"

timeToLiveSeconds="0"

overflowToDisk="false"

statistics="true">

</cache>

<cache name="shiro-activeSessionCache"

maxEntriesLocalHeap="2000"

eternal="false"

timeToIdleSeconds="3600"

timeToLiveSeconds="0"

overflowToDisk="false"

statistics="true">

</cache>

<defaultCache

maxElementsInMemory="1000"

eternal="false"

timeToIdleSeconds="120"

timeToLiveSeconds="120"

overflowToDisk="true"

maxElementsOnDisk="10000"

diskSpoolBufferSizeMB="30"

diskPersistent="false"

diskExpiryThreadIntervalSeconds="120"

memoryStoreEvictionPolicy="LRU"

statistics="false"

/>

</ehcache>

shiro集成spring使用cas单点登录配置

相关推荐