原文地址:https://412887952-qq-com.iteye.com/blog/2299777 前端
集成shiro大概分這麼一個步驟:java
(a) pom.xml中添加Shiro依賴;
(b) 注入Shiro Factory和SecurityManager。
(c) 身份認證
(d) 權限控制
(a) pom.xml中添加Shiro依賴;mysql
要使用Shiro進行權限控制,那麼很明顯的就須要添加對Shiro的依賴包,在pom.xml中加入以下配置:web
- <!-- shiro spring. -->
- <dependency>
- <groupId>org.apache.shiro</groupId>
- <artifactId>shiro-spring</artifactId>
- <version>1.2.2</version>
- </dependency>
(b) 注入Shiro Factory和SecurityManager:
算法
在Spring中注入類都是使用配置文件的方式,在Spring Boot中是使用註解的方式,那麼應該如何進行實現呢?spring
咱們在上一節說過,Shiro幾個核心的類,第一就是ShiroFilterFactory,第二就是SecurityManager,那麼最簡單的配置就是注入這兩個類就ok了,那麼如何注入呢?看以下代碼:sql
新建類 com.kfit.config.shiro.ShiroConfiguration:數據庫
- package com.kfit.config.shiro;
-
- import java.util.LinkedHashMap;
- import java.util.Map;
-
- import org.apache.shiro.mgt.SecurityManager;
- import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
- import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
-
- /**
- * Shiro 配置
- *
- Apache Shiro 核心經過 Filter 來實現,就好像SpringMvc 經過DispachServlet 來主控制同樣。
- 既然是使用 Filter 通常也就能猜到,是經過URL規則來進行過濾和權限校驗,因此咱們須要定義一系列關於URL的規則和訪問權限。
- *
- * @author Angel(QQ:412887952)
- * @version v.0.1
- */
- @Configuration
- public class ShiroConfiguration {
-
-
- /**
- * ShiroFilterFactoryBean 處理攔截資源文件問題。
- * 注意:單獨一個ShiroFilterFactoryBean配置是或報錯的,覺得在
- * 初始化ShiroFilterFactoryBean的時候須要注入:SecurityManager
- *
- Filter Chain定義說明
- 一、一個URL能夠配置多個Filter,使用逗號分隔
- 二、當設置多個過濾器時,所有驗證經過,才視爲經過
- 三、部分過濾器可指定參數,如perms,roles
- *
- */
- @Bean
- public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager){
- System.out.println("ShiroConfiguration.shirFilter()");
- ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
-
- // 必須設置 SecurityManager
- shiroFilterFactoryBean.setSecurityManager(securityManager);
-
-
-
- //攔截器.
- Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>();
-
- //配置退出過濾器,其中的具體的退出代碼Shiro已經替咱們實現了
- filterChainDefinitionMap.put("/logout", "logout");
-
- //<!-- 過濾鏈定義,從上向下順序執行,通常將 /**放在最爲下邊 -->:這是一個坑呢,一不當心代碼就很差使了;
- //<!-- authc:全部url都必須認證經過才能夠訪問; anon:全部url都均可以匿名訪問-->
- filterChainDefinitionMap.put("/**", "authc");
-
- // 若是不設置默認會自動尋找Web工程根目錄下的"/login.jsp"頁面
- shiroFilterFactoryBean.setLoginUrl("/login");
- // 登陸成功後要跳轉的連接
- shiroFilterFactoryBean.setSuccessUrl("/index");
- //未受權界面;
- shiroFilterFactoryBean.setUnauthorizedUrl("/403");
-
- shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
- returnshiroFilterFactoryBean;
- }
-
-
- @Bean
- public SecurityManager securityManager(){
- DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
- return securityManager;
- }
-
-
-
- }
這裏說下:ShiroFilterFactory中已經由Shiro官方實現的過濾器:apache
Shiro內置的FilterChain編程
Filter Name |
Class |
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 |
anon:全部url都均可以匿名訪問;
authc: 須要認證才能進行訪問;
user:配置記住我或認證經過能夠訪問;
這幾個是咱們會用到的,在這裏說明下,其它的請自行查詢文檔進行學習。
這時候咱們運行程序,訪問/index頁面咱們會發現自動跳轉到了login頁面,固然這個時候輸入帳號和密碼是沒法進行訪問的。下面這纔是重點:任何身份認證,如何權限控制。
(c) 身份認證
在認證、受權內部實現機制中都有提到,最終處理都將交給Real進行處理。由於在Shiro中,最終是經過Realm來獲取應用程序中的用戶、角色及權限信息的。一般狀況下,在Realm中會直接從咱們的數據源中獲取Shiro須要的驗證信息。能夠說,Realm是專用於安全框架的DAO.
認證明現
Shiro的認證過程最終會交由Realm執行,這時會調用Realm的getAuthenticationInfo(token)方法。
該方法主要執行如下操做:
一、檢查提交的進行認證的令牌信息
二、根據令牌信息從數據源(一般爲數據庫)中獲取用戶信息
三、對用戶信息進行匹配驗證。
四、驗證經過將返回一個封裝了用戶信息的AuthenticationInfo實例。
五、驗證失敗則拋出AuthenticationException異常信息。
而在咱們的應用程序中要作的就是自定義一個Realm類,繼承AuthorizingRealm抽象類,重載doGetAuthenticationInfo (),重寫獲取用戶信息的方法。
既然須要進行身份權限控制,那麼少不了建立用戶實體類,權限實體類。
在權限管理系統中,有這麼幾個角色很重要,這個要是不清楚的話,那麼就很難理解,咱們爲何這麼編碼了。第一是用戶表:在用戶表中保存了用戶的基本信息,帳號、密碼、姓名,性別等;第二是:權限表(資源+控制權限):這個表中主要是保存了用戶的URL地址,權限信息;第三就是角色表:在這個表重要保存了系統存在的角色;第四就是關聯表:用戶-角色管理表(用戶在系統中都有什麼角色,好比admin,vip等),角色-權限關聯表(每一個角色都有什麼權限能夠進行操做)。依據這個理論,咱們進行來進行編碼,很明顯的咱們第一步就是要進行實體類的建立。在這裏咱們使用Mysql和JPA進行操做數據庫。
那麼咱們先在pom.xml中引入mysql和JPA的依賴:
- <!-- Spirng data JPA依賴; -->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-data-jpa</artifactId>
- </dependency>
-
- <!-- mysql驅動; -->
- <dependency>
- <groupId>mysql</groupId>
- <artifactId>mysql-connector-java</artifactId>
- </dependency>
配置src/main/resouces/application.properties配置數據庫和jpa(application.properties新建一個便可):
- ########################################################
- ###datasource
- ########################################################
- spring.datasource.url = jdbc:mysql://localhost:3306/test
- spring.datasource.username = root
- spring.datasource.password = root
- spring.datasource.driverClassName = com.mysql.jdbc.Driver
- spring.datasource.max-active=20
- spring.datasource.max-idle=8
- spring.datasource.min-idle=8
- spring.datasource.initial-size=10
-
-
-
- ########################################################
- ### Java Persistence Api
- ########################################################
- # Specify the DBMS
- spring.jpa.database = MYSQL
- # Show or not log for each sql query
- spring.jpa.show-sql = true
- # Hibernate ddl auto (create, create-drop, update)
- spring.jpa.hibernate.ddl-auto = update
- # Naming strategy
- #[org.hibernate.cfg.ImprovedNamingStrategy | org.hibernate.cfg.DefaultNamingStrategy]
- spring.jpa.hibernate.naming-strategy = org.hibernate.cfg.DefaultNamingStrategy
- # stripped before adding them to the entity manager)
- spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect
準備工做準備好以後,那麼就能夠編寫實體類了:
UserInfo.java、SysRole.java、SysPermission.java至於以前的關聯表咱們使用JPA進行自動生成。
用戶:com.kfit.core.bean.UserInfo :
- package com.kfit.core.bean;
-
- import java.io.Serializable;
- import java.util.List;
-
- import javax.persistence.Column;
- import javax.persistence.Entity;
- import javax.persistence.FetchType;
- import javax.persistence.GeneratedValue;
- import javax.persistence.Id;
- import javax.persistence.JoinColumn;
- import javax.persistence.JoinTable;
- import javax.persistence.ManyToMany;
-
- /**
- * 用戶信息.
- * @author Angel(QQ:412887952)
- * @version v.0.1
- */
- @Entity
- public class UserInfo implements Serializable{
- private static final long serialVersionUID = 1L;
- @Id@GeneratedValue
- privatelonguid;//用戶id;
-
- @Column(unique=true)
- private String username;//帳號.
-
- private String name;//名稱(暱稱或者真實姓名,不一樣系統不一樣定義)
-
- private String password; //密碼;
- private String salt;//加密密碼的鹽
-
- private byte state;//用戶狀態,0:建立未認證(好比沒有激活,沒有輸入驗證碼等等)--等待驗證的用戶 , 1:正常狀態,2:用戶被鎖定.
-
-
- @ManyToMany(fetch=FetchType.EAGER)//當即從數據庫中進行加載數據;
- @JoinTable(name = "SysUserRole", joinColumns = { @JoinColumn(name = "uid") }, inverseJoinColumns ={@JoinColumn(name = "roleId") })
- private List<SysRole> roleList;// 一個用戶具備多個角色
-
- public List<SysRole> getRoleList() {
- return roleList;
- }
-
- public void setRoleList(List<SysRole> roleList) {
- this.roleList = roleList;
- }
-
- public long getUid() {
- return uid;
- }
-
- public void setUid(longuid) {
- this.uid = uid;
- }
-
- public String getUsername() {
- return username;
- }
-
- public void setUsername(String username) {
- this.username = username;
- }
-
- public String getName() {
- return name;
- }
-
- publicvoid setName(String name) {
- this.name = name;
- }
-
- public String getPassword() {
- return password;
- }
-
- public void setPassword(String password) {
- this.password = password;
- }
-
- public String getSalt() {
- return salt;
- }
-
- public void setSalt(String salt) {
- this.salt = salt;
- }
-
- public byte getState() {
- return state;
- }
-
- public void setState(bytestate) {
- this.state = state;
- }
-
- /**
- * 密碼鹽.
- * @return
- */
- public String getCredentialsSalt(){
- return this.username+this.salt;
- }
-
- @Override
- public String toString() {
- return "UserInfo [uid=" + uid + ", username=" + username + ", name=" + name + ", password=" + password
- + ", salt=" + salt + ", state=" + state + "]";
- }
-
-
- }
在這裏salt主要是用來進行密碼加密的,固然也可使用明文進行編碼測試,實際開發中仍是建議密碼進行加密。
getCredentialsSalt()
這個方法從新對鹽從新進行了定義,用戶名+salt,這樣就更加不容易被破解了。
角色類 > com.kfit.core.bean.SysRole:
- package com.kfit.core.bean;
-
- import java.io.Serializable;
- import java.util.List;
-
- import javax.persistence.Entity;
- import javax.persistence.FetchType;
- import javax.persistence.GeneratedValue;
- import javax.persistence.Id;
- import javax.persistence.JoinColumn;
- import javax.persistence.JoinTable;
- import javax.persistence.ManyToMany;
-
-
- /**
- * 系統角色實體類;
- * @author Angel(QQ:412887952)
- * @version v.0.1
- */
- @Entity
- public class SysRole implements Serializable{
- private static final long serialVersionUID = 1L;
- @Id@GeneratedValue
- private Long id; // 編號
- private String role; // 角色標識程序中判斷使用,如"admin",這個是惟一的:
- private String description; // 角色描述,UI界面顯示使用
- private Boolean available = Boolean.FALSE; // 是否可用,若是不可用將不會添加給用戶
-
- //角色 -- 權限關係:多對多關係;
- @ManyToMany(fetch=FetchType.EAGER)
- @JoinTable(name="SysRolePermission",joinColumns={@JoinColumn(name="roleId")},inverseJoinColumns={@JoinColumn(name="permissionId")})
- private List<SysPermission> permissions;
-
- // 用戶 - 角色關係定義;
- @ManyToMany
- @JoinTable(name="SysUserRole",joinColumns={@JoinColumn(name="roleId")},inverseJoinColumns={@JoinColumn(name="uid")})
- private List<UserInfo> userInfos;// 一個角色對應多個用戶
-
- public List<UserInfo> getUserInfos() {
- return userInfos;
- }
- public void setUserInfos(List<UserInfo> userInfos) {
- this.userInfos = userInfos;
- }
- public Long getId() {
- return id;
- }
- publicvoid setId(Long id) {
- this.id = id;
- }
- public String getRole() {
- return role;
- }
- public void setRole(String role) {
- this.role = role;
- }
- public String getDescription() {
- return description;
- }
- public void setDescription(String description) {
- this.description = description;
- }
- public Boolean getAvailable() {
- return available;
- }
- public void setAvailable(Boolean available) {
- this.available = available;
- }
- public List<SysPermission> getPermissions() {
- return permissions;
- }
- public void setPermissions(List<SysPermission> permissions) {
- this.permissions = permissions;
- }
- @Override
- public String toString() {
- return "SysRole [id=" + id + ", role=" + role + ", description=" + description + ", available=" + available
- + ", permissions=" + permissions + "]";
- }
- }
權限 > com.kfit.core.bean.SysPermission :
- package com.kfit.core.bean;
-
- import java.io.Serializable;
- import java.util.List;
-
- import javax.persistence.Column;
- import javax.persistence.Entity;
- import javax.persistence.GeneratedValue;
- import javax.persistence.Id;
- import javax.persistence.JoinColumn;
- import javax.persistence.JoinTable;
- import javax.persistence.ManyToMany;
-
- /**
- * 權限實體類;
- * @author Angel(QQ:412887952)
- * @version v.0.1
- */
- @Entity
- public class SysPermission implements Serializable{
- private static final long serialVersionUID = 1L;
-
- @Id@GeneratedValue
- privatelongid;//主鍵.
- private String name;//名稱.
-
- @Column(columnDefinition="enum('menu','button')")
- private String resourceType;//資源類型,[menu|button]
- private String url;//資源路徑.
- private String permission; //權限字符串,menu例子:role:*,button例子:role:create,role:update,role:delete,role:view
- private Long parentId; //父編號
- private String parentIds; //父編號列表
- private Boolean available = Boolean.FALSE;
-
- @ManyToMany
- @JoinTable(name="SysRolePermission",joinColumns={@JoinColumn(name="permissionId")},inverseJoinColumns={@JoinColumn(name="roleId")})
- private List<SysRole> roles;
-
- public long getId() {
- return id;
- }
- public void setId(longid) {
- this.id = id;
- }
- public String getName() {
- return name;
- }
- publicvoid setName(String name) {
- this.name = name;
- }
- public String getResourceType() {
- return resourceType;
- }
- public void setResourceType(String resourceType) {
- this.resourceType = resourceType;
- }
- public String getUrl() {
- return url;
- }
- public void setUrl(String url) {
- this.url = url;
- }
- public String getPermission() {
- return permission;
- }
- public void setPermission(String permission) {
- this.permission = permission;
- }
- public Long getParentId() {
- return parentId;
- }
- public void setParentId(Long parentId) {
- this.parentId = parentId;
- }
- public String getParentIds() {
- return parentIds;
- }
- public void setParentIds(String parentIds) {
- this.parentIds = parentIds;
- }
- public Boolean getAvailable() {
- return available;
- }
- public void setAvailable(Boolean available) {
- this.available = available;
- }
- public List<SysRole> getRoles() {
- return roles;
- }
- publicvoid setRoles(List<SysRole> roles) {
- this.roles = roles;
- }
- @Override
- public String toString() {
- return "SysPermission [id=" + id + ", name=" + name + ", resourceType=" + resourceType + ", url=" + url
- + ", permission=" + permission + ", parentId=" + parentId + ", parentIds=" + parentIds + ", available="
- + available + ", roles=" + roles + "]";
- }
-
- }
ok,到這裏實體類就編碼完畢了,在這裏咱們看到的是3個實體類,UserInfo,SysRole,SysPermission,對應的是數據庫的五張表:
1表UserInfo、2表SysUserRole、3表SysRole、4表SysRolePermission、5表SysPermission
這時候運行程序,就會自動建表,而後咱們添加一些數據:
- INSERT INTO `SysPermission` VALUES ('1', '1', '用戶管理', '0', '0/', 'userInfo:view', 'menu', 'userInfo/userList');
- INSERT INTO `SysPermission` VALUES ('2', '1', '用戶添加', '1', '0/1', 'userInfo:add', 'button', 'userInfo/userAdd');
- INSERT INTO `SysPermission` VALUES ('3', '1', '用戶刪除', '1', '0/1', 'userInfo:del', 'button', 'userInfo/userDel');
- INSERT INTO `SysRole` VALUES ('1', '1', '管理員', 'admin');
- INSERT INTO `SysRole` VALUES ('2', '1', 'VIP會員', 'vip');
- INSERT INTO `SysRolePermission` VALUES ('1', '1');
- INSERT INTO `SysRolePermission` VALUES ('1', '2');
- INSERT INTO `SysUserRole` VALUES ('1', '1');
- INSERT INTO `SysUserRole` VALUES ('1', '2');
- INSERT INTO `UserInfo` VALUES ('1', '管理員', 'admin', 'd3c59d25033dbf980d29554025c23a75', '8d78869f470951332959580424d4bf4f', '0');
這時候數據都準備完畢了,那麼接下來就應該編寫Repository進行訪問數據了(在下載源碼中有shiro.sql文件,可自行導入使用便可)。
com.kfit.core.repository.UserInfoRepository :
- package com.kfit.core.repository;
-
- import org.springframework.data.repository.CrudRepository;
-
- import com.kfit.core.bean.UserInfo;
-
- /**
- * UserInfo持久化類;
- * @author Angel(QQ:412887952)
- * @version v.0.1
- */
- public interface UserInfoRepository extends CrudRepository<UserInfo,Long>{
-
- /**經過username查找用戶信息;*/
- public UserInfo findByUsername(String username);
-
- }
在這裏你會發現咱們只編寫了UserInfo的數據庫操做,那麼咱們怎麼獲取咱們的權限信息了,經過userInfo.getRoleList()能夠獲取到對應的角色信息,而後在經過對應的角色能夠獲取到權限信息,固然這些都是JPA幫咱們實現了,咱們也能夠進行直接獲取到權限信息,只要寫一個關聯查詢而後過濾掉重複的權限便可,這裏不進行實現。
編寫一個業務處理類UserInfoService>
com.kfit.core.service.UserInfoService :
- package com.kfit.core.service;
-
- import com.kfit.core.bean.UserInfo;
-
- public interface UserInfoService {
-
- /**經過username查找用戶信息;*/
- public UserInfo findByUsername(String username);
-
- }
com.kfit.core.service.impl.UserInfoServiceImpl :
- package com.kfit.core.service.impl;
-
- import javax.annotation.Resource;
-
- import org.springframework.stereotype.Service;
-
- import com.kfit.core.bean.UserInfo;
- import com.kfit.core.repository.UserInfoRepository;
- import com.kfit.core.service.UserInfoService;
-
- @Service
- public class UserInfoServiceImpl implements UserInfoService{
-
- @Resource
- private UserInfoRepository userInfoRepository;
-
- @Override
- public UserInfo findByUsername(String username) {
- System.out.println("UserInfoServiceImpl.findByUsername()");
- return userInfoRepository.findByUsername(username);
- }
-
- }
這裏主要是爲了知足MVC編程模式,在例子中直接訪問DAO層也何嘗不可,實際開發中建議仍是遵循MVC開發模式,至於有什麼好處,請自行百度MVC。
好了以上都是爲了實現身份認證,權限控制的準備工做,想必你們看了也有點困惑了,堅持住,這個技術點不是每一個人都能進行編碼的,你會發如今一個公司裏都是技術領導幫你實現了這一部分很複雜的編碼,你只須要經過界面進行配置而已,因此嘛,要作一個技術領導這一部分不掌握好像還不行了。好了,說重點吧,基本工做準備好以後,剩下的纔是重點,shiro的認證最終是交給了Realm進行執行了,因此咱們須要本身從新實現一個Realm,此Realm繼承AuthorizingRealm。
com.kfit.config.shiro.MyShiroRealm :
- package com.kfit.config.shiro;
-
- import javax.annotation.Resource;
-
- 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.apache.shiro.util.ByteSource;
-
- import com.kfit.core.bean.SysPermission;
- import com.kfit.core.bean.SysRole;
- import com.kfit.core.bean.UserInfo;
- import com.kfit.core.service.UserInfoService;
-
- /**
- * 身份校驗覈心類;
- * @author Angel(QQ:412887952)
- * @version v.0.1
- */
- public class MyShiroRealm extends AuthorizingRealm{
-
-
- @Resource
- private UserInfoService userInfoService;
-
- /**
- * 認證信息.(身份驗證)
- * :
- * Authentication 是用來驗證用戶身份
- * @param token
- * @return
- * @throws AuthenticationException
- */
- @Override
- protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
- System.out.println("MyShiroRealm.doGetAuthenticationInfo()");
-
-
- //獲取用戶的輸入的帳號.
- String username = (String)token.getPrincipal();
- System.out.println(token.getCredentials());
-
- //經過username從數據庫中查找 User對象,若是找到,沒找到.
- //實際項目中,這裏能夠根據實際狀況作緩存,若是不作,Shiro本身也是有時間間隔機制,2分鐘內不會重複執行該方法
- UserInfo userInfo = userInfoService.findByUsername(username);
- System.out.println("----->>userInfo="+userInfo);
- if(userInfo == null){
- return null;
- }
-
- /*
- * 獲取權限信息:這裏沒有進行實現,
- * 請自行根據UserInfo,Role,Permission進行實現;
- * 獲取以後能夠在前端for循環顯示全部連接;
- */
- //userInfo.setPermissions(userService.findPermissions(user));
-
-
- //帳號判斷;
-
- //加密方式;
- //交給AuthenticatingRealm使用CredentialsMatcher進行密碼匹配,若是以爲人家的很差能夠自定義實現
- SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
- userInfo, //用戶名
- userInfo.getPassword(), //密碼
- ByteSource.Util.bytes(userInfo.getCredentialsSalt()),//salt=username+salt
- getName() //realm name
- );
-
- //明文: 若存在,將此用戶存放到登陸認證info中,無需本身作密碼對比,Shiro會爲咱們進行密碼對比校驗
- // SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
- // userInfo, //用戶名
- // userInfo.getPassword(), //密碼
- // getName() //realm name
- // );
-
- return authenticationInfo;
- }
-
-
-
- /**
- * 此方法調用 hasRole,hasPermission的時候纔會進行回調.
- *
- * 權限信息.(受權):
- * 一、若是用戶正常退出,緩存自動清空;
- * 二、若是用戶非正常退出,緩存自動清空;
- * 三、若是咱們修改了用戶的權限,而用戶不退出系統,修改的權限沒法當即生效。
- * (須要手動編程進行實現;放在service進行調用)
- * 在權限修改後調用realm中的方法,realm已經由spring管理,因此從spring中獲取realm實例,
- * 調用clearCached方法;
- * :Authorization 是受權訪問控制,用於對用戶進行的操做受權,證實該用戶是否容許進行當前操做,如訪問某個連接,某個資源文件等。
- * @param principals
- * @return
- */
- @Override
- protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
- /*
- * 當沒有使用緩存的時候,不斷刷新頁面的話,這個代碼會不斷執行,
- * 當其實沒有必要每次都從新設置權限信息,因此咱們須要放到緩存中進行管理;
- * 當放到緩存中時,這樣的話,doGetAuthorizationInfo就只會執行一次了,
- * 緩存過時以後會再次執行。
- */
- System.out.println("權限配置-->MyShiroRealm.doGetAuthorizationInfo()");
-
- SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
- UserInfo userInfo = (UserInfo)principals.getPrimaryPrincipal();
-
- //實際項目中,這裏能夠根據實際狀況作緩存,若是不作,Shiro本身也是有時間間隔機制,2分鐘內不會重複執行該方法
- // UserInfo userInfo = userInfoService.findByUsername(username)
-
-
- //權限單個添加;
- // 或者按下面這樣添加
- //添加一個角色,不是配置意義上的添加,而是證實該用戶擁有admin角色
- // authorizationInfo.addRole("admin");
- //添加權限
- // authorizationInfo.addStringPermission("userInfo:query");
-
-
-
- ///在認證成功以後返回.
- //設置角色信息.
- //支持 Set集合,
- //用戶的角色對應的全部權限,若是隻使用角色定義訪問權限,下面的四行能夠不要
- // List<Role> roleList=user.getRoleList();
- // for (Role role : roleList) {
- // info.addStringPermissions(role.getPermissionsName());
- // }
- for(SysRole role:userInfo.getRoleList()){
- authorizationInfo.addRole(role.getRole());
- for(SysPermission p:role.getPermissions()){
- authorizationInfo.addStringPermission(p.getPermission());
- }
- }
-
- //設置權限信息.
- // authorizationInfo.setStringPermissions(getStringPermissions(userInfo.getRoleList()));
-
- return authorizationInfo;
- }
-
-
- /**
- * 將權限對象中的權限code取出.
- * @param permissions
- * @return
- */
- // public Set<String> getStringPermissions(Set<SysPermission> permissions){
- // Set<String> stringPermissions = new HashSet<String>();
- // if(permissions != null){
- // for(SysPermission p : permissions) {
- // stringPermissions.add(p.getPermission());
- // }
- // }
- // return stringPermissions;
- // }
-
- }
繼承AuthorizingRealm主要須要實現兩個方法:
doGetAuthenticationInfo();
doGetAuthorizationInfo();
其中doGetAuthenticationInfo主要是用來進行身份認證的,也就是說驗證用戶輸入的帳號和密碼是否正確。
- SimpleAuthenticationInfoauthenticationInfo =
- new SimpleAuthenticationInfo(
- userInfo, //用戶名
- userInfo.getPassword(), //密碼
- ByteSource.Util.bytes(userInfo.getCredentialsSalt()),//salt=username+salt
- getName() //realm name
- );
交給AuthenticatingRealm使用CredentialsMatcher進行密碼匹配,若是以爲人家的很差能夠自定義實現
若是你是進行明文進行編碼的話,那麼使用使用以下方式:
- SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
- userInfo, //用戶名
- userInfo.getPassword(), //密碼
- getName() //realm name
- );
至於doGetAuthorizationInfo()是權限控制,當訪問到頁面的時候,使用了相應的註解或者shiro標籤纔會執行此方法不然不會執行,因此若是隻是簡單的身份認證沒有權限的控制的話,那麼這個方法能夠不進行實現,直接返回null便可。
在這個方法中主要是使用類:SimpleAuthorizationInfo
進行角色的添加和權限的添加。
authorizationInfo.addRole(role.getRole());
authorizationInfo.addStringPermission(p.getPermission());
固然也能夠添加集合:
authorizationInfo.setRoles(roles);
authorizationInfo.setStringPermissions(stringPermissions);
到這裏咱們還須要有一個步驟很重要就是將咱們自定義的Realm注入到SecurityManager中。
在com.kfit.config.shiro.ShiroConfiguration中添加方法:
- /**
- * 身份認證realm;
- * (這個須要本身寫,帳號密碼校驗;權限等)
- * @return
- */
- @Bean
- public MyShiroRealm myShiroRealm(){
- MyShiroRealm myShiroRealm = new MyShiroRealm();
- return myShiroRealm;
- }
將myShiroRealm注入到securityManager中:
- @Bean
- public SecurityManager securityManager(){
- DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
- //設置realm.
- securityManager.setRealm(myShiroRealm());
- return securityManager;
- }
到這裏的話身份認證權限控制基本是完成了,最後咱們在編寫一個登陸的時候,登陸的處理:
在com.kfit.root.controller.HomeController中添加login post處理:
- // 登陸提交地址和applicationontext-shiro.xml配置的loginurl一致。 (配置文件方式的說法)
- @RequestMapping(value="/login",method=RequestMethod.POST)
- public String login(HttpServletRequest request, Map<String, Object> map) throws Exception {
- System.out.println("HomeController.login()");
- // 登陸失敗從request中獲取shiro處理的異常信息。
- // shiroLoginFailure:就是shiro異常類的全類名.
- String exception = (String) request.getAttribute("shiroLoginFailure");
-
- System.out.println("exception=" + exception);
- String msg = "";
- if (exception != null) {
- if (UnknownAccountException.class.getName().equals(exception)) {
- System.out.println("UnknownAccountException -- > 帳號不存在:");
- msg = "UnknownAccountException -- > 帳號不存在:";
- } elseif (IncorrectCredentialsException.class.getName().equals(exception)) {
- System.out.println("IncorrectCredentialsException -- > 密碼不正確:");
- msg = "IncorrectCredentialsException -- > 密碼不正確:";
- } elseif ("kaptchaValidateFailed".equals(exception)) {
- System.out.println("kaptchaValidateFailed -- > 驗證碼錯誤");
- msg = "kaptchaValidateFailed -- > 驗證碼錯誤";
- } else {
- msg = "else >> "+exception;
- System.out.println("else -- >" + exception);
- }
- }
- map.put("msg", msg);
- // 此方法不處理登陸成功,由shiro進行處理.
- return "/login";
- }
這時候咱們啓動應用程序,訪問http://127.0.0.1:8080/index
會自動跳轉到http://127.0.0.1:8080/login 界面,而後輸入帳號和密碼:admin/123456,這時候會提示:IncorrectCredentialsException -- > 密碼不正確。
這主要是由於咱們在上面進行了密文的方式,那麼怎麼加密方式,咱們並無告訴Shiro,因此認證失敗了。
在這裏咱們須要編寫一個加密算法類,固然Shiro也已經有了具體的實現HashedCredentialsMatcher
咱們只須要進行注入使用便可:
在com.kfit.config.shiro.ShiroConfiguration中加入方法:
- /**
- * 憑證匹配器
- * (因爲咱們的密碼校驗交給Shiro的SimpleAuthenticationInfo進行處理了
- * 因此咱們須要修改下doGetAuthenticationInfo中的代碼;
- * )
- * @return
- */
- @Bean
- public HashedCredentialsMatcher hashedCredentialsMatcher(){
- HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
-
- hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:這裏使用MD5算法;
- hashedCredentialsMatcher.setHashIterations(2);//散列的次數,好比散列兩次,至關於 md5(md5(""));
-
- return hashedCredentialsMatcher;
- }
在myShiroRealm()方法中注入憑證匹配器:
- /**
- * 身份認證realm;
- * (這個須要本身寫,帳號密碼校驗;權限等)
- * @return
- */
- @Bean
- public MyShiroRealm myShiroRealm(){
- MyShiroRealm myShiroRealm = new MyShiroRealm();
- myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());;
- return myShiroRealm;
- }
這時候在訪問/login進行登陸就能夠登錄到/index界面了。
這一節就先到這裏吧,實在是有點頭大了是吧。到時候奉上源代碼。
(d) 權限控制
在上一小節咱們已經能夠登陸了。
在咱們新建一個UserInfoController
com.kfit.core.controller.UserInfoController :
- package com.kfit.core.controller;
-
- import org.apache.shiro.authz.annotation.RequiresPermissions;
- import org.springframework.stereotype.Controller;
- import org.springframework.web.bind.annotation.RequestMapping;
-
- @Controller
- @RequestMapping("/userInfo")
- public class UserInfoController {
-
- /**
- * 用戶查詢.
- * @return
- */
- @RequestMapping("/userList")
- public String userInfo(){
- return "userInfo";
- }
-
- /**
- * 用戶添加;
- * @return
- */
- @RequestMapping("/userAdd")
- public String userInfoAdd(){
- return "userInfoAdd";
- }
-
- }
而後運行登陸進行訪問:http://127.0.0.1:8080/userInfo/userAdd
並無執行doGetAuthorizationInfo()打印信息,因此咱們會發現咱們的身份認證是好使了,可是權限控制好像沒有什麼做用哦。
咱們少了幾部分代碼,
第一就是開啓shiro aop註解支持,這個只須要在com.kfit.config.shiro.ShiroConfiguration加入以下方法進行開啓便可:
- /**
- * 開啓shiro aop註解支持.
- * 使用代理方式;因此須要開啓代碼支持;
- * @param securityManager
- * @return
- */
- @Bean
- public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
- AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
- authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
- return authorizationAttributeSourceAdvisor;
- }
第二就是在controller方法中加入相應的註解:
- /**
- * 用戶添加;
- * @return
- */
- @RequestMapping("/userAdd")
- @RequiresPermissions("userInfo:add")//權限管理;
- public String userInfoAdd(){
- return "userInfoAdd";
- }
這時候在訪問http://127.0.0.1:8080/userInfo/userAdd 會看到控制檯打印信息:
權限配置-->MyShiroRealm.doGetAuthorizationInfo()
若是訪問:http://127.0.0.1:8080/userInfo/userDel 會看到
- Whitelabel Error Page
- This application has no explicit mapping for /error, so you are seeing this as a fallback.
- Sat May 21 22:17:55 CST 2016
- There was an unexpected error (type=Internal Server Error, status=500).
- Subject does not have permission [userInfo:del]
固然咱們須要在UserInfoController方法中加入:
- /**
- * 用戶刪除;
- * @return
- */
- @RequestMapping("/userDel")
- @RequiresPermissions("userInfo:del")//權限管理;
- public String userDel(){
- return "userInfoDel";
- }
在上面的錯誤信息中Subject does not have permission能夠看出此用戶沒有這個權限。好了,至此Shiro的權限控制到此先告一段落。在這裏我先拋出一個問題:咱們不斷的訪問http://127.0.0.1:8080/userInfo/userAdd 你會看到
- 權限配置-->MyShiroRealm.doGetAuthorizationInfo()
- 2016-05-21 22:20:26.263 INFO 11692 --- [nio-8080-exec-1] org.apache.shiro.realm.AuthorizingRealm : No cache or cacheManager properties have been set. Authorization cache cannot be obtained.
- 權限配置-->MyShiroRealm.doGetAuthorizationInfo()
- 2016-05-21 22:20:26.385 INFO 11692 --- [nio-8080-exec-2] org.apache.shiro.realm.AuthorizingRealm : No cache or cacheManager properties have been set. Authorization cache cannot be obtained.
- 權限配置-->MyShiroRealm.doGetAuthorizationInfo()
- 2016-05-21 22:20:26.538 INFO 11692 --- [nio-8080-exec-3] org.apache.shiro.realm.AuthorizingRealm : No cache or cacheManager properties have been set. Authorization cache cannot be obtained.
- 權限配置-->MyShiroRealm.doGetAuthorizationInfo()
這說明咱們不斷的訪問權限信息,可是實際中咱們的權限信息是不怎麼會改變的,因此咱們但願是第一次訪問,而後進行緩存處理,那麼Shiro是否支持呢,答案是確定的,咱們在下一小節進行講解,如何在Shiro中加入緩存機制。