Apache Shiro 快速入門教程,shiro 基礎教程

swingpyzf發表於2015-06-03



第一部分 什麼是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的其他元件提供快取支援。 




5、Shiro 完整架構圖: 



圖片轉自:http://kdboy.iteye.com/blog/1154644



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


1、準備工具:


持久層框架:Hibernate4  這邊我使用了Hibernate來對資料持久層進行操作

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

資料庫:MySql

準備好所需要的jar放到專案中。

 


2、建立資料庫:



首先需要四張表,分別為 user(使用者)、role(角色)、permission(許可權)、userRole(使用者角色關係表)

這邊分別建立四張表的實體類,通過Hiberantehibernate.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類 實現了AuthorizationInfoAuthenticationInfo兩個方法,用於獲取使用者許可權和認證使用者登入資訊


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頁面 就會跳轉到登入頁面:  




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


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

專案原始碼:github:https://github.com/swinglife/shiro_ex



因為工作的原因寫部落格和教程的時間越來越少了,所以今後的部落格儘量簡短明瞭,以實踐使用為主,

同時也歡迎大家關注我的個人公眾號來提相關問題,我會力所能及的回答。




相關文章