spring boot整合Shiro實現(xiàn)單點登錄的示例代碼
Shiro是什么
Shiro是一個Java平臺的開源權限框架,用于認證和訪問授權。具體來說,滿足對如下元素的支持:
- 用戶,角色,權限(僅僅是操作權限,數(shù)據(jù)權限必須與業(yè)務需求緊密結合),資源(url)。
- 用戶分配角色,角色定義權限。
- 訪問授權時支持角色或者權限,并且支持多級的權限定義。
Q:對組的支持?
A:shiro默認不支持對組設置權限。
Q:是否可以滿足對組進行角色分配的需求?
A:擴展Realm,可以支持對組進行分配角色,其實就是給該組下的所有用戶分配權限。
Q:對數(shù)據(jù)權限的支持? 在業(yè)務系統(tǒng)中定義?
A:shiro僅僅實現(xiàn)對操作權限的控制,用于在前端控制元素隱藏或者顯示,以及對資源訪問權限進行檢查。數(shù)據(jù)權限與具體的業(yè)務需求緊密關聯(lián),shiro本身無法實現(xiàn)對數(shù)據(jù)權限的控制。
Q:動態(tài)權限分配?
A:擴展org.apache.shiro.realm.Realm,支持動態(tài)權限分配。
Q:與Spring集成?
A:可以支持與Spring集成,shiro還支持jsp標簽。
前面的博客中,我們說道了Shiro的兩個最大的特點,認證和授權,而單點登錄也是屬于認證的一部分,默認情況下,Shiro已經為我們實現(xiàn)了和Cas的集成,我們加入集成的一些配置就ok了。
1、加入shiro-cas包
<!-- shiro整合cas單點 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-cas</artifactId>
<version>1.2.4</version>
</dependency>
2、加入單點登錄的配置
這里,我將所有的配置都貼出來,方便參考,配置里面已經加了詳盡的說明。
package com.chhliu.springboot.shiro.config;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.servlet.Filter;
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.jasig.cas.client.session.SingleSignOutFilter;
import org.jasig.cas.client.session.SingleSignOutHttpSessionListener;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.web.filter.DelegatingFilterProxy;
/**
* Shiro 配置
*
* Apache Shiro 核心通過 Filter 來實現(xiàn),就好像SpringMvc 通過DispachServlet 來主控制一樣。 既然是使用
* Filter 一般也就能猜到,是通過URL規(guī)則來進行過濾和權限校驗,所以我們需要定義一系列關于URL的規(guī)則和訪問權限。
*
* @author chhliu
*/
@Configuration
public class ShiroConfiguration {
// cas server地址
public static final String casServerUrlPrefix = "http://127.0.0.1";
// Cas登錄頁面地址
public static final String casLoginUrl = casServerUrlPrefix + "/login";
// Cas登出頁面地址
public static final String casLogoutUrl = casServerUrlPrefix + "/logout";
// 當前工程對外提供的服務地址
public static final String shiroServerUrlPrefix = "http://127.0.1.28:8080";
// casFilter UrlPattern
public static final String casFilterUrlPattern = "/index";
// 登錄地址
public static final String loginUrl = casLoginUrl + "?service=" + shiroServerUrlPrefix + casFilterUrlPattern;
// 登出地址(casserver啟用service跳轉功能,需在webapps\cas\WEB-INF\cas.properties文件中啟用cas.logout.followServiceRedirects=true)
public static final String logoutUrl = casLogoutUrl+"?service="+loginUrl;
// 登錄成功地址
// public static final String loginSuccessUrl = "/index";
// 權限認證失敗跳轉地址
public static final String unauthorizedUrl = "/error/403.html";
/**
* 實例化SecurityManager,該類是shiro的核心類
* @return
*/
@Bean
public DefaultWebSecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myShiroCasRealm());
// <!-- 用戶授權/認證信息Cache, 采用EhCache 緩存 -->
securityManager.setCacheManager(getEhCacheManager());
// 指定 SubjectFactory,如果要實現(xiàn)cas的remember me的功能,需要用到下面這個CasSubjectFactory,并設置到securityManager的subjectFactory中
securityManager.setSubjectFactory(new CasSubjectFactory());
return securityManager;
}
/**
* 配置緩存
* @return
*/
@Bean
public EhCacheManager getEhCacheManager() {
EhCacheManager em = new EhCacheManager();
em.setCacheManagerConfigFile("classpath:config/ehcache-shiro.xml");
return em;
}
/**
* 配置Realm,由于我們使用的是CasRealm,所以已經集成了單點登錄的功能
* @param cacheManager
* @return
*/
@Bean
public MyShiroRealm myShiroCasRealm() {
MyShiroRealm realm = new MyShiroRealm();
// cas登錄服務器地址前綴
realm.setCasServerUrlPrefix(ShiroConfiguration.casServerUrlPrefix);
// 客戶端回調地址,登錄成功后的跳轉地址(自己的服務地址)
realm.setCasService(ShiroConfiguration.shiroServerUrlPrefix + ShiroConfiguration.casFilterUrlPattern);
// 登錄成功后的默認角色,此處默認為user角色
realm.setDefaultRoles("user");
return realm;
}
/**
* 注冊單點登出的listener
* @return
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)// 優(yōu)先級需要高于Cas的Filter
public ServletListenerRegistrationBean<?> singleSignOutHttpSessionListener(){
ServletListenerRegistrationBean bean = new ServletListenerRegistrationBean();
bean.setListener(new SingleSignOutHttpSessionListener());
bean.setEnabled(true);
return bean;
}
/**
* 注冊單點登出filter
* @return
*/
@Bean
public FilterRegistrationBean singleSignOutFilter(){
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setName("singleSignOutFilter");
bean.setFilter(new SingleSignOutFilter());
bean.addUrlPatterns("/*");
bean.setEnabled(true);
return bean;
}
/**
* 注冊DelegatingFilterProxy(Shiro)
*/
@Bean
public FilterRegistrationBean delegatingFilterProxy() {
FilterRegistrationBean filterRegistration = new FilterRegistrationBean();
filterRegistration.setFilter(new DelegatingFilterProxy("shiroFilter"));
// 該值缺省為false,表示生命周期由SpringApplicationContext管理,設置為true則表示由ServletContainer管理
filterRegistration.addInitParameter("targetFilterLifecycle", "true");
filterRegistration.setEnabled(true);
filterRegistration.addUrlPatterns("/*");
return filterRegistration;
}
/**
* 該類可以保證實現(xiàn)了org.apache.shiro.util.Initializable接口的shiro對象的init或者是destory方法被自動調用,
* 而不用手動指定init-method或者是destory-method方法
* 注意:如果使用了該類,則不需要手動指定初始化方法和銷毀方法,否則會出錯
* @return
*/
@Bean(name = "lifecycleBeanPostProcessor")
public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
/**
* 下面兩個配置主要用來開啟shiro aop注解支持. 使用代理方式;所以需要開啟代碼支持;
* @return
*/
@Bean
@DependsOn("lifecycleBeanPostProcessor")
public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator daap = new DefaultAdvisorAutoProxyCreator();
daap.setProxyTargetClass(true);
return daap;
}
/**
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
/**
* CAS過濾器
* @return
*/
@Bean(name = "casFilter")
public CasFilter getCasFilter() {
CasFilter casFilter = new CasFilter();
casFilter.setName("casFilter");
casFilter.setEnabled(true);
// 登錄失敗后跳轉的URL,也就是 Shiro 執(zhí)行 CasRealm 的 doGetAuthenticationInfo 方法向CasServer驗證tiket
casFilter.setFailureUrl(loginUrl);// 我們選擇認證失敗后再打開登錄頁面
casFilter.setLoginUrl(loginUrl);
return casFilter;
}
/**
* 使用工廠模式,創(chuàng)建并初始化ShiroFilter
* @param securityManager
* @param casFilter
* @return
*/
@Bean(name = "shiroFilter")
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager, CasFilter casFilter) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 必須設置 SecurityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 如果不設置默認會自動尋找Web工程根目錄下的"/login.jsp"頁面
shiroFilterFactoryBean.setLoginUrl(loginUrl);
/*
* 登錄成功后要跳轉的連接,不設置的時候,會默認跳轉到前一步的url
* 比如先在瀏覽器中輸入了http://localhost:8080/userlist,但是現(xiàn)在用戶卻沒有登錄,于是會跳轉到登錄頁面,等登錄認證通過后,
* 頁面會再次自動跳轉到http://localhost:8080/userlist頁面而不是登錄成功后的index頁面
* 建議不要設置這個字段
*/
// shiroFilterFactoryBean.setSuccessUrl(loginSuccessUrl);
// 設置無權限訪問頁面
shiroFilterFactoryBean.setUnauthorizedUrl(unauthorizedUrl);
/*
* 添加casFilter到shiroFilter中,注意,casFilter需要放到shiroFilter的前面,
* 從而保證程序在進入shiro的login登錄之前就會進入單點認證
*/
Map<String, Filter> filters = new LinkedHashMap<>();
filters.put("casFilter", casFilter);
// logout已經被單點登錄的logout取代
// filters.put("logout",logoutFilter());
shiroFilterFactoryBean.setFilters(filters);
loadShiroFilterChain(shiroFilterFactoryBean);
return shiroFilterFactoryBean;
}
/**
* 加載shiroFilter權限控制規(guī)則(從數(shù)據(jù)庫讀取然后配置),角色/權限信息由MyShiroCasRealm對象提供doGetAuthorizationInfo實現(xiàn)獲取來的
* 生產中會將這部分規(guī)則放到數(shù)據(jù)庫中
* @param shiroFilterFactoryBean
*/
private void loadShiroFilterChain(ShiroFilterFactoryBean shiroFilterFactoryBean){
/////////////////////// 下面這些規(guī)則配置最好配置到配置文件中,注意,此處加入的filter需要保證有序,所以用的LinkedHashMap ///////////////////////
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
filterChainDefinitionMap.put(casFilterUrlPattern, "casFilter");
//2.不攔截的請求
filterChainDefinitionMap.put("/css/**","anon");
filterChainDefinitionMap.put("/js/**","anon");
filterChainDefinitionMap.put("/login", "anon");
// 此處將logout頁面設置為anon,而不是logout,因為logout被單點處理,而不需要再被shiro的logoutFilter進行攔截
filterChainDefinitionMap.put("/logout","anon");
filterChainDefinitionMap.put("/error","anon");
//3.攔截的請求(從本地數(shù)據(jù)庫獲取或者從casserver獲取(webservice,http等遠程方式),看你的角色權限配置在哪里)
filterChainDefinitionMap.put("/user", "authc"); //需要登錄
//4.登錄過的不攔截
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
}
}
部分配置參考:http://shiro.apache.org/spring.html
3、編寫Realm
由于需要集成單點登錄的功能,所以需要集成CasRealm類,該類已經為我們實現(xiàn)了單點認證的功能,我們要做的就是實現(xiàn)授權部分的功能,示例代碼如下:
package com.chhliu.springboot.shiro.config;
import javax.annotation.Resource;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
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 com.chhliu.springboot.shiro.mode.SysPermission;
import com.chhliu.springboot.shiro.mode.SysRole;
import com.chhliu.springboot.shiro.mode.UserInfo;
import com.chhliu.springboot.shiro.service.UserInfoService;
/**
* 權限校驗核心類; 由于使用了單點登錄,所以無需再進行身份認證 只需要授權即可
*
* @author chhliu
*/
public class MyShiroRealm extends CasRealm {
@Resource
private UserInfoService userInfoService;
/**
* 1、CAS認證 ,驗證用戶身份
* 2、將用戶基本信息設置到會話中,方便獲取
* 3、該方法可以直接使用CasRealm中的認證方法,此處僅用作測試
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) {
// 調用父類中的認證方法,CasRealm已經為我們實現(xiàn)了單點認證。
AuthenticationInfo authc = super.doGetAuthenticationInfo(token);
// 獲取登錄的賬號,cas認證成功后,會將賬號存起來
String account = (String) authc.getPrincipals().getPrimaryPrincipal();
// 將用戶信息存入session中,方便程序獲取,此處可以將根據(jù)登錄賬號查詢出的用戶信息放到session中
SecurityUtils.getSubject().getSession().setAttribute("no", account);
return authc;
}
/**
* 此方法調用 hasRole,hasPermission的時候才會進行回調.
*
* 權限信息.(授權): 1、如果用戶正常退出,緩存自動清空; 2、如果用戶非正常退出,緩存自動清空;
* 3、如果我們修改了用戶的權限,而用戶不退出系統(tǒng),修改的權限無法立即生效。 (需要手動編程進行實現(xiàn);放在service進行調用)
* 在權限修改后調用realm中的方法,realm已經由spring管理,所以從spring中獲取realm實例, 調用clearCached方法;
* :Authorization 是授權訪問控制,用于對用戶進行的操作授權,證明該用戶是否允許進行當前操作,如訪問某個鏈接,某個資源文件等。
*
* @param principals
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
System.out.println("權限配置-->MyShiroRealm.doGetAuthorizationInfo()");
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
// 獲取單點登陸后的用戶名,也可以從session中獲取,因為在認證成功后,已經將用戶名放到session中去了
String userName = (String) super.getAvailablePrincipal(principals);
// principals.getPrimaryPrincipal(); 這種方式也可以獲取用戶名
// 根據(jù)用戶名獲取該用戶的角色和權限信息
UserInfo userInfo = userInfoService.findByUsername(userName);
// 將用戶對應的角色和權限信息打包放到AuthorizationInfo中
for (SysRole role : userInfo.getRoleList()) {
authorizationInfo.addRole(role.getRole());
for (SysPermission p : role.getPermissions()) {
authorizationInfo.addStringPermission(p.getPermission());
}
}
return authorizationInfo;
}
}
下面,我們就可以進行驗證測試了!
在瀏覽器輸入http:127.0.1.28:8080/userInfo/userList 我們會發(fā)現(xiàn),會自動跳轉到單點的登錄頁面

然后我們輸入用戶名和密碼,就會自動跳轉到http:127.0.1.28:8080/userInfo/userList頁面了。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關文章
使用AOP攔截Controller獲取@PathVariable注解傳入的參數(shù)
這篇文章主要介紹了使用AOP攔截Controller獲取@PathVariable注解傳入的參數(shù),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-08-08
SSM框架中測試單元的使用 spring整合Junit過程詳解
這篇文章主要介紹了SSM框架中測試單元的使用 spring整合Junit過程詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2019-09-09
Java并發(fā) synchronized鎖住的內容解析
這篇文章主要介紹了Java并發(fā) synchronized鎖住的內容解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2019-09-09
使用Springboot+poi上傳并處理百萬級數(shù)據(jù)EXCEL
這篇文章主要介紹了使用Springboot+poi上傳并處理百萬級數(shù)據(jù)EXCEL,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-12-12
HashMap原理及手寫實現(xiàn)部分區(qū)塊鏈特征
這篇文章主要為大家介紹了HashMap原理及手寫實現(xiàn)部分區(qū)塊鏈特征,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-09-09

