Shiro實現(一): SSM整合筆記實現登入,授權功能
開篇
需要明白兩個點:
- 通過Subject.login() 登入成功後,使用者資訊就會儲存在安全管理器上,也就是 SecurityManager。就可以在程式任何地方獲取到該使用者物件。
- 在重寫攔截器兩個方法是重點,在登入的時候就需要把授權資訊也存到安全管理器上,所以登入成功後,所有判斷許可權都不需要在業務邏輯上做判斷,shiro框架已經幫你攔截並判斷好。
1. jar包
shiro有很多種型別的包,用途有web的,非web的等,*-all 代表所有都在裡面
shiro-all.jar
2.配置
其實shiro許可權控制就是通過攔截器來進行判斷使用者許可權的,因此shiro攔截器的配置跟springMVC的攔截器配置是類似的。
第一步
盡然是通過aop來使用shiro,那就需要在web.xml裡新增一個shiro的攔截器。
org.springframework.web.context.ContextLoaderListener > ```ps:這個shiro攔截器是如何載入的呢? 因為這個專案shiro與spring整合了,所有執行專案的時候,spring監聽器會去尋找並載入shiro攔截器
<!-- Spring監聽器 -->
shiro攔截器配置如下:
<!-- 配置由Spring提供的過濾器,用於整合shiro框架 -->
<!-- 在專案啟動的過程中,當前過濾器會從Spring工廠中提取同名物件 -->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>
org.springframework.web.filter.DelegatingFilterProxy
</filter-class>
</filter>
<filter-mapping >
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
第二步
類似springmvc一樣,需要寫一個配置檔案來配置攔截器。
在shiro配置檔案中,有兩種型別。
- ini配置檔案 (很多部落格教程上都是使用這種配置方式。)
- xml配置檔案 (這次專案使用這種配置方式)
首先建立shiro-context.xml
ps: 很簡單,就相當於spring的配置檔案一樣,因為shiro是跟spring很好結合的。
下面會慢慢解釋這個配置檔案
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager" /> <!--載入管理器-->
<property name="loginUrl" value="/user/login" /> <!--沒有登入的時候,跳轉到這個頁面-->
<property name="unauthorizedUrl" value="/user/nopermission" /> <!--當沒有許可權的時候,跳轉到這個url-->
<property name="filterChainDefinitions">
<value>
/user/login = anon <!--可以不需要登入-->
/user/readName = authc, perms[/readName] <!-- perms 表示需要該許可權才能訪問的頁面 -->
/user/readData = authc, perms[/readData]
/user/* = authc <!-- authc 表示需要認證才能訪問的頁面 -->
</value>
</property>
</bean>
<!-- 自定義Realm -->
<bean id="myShiroRealm" class="com.Shiro.MyShiroReaml">
<!-- businessManager 用來實現使用者名稱密碼的查詢 -->
<property name="shiroService" ref="accountService" />
</bean>
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!-- 注入realm -->
<property name="realm" ref="myShiroRealm"/>
</bean>
<!--宣告一個Service 注入到自定義Realm-->
<bean id="accountService" class="com.Service.Impl.ShiroServiceImpl"/>
<!-- <bean id="shiroCacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<property name="cacheManager" ref="cacheManager" /> </bean> -->
</beans>
- 攔截器需要載入一個安全管理器,SecurityManager 是整個shiro框架的核心
<property name="securityManager" ref="securityManager" /> <!--載入管理器-->
- 攔截url,這裡就是配置攔截url。anon.authc.這些名詞其實就是shiro已經寫好的攔截器,只需要呼叫可以了。在如果許可權不夠,則會跳轉到指定的url
<property name="filterChainDefinitions">
<value>
/user/login = anon <!--可以不需要登入-->
/user/readName = authc, perms[/readName] <!-- perms 表示需要該許可權才能訪問的頁面 -->
/user/readData = authc, perms[/readData]
/user/* = authc <!-- authc 表示需要認證才能訪問的頁面 -->
</value>
</property>
過濾器列表:
第三步 攔截後,對使用者進行驗證
當你攔截器設定好了,可以成功攔截使用者的操作,然後我們需要對使用者進行許可權驗證。所以我們需要繼承shiro的AuthorizingRealm攔截器,重寫兩個方法。
- 重寫doGetAuthenticationInfo方法是:登入驗證,當需要登入的時候,就會呼叫該方法進行驗證。
- 重寫doGetAuthorizationInfo方法:這個是授權驗證,與上面的過濾器相結合。
思路如下:
- 登入驗證: 根據賬號從資料庫獲取賬號密碼進行比較,如果一致則登入成功,就會儲存到,否則登入失敗
- 授權驗證:在登入成功後,根據使用者id獲取到該使用者的許可權,並把許可權儲存在安全管理器之中,當用戶訪問的時候,會從管理器中判斷該使用者是否有許可權去訪問該url。
程式碼如下:
public class MyShiroReaml extends AuthorizingRealm {
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection pc) {
/**
*
* 流程
* 1.根據使用者user->2.獲取角色id->3.根據角色id獲取許可權permission
*/
//方法一:獲得user物件
User user=(User)pc.getPrimaryPrincipal();
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//獲取permission
if(user!=null) {
List<Permission> permissionsByUser = shiroService.getPermissionsByUser(user);
if (permissionsByUser.size()!=0) {
for (Permission p: permissionsByUser) {
info.addStringPermission(p.getUrl());
}
return info;
}
}
//方法二: 從subject管理器裡獲取user
// Subject subject = SecurityUtils.getSubject();
// User _user = (User) subject.getPrincipal();
// System.out.println("subject"+_user.getUsername());
return null;
}
// 認證方法
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("進來驗證了");
//驗證賬號密碼
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
System.out.println("1:"+token.getUsername());
User user = shiroService.getUserByUserName(token.getUsername());
System.out.println("2");
if(user==null){
return null;
}
//最後的比對需要交給安全管理器
//三個引數進行初步的簡單認證資訊物件的包裝
AuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(), this.getClass().getSimpleName());
return info;
}
private ShiroService shiroService;
public ShiroService getShiroService() {
return shiroService;
}
public void setShiroService(ShiroService shiroService) {
this.shiroService = shiroService;
}
}
第四步
shrio的基本配置已經完成了,接下來是基礎程式碼塊了, 也許還有很多疑問,沒關係。下載程式碼下來看看,就明白了
Controller層
1.登入程式碼塊
@RequestMapping(value = "/login")
public String Login(String username, String password, HttpSession session, Model model){
if(username==null){
model.addAttribute("message", "賬號不為空");
return "login";
}
//主體,當前狀態為沒有認證的狀態“未認證”
Subject subject = SecurityUtils.getSubject();
// 登入後存放進shiro token
UsernamePasswordToken token=new UsernamePasswordToken(username,password);
User user;
//登入方法(認證是否通過)
//使用subject呼叫securityManager,安全管理器呼叫Realm
try {
//利用異常操作
//需要開始呼叫到Realm中
System.out.println("========================================");
System.out.println("1、進入認證方法");
subject.login(token);
user = (User)subject.getPrincipal();
session.setAttribute("user",subject);
model.addAttribute("message", "登入完成");
System.out.println("登入完成");
} catch (UnknownAccountException e) {
model.addAttribute("message", "賬號密碼不正確");
return "index";
}
return "test";
}
- 許可權程式碼塊
shiro已經幫我們驗證了,所以我們只需要寫基本業務邏輯就可以,不需要再寫許可權驗證程式碼了
@RequestMapping("/readName")
public String readName(HttpSession session){
return "name";
}
@RequestMapping("/readData")
public String readData(){
return "data";
}
@RequestMapping("/nopermission")
public String noPermission(){
return "error";
}
Service 層
public class ShiroServiceImpl implements ShiroService {
@Autowired
private ShiroDao shiroDao;
public User getUserByUserName(String username) {
//根據賬號獲取賬號密碼
User userByUserName = shiroDao.getUserByUserName(username);
return userByUserName;
}
public List<Permission> getPermissionsByUser(User user) {
//獲取到使用者角色userRole
List<Integer> roleId = shiroDao.getUserRoleByUserId(user.getId());
List<Permission> perArrary = new ArrayList<>();
if (roleId!=null&&roleId.size()!=0) {
//根據roleid獲取peimission
for (Integer i : roleId) {
perArrary.addAll(shiroDao.getPermissionsByRoleId(i));
}
}
System.out.println(perArrary);
return perArrary;
}
}
POJO層
public class Permission {
private int id;
private String token;
/**資源url**/
private String url;
/**許可權說明**/
private String description;
/**所屬角色編號**/
private int roleId;
}
public class Role {
private int id;
/**角色**/
private String role;
/**說明**/
private String description;
}
public class User {
private int id;
private String account;
private String password;
}
第5步 資料庫表的建立
資料表已經附加到程式碼上傳到github
資料庫中新增一條使用者、角色、以及許可權資料,並且在關聯表中新增一條關聯資料
- Permission 許可權表
- 角色管理表
- 使用者表
- 使用者角色對照表
總結
- Shiro框架 與spring結合,對原有的程式碼塊改變很少。完美得實現了AOP思想
- Shiro的功能很強大,該專案實現了登入已經授權的基本功能,接下來還會有單點登入,快取機制,密碼加密等功能的演示。
- 程式碼已放到github上,可以clone下來細看。
- 程式碼