1.簡介
CAS:Yale 大學發起的一個開源項目,旨在爲 Web 應用系統提供一種可靠的單點登錄方法。
Shiro:Apache Shiro是一個Java安全框架,可以幫助我們完成認證、授權、會話管理、加密等,並且提供與web集成、緩存、rememberMed等功能。
Shiro支持與CAS進行整合使用.
2.整合步驟
2.1.新建工程
2.2.導入項目中需要的依賴
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.bruce.cas.shiro.demo</groupId>
<artifactId>CasShiro</artifactId>
<version>1.0-SNAPSHOT</version>
<!--導入SpringBoot的依賴-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
<relativePath/>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<shiro.version>1.2.4</shiro.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-cas</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>net.mingsoft</groupId>
<artifactId>shiro-freemarker-tags</artifactId>
<version>0.1</version>
</dependency>
<!--導入thymeleaf支持shiro的標籤-->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2.3.啓動類
package com.bruce;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @BelongsProject: CasShiro
* @BelongsPackage: com.bruce
* @Author: bruceliu
* @QQ:1241488705
* @CreateTime: 2020-04-10 23:08
* @Description: TODO
*/
@SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class,args);
}
}
2.4.配置文件
server.port=8888
#Thymeleaf配置
spring.thymeleaf.cache=false
spring.thymeleaf.encoding=utf-8
#設置爲LEGACYHTML5編碼格式
spring.thymeleaf.mode=LEGACYHTML5
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
cas.casServerUrlPrefix=http://127.0.0.1:8080
cas.casServerLoginUrl=${cas.casServerUrlPrefix}/login
#cas.casServerLoginUrl=http://127.0.0.1:8081/shiroCAS/toLogin
cas.casServerLogoutUrl=${cas.casServerUrlPrefix}/logout?service=http://www.baidu.com
cas.casFilterUrlPattern=/shiro-cas
cas.localServerUrlPrefix=http://localhost:${server.port}
cas.localServerLoginUrl=${cas.casServerLoginUrl}?service=${cas.localServerUrlPrefix}${cas.casFilterUrlPattern}
2.5.加載CAS的配置文件
package com.bruce.config;
/**
* @BelongsProject: CasShiro
* @BelongsPackage: com.bruce.config
* @Author: bruceliu
* @QQ:1241488705
* @CreateTime: 2020-04-17 22:45
* @Description: TODO
*/
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* 這個類是shiro整合cas的時候需要的一些配置
* 申明的一些變量
*/
@SpringBootConfiguration
@ConfigurationProperties(prefix = "cas")
public class CasConfig {
private String casServerUrlPrefix = "https://server.fable.com:8443/cas";
private String casServerLoginUrl = casServerUrlPrefix + "/login";
private String casServerLogoutUrl = casServerUrlPrefix + "/logout";
private String localServerUrlPrefix = "http://client1.fable.com:8081/aaaa";
private String casFilterUrlPattern = "/shiro-cas";
private String localServerLoginUrl = casServerLoginUrl + "?service=" + localServerUrlPrefix + casFilterUrlPattern;
public String getCasServerUrlPrefix() {
return casServerUrlPrefix;
}
public void setCasServerUrlPrefix(String casServerUrlPrefix) {
this.casServerUrlPrefix = casServerUrlPrefix;
}
public String getCasServerLoginUrl() {
return casServerLoginUrl;
}
public void setCasServerLoginUrl(String casServerLoginUrl) {
this.casServerLoginUrl = casServerLoginUrl;
}
public String getCasServerLogoutUrl() {
return casServerLogoutUrl;
}
public void setCasServerLogoutUrl(String casServerLogoutUrl) {
this.casServerLogoutUrl = casServerLogoutUrl;
}
public String getLocalServerUrlPrefix() {
return localServerUrlPrefix;
}
public void setLocalServerUrlPrefix(String localServerUrlPrefix) {
this.localServerUrlPrefix = localServerUrlPrefix;
}
public String getCasFilterUrlPattern() {
return casFilterUrlPattern;
}
public void setCasFilterUrlPattern(String casFilterUrlPattern) {
this.casFilterUrlPattern = casFilterUrlPattern;
}
public String getLocalServerLoginUrl() {
return localServerLoginUrl;
}
public void setLocalServerLoginUrl(String localServerLoginUrl) {
this.localServerLoginUrl = localServerLoginUrl;
}
public String getCasService() {
return localServerUrlPrefix + casFilterUrlPattern;
}
}
2.6.配置Realm
package com.bruce.shiro;
import com.bruce.pojo.User;
import com.bruce.service.IUserService;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.cas.CasRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.HashSet;
import java.util.Set;
/**
* shiro和cas整合之後 繼承CasRealm
*/
public class MyShiroCasRealm extends CasRealm{
@Autowired
private IUserService userService;
/**
* 權限認證(爲當前登錄的Subject授予角色和權限)
*
* 該方法的調用時機爲需授權資源被訪問時,並且每次訪問需授權資源都會執行該方法中的邏輯,這表明本例中並未啓用AuthorizationCache,
* 如果連續訪問同一個URL(比如刷新),該方法不會被重複調用,Shiro有一個時間間隔(也就是cache時間,在ehcache-shiro.xml中配置),
* 超過這個時間間隔再刷新頁面,該方法會被執行
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String loginName = (String) super.getAvailablePrincipal(principals);
User user = userService.findByUsername(loginName);
if (user != null){
// 權限信息對象info,用來存放查出的用戶的所有的角色及權限
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//下面就是查詢用戶的權限
Set<String> roles=new HashSet<String>();
roles.add("admin");
roles.add("normal");
//下面也就可以查詢用戶的角色
Set<String> perms=new HashSet<String>();
perms.add("del");
perms.add("query");
perms.add("user:query");
info.setRoles(roles);
info.setStringPermissions(perms);
System.out.println("這裏是授權的地方..............................");
return info;
}
// 返回null將會導致用戶訪問任何被攔截的請求時都會自動跳轉到unauthorizedUrl指定的地址
return null;
}
}
2.7.配置ShiroFilterFactoryBean
package com.bruce.shiro;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.filter.mgt.FilterChainManager;
import org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver;
import org.apache.shiro.web.mgt.WebSecurityManager;
import org.apache.shiro.web.servlet.AbstractShiroFilter;
import org.springframework.beans.factory.BeanInitializationException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
/**
* 繼承ShiroFilterFactoryBean處理攔截資源文件問題
*/
public class MyShiroFilterFactoryBean extends ShiroFilterFactoryBean {
// ShiroFilter將直接忽略的請求
private Set<String> ignoreExt;
public MyShiroFilterFactoryBean() {
super();
ignoreExt = new HashSet<String>();
ignoreExt.add(".jpg");
ignoreExt.add(".png");
ignoreExt.add(".gif");
ignoreExt.add(".bmp");
ignoreExt.add(".js");
ignoreExt.add(".css");
}
@Override
protected AbstractShiroFilter createInstance() throws Exception {
SecurityManager securityManager = getSecurityManager();
if (securityManager == null) {
throw new BeanInitializationException("SecurityManager property must be set.");
}
if (!(securityManager instanceof WebSecurityManager)) {
throw new BeanInitializationException("The security manager does not implement the WebSecurityManager interface.");
}
PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver();
FilterChainManager chainManager = createFilterChainManager();
chainResolver.setFilterChainManager(chainManager);
return new MySpringShiroFilter((WebSecurityManager) securityManager, chainResolver);
}
private class MySpringShiroFilter extends AbstractShiroFilter {
public MySpringShiroFilter(
WebSecurityManager securityManager, PathMatchingFilterChainResolver chainResolver) {
super();
if (securityManager == null) {
throw new IllegalArgumentException("WebSecurityManager property cannot be null.");
}
setSecurityManager(securityManager);
if (chainResolver != null) {
setFilterChainResolver(chainResolver);
}
}
@Override
protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse,
FilterChain chain)
throws ServletException, IOException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
String str = request.getRequestURI().toLowerCase();
boolean flag = true;
int idx = 0;
if ((idx = str.indexOf(".")) > 0) {
str = str.substring(idx);
if (ignoreExt.contains(str.toLowerCase())) {
flag = false;
}
}
if (flag) {
super.doFilterInternal(servletRequest, servletResponse, chain);
} else {
chain.doFilter(servletRequest, servletResponse);
}
}
}
}
2.8. Shiro集成CAS配置
package com.bruce.shiro;
import com.bruce.config.CasConfig;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.cas.CasFilter;
import org.apache.shiro.cas.CasSubjectFactory;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.DelegatingFilterProxy;
import javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Shiro集成CAS配置
*/
@Configuration
@EnableConfigurationProperties(CasConfig.class)
public class ShiroCasConfiguration {
private static final String CAS_FILTER = "casFilter";
@Bean
public EhCacheManager ehcacheManager(){
EhCacheManager ehcacheManager = new EhCacheManager();
ehcacheManager.setCacheManagerConfigFile("classpath:ehcache-shiro.xml");
return ehcacheManager;
}
@Bean(name = "myShiroCasRealm")
public MyShiroCasRealm myShiroRealm(EhCacheManager ehCacheManager, CasConfig casConfig){
MyShiroCasRealm realm = new MyShiroCasRealm();
realm.setCacheManager(ehCacheManager);
realm.setCasServerUrlPrefix(casConfig.getCasServerUrlPrefix());
realm.setCasService(casConfig.getCasService());
return realm;
}
/**
* 註冊shiroFilter
*/
@Bean
public FilterRegistrationBean filterRegistrationBean(){
FilterRegistrationBean filterRegistration = new FilterRegistrationBean();
filterRegistration.setFilter(new DelegatingFilterProxy("shiroFilter"));
// 該值缺省爲false,表示生命週期有SpringApplicationContext管理,設置爲true則表示由ServletContainer管理
filterRegistration.addInitParameter("targetFilterLifecycle", "true");
filterRegistration.setEnabled(true);
filterRegistration.addUrlPatterns("/*");
return filterRegistration;
}
@Bean(name = "lifecycleBeanPostProcessor")
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){
return new LifecycleBeanPostProcessor();
}
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){
DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
creator.setProxyTargetClass(true);
return creator;
}
@Bean(name = "securityManager")
public DefaultWebSecurityManager defaultWebSecurityManager(MyShiroCasRealm realm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(realm);
securityManager.setCacheManager(ehcacheManager());
// 指定SubjectFactory
securityManager.setSubjectFactory(new CasSubjectFactory());
return securityManager;
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager){
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
@Bean(name = "shiroFilter")
public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager, CasConfig casConfig, CasFilter casFilter){
ShiroFilterFactoryBean factoryBean = new MyShiroFilterFactoryBean();
factoryBean.setSecurityManager(securityManager);
factoryBean.setLoginUrl(casConfig.getLocalServerLoginUrl());
factoryBean.setSuccessUrl("/user");
factoryBean.setUnauthorizedUrl("/403");
// 添加casFilter到shiroFilter中
Map<String, Filter> filterMap = new HashMap<String, Filter>(1);
filterMap.put(CAS_FILTER, casFilter);
factoryBean.setFilters(filterMap);
loadShiroFilterChain(factoryBean, casConfig);
return factoryBean;
}
/**
* 加載ShiroFilter權限控制規則
*/
private void loadShiroFilterChain(ShiroFilterFactoryBean factoryBean, CasConfig casConfig) {
/**下面這些規則配置最好配置到配置文件中*/
Map<String, String> filterChainMap = new LinkedHashMap<String, String>();
filterChainMap.put(casConfig.getCasFilterUrlPattern(), CAS_FILTER);//shiro集成cas後,首先添加該規則
filterChainMap.put("/main", "anon");
filterChainMap.put("/**", "authc");
factoryBean.setFilterChainDefinitionMap(filterChainMap);
}
/**
* CAS過濾器
*/
@Bean
public CasFilter casFilter(CasConfig casConfig){
CasFilter casFilter = new CasFilter();
casFilter.setName(CAS_FILTER);
casFilter.setEnabled(true);
casFilter.setFailureUrl(casConfig.getLocalServerLoginUrl());
return casFilter;
}
}
2.9.集成Shiro標籤
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.context.annotation.Bean;
/**
* 集成Shiro標籤
*/
@SpringBootConfiguration
public class ShiroTagThymeleafConfigurer{
/**
* 配置的是方言
* @return
*/
@Bean
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}
}
2.10.頁面使用
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
<meta charset="UTF-8"/>
<title>Title</title>
</head>
<body>
<h1>用戶列表-<a href="logout">登出</a></h1>
<h2>權限列表</h2>
<shiro:authenticated>用戶已經登錄顯示此內容</shiro:authenticated><br/>
<shiro:hasRole name="manager">manager角色登錄顯示此內容</shiro:hasRole><br/>
<shiro:hasRole name="admin">admin角色登錄顯示此內容</shiro:hasRole><br/>
<shiro:hasRole name="normal">normal角色登錄顯示此內容</shiro:hasRole><br/>
<shiro:hasAnyRoles name="manager,admin">manager或admin角色用戶登錄顯示此內容</shiro:hasAnyRoles><br/>
<shiro:principal>-顯示當前登錄用戶名-</shiro:principal><br/>
<shiro:hasPermission name="add">add權限用戶顯示此內容</shiro:hasPermission><br/>
<br/>所有用戶列表:<br/>
<ul th:each="user1,iterStat: ${userList}">
<li th:text="${user1.userName}"></li>
</ul>
</body>
</html>