-
什么是Shiro
- Apache的強大靈活的開源安全框架
- 認證、授權(quán)、企業(yè)會話管理、安全加密
-
Shiro 與 Spring Security 比較
- Apache Shiro
- 簡單、靈活
- 可脫離Spring
- 粒度較粗
- Spring Security
- 復(fù)雜、笨重
- 不可脫離Spring
- 粒度更細
- Apache Shiro

Authenticator認證器:登錄登出
Authorizer授權(quán)器:賦予主體有哪些權(quán)限
Session Manager - Session管理器:Shiro自身實現(xiàn)的Session管理機制,可以在不借助任何Web容器的情況下使用Session。
Session DAO - Session的操作:CRUD
Cache Manager緩存管理器:緩存角色數(shù)據(jù)和權(quán)限數(shù)據(jù)
Realms:Shiro和數(shù)據(jù)庫、數(shù)據(jù)源的一個橋梁,Shiro獲取認證信息、權(quán)限數(shù)據(jù)、角色數(shù)據(jù)都是通過該部分。
主體提交請求,到Security Manager調(diào)用Authenticator認證;Authenticator獲取認證數(shù)據(jù)是通過Realms獲取,從數(shù)據(jù)庫獲取的認證信息和主體提交的認證數(shù)據(jù)去做比對;
主體賦予權(quán)限也是通過Realms從數(shù)據(jù)庫或者緩存中來獲取我們的角色數(shù)據(jù)或權(quán)限數(shù)據(jù)。
Cryptography加密:可以使用其快速加密數(shù)據(jù)。

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.realm.SimpleAccountRealm;
import org.apache.shiro.subject.Subject;
import org.junit.Before;
import org.junit.Test;
public class AuthenticationTest {
// 指定我們的Reamls
SimpleAccountRealm simpleAccountRealm = new SimpleAccountRealm() ;
@Before
public void addUser() {
simpleAccountRealm.addAccount("Mark", "123456"); // 模擬查詢出用戶名
simpleAccountRealm.addAccount("Admin", "123456","admin"); // 模擬查詢出用戶名及權(quán)限,角色可以有多個
}
@Test
public void loginAuthentication() {
// 1、構(gòu)建SecurityManager環(huán)境
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
defaultSecurityManager.setRealm(simpleAccountRealm); // 將Realm設(shè)置到SecurityManager環(huán)境中
SecurityUtils.setSecurityManager(defaultSecurityManager); // 先要設(shè)置SecurityManager環(huán)境
// 2、主體提交認證請求(Subject)
Subject subject = SecurityUtils.getSubject() ;
UsernamePasswordToken token = new UsernamePasswordToken("Mark","123456") ;
subject.login(token);
// 3、通過SecurityManager認證
boolean flag = subject.isAuthenticated() ; // 是否通過認證,SecurityManager通過Authenticator 認證,Authenticator獲取Realm,通過Realm 取認證數(shù)據(jù)
System.out.println("isAuthenticated:" + flag);
subject.logout(); // 退出
System.out.println("logout isAuthenticated:" + subject.isAuthenticated());
}
}

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.realm.SimpleAccountRealm;
import org.apache.shiro.subject.Subject;
import org.junit.Before;
import org.junit.Test;
/**
*
* @author Administrator
*
*/
public class AuthenticationTest {
// 指定我們的Reamls
SimpleAccountRealm simpleAccountRealm = new SimpleAccountRealm() ;
@Before
public void addUser() {
simpleAccountRealm.addAccount("Mark", "123456"); // 模擬查詢出用戶名
simpleAccountRealm.addAccount("Admin", "123456","admin"); // 模擬查詢出用戶名及權(quán)限,角色可以有多個
}
@Test
public void powerAuthentication() {
// 1、構(gòu)建SecurityManager環(huán)境
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
defaultSecurityManager.setRealm(simpleAccountRealm); // 將Realm設(shè)置到SecurityManager環(huán)境中
SecurityUtils.setSecurityManager(defaultSecurityManager); // 先要設(shè)置SecurityManager環(huán)境
// 2、主體提交認證請求(Subject)
Subject subject = SecurityUtils.getSubject() ;
UsernamePasswordToken token = new UsernamePasswordToken("Admin","123456") ;
subject.login(token);
// 3、通過SecurityManager認證
boolean flag = subject.isAuthenticated() ; // 是否通過認證,SecurityManager通過Authenticator 認證,Authenticator獲取Realm,通過Realm 取認證數(shù)據(jù)
System.out.println("isAuthenticated:" + flag);
subject.checkRole("admin"); // 檢查用戶是否具備該角色,可以檢查多個
}
}
- Shiro自定義Realm
- 內(nèi)置Realm:
- IniRealm
- JdbcRealm
- 內(nèi)置Realm:
user.ini文件
[users]
Mark=123456,admin // 用戶名=密碼,角色
[roles]
admin=user:delete // 角色=擁有刪除用戶的權(quán)限
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.realm.text.IniRealm;
import org.apache.shiro.subject.Subject;
import org.junit.Test;
public class IniRealmTest {
@Test
public void iniRealmAuthentication() {
IniRealm iniRealm = new IniRealm("classpath:user.ini"); // ini文件的路徑
// 1、構(gòu)建SecurityManager環(huán)境
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
defaultSecurityManager.setRealm(iniRealm);
SecurityUtils.setSecurityManager(defaultSecurityManager); // 先要設(shè)置SecurityManager環(huán)境
// 2、主體提交認證請求(Subject)
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("Mark", "123456");
subject.login(token);
// 3、通過SecurityManager認證
boolean flag = subject.isAuthenticated(); // 是否通過認證,SecurityManager通過Authenticator
// 認證,Authenticator獲取Realm,通過Realm 取認證數(shù)據(jù)
System.out.println("isAuthenticated:" + flag);
subject.checkRole("admin"); // 驗證角色
subject.checkPermission("user:delete "); // 驗證權(quán)限
subject.checkPermission("user:update");
}
}
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.realm.jdbc.JdbcRealm;
import org.apache.shiro.subject.Subject;
import org.junit.Test;
import com.alibaba.druid.pool.DruidDataSource;
public class JdbcRealmTest {
DruidDataSource dataSource = new DruidDataSource();
{
dataSource.setUrl("jdbc:mysql://localhost:3306/test");
dataSource.setUsername("root");
dataSource.setPassword("123456");
}
@Test
public void jdbcRealmAuthentication() {
JdbcRealm jdbcRealm = new JdbcRealm(); // 需要訪問數(shù)據(jù)庫
// 設(shè)置數(shù)據(jù)源
jdbcRealm.setDataSource(dataSource);
jdbcRealm.setPermissionsLookupEnabled(true); // 使用JDBC需要設(shè)置權(quán)限開關(guān),否則即使配置權(quán)限仍不能認證。默認為false。
// 自定義sql查詢認證與權(quán)限,不設(shè)置則使用jdbcRealm本身的查詢語句
String sql = "select password from test_user where user_name = ?";
jdbcRealm.setAuthenticationQuery(sql); // 使用自定義sql
String roleSql = "select role_name from test_role_user where user_name = ?";
jdbcRealm.setUserRolesQuery(roleSql);
// 1、構(gòu)建SecurityManager環(huán)境
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
defaultSecurityManager.setRealm(jdbcRealm);
SecurityUtils.setSecurityManager(defaultSecurityManager); // 先要設(shè)置SecurityManager環(huán)境
// 2、主體提交認證請求(Subject)
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("Mark", "123456");
subject.login(token);
// 3、通過SecurityManager認證
boolean flag = subject.isAuthenticated(); // 是否通過認證,SecurityManager通過Authenticator
// 認證,Authenticator獲取Realm,通過Realm 取認證數(shù)據(jù)
System.out.println("isAuthenticated:" + flag);
subject.checkRole("admin"); // 驗證角色
subject.checkPermission("user:delete"); // 驗證權(quán)限
subject.checkPermission("user:update");
}
}
Shiro加密
- Shiro散列配置
- HashedCredentialsMatcher
- 自定義Realm中使用散列
- 鹽的使用
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
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.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.crypto.hash.Md5Hash;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
/**
* 自定義Realm
*
* @author Administrator
*
*/
public class CustomRealm extends AuthorizingRealm {
Map<String, String> userMap = new HashMap<>();
{
userMap.put("Mark", "283538989cef48f3d7d8a1c1bdf2008f"); // Md5解密后的密文
}
/**
* 授權(quán)
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String userName = (String) principals.getPrimaryPrincipal();
// 從數(shù)據(jù)庫或緩存中獲取角色數(shù)據(jù)
Set<String> roles = getRelosByUserName(userName);
Set<String> permissions = getPermissionsByUserName(userName);
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.setRoles(roles);
simpleAuthorizationInfo.setStringPermissions(permissions);
return simpleAuthorizationInfo;
}
/**
* 模擬數(shù)據(jù)庫獲取角色數(shù)據(jù)
*
* @param userName
* @return
*/
private Set<String> getRelosByUserName(String userName) {
Set<String> roles = new HashSet<>();
roles.add("admin");
roles.add("user");
return roles;
}
/**
* 模擬數(shù)據(jù)庫獲取權(quán)限數(shù)據(jù)
*
* @param userName
* @return
*/
private Set<String> getPermissionsByUserName(String userName) {
Set<String> permissions = new HashSet<>();
permissions.add("user:delete");
permissions.add("user:update");
return permissions;
}
/**
* 認證
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 1、通過主體傳來的認證信息中,獲取用戶名
String userName = (String) token.getPrincipal();
// 2、通過用戶名到數(shù)據(jù)庫中獲取憑證
String password = getPasswordByUserName(userName);
if (password == null) {
return null;
}
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo("Mark", password, "customRealm");
// 密碼加密設(shè)置鹽,此時需要設(shè)置鹽
authenticationInfo.setCredentialsSalt(ByteSource.Util.bytes(userName));
return authenticationInfo;
}
/**
* 模擬數(shù)據(jù)庫查詢憑證
*
* @param userName
* @return
*/
private String getPasswordByUserName(String userName) {
return userMap.get(userName);
}
public static void main(String[] args) {
// Md5Hash md5Hash = new Md5Hash("123456") ; // 單純加密
Md5Hash md5Hash = new Md5Hash("123456","Mark") ; // 加密,加鹽
System.out.println(md5Hash.toString());
}
}
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.subject.Subject;
import org.junit.Test;
import com.shiro.pro.CustomRealm;
public class CustomRealmTest {
@Test
public void customRealmAuthentication() {
CustomRealm customRealm = new CustomRealm();
// 1、構(gòu)建SecurityManager環(huán)境
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
defaultSecurityManager.setRealm(customRealm);
SecurityUtils.setSecurityManager(defaultSecurityManager); // 先要設(shè)置SecurityManager環(huán)境
// 加密
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher() ;
matcher.setHashAlgorithmName("md5"); // 設(shè)置算法名稱
matcher.setHashIterations(1); // 設(shè)置加密次數(shù)
customRealm.setCredentialsMatcher(matcher); // 設(shè)置加密的HashedCredentialsMatcher對象
// 2、主體提交認證請求(Subject)
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("Mark", "123456");
subject.login(token);
// 3、通過SecurityManager認證
boolean flag = subject.isAuthenticated(); // 是否通過認證,SecurityManager通過Authenticator
// 認證,Authenticator獲取Realm,通過Realm 取認證數(shù)據(jù)
System.out.println("isAuthenticated:" + flag);
subject.checkRoles("admin","user");
subject.checkPermissions("user:delete","user:update");
}
}
Shiro集成Spring
啟動項目報:Failed to start component [StandardEngine[Catalina].StandardHost[localhost].看了半天配置文件也沒見到毛病,想了個辦法:用 maven install 找到了錯誤,原來是shiro-crypto-cipher-1.4.0.jar沒下載完整,刪除本地倉庫,重新update一下,OK.
項目結(jié)構(gòu)
<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>com.shiro.pro</groupId>
<artifactId>imooc-shiro</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.4.0</version>
</dependency>
</dependencies>
</project>
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<!-- Shiro攔截器 -->
<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>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:spring/spring.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<listener>
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:spring/springmvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
spring.xml
<?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:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd">
<bean id="shiroFilter"
class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager" /> <!-- 配置SecurityManager對象 -->
<property name="loginUrl" value="login.html" /><!-- 登錄頁的URL -->
<property name="unauthorizedUrl" value="403.html" /><!-- 未認證的跳轉(zhuǎn)頁面 -->
<property name="filterChainDefinitions"><!-- 過濾器鏈 -->
<value><!-- 從上往下匹配 -->
/login.html = anon <!-- 不需要驗證,直接訪問 -->
/subLogin = anon <!-- 提交表單的頁面也不需要驗證 -->
/* = authc <!-- 需要經(jīng)過認證才可以訪問相應(yīng)的路徑 -->
</value>
</property>
</bean>
<!-- 創(chuàng)建SecurityManager對象 -->
<bean class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"
id="securityManager">
<property name="realm" ref="realm" />
</bean>
<!-- 創(chuàng)建Realm對象,將Realm設(shè)置到SecurityManager環(huán)境中 -->
<bean class="com.imooc.shiro.realm.CustomRealm" id="realm">
<property name="credentialsMatcher" ref="credentialsMatcher" />
</bean>
<!-- 設(shè)置加密的算法 -->
<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher" id="credentialsMatcher" >
<property name="hashAlgorithmName" value="md5" /><!-- 設(shè)置加密算法 -->
<property name="hashIterations" value="1"/><!-- 設(shè)置加密次數(shù) -->
</bean>
</beans>
spring-mvc.xml
<?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"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd">
<context:component-scan base-package="com.imooc.controller"/>
<mvc:annotation-driven/>
<!--排除靜態(tài)文件-->
<mvc:resources mapping="/*" location="/"/>
</beans>
User.java
public class User {
private String username;
private String password;
// 省略getter和setter
}
UserController.java
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
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.ResponseBody;
import com.imooc.vo.User;
@Controller
public class UserController {
@RequestMapping(value = "/subLogin",method=RequestMethod.POST, produces="application/json; charset=UTF-8")
@ResponseBody // produces="application/json; charset=UTF-8"解決返回的JSON亂碼問題
public String subLogin(User user) {
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(),user.getPassword()) ;
try {
subject.login(token);
} catch (AuthenticationException e) {
return e.getMessage() ;
}
return "登錄成功";
}
}
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
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.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.crypto.hash.Md5Hash;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
/**
* 自定義Realm
*
* @author Administrator
*
*/
public class CustomRealm extends AuthorizingRealm {
Map<String, String> userMap = new HashMap<>();
{
userMap.put("Mark", "123456"); // Md5解密后的密文
}
/**
* 授權(quán)
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String userName = (String) principals.getPrimaryPrincipal();
// 從數(shù)據(jù)庫或緩存中獲取角色數(shù)據(jù)
Set<String> roles = getRelosByUserName(userName);
Set<String> permissions = getPermissionsByUserName(userName);
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.setRoles(roles);
simpleAuthorizationInfo.setStringPermissions(permissions);
return simpleAuthorizationInfo;
}
/**
* 模擬數(shù)據(jù)庫獲取角色數(shù)據(jù)
*
* @param userName
* @return
*/
private Set<String> getRelosByUserName(String userName) {
Set<String> roles = new HashSet<>();
roles.add("admin");
roles.add("user");
return roles;
}
/**
* 模擬數(shù)據(jù)庫獲取權(quán)限數(shù)據(jù)
*
* @param userName
* @return
*/
private Set<String> getPermissionsByUserName(String userName) {
Set<String> permissions = new HashSet<>();
permissions.add("user:delete");
permissions.add("user:update");
return permissions;
}
/**
* 認證
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 1、通過主體傳來的認證信息中,獲取用戶名
String userName = (String) token.getPrincipal();
// 2、通過用戶名到數(shù)據(jù)庫中獲取憑證
String password = getPasswordByUserName(userName);
if (password == null) {
return null;
}
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo("Mark", password, "customRealm");
return authenticationInfo;
}
/**
* 模擬數(shù)據(jù)庫查詢憑證
*
* @param userName
* @return
*/
private String getPasswordByUserName(String userName) {
return userMap.get(userName);
}
}
Shiro繼承Spring-JDBC
pom.xml 添加
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
spring-dao.xml
<?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:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd">
<bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource">
<property name="url" value="jdbc:mysql://localhost:3306/test" />
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>
<bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
spring.xml 添加
<import resource="spring-dao.xml"/><!-- 導(dǎo)入JDBC的xml配置 -->
UserController.java
@RequestMapping(value = "/subLogin",method=RequestMethod.POST, produces="application/json; charset=UTF-8")
@ResponseBody // produces="application/json; charset=UTF-8"解決返回的JSON亂碼問題
public String subLogin(User user) {
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(),user.getPassword()) ;
try {
subject.login(token);
} catch (AuthenticationException e) {
return e.getMessage() ;
}
if(subject.hasRole("admin")) { // 判斷是否有admin權(quán)限
return "登錄成功";
}
return "登錄失敗";
}
UserDaoImpl.java
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Set;
import javax.annotation.Resource;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;
import com.imooc.dao.UserDao;
import com.imooc.vo.User;
@Repository
public class UserDaoImpl implements UserDao{
@Resource
private JdbcTemplate jdbcTemplate ;
@Override
public User getUserByUserName(String userName) {
final String sql = "SELECT username,password FROM users WHERE username = ? " ;
User user = jdbcTemplate.queryForObject(sql, new String[] {userName}, new RowMapper<User>() {
@Override
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
User user = new User() ;
user.setUsername(rs.getString("username"));
user.setPassword(rs.getString("password"));
return user;
}
});
return user ;
/*
List<User> userList = jdbcTemplate.query(sql, new String[] {userName}, new RowMapper<User>(){
@Override
public User mapRow(ResultSet resultSet, int i) throws SQLException {
User user = new User() ;
user.setUsername(resultSet.getString("username"));
user.setPassword(resultSet.getString("password"));
return user;
}
});
if(CollectionUtils.isEmpty(userList)) {
return null ;
}
return userList.get(0);
*/
}
@Override
public List<String> queryRelosByUserName(String userName) {
final String sql = "SELECT role_name FROM user_roles WHERE username = ? " ;
return jdbcTemplate.query(sql, new String[] {userName}, new RowMapper<String>(){
@Override
public String mapRow(ResultSet resultSet, int i) throws SQLException {
return (resultSet.getString("role_name"));
}
});
}
}
CustomRealm.java
@Resource
private UserDao userDao ;
/**
* 模擬數(shù)據(jù)庫獲取角色數(shù)據(jù)
*
* @param userName
* @return
*/
private Set<String> getRelosByUserName(String userName) {
List<String> list =userDao.queryRelosByUserName(userName);
Set<String> roles = new HashSet<>(list);
return roles;
}
/**
* 模擬數(shù)據(jù)庫查詢憑證
*
* @param userName
* @return
*/
private String getPasswordByUserName(String userName) {
User user = userDao.getUserByUserName(userName);
if(user != null) {
return user.getPassword() ;
}
return null ;
}
通過注解配置授權(quán)
pom.xml添加
<!-- 通過注解授權(quán) -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.9</version>
</dependency>
springmvc.xml
<?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"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.2.xsd">
<context:component-scan base-package="com.imooc.controller"/>
<mvc:annotation-driven/>
<!--排除靜態(tài)文件-->
<mvc:resources mapping="/*" location="/"/>
<!-- Shiro通過注解控制權(quán)限 -->
<aop:config proxy-target-class="true"/>
<!-- 保證Shiro內(nèi)部執(zhí)行Lifecycle對象 -->
<bean class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/><!-- 注入securityManager對象 -->
</bean>
</beans>
UserController.java
@RequiresRoles("admin") // 只有admin權(quán)限才可以訪問這個鏈接;可以傳入多個權(quán)限值
@RequestMapping(value="/testAnnoRole",method=RequestMethod.GET)
@ResponseBody
public String testAnnoRole() {
return "@RequiresRoles注解控制權(quán)限" ; // 頁面中文亂碼
}
@RequiresPermissions("user:delete") // 可以傳入多個操作權(quán)限值,表示當(dāng)前主體具備括號中相應(yīng)的操作權(quán)限時,才可以訪問相應(yīng)的方法
@RequestMapping(value="/testAnnoRole2",method=RequestMethod.GET)
@ResponseBody
public String testAnnoRole2() {
return "@RequiresPermissions注解控制權(quán)限" ;
}
Shiro過濾器
- Shiro內(nèi)置過濾器
- anon、authBasic、authc、user、logout :和認證相關(guān)的過濾器
- anon:不需要認證直接訪問
- authBasic:HTTP Basic
- authc:需要認證后才可以訪問
- user:需要當(dāng)前存在用戶才可以訪問
- logout:退出
- perms、roles、ssl、port:授權(quán)相關(guān)的過濾器
- perms:需要具備相關(guān)權(quán)限才可訪問
- roles:和perms差不多,需要滿足其中括號后面一些的角色才可以訪問
- ssl:要求安全的協(xié)議,即HTTPS
- port:要求端口時其后面的設(shè)定的端口
- anon、authBasic、authc、user、logout :和認證相關(guān)的過濾器
spring.xml
<bean id="shiroFilter"
class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager" /> <!-- 配置SecurityManager對象 -->
<property name="loginUrl" value="login.html" /><!-- 登錄頁的URL -->
<property name="unauthorizedUrl" value="403.html" /><!-- 未認證的跳轉(zhuǎn)頁面 -->
<property name="filterChainDefinitions"><!-- 過濾器鏈 -->
<value><!-- 從上往下匹配 -->
/login.html = anon <!-- 不需要驗證,直接訪問 -->
/subLogin = anon <!-- 提交表單的頁面也不需要驗證 -->
/testRoles = roles["admin"] <!-- /testRoles需要admin權(quán)限才可以訪問 -->
/testRoles1 = roles["admin","admin1"] <!-- /testRoles1需要同時具有admin和admin1的權(quán)限才可以訪問 -->
/testPerms = perms["user:delete"] <!-- /testPerms需要具備user:delete的操作權(quán)限才可以訪問 -->
/testPerms1 = perms["user:delete","user:update"] <!-- /testPerms1需要同時具備user:delete和user:update的操作權(quán)限才可以訪問 -->
/* = authc <!-- 需要經(jīng)過認證才可以訪問相應(yīng)的路徑 -->
</value>
</property>
</bean>
UserController.java
@RequestMapping(value="/testRoles",method=RequestMethod.GET)
@ResponseBody
public String testRoles() {
return "testRoles success" ;
}
@RequestMapping(value="/testRoles1",method=RequestMethod.GET)
@ResponseBody
public String testRoles1() {
return "testRoles1 success" ;
}
@RequestMapping(value="/testPerms",method=RequestMethod.GET)
@ResponseBody
public String testPerms() {
return "testPerms success" ;
}
@RequestMapping(value="/testPerms1",method=RequestMethod.GET)
@ResponseBody
public String testPerms1() {
return "testPerms1 success" ;
}
自定義filter
pom.xml添加
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.0</version>
<scope>provided</scope>
</dependency>
- 不引入該包,繼承AuthorizationFilter會報The hierarchy of the type RoleOrFilter is inconsistent,由于isAccessAllowed()方法的參數(shù)ServletRequest 和ServletResponse本地沒有依賴
RoleOrFilter.java
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authz.AuthorizationFilter;
/**
* 實現(xiàn)權(quán)限or的驗證
* @author Administrator
*
*/
public class RoleOrFilter extends AuthorizationFilter {
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception{
Subject subject = getSubject(request, response);
String[] roles = (String[]) mappedValue ;
if(roles ==null || roles.length == 0) {
return true;
}
for (String role : roles) {
if(subject.hasRole(role)) {
return true ;
}
}
return false;
}
}
- 授權(quán)相關(guān)的Filter繼承AuthorizationFilter
- 認證相關(guān)的Filter繼承AuthenticationFilter
spring.xml
<?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:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-4.2.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd">
<import resource="spring-dao.xml"/><!-- 導(dǎo)入JDBC的xml配置 -->
<context:component-scan base-package="com.imooc"/>
<bean id="shiroFilter"
class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager" /> <!-- 配置SecurityManager對象 -->
<property name="loginUrl" value="login.html" /><!-- 登錄頁的URL -->
<property name="unauthorizedUrl" value="403.html" /><!-- 未認證的跳轉(zhuǎn)頁面 -->
<property name="filterChainDefinitions"><!-- 過濾器鏈 -->
<value><!-- 從上往下匹配 -->
/login.html = anon <!-- 不需要驗證,直接訪問 -->
/subLogin = anon <!-- 提交表單的頁面也不需要驗證 -->
/testRoles = roles["admin","admin1"] <!-- /testRoles需要admin權(quán)限才可以訪問 -->
<!-- /testRoles1 = roles["admin","admin1"] /testRoles1需要同時具有admin和admin1的權(quán)限才可以訪問 -->
/testRoles1 = rolesOr["admin","admin1"]
<!-- /testPerms = perms["user:delete"] /testPerms需要具備user:delete的操作權(quán)限才可以訪問 -->
<!-- /testPerms1 = perms["user:delete","user:update"] /testPerms1需要同時具備user:delete和user:update的操作權(quán)限才可以訪問 -->
/* = authc <!-- 需要經(jīng)過認證才可以訪問相應(yīng)的路徑 -->
</value>
</property>
<property name="filters">
<util:map>
<entry key="rolesOr" value-ref="rolesOrFilter"/>
</util:map>
</property>
</bean>
<bean class="com.imooc.filter.RoleOrFilter" id="rolesOrFilter"/>
<!-- 其他省略,和之前一樣 -->
</beans>
Shiro會話管理
- Shiro Session管理:Shiro自己實現(xiàn)了一套Session管理體系,再不借助任何Web容器或Servlet的情況下,可以使用Session
- SessionManager、SessionDAO
- Redis實現(xiàn)Session共享
- Redis實現(xiàn)Session共享存在的問題
pom.xml添加
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.8.0</version>
</dependency>
spring-redis.xml
<?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-4.2.xsd">
<bean class="redis.clients.jedis.JedisPoo" id="jedisPool">
<constructor-arg ref="jedisPoolConfig"></constructor-arg>
<constructor-arg value="127.0.0.1"></constructor-arg>
<constructor-arg value="6379"></constructor-arg>
</bean>
<bean class="redis.clients.jedis.JedisPoolConfig" id="jedisPoolConfig"/>
</beans>
spring.xml加入
<import resource="spring-redis.xml"/>
<!-- 創(chuàng)建SecurityManager對象 -->
<bean class="org.apache.shiro.web.mgt.DefaultWebSecurityManager" id="securityManager">
<property name="realm" ref="realm" />
<property name="sessionManager" ref="sessionManager"></property>
</bean>
<bean class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager" id="sessionManager">
<property name="sessionDAO" ref="redisSessionDao"/>
</bean>
<bean class="com.imooc.session.RedisSessionDao" id="redisSessionDao"/>
JedisUtil.java
import java.util.Set;
import javax.annotation.Resource;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
@Component
public class JedisUtil {
@Resource
private JedisPool jedisPool ;
/**
* 獲取連接
*/
private Jedis getResource() {
return jedisPool.getResource() ;
}
/**
* 設(shè)置session
* @param key
* @param value
* @return
*/
public byte[] set(byte[] key, byte[] value) {
Jedis jedis = getResource() ;
try {
jedis.set(key, value);
return value ;
}finally {
jedis.close();
}
}
/**
* 通過key獲取序列化的session值
* @param key
* @return
*/
public byte[] get(byte[] key) {
Jedis jedis = getResource() ;
try {
return jedis.get(key);
}finally {
jedis.close();
}
}
/**
* 設(shè)置指定key的超時時間
* @param key
* @param i
*/
public void expire(byte[] key, int i) {
Jedis jedis = getResource() ;
try {
jedis.expire(key, i);
}finally {
jedis.close();
}
}
/**
* 刪除key
* @param key
*/
public void del(byte[] key) {
Jedis jedis = getResource() ;
try {
jedis.del(key);
}finally {
jedis.close();
}
}
/**
* 獲取指定前綴的所有key
* @param shiro_session_prefix
* @return
*/
public Set<byte[]> keys(String shiro_session_prefix) {
Jedis jedis = getResource() ;
try {
return jedis.keys((shiro_session_prefix + "*").getBytes());
}finally {
jedis.close();
}
}
}
RedisSessionDao.java
import java.io.Serializable;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import javax.annotation.Resource;
import org.apache.commons.collections.CollectionUtils;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.eis.AbstractSessionDAO;
import org.springframework.util.SerializationUtils;
import com.imooc.util.JedisUtil;
public class RedisSessionDao extends AbstractSessionDAO{
@Resource
private JedisUtil jedisUtil ;
private final String SHIRO_SESSION_PREFIX = "imooc-session:" ;
/**
* 在Redis中以二進制的形式存儲
* @param key
* @return
*/
private byte[] getKey(String key) {
return ( SHIRO_SESSION_PREFIX + key ).getBytes() ;
}
/**
* 保存session
*/
private void saveSession(Session session) {
if(session != null && session.getId() != null) {
byte[] key = getKey(session.getId().toString()) ;
byte[] value = SerializationUtils.serialize(session); // 將Session對象保存為byte數(shù)組
jedisUtil.set(key,value) ;
jedisUtil.expire(key,600);
}
}
@Override
public void update(Session session) throws UnknownSessionException {
saveSession(session) ;
}
@Override
public void delete(Session session) {
if( session == null || session.getId() == null ) {
return ;
}
byte[] key = getKey(session.getId().toString());
jedisUtil.del(key) ;
}
/**
* 獲取指定的存活的session
*/
@Override
public Collection<Session> getActiveSessions() {
Set<byte[]> keys = jedisUtil.keys(SHIRO_SESSION_PREFIX) ; // 通過之前添加的前綴獲取所有的key
Set<Session> sessions = new HashSet<>() ;
if(CollectionUtils.isEmpty(keys)) {
return sessions ;
}
for (byte[] key : keys) {
Session session = (Session) SerializationUtils.deserialize( jedisUtil.get(key));
sessions.add(session) ;
}
return sessions ;
}
@Override
protected Serializable doCreate(Session session) {
Serializable sessionId = generateSessionId(session);
saveSession(session) ;
return sessionId;
}
@Override
protected Session doReadSession(Serializable sessionId) {
// 這塊會連續(xù)讀取很多遍,影響性能;需要改造
System.out.println("Read Session");
if(sessionId == null ) {
return null ;
}
byte[] key = getKey(sessionId.toString());
byte[] value = jedisUtil.get(key) ;
return (Session) SerializationUtils.deserialize(value); // 將byte數(shù)組反序列化成session對象
}
}
解決多次訪問Redis的問題
CustomSessionManager.java
import java.io.Serializable;
import javax.servlet.ServletRequest;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.SessionKey;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.session.mgt.WebSessionKey;
/**
* 優(yōu)化連續(xù)讀取session的帶來的性能差
* @author Administrator
*
*/
public class CustomSessionManager extends DefaultWebSessionManager {
@Override
protected Session retrieveSession(SessionKey sessionKey) throws UnknownSessionException {
Serializable sessionId = getSessionId(sessionKey);
ServletRequest request = null ;
if(sessionKey instanceof WebSessionKey) {
request = ((WebSessionKey) sessionKey).getServletRequest() ;
}
if(request != null && sessionId != null) {
Session session = (Session)request.getAttribute(sessionId.toString()) ;
if(session != null) {
return session ;
}
}
Session session = super.retrieveSession(sessionKey);
if(request != null && sessionId != null) {
request.setAttribute(sessionId.toString(), session);
}
return session;
}
}
spring.xml
<bean class="com.imooc.session.CustomSessionManager" id="sessionManager">
<property name="sessionDAO" ref="redisSessionDao"/>
</bean>
Shiro緩存管理
RedisCacheManager.java
import javax.annotation.Resource;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.apache.shiro.cache.CacheManager;
public class RedisCacheManager implements CacheManager {
@Resource
private RedisCache redisCache ;
@Override
public <K, V> Cache<K, V> getCache(String arg0) throws CacheException {
return redisCache;
}
}
RedisCache.java
import java.util.Collection;
import java.util.Set;
import javax.annotation.Resource;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.springframework.stereotype.Component;
import org.springframework.util.SerializationUtils;
import com.imooc.util.JedisUtil;
@Component
public class RedisCache<K, V> implements Cache<K, V> {
@Resource
JedisUtil jedisUtil ;
private final String CACHE_PREFIX = "imooc-cache:" ;
private byte[] getKey(K k) {
if(k instanceof String) {
return (CACHE_PREFIX + k).getBytes() ;
}
return SerializationUtils.serialize(k);
}
@Override
public void clear() throws CacheException {
// TODO Auto-generated method stub
}
@Override
public V get(K k) throws CacheException {
System.out.println("從Redis中獲取授權(quán)數(shù)據(jù)"); // 先從Redis中獲取權(quán)限數(shù)據(jù) - 這塊可以利用本地的二級緩存
byte[] value = jedisUtil.get(getKey(k)) ;
if(value != null) {
return (V)SerializationUtils.deserialize(value) ;
}
return null;
}
@Override
public Set<K> keys() {
// TODO Auto-generated method stub
return null;
}
@Override
public V put(K k, V v) throws CacheException {
byte[] key = getKey(k);
byte[] value = SerializationUtils.serialize(v);
jedisUtil.set(key, value);
jedisUtil.expire(key, 600);
return v;
}
@Override
public V remove(K k) throws CacheException {
byte[] key = getKey(k);
byte[] value = jedisUtil.get(key) ;
jedisUtil.del(key);
if(value != null) {
return (V) SerializationUtils.deserialize(value) ;
}
return null;
}
@Override
public int size() {
// TODO Auto-generated method stub
return 0;
}
@Override
public Collection<V> values() {
// TODO Auto-generated method stub
return null;
}
}
spring.xml
<!-- 創(chuàng)建SecurityManager對象 -->
<bean class="org.apache.shiro.web.mgt.DefaultWebSecurityManager" id="securityManager">
<property name="realm" ref="realm" />
<property name="sessionManager" ref="sessionManager"></property>
<property name="cacheManager" ref="cacheManager"></property>
</bean>
<!-- 創(chuàng)建RedisCacheManager對象 -->
<bean class="com.imooc.cache.RedisCacheManager" id="cacheManager"/>
CustomRealm.java
/**
* 模擬數(shù)據(jù)庫獲取角色數(shù)據(jù)
*
* @param userName
* @return
*/
private Set<String> getRelosByUserName(String userName) {
System.out.println("從數(shù)據(jù)庫中獲取授權(quán)數(shù)據(jù)");
List<String> list =userDao.queryRelosByUserName(userName);
Set<String> roles = new HashSet<>(list);
return roles;
}
Shiro自動登錄
- Shiro RememberMe
spring.xml
<!-- 創(chuàng)建SecurityManager對象 -->
<bean class="org.apache.shiro.web.mgt.DefaultWebSecurityManager" id="securityManager">
<property name="realm" ref="realm" />
<property name="sessionManager" ref="sessionManager"/>
<property name="cacheManager" ref="cacheManager"/>
<property name="rememberMeManager" ref="cookieRememberMeManager" />
</bean>
<bean class="org.apache.shiro.web.mgt.CookieRememberMeManager" id="cookieRememberMeManager">
<property name="cookie" ref="cookie" />
</bean>
<bean class="org.apache.shiro.web.servlet.SimpleCookie" id="cookie">
<constructor-arg value="rememberMe" /><!-- cookie名稱 -->
<property name="maxAge" value="60" />
</bean>
login.html
<form action="subLogin" method="post">
用戶名:<input type="text" name="username" /><br>
密碼:<input type="password" name="password"><br>
<input type="checkbox" name="rememberMe" />記住我<br>
<input type="submit" value="登錄">
</form>
User.java
private boolean rememberMe ;
UserController.java
try {
token.setRememberMe(user.getRememberMe()); // 設(shè)置是否記住
subject.login(token);
} catch (AuthenticationException e) {
return e.getMessage() ;
}
