Apache Shiro 是Java的安全框架,提供了認證(Authentication)、受權(Authorization)、會話(Session)管理、加密(Cryptography)等功能,且Shiro與Spring Security等安全框架相比具備簡單性、靈活性、支持細粒度鑑權、支持一級緩存等,還有Shiro不跟任何容器(Tomcat等)和框架(Sping等)捆綁,能夠獨立運行,這也造就了Shiro不只僅是能夠用在Java EE上還能夠用在Java SE上。html
在開始以前,首先了解一下Shiro的四大功能,俗話說「知己知彼百戰不殆」。 前端
認證就是用戶訪問系統的時候,系統要驗證用戶身份的合法性,好比咱們一般所說的「登陸」就是認證的一種方式,只有登陸成功了以後咱們才能訪問相應的資源。在Shiro中,咱們能夠將用戶理解爲Subject主體,在用戶身份認證的時候,用戶須要提供能證實他身份的信息,如用戶名、密碼等,用戶所提供的這些用戶名、密碼則對應Shiro中的Principal、 Credentials,即在Subject進行身份認證的時候,須要提供相應的Principal、 Credentials,對應的代碼以下:java
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
Subject subject = SecurityUtils.getSubject();
subject.login(token); //提交認證
複製代碼
咱們知道Http協議是無狀態的,因此用戶認證成功後怎麼才能保持認證成功的狀態呢?若是是咱們開發的話通常都是登陸成功後將Session儲存在服務器,而後再將Session返回給用戶,以後的請求用戶都將這個Session帶上,而後服務器根據用戶請求攜帶的Session和服務器儲存的Session進行比較來判斷用戶是否已認證。可是使用Shiro後,Shiro已經幫咱們作好這個了(下面介紹的會話管理),是否是feel爽~mysql
受權能夠理解爲訪問控制,在用戶認證(登陸)成功以後,系統對用戶訪問資源的權限進行控制,即肯定什麼用戶能訪問什麼資源,如普通用戶不能訪問後臺,可是管理員能夠。在這裏咱們還須要認識幾個概念,資源(Resource)、角色(Role)、權限(Permission),上面提到的Subject主體能夠有多個角色,每一個角色又對應多個資源的多個權限,這種基於資源的訪問控制能夠實現細粒度的權限。對主體設置角色、權限的代碼以下:git
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
// 添加用戶的角色
authorizationInfo.addRoles(roleIdList);
// 添加用戶的權限
authorizationInfo.addStringPermissions(resourceIdList);
複製代碼
若是要實現這樣的受權功能,咱們一定須要設計一個用戶組、權限,給每一個方法或者URL加上判斷,是否當前登陸的用戶知足條件。可是使用Shiro後,Shiro也幫咱們幫這些都作好了。github
會話管理的會話即Session,所謂會話,即用戶訪問應用時保持的鏈接關係,在屢次交互中應用可以識別出當前訪問的用戶是誰,且能夠在屢次交互中保存一些數據。如訪問一些網站時登陸成功後,網站能夠記住用戶,且在退出以前均可以識別當前用戶是誰。在Shiro中,與用戶有關的一切信息均可以經過Shiro的接口得到,和用戶的會話Session也都由Shiro管理。如實現「記住我」或者「下次自動登陸」的功能,若是要本身去開發的話,估計又得話很多時間。可是使用Shiro後,Shiro也幫咱們幫這些都作好了。web
用戶密碼明文保存是否是安全,應不該該MD5加密,是否是應該加鹽,又要寫密碼加密的代碼。 這些Shiro已經幫你作好了。spring
從總體概念上理解,Shiro的體系架構有三個主要的概念,Subject(主體),Security Manager (安全管理器)和 Realms (域)。 sql
主體是當前正在操做的用戶的特定數據集合。主體能夠是一我的,也能夠表明第三方服務,守護進程,定時任務或相似的東西,也就是幾乎全部與該應用進行交互的事物。全部Subject都綁定到SecurityManager
,與Subject的全部交互都會委託給 SecurityManager,能夠把 Subject 認爲是一個門面,SecurityManager 纔是實際的執行者。數據庫
安全管理器,即全部與安全有關的操做都會與SecurityManager
交互,且它管理着全部Subject能夠看出它是Shiro的核心,它負責與後邊介紹的其餘組件進行交互,若是學習過 SpringMVC,你能夠把它當作DispatcherServlet前端控制器,通常來講,一個應用只會存在一個SecurityManager實例。
域,Shiro從Realm獲取安全數據(如用戶、角色、權限),就是說SecurityManager要驗證用戶身份,那麼它須要從Realm獲取相應的用戶進行比較以肯定用戶身份是否合法,也須要從Realm獲得用戶相應的角色 / 權限進行驗證用戶是否能進行操做,即Realms做爲Shiro與應用程序安全數據之間的「橋樑」。從這個意義上講,Realm實質上是一個安全相關的DAO,它封裝了數據源的鏈接細節,並在須要時將相關數據提供給Shiro。其中Realm有2個方法,doGetAuthenticationInfo
用來認證,doGetAuthorizationInfo
用來受權。
pox.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>shiro</groupId>
<artifactId>shiro</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>shiro Maven Webapp</name>
<!-- FIXME change it to the project's website --> <url>http://www.example.com</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.7</maven.compiler.source> <maven.compiler.target>1.7</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <!--Sping核心依賴--> <!-- https://mvnrepository.com/artifact/org.springframework/spring-core --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>5.1.3.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-web --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>5.1.3.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.1.3.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.1.3.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-context --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.1.3.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-context-support --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>5.1.3.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-aop --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>5.1.3.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-test --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.1.3.RELEASE</version> <scope>test</scope> </dependency> <!--Mybatis依賴--> <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.6</version> </dependency> <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.3.2</version> </dependency> <!--MySQL鏈接驅動--> <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.13</version> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-core --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.4.0</version> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-web --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> <version>1.4.0</version> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-spring --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.0</version> </dependency> <!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency> </dependencies> <build> <finalName>shiro</finalName> <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) --> <plugins> <plugin> <artifactId>maven-clean-plugin</artifactId> <version>3.1.0</version> </plugin> <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging --> <plugin> <artifactId>maven-resources-plugin</artifactId> <version>3.0.2</version> </plugin> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.0</version> </plugin> <plugin> <artifactId>maven-surefire-plugin</artifactId> <version>2.22.1</version> </plugin> <plugin> <artifactId>maven-war-plugin</artifactId> <version>3.2.2</version> </plugin> <plugin> <artifactId>maven-install-plugin</artifactId> <version>2.5.2</version> </plugin> <plugin> <artifactId>maven-deploy-plugin</artifactId> <version>2.8.2</version> </plugin> </plugins> </pluginManagement> </build> </project> 複製代碼
爲了減小篇幅,只作簡單介紹,詳情能夠查看源碼,數據庫文件在本項目根目錄。
id
,name
兩個字段,分別對應資源id和權限。id
,name
兩個字段,分別對應角色id和角色名。id
,roleid
,resid
三個字段,分別對應自增id、角色id和資源id。id
,username
,password
三個字段,分別對應自增id、用戶名和密碼。id
,uid
,rid
三個字段,分別對應自增id、用戶id、和角色id。AccountDao.java:
public interface AccountDao {
User findUserByUsername(String username);
List<Role> findRoleByUserId(int id);
List<Resource> findResourceByUserId(int id);
}
複製代碼
AccountService.java:
public interface AccountService {
User findUserByUsername(String username);
List<Role> findRoleByUserId(int id);
List<Resource> findResourceByUserId(int id);
boolean login(User user);
}
複製代碼
AccountServiceImpl.java:
package com.shiro.service.impl;
import com.shiro.dao.AccountDao;
import com.shiro.entity.Role;
import com.shiro.entity.User;
import com.shiro.service.AccountService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
/**
* @program: shiro
* @description:
* @author: Xue 8
* @create: 2019-02-01 15:37
**/
@Service
public class AccountServiceImpl implements AccountService {
@Resource
AccountDao accountDao;
/**
* @description: 根據用戶名查找用戶信息
* @param: [username]
* @return: com.shiro.entity.User
* @author: Xue 8
* @date: 2019/2/1
*/
@Override
public User findUserByUsername(String username) {
return accountDao.findUserByUsername(username);
}
@Override
public List<Role> findRoleByUserId(int id) {
return accountDao.findRoleByUserId(id);
}
@Override
public List<com.shiro.entity.Resource> findResourceByUserId(int id) {
return accountDao.findResourceByUserId(id);
}
public boolean login(User user){
// 獲取當前用戶對象subject
Subject subject = SecurityUtils.getSubject();
System.out.println("subject:" + subject.toString());
// 建立用戶名/密碼身份證驗證Token
UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(), user.getPassword());
System.out.println("token" + token);
try {
subject.login(token);
System.out.println("登陸成功");
return true;
} catch (Exception e) {
System.out.println("登陸失敗" + e);
return false;
}
}
}
複製代碼
MyRealm.java
package com.shiro.service.impl;
import com.shiro.entity.Role;
import com.shiro.entity.User;
import com.shiro.service.AccountService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
/**
* @program: shiro
* @description:
* @author: Xue 8
* @create: 2019-02-01 15:16
**/
public class MyRealm extends AuthorizingRealm {
@Resource
AccountService accountService;
/**
* 身份認證的方法 認證成功獲取身份驗證信息
* 這裏最主要的是user.login(token);這裏有一個參數token,這個token就是用戶輸入的用戶密碼,
* 咱們平時可能會用一個對象user來封裝用戶名和密碼,shiro用的是token,這個是控制層的代碼,還沒到shiro,
* 當調用user.login(token)後,就交給shiro去處理了,接下shiro應該是去token中取出用戶名,而後根據用戶去查數據庫,
* 把數據庫中的密碼查出來。這部分代碼通常都是要求咱們自定義實現,自定義一個realm,重寫doGetAuthenticationInfo方法
**/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
// 獲取用戶輸入的用戶名和密碼
// 實際上這個token是從UserResource面currentUser.login(token)傳過來的
// 兩個token的引用都是同樣的
String username = (String) authenticationToken.getPrincipal();
// 密碼要用字符數組來接受 由於UsernamePasswordToken(username, password) 儲存密碼的時候是將字符串類型轉成字符數組的 查看源碼能夠看出
String password = new String((char[]) authenticationToken.getCredentials());
// 調用service 根據用戶名查詢用戶信息
User user = accountService.findUserByUsername(username);
// String password = user.getPassword();
// 判斷用戶是否存在 不存在則拋出異常
if (user != null) {
// 判斷用戶密碼是否匹配 匹配則不匹配則拋出異常
if (user.getPassword().equals(password)) {
// 登陸成功 把用戶信息儲存在Session中
Session session = SecurityUtils.getSubject().getSession();
session.setAttribute("userSession", user);
session.setAttribute("userSessionId", user.getId());
// 認證成功 返回一個AuthenticationInfo的實現
return new SimpleAuthenticationInfo(username, password, getName());
} else {
System.out.println("密碼不正確");
throw new IncorrectCredentialsException();
}
} else {
System.out.println("帳號不存在");
throw new UnknownAccountException();
}
}
/**
* 受權的方法
* 一、subject.hasRole(「admin」) 或 subject.isPermitted(「admin」):本身去調用這個是否有什麼角色或者是否有什麼權限的時候;
*
* 二、@RequiresRoles("admin") :在方法上加註解的時候;
*
* 三、[@shiro.hasPermission name = "admin"][/@shiro.hasPermission]:在頁面上加shiro標籤的時候,即進這個頁面的時候掃描到有這個標籤的時候。
* 四、xml配置權限的時候也會走
**/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("受權");
// 從principalCollection獲取用戶信息
// 若是doGetAuthenticationInfo(user,password,getName()); 傳入的是user類型的數據 那這裏getPrimaryPrincipal獲取到的也是user類型的數據
String username = (String) principalCollection.getPrimaryPrincipal();
User user = accountService.findUserByUsername(username);
// 獲取該用戶的全部角色
List<Role> roleList = accountService.findRoleByUserId(user.getId());
// 將角色的id放到一個String列表中 由於authorizationInfo.addRoles()方法只支持角色的String列表或者單個角色String
List<String> roleIdList = new ArrayList<String>();
for (Role role:roleList) {
roleIdList.add(role.getName());
}
// 獲取該用戶的全部權限
List<com.shiro.entity.Resource> resourceList = accountService.findResourceByUserId(user.getId());
List<String> resourceIdList = new ArrayList<String>();
// 將權限id放到一個String列表中 由於authorizationInfo.addRoles()方法只支持角色的String列表或者單個角色String
for (com.shiro.entity.Resource resource:resourceList) {
resourceIdList.add(resource.getName());
}
System.out.println("受權11");
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
// 添加用戶的角色
authorizationInfo.addRoles(roleIdList);
// 添加用戶的權限
authorizationInfo.addStringPermissions(resourceIdList);
return authorizationInfo;
}
}
複製代碼
AccountController.java
package com.shiro.controller;
import com.shiro.entity.User;
import com.shiro.service.AccountService;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
/**
* @program: shiro
* @description:
* @author: Xue 8
* @create: 2019-02-01 13:14
**/
@Controller
public class AccountController {
@Resource
AccountService accountService;
@Resource
HttpServletRequest servletRequest;
@RequestMapping(value = "/home")
public String home(){
return "home";
}
@RequestMapping(value = "/login", method = RequestMethod.GET)
public String getLogin(){
return "login";
}
@RequestMapping(value = "/login", method = RequestMethod.POST)
public String doLogin(@RequestParam(value = "username") String username,
@RequestParam(value = "password") String password){
User user = new User();
user.setUsername(username);
user.setPassword(password);
if (accountService.login(user)) {
return "/home";
}
return "/login";
}
}
複製代碼
以GET
方法訪問/login
的時候,會出現登陸頁面,輸入帳號密碼點擊登陸數據將以POST
方式提交給/login
,若是帳號密碼匹配返回/home
的頁面,不然返回/login
的頁面。/home
頁面只有在登陸且有權限的狀況下才能夠訪問,未登陸狀況下訪問會轉跳/login
頁面,這個在Shiro的配置文件裏面配置。
applicationContext.xml:配置Spring
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!--開啓掃描註冊-->
<context:component-scan base-package="com.shiro"></context:component-scan>
<!--讀取properties配置-->
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="classpath:jdbcConfig.properties"></property>
</bean>
<!--配置數據源-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${driverClassName}"></property>
<property name="username" value="${username}"></property>
<property name="password" value="${password}"></property>
<property name="url" value="${url}"></property>
</bean>
<!--配置session工廠-->
<bean id="sessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"></property>
<property name="configLocation" value="classpath:mybatis-config.xml"></property>
<property name="mapperLocations" value="classpath:mapping/*.xml"></property>
</bean>
<!--配置掃描mapping-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.shiro.dao"></property>
<property name="sqlSessionFactoryBeanName" value="sessionFactoryBean"></property>
</bean>
</beans>
複製代碼
spring-shiro.xml:配置Shiro
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
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.xsd">
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="myRealm"></property>
</bean>
<bean id="myRealm" class="com.shiro.service.impl.MyRealm">
<!--關閉權限緩存 否則doGetAuthorizationInfo受權方法不執行-->
<property name="authorizationCachingEnabled" value="false"/>
</bean>
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"></property>
<property name="successUrl" value="/success"></property>
<!--登陸頁面-->
<property name="loginUrl" value="/login"></property>
<property name="filterChainDefinitions">
<value>
<!--配置`/home`只有擁有`admin`角色的用戶才能夠訪問-->
/home = authc,roles[admin]
</value>
</property>
</bean>
</beans>
複製代碼
這裏須要注意的是 在配置Realm的時候,若是沒用上緩存功能的話,須要將緩存關掉,否則進不到doGetAuthorizationInfo受權方法。
打開http://localhost:8080/login
登陸頁面,填寫正確用戶名和密碼登陸
http://localhost:8080/home
頁面,自動轉跳到了
/login
登陸頁面(即沒有權限訪問),登陸帳戶,再次打開
http://localhost:8080/home
頁面便可正常訪問。
這是我學習Shiro時候根據本身的狀況記錄下來的,但願對你們有所幫助,若是你們想對Shiro進一步研究的話,推薦你們看張開濤老師的《跟我學Shiro》,最後附上本項目的Github地址:github.com/xue8/Java-D…
原文地址:ddnd.cn/2019/02/02/…