shiro權限控制


https://www.cnblogs.com/tohxyblog/p/8426230.html  分布式SHIRO

第一部分 什么是Apache Shiro

 
 

1、什么是 apache shiro :

 

Apache Shiro是一個功能強大且易於使用的Java安全框架,提供了認證,授權,加密,和會話管理

如同 spring security 一樣都是是一個權限安全框架,但是與Spring Security相比,在於他使用了和比較簡潔易懂的認證和授權方式。

 

 

 

2、Apache Shiro 的三大核心組件:

 

1、Subject :當前用戶的操作

2、SecurityManager:用於管理所有的Subject

3、Realms:用於進行權限信息的驗證

 

Subject:即當前用戶,在權限管理的應用程序里往往需要知道誰能夠操作什么,誰擁有操作該程序的權利,shiro中則需要通過Subject來提供基礎的當前用戶信息,Subject 不僅僅代表某個用戶,也可以是第三方進程、后台帳戶(Daemon Account)或其他類似事物。

SecurityManager:即所有Subject的管理者,這是Shiro框架的核心組件,可以把他看做是一個Shiro框架的全局管理組件,用於調度各種Shiro框架的服務。

Realms:Realms則是用戶的信息認證器和用戶的權限人證器,我們需要自己來實現Realms來自定義的管理我們自己系統內部的權限規則。

 

 

 

3、Authentication 和 Authorization

 

在shiro的用戶權限認證過程中其通過兩個方法來實現:

1、Authentication:是驗證用戶身份的過程。

2、Authorization:是授權訪問控制,用於對用戶進行的操作進行人證授權,證明該用戶是否允許進行當前操作,如訪問某個鏈接,某個資源文件等。

 

 

 

4、其他組件:

 

除了以上幾個組件外,Shiro還有幾個其他組件:

1、SessionManager :Shiro為任何應用提供了一個會話編程范式。

2、CacheManager :對Shiro的其他組件提供緩存支持。 

 

 

 

 

第二部分 Apache Shiro 整合Spring的Web程序構建

 

 

1、准備工具:

 

持久層框架:Hibernate4  這邊我使用了hibernate來對數據持久層進行操作

控制顯示層框架:SpringMVC 這邊我使用了SpringMVC實際開發中也可以是其他框架

數據庫MySQL

准備好所需要的jar放到項目中。

 

 

2、創建數據庫:

 
 

首先需要四張表,分別為 user(用戶)、role(角色)、permission(權限)、userRole(用戶角色關系表)

這邊分別創建四張表的實體類,通過Hiberante的hibernate.hbm2ddl.auto屬性的update 來自動生成數據表結構。

 

/*** 
 * 用戶表 
 *  
 * @author Swinglife 
 *  
 */  
@Table(name = "t_user")  
@Entity  
public class User {  
  
    @Id  
    @GeneratedValue(strategy = GenerationType.AUTO)  
    Integer id;  
    /** 用戶名 **/  
    String username;  
    /** 密碼 **/  
    String password;  
    /** 是否刪除 **/  
    Integer isDelete;  
    /** 創建時間 **/  
    Date createDate;  
    //多對多用戶權限表  
    @OneToMany(mappedBy = "user",cascade=CascadeType.ALL)  
    List<UserRole> userRoles;  
  
省略get set….  
  
}  
/**** 
 * 角色表 
 *  
 * @author Swinglife 
 *  
 */  
@Entity  
@Table(name = "t_role")  
public class Role {  
    @Id  
    @GeneratedValue(strategy = GenerationType.AUTO)  
    Integer id;  
    /**角色名**/  
    String name;  
    /**角色說明**/  
    String description;  
  
  
}  
/**** 
 * 權限表 
 *  
 * @author Swinglife 
 *  
 */  
@Entity  
@Table(name = "t_permission")  
public class Permission {  
  
    @Id  
    @GeneratedValue(strategy = GenerationType.AUTO)  
    Integer id;  
    /**token**/  
    String token;  
    /**資源url**/  
    String url;  
    /**權限說明**/  
    String description;  
    /**所屬角色編號**/  
    Integer roleId;  
  
}  
/*** 
 * 用戶角色表 
 *  
 * @author Swinglife 
 *  
 */  
@Entity  
@Table(name = "t_user_role")  
public class UserRole {  
  
    @Id  
    @GeneratedValue(strategy = GenerationType.AUTO)  
    Integer id;  
  
    @ManyToOne(cascade = CascadeType.ALL)  
    @JoinColumn(name = "userId", unique = true)  
    User user;  
    @ManyToOne  
    @JoinColumn(name = "roleId", unique = true)  
    Role role;  
  
}  

3、編寫操作用戶業務的Service:

 

@Service  
public class AccountService {  
  
    /**** 
     * 通過用戶名獲取用戶對象 
     *  
     * @param username 
     * @return 
     */  
    public User getUserByUserName(String username) {  
        User user = (User) dao.findObjectByHQL("FROM User WHERE username = ?", new Object[] { username });  
        return user;  
    }  
  
    /*** 
     * 通過用戶名獲取權限資源 
     *  
     * @param username 
     * @return 
     */  
    public List<String> getPermissionsByUserName(String username) {  
        System.out.println("調用");  
        User user = getUserByUserName(username);  
        if (user == null) {  
            return null;  
        }  
        List<String> list = new ArrayList<String>();  
        // System.out.println(user.getUserRoles().get(0).get);  
        for (UserRole userRole : user.getUserRoles()) {  
            Role role = userRole.getRole();  
            List<Permission> permissions = dao.findAllByHQL("FROM Permission WHERE roleId = ?", new Object[] { role.getId() });  
            for (Permission p : permissions) {  
                list.add(p.getUrl());  
            }  
        }  
        return list;  
    }  
  
    // 公共的數據庫訪問接口  
    // 這里省略BaseDao dao的編寫  
    @Autowired  
    private BaseDao dao;  
} 

4、編寫shiro組件自定義Realm:

 

package org.swinglife.shiro;  
  
import java.util.List;  
  
import org.apache.shiro.authc.AuthenticationException;  
import org.apache.shiro.authc.AuthenticationInfo;  
import org.apache.shiro.authc.AuthenticationToken;  
import org.apache.shiro.authc.SimpleAuthenticationInfo;  
import org.apache.shiro.authc.UsernamePasswordToken;  
import org.apache.shiro.authz.AuthorizationInfo;  
import org.apache.shiro.authz.SimpleAuthorizationInfo;  
import org.apache.shiro.realm.AuthorizingRealm;  
import org.apache.shiro.subject.PrincipalCollection;  
import org.swinglife.model.User;  
import org.swinglife.service.AccountService;  
  
/**** 
 * 自定義Realm 
 *  
 * @author Swinglife 
 *  
 */  
public class MyShiroRealm extends AuthorizingRealm {  
  
    /*** 
     * 獲取授權信息 
     */  
    @Override  
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection pc) {  
        //根據自己系統規則的需要編寫獲取授權信息,這里為了快速入門只獲取了用戶對應角色的資源url信息  
        String username = (String) pc.fromRealm(getName()).iterator().next();  
        if (username != null) {  
            List<String> pers = accountService.getPermissionsByUserName(username);  
            if (pers != null && !pers.isEmpty()) {  
                SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();  
                for (String each : pers) {  
                    //將權限資源添加到用戶信息中  
                    info.addStringPermission(each);  
                }  
                return info;  
            }  
        }  
        return null;  
    }  
    /*** 
     * 獲取認證信息 
     */  
    @Override  
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken at) throws AuthenticationException {  
        UsernamePasswordToken token = (UsernamePasswordToken) at;  
        // 通過表單接收的用戶名  
        String username = token.getUsername();  
        if (username != null && !"".equals(username)) {  
            User user = accountService.getUserByUserName(username);  
            if (user != null) {  
                return new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), getName());  
            }  
        }  
  
        return null;  
    }  
      
    /**用戶的業務類**/  
    private AccountService accountService;  
      
    public AccountService getAccountService() {  
        return accountService;  
    }  
  
    public void setAccountService(AccountService accountService) {  
        this.accountService = accountService;  
    }  
  
}  

上述類繼承了Shiro的AuthorizingRealm類 實現了AuthorizationInfo和AuthenticationInfo兩個方法,用於獲取用戶權限和認證用戶登錄信息

 

 

5、編寫LoginController:

 

package org.swinglife.controller;  
  
import org.apache.shiro.SecurityUtils;  
import org.apache.shiro.authc.UsernamePasswordToken;  
import org.apache.shiro.subject.Subject;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.stereotype.Controller;  
import org.springframework.web.bind.annotation.RequestMapping;  
import org.springframework.web.bind.annotation.RequestMethod;  
import org.springframework.web.portlet.ModelAndView;  
import org.swinglife.model.User;  
import org.swinglife.service.AccountService;  
  
/**** 
 * 用戶登錄Controller 
 *  
 * @author Swinglife 
 *  
 */  
@Controller  
public class LoginController {  
  
    /*** 
     * 跳轉到登錄頁面 
     *  
     * @return 
     */  
    @RequestMapping(value = "toLogin")  
    public String toLogin() {  
        // 跳轉到/page/login.jsp頁面  
        return "login";  
    }  
  
    /*** 
     * 實現用戶登錄 
     *  
     * @param username 
     * @param password 
     * @return 
     */  
    @RequestMapping(value = "login")  
    public ModelAndView Login(String username, String password) {  
        ModelAndView mav = new ModelAndView();  
        User user = accountService.getUserByUserName(username);  
        if (user == null) {  
            mav.setView("toLogin");  
            mav.addObject("msg", "用戶不存在");  
            return mav;  
        }  
        if (!user.getPassword().equals(password)) {  
            mav.setView("toLogin");  
            mav.addObject("msg", "賬號密碼錯誤");  
            return mav;  
        }  
        SecurityUtils.getSecurityManager().logout(SecurityUtils.getSubject());  
        // 登錄后存放進shiro token  
        UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(), user.getPassword());  
        Subject subject = SecurityUtils.getSubject();  
        subject.login(token);  
        // 登錄成功后會跳轉到successUrl配置的鏈接,不用管下面返回的鏈接。  
        mav.setView("redirect:/home");  
        return mav;  
    }  
  
    // 處理用戶業務類  
    @Autowired  
    private AccountService accountService;  
}  

6、編寫信息認證成功后的跳轉頁面:

package org.swinglife.controller;  
  
import org.springframework.stereotype.Controller;  
import org.springframework.web.bind.annotation.RequestMapping;  
  
@Controller  
public class IndexController {  
  
    @RequestMapping("home")  
    public String index() {  
        System.out.println("登錄成功");  
        return "home";  
    }  
} 

7、Shiro的配置文件.xml

 

<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">  
        <property name="securityManager" ref="securityManager" />  
        <property name="loginUrl" value="/toLogin" />  
        <property name="successUrl" value="/home" />  
        <property name="unauthorizedUrl" value="/403" />  
           
        <property name="filterChainDefinitions">  
            <value>  
                /toLogin = authc <!-- authc 表示需要認證才能訪問的頁面 -->  
                /home = authc, perms[/home]  <!-- perms 表示需要該權限才能訪問的頁面 -->  
            </value>  
        </property>  
    </bean>  
  
  
  
  
    <bean id="myShiroRealm" class="org.swinglife.shiro.MyShiroRealm">  
        <property name="accountService" ref="accountService" />  
    </bean>  
  
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">  
        <property name="realm" ref="myShiroRealm"></property>  
    </bean>  
  
    <bean id="accountService" class="org.swinglife.service.AccountService"></bean>  
  
    <!-- <bean id="shiroCacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">   
        <property name="cacheManager" ref="cacheManager" /> </bean> -->  

loginUrl 用於配置登陸頁

successUrl 用於配置登錄成功后返回的頁面,不過該參數只會在當登錄頁面中並沒有任何返回頁面時才會生效,否則會跳轉到登錄Controller中的指定頁面。

unauthorizedUrl 用於配置沒有權限訪問頁面時跳轉的頁面

 

filterChainDefinitions:apache shiro通過filterChainDefinitions參數來分配鏈接的過濾,資源過濾有常用的以下幾個參數:

1、authc 表示需要認證的鏈接

2、perms[/url] 表示該鏈接需要擁有對應的資源/權限才能訪問

3、roles[admin] 表示需要對應的角色才能訪問

4、perms[admin:url] 表示需要對應角色的資源才能訪問

 

8、登陸頁login.jsp

 

<body>  
  
<h1>user login</h1>  
<form action="login" method="post">  
username:<input type="text" name="username"><p>  
password:<input type="password" name="password">  
<p>  
${msg }  
<input type="submit" value="submit">  
</form>  
</body> 

9、運行程序

 

在數據庫中添加一條用戶、角色、以及權限數據,並且在關聯表中添加一條關聯數據:

 

 

在瀏覽器中訪問: home頁面 就會跳轉到登錄頁面:  

 

 

最后輸入 賬號密碼 就會跳轉到登錄成功頁面。

關於如何實現同一時刻只有一個相同賬戶登錄的問題:

  DefaultWebSecurityManager securityManager = (DefaultWebSecurityManager) SecurityUtils.getSecurityManager();
            DefaultWebSessionManager sessionManager = (DefaultWebSessionManager)securityManager.getSessionManager();
            Collection<Session> sessions = sessionManager.getSessionDAO().getActiveSessions();

通過以上代碼得到所有的session,循環遍歷session:

    for(Session session:sessions){

        System.out.println("登錄ip:"+session.getHost());

        System.out.println("登錄用戶"+session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY));

        System.out.println("最后操作日期:"+session.getLastAccessTime());

        }

然后自行判斷是否存在某一個賬戶,如果不存在可以登錄,如果存在return null

 

如果只進入了doGetAuthenticationInfo方法,沒有進入doGetAuthorizationInfo方法,並且配置無異常,無遺漏,可能是因為在過濾器中權限配置沒有配置到你即將進入的頁面,如果沒有配置perms[],則shiro不會自行進入doGetAuthorizationInfo方法進行權限判斷。

 

shiro jar:http://download.csdn.net/detail/swingpyzf/8766673

項目源碼:github:https://github.com/swinglife/shiro_ex

本文轉自:http://blog.csdn.net/swingpyzf/article/details/46342023/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM