Spring Boot Shiro CAS集成
1 CAS Server搭建
1.1 获取CAS Server源码
GitHub地址 https://github.com/apereo/cas
1.2 去掉HTTPS协议
1.2.1 修改HTTPSandIMAPS-10000001.json中serviceId
修改cas-server-webapp模块下services/HTTPSandIMAPS-10000001.json修改”serviceId”: 为”^(https|imaps|http)://.*”
修改前:
{
"@class": "org.jasig.cas.services.RegexRegisteredService",
"serviceId": "^(https|imaps)://.*",
"name": "HTTPS and IMAPS",
"id": 10000001,
"description": "This service definition authorized all application urls that support HTTPS and IMAPS protocols.",
"proxyPolicy": {
"@class": "org.jasig.cas.services.RefuseRegisteredServiceProxyPolicy"
},
......
}
修改后:
{
"@class": "org.jasig.cas.services.RegexRegisteredService",
"serviceId": "^(https|imaps|http)://.*",
"name": "HTTPS and IMAPS",
"id": 10000001,
"description": "This service definition authorized all application urls that support HTTPS and IMAPS protocols.",
"proxyPolicy": {
"@class": "org.jasig.cas.services.RefuseRegisteredServiceProxyPolicy"
},
......
}
1.2.2 修改一services/Apereo-10000002.json中serviceId
修改cas-server-webapp模块下services/HTTPSandIMAPS-10000001.json修改”serviceId”: 为”^(https|imaps|http)://.*”
修改前:
{
"@class" : "org.jasig.cas.services.RegexRegisteredService",
"serviceId" : "^(https|imaps)://.*",
"name" : "Apereo",
"theme" : "apereo",
"id" : 10000002,
"description" : "Apereo foundation sample service",
"proxyPolicy" : {
"@class" : "org.jasig.cas.services.RefuseRegisteredServiceProxyPolicy"
},
......
}
修改后:
{
"@class" : "org.jasig.cas.services.RegexRegisteredService",
"serviceId" : "^(https|imaps|http)://.*",
"name" : "Apereo",
"theme" : "apereo",
"id" : 10000002,
"description" : "Apereo foundation sample service",
"proxyPolicy" : {
"@class" : "org.jasig.cas.services.RefuseRegisteredServiceProxyPolicy"
},
......
}
1.2.3 修改二ticketGrantingTicketCookieGenerator.xml中p:cookieSecure=”false”
修改cas-server-webapp模块下WEB-INF/spring-configuration/ticketGrantingTicketCookieGenerator.xml
p:cookieSecure=”true”为false
修改前:
<bean id="ticketGrantingTicketCookieGenerator" class="org.jasig.cas.web.support.CookieRetrievingCookieGenerator"
c:casCookieValueManager-ref="cookieValueManager"
p:cookieSecure="true"
p:cookieMaxAge="-1"
p:cookieName="TGC"
p:cookiePath=""/>
修改后:
<bean id="ticketGrantingTicketCookieGenerator" class="org.jasig.cas.web.support.CookieRetrievingCookieGenerator"
c:casCookieValueManager-ref="cookieValueManager"
p:cookieSecure="false"
p:cookieMaxAge="-1"
p:cookieName="TGC"
p:cookiePath=""/>
1.2.4 修改warnCookieGenerator.xml中p:cookieSecure=”false”
修改cas-server-webapp模块下WEB-INF\spring-configuration\warnCookieGenerator.xml
p:cookieSecure=”true”为false
修改前:
<bean id="warnCookieGenerator" class="org.jasig.cas.web.support.CookieRetrievingCookieGenerator"
p:cookieHttpOnly="true"
p:cookieSecure="true"
p:cookieMaxAge="-1"
p:cookieName="CASPRIVACY"
p:cookiePath=""/>
修改后:
<bean id="warnCookieGenerator" class="org.jasig.cas.web.support.CookieRetrievingCookieGenerator"
p:cookieHttpOnly="true"
p:cookieSecure="false"
p:cookieMaxAge="-1"
p:cookieName="CASPRIVACY"
p:cookiePath=""/>
1.3 连接数据库
1.3.1 添加jar包
在cas-server-webapp pom.xml中添加jar
<dependency>
<groupId>org.jasig.cas</groupId>
<artifactId>cas-server-support-jdbc</artifactId>
<version>4.1.10</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.1</version>
</dependency>
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.4</version>
</dependency>
1.3.2 修改配置文件deployerConfigContext.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:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:sec="http://www.springframework.org/schema/security"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<!--数据库配置开始-->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
<property name="url" value="jdbc:oracle:thin:@192.168.2.251:1521:orcl"/>
<property name="username" value="goisan_hunger"/>
<property name="password" value="hunger"/>
</bean>
<!--数据库配置结束-->
<!-- 配置登陆开始 -->
<bean id="primaryAuthenticationHandler" class="org.jasig.cas.adaptors.jdbc.QueryDatabaseAuthenticationHandler">
<property name="dataSource" ref="dataSource"/>
<property name="sql" value="select password from T_SYS_USER where USER_ACCOUNT = ?"/>
<property name="passwordEncoder" ref="MD5PasswordEncoder"/>
</bean>
<bean id="serviceRegistryDao" class="org.jasig.cas.services.InMemoryServiceRegistryDaoImpl">
<property name="registeredServices">
<list>
<bean class="org.jasig.cas.services.RegexRegisteredService"
p:id="0" p:name="HTTP and IMAP" p:description="Allows HTTP(S) and IMAP(S) protocols"
p:serviceId="^(http?|https?|imaps?)://.*" p:evaluationOrder="10000001"/>
</list>
</property>
</bean>
<!-- 配置登陆结束 -->
<!-- 返回用户信息 -->
<bean id="attributeRepository" class="org.jasig.services.persondir.support.jdbc.SingleRowJdbcPersonAttributeDao">
<constructor-arg index="0" ref="dataSource"/>
<constructor-arg index="1" value="SELECT user_account,user_type FROM T_SYS_USER WHERE {0}"/>
<property name="queryAttributeMapping">
<map>
<!--这里的key需写username,value对应数据库用户名字段-->
<entry key="username" value="user_account"/>
</map>
</property>
<property name="resultAttributeMapping">
<map>
<!--key为对应的数据库字段名称,value为提供给客户端获取的属性名字,系统会自动填充值 -->
<entry key="user_account" value="user_account"/>
<entry key="user_type" value="user_type"/>
</map>
</property>
</bean>
<!-- 返回用户信息结束 -->
<!--配置md5加密-->
<bean id="MD5PasswordEncoder" class="org.jasig.cas.authentication.handler.DefaultPasswordEncoder" autowire="byName">
<constructor-arg value="MD5"/>
</bean>
<!--配置md5加密结束-->
<bean id="authenticationManager" class="org.jasig.cas.authentication.PolicyBasedAuthenticationManager">
<constructor-arg>
<map>
<entry key-ref="proxyAuthenticationHandler" value-ref="proxyPrincipalResolver"/>
<entry key-ref="primaryAuthenticationHandler" value-ref="primaryPrincipalResolver"/>
</map>
</constructor-arg>
<property name="authenticationPolicy">
<bean class="org.jasig.cas.authentication.AnyAuthenticationPolicy"/>
</property>
</bean>
<bean id="proxyAuthenticationHandler"
class="org.jasig.cas.authentication.handler.support.HttpBasedServiceCredentialsAuthenticationHandler"
p:httpClient-ref="supportsTrustStoreSslSocketFactoryHttpClient"/>
<bean id="proxyPrincipalResolver"
class="org.jasig.cas.authentication.principal.BasicPrincipalResolver"/>
<bean id="primaryPrincipalResolver"
class="org.jasig.cas.authentication.principal.PersonDirectoryPrincipalResolver"
p:principalFactory-ref="principalFactory"
p:attributeRepository-ref="attributeRepository"/>
<bean id="auditTrailManager" class="org.jasig.inspektr.audit.support.Slf4jLoggingAuditTrailManager"/>
<bean id="healthCheckMonitor" class="org.jasig.cas.monitor.HealthCheckMonitor" p:monitors-ref="monitorsList"/>
<util:list id="monitorsList">
<bean class="org.jasig.cas.monitor.MemoryMonitor" p:freeMemoryWarnThreshold="10"/>
<bean class="org.jasig.cas.monitor.SessionMonitor"
p:ticketRegistry-ref="ticketRegistry"
p:serviceTicketCountWarnThreshold="5000"
p:sessionCountWarnThreshold="100000"/>
</util:list>
</beans>
2 Spring boot Shiro Cas集成
2.1 修改pom.xml
在项目pom.xml中添加如下依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-cas</artifactId>
<version>1.4.0</version>
<!--Spring Boot 内嵌Tomcat不能有servlet依赖,需要将其排除掉-->
<exclusions>
<exclusion>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</exclusion>
<exclusion>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency>
<dependency>
<groupId>org.opensaml</groupId>
<artifactId>opensaml</artifactId>
<version>1.1</version>
</dependency>
<dependency>
<groupId>org.apache.santuario</groupId>
<artifactId>xmlsec</artifactId>
<version>1.4.3</version>
<!--Spring Boot 内嵌Tomcat不能有servlet依赖,需要将其排除掉-->
<exclusions>
<exclusion>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</exclusion>
<exclusion>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
</exclusion>
</exclusions>
</dependency>
2.2 编写ShiroConfig
/**
* 没集成cas我们会配置CredentialsMatcher,来做密码加密
* 集成cas不可以配置CredentialsMatcher
*/
@Configuration
public class ShiroConfig {
@Bean(name = "shiroFilterFactoryBean")
public ShiroFilterFactoryBean shiroFilterFactoryBean(WebSecurityManager webSecurityManager,
@Value("${cas.server-url}") String casServerUrl,
@Value("${cas.service}") String casService) {
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
shiroFilter.setSecurityManager(webSecurityManager);
//拦截器.
Map<String, Filter> filters = new HashMap<>();
filters.put("cas", casFilter());
shiroFilter.setFilters(filters);
/**
* 配置哪些页面需要受保护.
* 以及访问这些页面需要的权限.
* 1). anon 可以被匿名访问
* 2). authc 必须认证(即登录)后才可能访问的页面.
* 3). logout 登出.
* 4). roles 角色过滤器
* 5). cas 是指这个URL会被filters中的拦截器拦截
*/
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
// 配置不会被拦截的链接 顺序判断
filterChainDefinitionMap.put("/static/**", "anon");
/**
* 这个拦截路径要和casService的地址匹配
*/
filterChainDefinitionMap.put("/cas", "cas");
//配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了
filterChainDefinitionMap.put("/logout", "logout");
//<!-- 过滤链定义,从上向下顺序执行,一般将/**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了;
//<!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
filterChainDefinitionMap.put("/**", "authc");
// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
shiroFilter.setLoginUrl(casServerUrl + "?service=" + casService + "/cas");
// 登录成功后要跳转的链接
shiroFilter.setSuccessUrl("/");
//未授权界面;
shiroFilter.setUnauthorizedUrl("/403");
shiroFilter.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilter;
}
@Bean
public WebSecurityManager webSecurityManager(Realm realm, RememberMeManager rememberMeManager) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(realm);
securityManager.setRememberMeManager(rememberMeManager);
return securityManager;
}
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
@Bean
public CasFilter casFilter() {
CasFilter casFilter = new CasFilter();
casFilter.setSuccessUrl("/");
casFilter.setFailureUrl("/403");
return casFilter;
}
/**
* 注解@DependsOn 让当前的bean在某个bean启动后启动
*/
@Bean
@DependsOn("lifecycleBeanPostProcessor")
public Realm realm(@Value("${cas.server-url}") String casServerUrl,
@Value("${cas.service}") String casService) {
LoginUserCasRealm realm = new LoginUserCasRealm();
realm.setAuthenticationCachingEnabled(true);
realm.setAuthenticationCacheName("authenticationCache");
realm.setAuthorizationCacheName("authorizationCache");
realm.setCasServerUrlPrefix(casServerUrl);
realm.setCasService(casService + "/cas");
return realm;
}
@Bean
public Cookie cookie() {
//这个参数是cookie的名称,对应前端的checkbox的name = rememberMe
SimpleCookie cookie = new SimpleCookie("JSID");
//<!-- 记住我cookie生效时间30天 ,单位秒;-->
cookie.setMaxAge(2592000);
cookie.setHttpOnly(true);
cookie.setSecure(true);
return cookie;
}
@Bean
public CookieRememberMeManager rememberMeManager(Cookie cookie) {
CookieRememberMeManager rememberMeManager = new CookieRememberMeManager();
rememberMeManager.setCookie(cookie);
//rememberMe cookie加密的密钥 建议每个项目都不一样 默认AES算法 密钥长度(128 256 512 位)
rememberMeManager.setCipherKey(Base64.decode("3AvVhmFLUs0KTA3Kprsdag=="));
return rememberMeManager;
}
@Bean
public AuthenticationStrategy authenticationStrategy() {
return new AtLeastOneSuccessfulStrategy();
}
@Bean
public Authenticator authenticator(AuthenticationStrategy authenticationStrategy) {
ModularRealmAuthenticator authenticator = new ModularRealmAuthenticator();
authenticator.setAuthenticationStrategy(authenticationStrategy);
return authenticator;
}
@Bean
public SessionDAO sessionDAO(SessionIdGenerator sessionIdGenerator) {
EnterpriseCacheSessionDAO sessionDAO = new EnterpriseCacheSessionDAO();
sessionDAO.setActiveSessionsCacheName("shiro-activeSessionCache");
sessionDAO.setSessionIdGenerator(sessionIdGenerator);
return sessionDAO;
}
@Bean
public SessionIdGenerator sessionIdGenerator() {
return new JavaUuidSessionIdGenerator();
}
@Bean
public SessionManager sessionManager(SessionDAO sessionDAO, Cookie cookie) {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setGlobalSessionTimeout(30 * 60 * 1000);
sessionManager.setSessionDAO(sessionDAO);
sessionManager.setSessionIdCookie(cookie);
return sessionManager;
}
}
2.3 编写Realm
public class XxxRealm extends CasRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
Set<String> strings = new HashSet<>();
strings.add("USER");
//这里获取用户角色和权限的逻辑,每个项目不一样,所以没写实现代码
//设置用户拥有的角色
authorizationInfo.setRoles(strings);
//设置用户拥有的权限
authorizationInfo.setStringPermissions(strings);
return authorizationInfo;
}
}
2.4 配置application.properties
#cas地址,外网访问需用外网IP
cas.server-url=http://localhost
#当前项目地址,外网访问需用外网IP
cas.service=http://localhost:8080