Shiro+Spring+SpringMVC+Mybatis+資料庫整合並進行登陸認證和授權詳細配置
本篇部落格將進行詳細介紹Shiro+Spring+SpringMVC+Mybatis+資料庫整合並進行登陸認證和授權詳細配置。
SSM的整合可以參考:https://blog.csdn.net/a745233700/article/details/81049763
下面主要介紹Shiro與SSM的整合和使用:
1、匯入Shiro需要的maven依賴:
<!-- shiro -->
<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-all -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.2.3</version>
</dependency>
或者一次性匯入shiro的所有依賴:
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-all</artifactId>
<version>1.2.3</version>
</dependency>
2、在web.xml檔案中配置shiro的filter攔截器:
在與Spring整合中,shiro也通過Filter進行攔截,但是攔截後的操作權交給spring中配置的filterChainDefinitions(過濾鏈)處理。
<!-- shiro的filter -->
<!-- shiro過慮器,DelegatingFilterProxy通過代理模式將spring容器中的bean和filter關聯起來 -->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<!-- 設定true由servlet容器控制filter的生命週期 -->
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
<!-- 設定spring容器filter的bean id,如果不設定則找與filter-name一致的bean-->
<init-param>
<param-name>targetBeanName</param-name>
<param-value>shiroFilter</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
3、配置Shiro框架的相關配置:(applicationContext-shiro.xml檔案中)
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
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-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd">
<!-- id屬性值要對應 web.xml中shiro的filter對應的bean -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"></property>
<!-- loginUrl認證提交地址,如果沒有認證將會請求此地址進行認證,請求地址將由formAuthenticationFilter進行表單認證 -->
<property name="loginUrl" value="/login.action"></property>
<!-- 認證成功統一跳轉到first.action,建議不配置,shiro認證成功會預設跳轉到上一個請求路徑 -->
<!-- <property name="successUrl" value="/first.action"></property> -->
<!-- 通過unauthorizedUrl指定沒有許可權操作時跳轉頁面,這個位置會攔截不到,下面有給出解決方法 -->
<!-- <property name="unauthorizedUrl" value="/refuse.jsp"></property> -->
<!-- 過濾器定義,從上到下執行,一般將/**放在最下面 -->
<property name="filterChainDefinitions">
<value>
<!-- 對靜態資源設定匿名訪問 -->
/images/** = anon
/js/** = anon
/styles/** = anon
/validatecode.jsp =anon
<!-- 請求logout.action地址,shiro去清除session -->
/logout.action = logout
<!-- /**=authc 所有的url都必須通過認證才可以訪問 -->
/** = authc
<!-- /**=anon 所有的url都可以匿名訪問,不能配置在最後一排,不然所有的請求都不會攔截 -->
</value>
</property>
</bean>
<!-- 解決shiro配置的沒有許可權訪問時,unauthorizedUrl不跳轉到指定路徑的問題 -->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="org.apache.shiro.authz.UnauthorizedException">/refuse</prop>
</props>
</property>
</bean>
<!-- securityManager安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="customRealm"></property>
</bean>
<!-- 配置自定義Realm -->
<bean id="customRealm" class="com.zwp.shiro.CustomRealm">
<!-- 將憑證匹配器設定到realm中,realm按照憑證匹配器的要求進行雜湊 -->
<property name="credentialsMatcher" ref="credentialsMatcher"></property>
</bean>
<!-- 憑證匹配器 -->
<bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<!-- 加密演算法 -->
<property name="hashAlgorithmName" value="md5"></property>
<!-- 迭代次數 -->
<property name="hashIterations" value="1"></property>
</bean>
</beans>
4、配置自定義的Realm,重寫認證的方法:
//自定義的Realm
public class CustomRealm extends AuthorizingRealm{
//注入service
@Autowired
private SysService sysService;
// 設定realm的名稱
@Override
public void setName(String name) {
super.setName("customRealm");
}
//認證的方法
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// token是使用者輸入的使用者名稱和密碼
// 第一步從token中取出使用者名稱
String userCode = (String) token.getPrincipal();
// 第二步:根據使用者輸入的userCode從資料庫查詢使用者資訊
SysUser sysUser = null;
try {
sysUser = sysService.findSysUserByUserCode(userCode);
} catch (Exception e1) {
e1.printStackTrace();
}
// 如果查詢不到返回null
if(sysUser==null){
return null;
}
// 從資料庫查詢到密碼
String password = sysUser.getPassword();
//鹽
String salt = sysUser.getSalt();
// 如果查詢到,返回認證資訊AuthenticationInfo
//activeUser就是使用者身份資訊
ActiveUser activeUser = new ActiveUser();
activeUser.setUserid(sysUser.getId());
activeUser.setUsercode(sysUser.getUsercode());
activeUser.setUsername(sysUser.getUsername());
//..
//根據使用者id取出選單
List<SysPermission> menus = null;
try {
//通過service取出選單
menus = sysService.findMenuListByUserId(sysUser.getId());
} catch (Exception e) {
e.printStackTrace();
}
//將使用者選單,設定到activeUser
activeUser.setMenus(menus);
//將activeUser設定simpleAuthenticationInfo
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
activeUser, password,ByteSource.Util.bytes(salt), this.getName());
return simpleAuthenticationInfo;
}
}
5、登陸:
(1)原理:使用FormAuthenticationFilter的過濾器實現。
①在使用者沒有認證時,請求loginUrl進行認證,使用者身份和使用者密碼提交到loginUrl;
②FormAuthenticationFilter攔截住,並取出request中的username和password(兩個引數名稱可以配置)
③FormAuthenticationFilter呼叫realm傳入一個token(即username和password);
④realm認證是根據username查詢使用者資訊,(並在ActiveUser中儲存,包括userid、usercode、username、menus等),如果查詢不到,realm返回null,FormAuthenticationFilter向requset域中填充一個引數(記錄了異常資訊)。
(2)登陸頁面:
由於FormAuthenticationFilter的使用者身份和密碼的input的預設值(username和password),修改頁面的賬號和密碼 的input的名稱為username和password。
(3)登陸程式碼實現:
@Controller
public class LoginController {
//loginUrl指定的認證提交地址
@RequestMapping("/login.action")
public String login(HttpServletRequest request) throws Exception{
//如果登陸失敗,則從request中獲取認證異常資訊,shiroLoginFailure就是shiro異常類的全限定名
String exceptionClassName=request.getParameter("shiroLoginFailure");
//根據shiro返回的異常路徑判斷,丟擲指定異常資訊
if(exceptionClassName!=null){
if(UnknownAccountException.class.getName().equals(exceptionClassName)){
throw new CustomException("賬戶不存在");
}else if(IncorrectCredentialsException.class.getName().equals(exceptionClassName)){
throw new CustomException("使用者名稱/密碼錯誤");
}else{
throw new Exception();
}
}
//此方法不處理登陸成功(認證成功),如果shiro認證成功會自動跳轉到上一個請求路徑。
//登陸失敗回到login頁面:
return "login";
}
}
(4)認證攔截器:/** = authu
6、退出登陸:
在shiro中,不需要我們去實現退出登陸介面,只要去訪問一個退出的url(該url是可以不存在),由LogoutFilter攔截住,清除session。
7、認證資訊在頁面顯示:
在controller層取出使用者資訊,並設定在Attribute中,first.action會跳轉到首頁頁面。
@RequestMapping("/first.action")
public String first(Model model)throws Exception{
//從shiro的session中取activeUser
Subject subject = SecurityUtils.getSubject();
//取身份資訊
ActiveUser activeUser = (ActiveUser) subject.getPrincipal();
//通過model傳到頁面
model.addAttribute("activeUser", activeUser);
return "/first";
}
、至此,Shiro+Spring+SpringMVC+Mybatis整合實現登陸認證和退出登陸的功能就完成了。啟動工程進行測試,在你沒有登陸認證成功之前,訪問專案中的任何路徑,都會被強制跳轉到loginUrl指定的路徑進行登陸提交。只有登陸認證成功,才可以訪問專案中的內容。
8、配置Shiro授權過濾器:使用PermissionsAuthorizationFilter
在applicationContext-shiro.xml中配置url所對應的許可權。
測試流程:
(1)在applicationContext-shiro.xml中配置filter規則
/permissionTest = perms[item:query] //即訪問“/permissionTest“” 路徑需要“item:query”許可權
(2)使用者在認證通過後,請求“/permissionTest”,被PermissionsAuthorizationFilter攔截,發現需要“item:query”許可權;
(3)PermissionsAuthorizationFilter 呼叫 doGetAuthorizationInfo 獲取資料庫中的正確許可權並返回;
(4)PermissionsAuthorizationFilter對“item:query”和從realm中獲取的許可權進行對比,如果“item:query”在realm返回的許可權列表中,授權通過。
9、建立授權失敗頁面refuse.jsp,並配置自動跳轉:(記錄一個在此處遇到的小問題)
如果授權失敗,跳轉到refuse.jsp,需要在spring容器中配置。
(1)授權失敗,頁面沒有自動跳轉:
在一開始,使用上面這種方法進行配置,但是配置完之後,發現授權失敗之後,頁面並沒有自動跳轉,而是直接丟擲異常。
(2)原因:通過檢視Shiro的原始碼:
private void applyUnauthorizedUrlIfNecessary(Filter filter) {
String unauthorizedUrl = getUnauthorizedUrl();
if (StringUtils.hasText(unauthorizedUrl) && (filter instanceof AuthorizationFilter)) {
AuthorizationFilter authzFilter = (AuthorizationFilter) filter;
//only apply the unauthorizedUrl if they haven't explicitly configured one already:
String existingUnauthorizedUrl = authzFilter.getUnauthorizedUrl();
if (existingUnauthorizedUrl == null) {
authzFilter.setUnauthorizedUrl(unauthorizedUrl);
}
}
}
發現是因為shiro原始碼中判斷了filter是否為AuthorizationFilter,只有perms,roles,ssl,rest,port才是屬於AuthorizationFilter,而anon,authcBasic,auchc,user是AuthenticationFilter,所以unauthorizedUrl設定後不起作用。
(3)解決方法:異常全路徑做key,錯誤頁面做value。
<!-- 解決shiro配置的沒有許可權訪問時,unauthorizedUrl不跳轉到指定路徑的問題 -->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="org.apache.shiro.authz.UnauthorizedException">/refuse</prop>
</props>
</property>
</bean>
10、shiro常見的預設過濾器:
過濾器簡稱 |
對應的java類 |
anon |
org.apache.shiro.web.filter.authc.AnonymousFilter |
authc |
org.apache.shiro.web.filter.authc.FormAuthenticationFilter |
authcBasic |
org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter |
perms |
org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter |
port |
org.apache.shiro.web.filter.authz.PortFilter |
rest |
org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter |
roles |
org.apache.shiro.web.filter.authz.RolesAuthorizationFilter |
ssl |
org.apache.shiro.web.filter.authz.SslFilter |
user |
org.apache.shiro.web.filter.authc.UserFilter |
logout |
org.apache.shiro.web.filter.authc.LogoutFilter |
anon:例子/admins/**=anon 沒有引數,表示可以匿名使用。
authc:例如/admins/user/**=authc表示需要認證(登入)才能使用,FormAuthenticationFilter是表單認證,沒有引數
perms:例子/admins/user/**=perms[user:add:*],引數可以寫多個,多個時必須加上引號,並且引數之間用逗號分割,例如/admins/user/**=perms["user:add:*,user:modify:*"],當有多個引數時必須每個引數都通過才通過,想當於isPermitedAll()方法。
user:例如/admins/user/**=user沒有參數列示必須存在使用者, 身份認證通過或通過記住我認證通過的可以訪問,當登入操作時不做檢查。
11、授權:重寫自定義realm的doGetAuthorizationInfo方法,從資料庫查詢許可權資訊。
通常使用註解式授權方法和Jsp標籤授權方法。
//自定義的Realm
public class CustomRealm extends AuthorizingRealm{
//注入service
@Autowired
private SysService sysService;
// 設定realm的名稱
@Override
public void setName(String name) {
super.setName("customRealm");
}
//授權的方法
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//從principals獲取主身份資訊
//將getPrimaryPrincipal方法返回值轉為真實身份型別(在上邊doGetAuthenticationInfo認證通過填充到SimpleAuthenticationInfo中的身份型別)
ActiveUser activeUser=(ActiveUser)principals.getPrimaryPrincipal();
//根據身份資訊獲取許可權資訊:從資料庫獲取到許可權資料
List<SysPermission> permissionList = null;
try{
permissionList = sysService.findPermissionListByUserId(activeUser.getUserid());
}catch (Exception e) {
e.printStackTrace();
}
//單獨定一個集合物件
List<String> permissions = new ArrayList<String>();
if(permissionList!=null){
for(SysPermission sysPermission:permissionList){
//將資料庫中許可權標籤符放入集合
permissions.add(sysPermission.getPercode());
}
}
//查到許可權資料,返回授權資訊(要包括上邊的permissions)
SimpleAuthorizationInfo simpleAuthorizationInfo =new SimpleAuthorizationInfo();
simpleAuthorizationInfo.addStringPermissions(permissions);//這裡新增使用者有的許可權列表
simpleAuthorizationInfo.addRole("manager");//這裡新增使用者所擁有的角色
return simpleAuthorizationInfo;
}
}
12、授權:開啟controller類的aop支援:
對系統中類的方法給使用者授權,建議在controller層進行方法授權。
在springmvc.xml檔案中配置:
<!-- 使用註解驅動:自動配置處理器對映器與處理器介面卡 -->
<mvc:annotation-driven />
<!-- 開啟aop,對類代理 -->
<aop:config proxy-target-class="true"></aop:config>
<!-- 開啟shiro註解支援 -->
<bean
class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager" />
</bean>
13、授權:在controller方法中新增註解:
//許可權測試方法
@RequestMapping("/permissionTest")
@RequiresPermissions("item:update")//執行此方法需要"item:update"許可權
public String permissionTest(Model model){
return "/permissionTest";
}
至此,專案啟動後,當訪問“/permissionTest”時,如果使用者沒有“item:update”許可權,將會自動跳轉到refuse.jsp頁面;如果如果使用者擁有該許可權,就會跳轉到permissionTest.jsp頁面。
14、授權:Jsp標籤授權:(permissionTest.jsp)
(1)jsp頁面新增:
<%@ tagliburi="http://shiro.apache.org/tags" prefix="shiro" %>
(2)jsp標籤授權:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ tagliburi="http://shiro.apache.org/tags" prefix="shiro" %>
<html>
<body>
<shiro:authenticated>
這是登陸之後 才可以看到的內容
</shiro:authenticated><br/>
<shiro:notAuthenticated>
這是未登陸的時候 才可以看到的內容
</shiro:notAuthenticated><br/>
<shiro:hasRole name="manager">
這是擁有商品管理員角色才可以看到的內容
</shiro:hasRole><br/>
<shiro:lacksRole name="manager">
這是沒有商品管理員角色才可以看到的內容
</shiro:lacksRole><br/>
<shiro:hasPermission name="item:update">
這是擁有item:update資源許可權才可以看到的內容
</shiro:hasPermission><br/>
<shiro:lacksPermission name="item:update">
這是沒有item:update資源許可權才可以看到的內容
</shiro:lacksPermission><br/>
<shiro:lacksPermission name="user:query">
這是沒有user:query資源許可權才可以看到的內容
</shiro:lacksPermission><br/>
</body>
</html>
至此,當使用者成功進入到授權成功頁面時,只能看到符合自己所屬許可權和角色的內容。
(3)常見的shiro授權標籤:
標籤名稱 |
標籤條件(均是顯示標籤內容) |
<shiro:authenticated> |
登入之後 |
<shiro:notAuthenticated> |
不在登入狀態時 |
<shiro:guest> |
使用者在沒有RememberMe時 |
<shiro:user> |
使用者在RememberMe時 |
<shiro:hasAnyRoles name="abc,123" > |
在有abc或者123角色時 |
<shiro:hasRole name="abc"> |
擁有角色abc |
<shiro:lacksRole name="abc"> |
沒有角色abc |
<shiro:hasPermission name="abc"> |
擁有許可權資源abc |
<shiro:lacksPermission name="abc"> |
沒有abc許可權資源 |
<shiro:principal> |
顯示使用者身份名稱 |
<shiro:principal property="username"/> |
顯示使用者身份中的屬性值 |
15、授權測試:
(1)當呼叫controller的一個方法,由於該 方法加了@RequiresPermissions("item:query") ,shiro呼叫realm獲取資料庫中的許可權資訊,看"item:query"是否在許可權資料中存在,如果不存在就拒絕訪問,如果存在就授權通過。
(2)當展示一個jsp頁面時,頁面中如果遇到<shiro:hasPermission name="item:update">,shiro呼叫realm獲取資料庫中的許可權資訊,看item:update是否在許可權資料中存在,如果不存在就拒絕訪問,如果存在就授權通過。
16、完成:
至此,使用Shiro整合Spring+SpringMVC+Mybatis進行登陸認證和授權就完成了,但是在這裡,授權的時候存在一個問題,只要遇到註解或jsp標籤的授權,都會呼叫realm方法查詢資料庫,因此需要使用快取解決此問題。
最後,推薦幾篇有關Shiro的文章:
https://www.cnblogs.com/learnhow/p/5694876.html
https://www.sojson.com/shiro#so358852059
https://www.xttblog.com/?p=1272
相關文章
- 中介軟體---登陸認證授權---Shiro
- 細說API - 認證、授權和憑證API
- SpringSecurity認證和授權流程詳解SpringGse
- Ceph配置與認證授權
- SpringBoot--- 使用SpringSecurity進行授權認證Spring BootGse
- MongoDB資料庫授權認證的實現JRMYMongoDB資料庫
- OAuth 2.0 授權認證詳解OAuth
- Spring boot 入門(四):整合 Shiro 實現登陸認證和許可權管理Spring Boot
- 授權(Authorization)和認證(Authentication)
- shiro授權和認證(四)
- 認證授權
- 認證授權:IdentityServer4 - 資料持久化IDEServer持久化
- 認證授權:IdentityServer4 - 單點登入IDEServer
- 認證授權方案之JwtBearer認證JWT
- 認證授權方案之授權初識
- Spring Boot 整合 Shiro實現認證及授權管理Spring Boot
- 認證授權方案之授權揭祕 (上篇)
- 【認證與授權】Spring Security的授權流程Spring
- 登陸認證框架:SpringSecurity框架SpringGse
- 認證授權:一鍵登入的背後過程
- Spring Security OAuth2.0認證授權四:分散式系統認證授權SpringOAuth分散式
- 認證授權:IdentityServer4IDEServer
- 認證授權:學習OIDC
- Ocelot(四)- 認證與授權
- OAuth 2.0 授權碼認證OAuth
- 微信授權註冊或微信登陸 微信授權登陸 基於若依vue 實現Vue
- mysql建立使用者並且對資料庫授權MySql資料庫
- Spring Security OAuth2.0認證授權六:前後端分離下的登入授權SpringOAuth後端
- EMQX Cloud 更新:外部認證授權MQCloud
- 認證授權問題概覽
- 認證授權:IdentityServer4 - 各種授權模式應用IDEServer模式
- ASP.NET Core之身份認證和授權JWTASP.NETJWT
- EMQX Cloud 更新:新增 Redis 和 JWT 外部認證授權MQCloudRedisJWT
- ASP.NET Core 6.0 新增 JWT 認證和授權ASP.NETJWT
- 【認證與授權】Spring Security系列之認證流程解析Spring
- 【認證與授權】2、基於session的認證方式Session
- java 微信授權登入配置Java
- CAS配置資料庫,實現資料庫使用者認證資料庫