-
什么是Shiro
- Apache的强大灵活的开源安全框架
- 认证、授权、企业会话管理、安全加密
-
Shiro 与 Spring Security 比较
- Apache Shiro
- 简单、灵活
- 可脱离Spring
- 粒度较粗
- Spring Security
- 复杂、笨重
- 不可脱离Spring
- 粒度更细
- Apache Shiro
Authenticator认证器:登录登出
Authorizer授权器:赋予主体有哪些权限
Session Manager - Session管理器:Shiro自身实现的Session管理机制,可以在不借助任何Web容器的情况下使用Session。
Session DAO - Session的操作:CRUD
Cache Manager缓存管理器:缓存角色数据和权限数据
Realms:Shiro和数据库、数据源的一个桥梁,Shiro获取认证信息、权限数据、角色数据都是通过该部分。
主体提交请求,到Security Manager调用Authenticator认证;Authenticator获取认证数据是通过Realms获取,从数据库获取的认证信息和主体提交的认证数据去做比对;
主体赋予权限也是通过Realms从数据库或者缓存中来获取我们的角色数据或权限数据。
Cryptography加密:可以使用其快速加密数据。
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"); // 模拟查询出用户名及权限,角色可以有多个
}
@Test
public void loginAuthentication() {
// 1、构建SecurityManager环境
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
defaultSecurityManager.setRealm(simpleAccountRealm); // 将Realm设置到SecurityManager环境中
SecurityUtils.setSecurityManager(defaultSecurityManager); // 先要设置SecurityManager环境
// 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 取认证数据
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"); // 模拟查询出用户名及权限,角色可以有多个
}
@Test
public void powerAuthentication() {
// 1、构建SecurityManager环境
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
defaultSecurityManager.setRealm(simpleAccountRealm); // 将Realm设置到SecurityManager环境中
SecurityUtils.setSecurityManager(defaultSecurityManager); // 先要设置SecurityManager环境
// 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 取认证数据
System.out.println("isAuthenticated:" + flag);
subject.checkRole("admin"); // 检查用户是否具备该角色,可以检查多个
}
}
- Shiro自定义Realm
- 内置Realm:
- IniRealm
- JdbcRealm
- 内置Realm:
user.ini文件
[users]
Mark=123456,admin // 用户名=密码,角色
[roles]
admin=user:delete // 角色=拥有删除用户的权限
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、构建SecurityManager环境
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
defaultSecurityManager.setRealm(iniRealm);
SecurityUtils.setSecurityManager(defaultSecurityManager); // 先要设置SecurityManager环境
// 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 取认证数据
System.out.println("isAuthenticated:" + flag);
subject.checkRole("admin"); // 验证角色
subject.checkPermission("user:delete "); // 验证权限
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(); // 需要访问数据库
// 设置数据源
jdbcRealm.setDataSource(dataSource);
jdbcRealm.setPermissionsLookupEnabled(true); // 使用JDBC需要设置权限开关,否则即使配置权限仍不能认证。默认为false。
// 自定义sql查询认证与权限,不设置则使用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、构建SecurityManager环境
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
defaultSecurityManager.setRealm(jdbcRealm);
SecurityUtils.setSecurityManager(defaultSecurityManager); // 先要设置SecurityManager环境
// 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 取认证数据
System.out.println("isAuthenticated:" + flag);
subject.checkRole("admin"); // 验证角色
subject.checkPermission("user:delete"); // 验证权限
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解密后的密文
}
/**
* 授权
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String userName = (String) principals.getPrimaryPrincipal();
// 从数据库或缓存中获取角色数据
Set<String> roles = getRelosByUserName(userName);
Set<String> permissions = getPermissionsByUserName(userName);
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.setRoles(roles);
simpleAuthorizationInfo.setStringPermissions(permissions);
return simpleAuthorizationInfo;
}
/**
* 模拟数据库获取角色数据
*
* @param userName
* @return
*/
private Set<String> getRelosByUserName(String userName) {
Set<String> roles = new HashSet<>();
roles.add("admin");
roles.add("user");
return roles;
}
/**
* 模拟数据库获取权限数据
*
* @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、通过用户名到数据库中获取凭证
String password = getPasswordByUserName(userName);
if (password == null) {
return null;
}
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo("Mark", password, "customRealm");
// 密码加密设置盐,此时需要设置盐
authenticationInfo.setCredentialsSalt(ByteSource.Util.bytes(userName));
return authenticationInfo;
}
/**
* 模拟数据库查询凭证
*
* @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、构建SecurityManager环境
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
defaultSecurityManager.setRealm(customRealm);
SecurityUtils.setSecurityManager(defaultSecurityManager); // 先要设置SecurityManager环境
// 加密
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher() ;
matcher.setHashAlgorithmName("md5"); // 设置算法名称
matcher.setHashIterations(1); // 设置加密次数
customRealm.setCredentialsMatcher(matcher); // 设置加密的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 取认证数据
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.
项目结构
<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" /><!-- 未认证的跳转页面 -->
<property name="filterChainDefinitions"><!-- 过滤器链 -->
<value><!-- 从上往下匹配 -->
/login.html = anon <!-- 不需要验证,直接访问 -->
/subLogin = anon <!-- 提交表单的页面也不需要验证 -->
/* = authc <!-- 需要经过认证才可以访问相应的路径 -->
</value>
</property>
</bean>
<!-- 创建SecurityManager对象 -->
<bean class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"
id="securityManager">
<property name="realm" ref="realm" />
</bean>
<!-- 创建Realm对象,将Realm设置到SecurityManager环境中 -->
<bean class="com.imooc.shiro.realm.CustomRealm" id="realm">
<property name="credentialsMatcher" ref="credentialsMatcher" />
</bean>
<!-- 设置加密的算法 -->
<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher" id="credentialsMatcher" >
<property name="hashAlgorithmName" value="md5" /><!-- 设置加密算法 -->
<property name="hashIterations" value="1"/><!-- 设置加密次数 -->
</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/>
<!--排除静态文件-->
<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解密后的密文
}
/**
* 授权
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String userName = (String) principals.getPrimaryPrincipal();
// 从数据库或缓存中获取角色数据
Set<String> roles = getRelosByUserName(userName);
Set<String> permissions = getPermissionsByUserName(userName);
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.setRoles(roles);
simpleAuthorizationInfo.setStringPermissions(permissions);
return simpleAuthorizationInfo;
}
/**
* 模拟数据库获取角色数据
*
* @param userName
* @return
*/
private Set<String> getRelosByUserName(String userName) {
Set<String> roles = new HashSet<>();
roles.add("admin");
roles.add("user");
return roles;
}
/**
* 模拟数据库获取权限数据
*
* @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、通过用户名到数据库中获取凭证
String password = getPasswordByUserName(userName);
if (password == null) {
return null;
}
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo("Mark", password, "customRealm");
return authenticationInfo;
}
/**
* 模拟数据库查询凭证
*
* @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"/><!-- 导入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权限
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 ;
/**
* 模拟数据库获取角色数据
*
* @param userName
* @return
*/
private Set<String> getRelosByUserName(String userName) {
List<String> list =userDao.queryRelosByUserName(userName);
Set<String> roles = new HashSet<>(list);
return roles;
}
/**
* 模拟数据库查询凭证
*
* @param userName
* @return
*/
private String getPasswordByUserName(String userName) {
User user = userDao.getUserByUserName(userName);
if(user != null) {
return user.getPassword() ;
}
return null ;
}
通过注解配置授权
pom.xml添加
<!-- 通过注解授权 -->
<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/>
<!--排除静态文件-->
<mvc:resources mapping="/*" location="/"/>
<!-- Shiro通过注解控制权限 -->
<aop:config proxy-target-class="true"/>
<!-- 保证Shiro内部执行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权限才可以访问这个链接;可以传入多个权限值
@RequestMapping(value="/testAnnoRole",method=RequestMethod.GET)
@ResponseBody
public String testAnnoRole() {
return "@RequiresRoles注解控制权限" ; // 页面中文乱码
}
@RequiresPermissions("user:delete") // 可以传入多个操作权限值,表示当前主体具备括号中相应的操作权限时,才可以访问相应的方法
@RequestMapping(value="/testAnnoRole2",method=RequestMethod.GET)
@ResponseBody
public String testAnnoRole2() {
return "@RequiresPermissions注解控制权限" ;
}
Shiro过滤器
- Shiro内置过滤器
- anon、authBasic、authc、user、logout :和认证相关的过滤器
- anon:不需要认证直接访问
- authBasic:HTTP Basic
- authc:需要认证后才可以访问
- user:需要当前存在用户才可以访问
- logout:退出
- perms、roles、ssl、port:授权相关的过滤器
- perms:需要具备相关权限才可访问
- roles:和perms差不多,需要满足其中括号后面一些的角色才可以访问
- ssl:要求安全的协议,即HTTPS
- port:要求端口时其后面的设定的端口
- anon、authBasic、authc、user、logout :和认证相关的过滤器
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" /><!-- 未认证的跳转页面 -->
<property name="filterChainDefinitions"><!-- 过滤器链 -->
<value><!-- 从上往下匹配 -->
/login.html = anon <!-- 不需要验证,直接访问 -->
/subLogin = anon <!-- 提交表单的页面也不需要验证 -->
/testRoles = roles["admin"] <!-- /testRoles需要admin权限才可以访问 -->
/testRoles1 = roles["admin","admin1"] <!-- /testRoles1需要同时具有admin和admin1的权限才可以访问 -->
/testPerms = perms["user:delete"] <!-- /testPerms需要具备user:delete的操作权限才可以访问 -->
/testPerms1 = perms["user:delete","user:update"] <!-- /testPerms1需要同时具备user:delete和user:update的操作权限才可以访问 -->
/* = authc <!-- 需要经过认证才可以访问相应的路径 -->
</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()方法的参数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;
/**
* 实现权限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;
}
}
- 授权相关的Filter继承AuthorizationFilter
- 认证相关的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"/><!-- 导入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" /><!-- 未认证的跳转页面 -->
<property name="filterChainDefinitions"><!-- 过滤器链 -->
<value><!-- 从上往下匹配 -->
/login.html = anon <!-- 不需要验证,直接访问 -->
/subLogin = anon <!-- 提交表单的页面也不需要验证 -->
/testRoles = roles["admin","admin1"] <!-- /testRoles需要admin权限才可以访问 -->
<!-- /testRoles1 = roles["admin","admin1"] /testRoles1需要同时具有admin和admin1的权限才可以访问 -->
/testRoles1 = rolesOr["admin","admin1"]
<!-- /testPerms = perms["user:delete"] /testPerms需要具备user:delete的操作权限才可以访问 -->
<!-- /testPerms1 = perms["user:delete","user:update"] /testPerms1需要同时具备user:delete和user:update的操作权限才可以访问 -->
/* = authc <!-- 需要经过认证才可以访问相应的路径 -->
</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自己实现了一套Session管理体系,再不借助任何Web容器或Servlet的情况下,可以使用Session
- SessionManager、SessionDAO
- Redis实现Session共享
- Redis实现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"/>
<!-- 创建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() ;
}
/**
* 设置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();
}
}
/**
* 设置指定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数组
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) {
// 这块会连续读取很多遍,影响性能;需要改造
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数组反序列化成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;
/**
* 优化连续读取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中获取授权数据"); // 先从Redis中获取权限数据 - 这块可以利用本地的二级缓存
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
<!-- 创建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>
<!-- 创建RedisCacheManager对象 -->
<bean class="com.imooc.cache.RedisCacheManager" id="cacheManager"/>
CustomRealm.java
/**
* 模拟数据库获取角色数据
*
* @param userName
* @return
*/
private Set<String> getRelosByUserName(String userName) {
System.out.println("从数据库中获取授权数据");
List<String> list =userDao.queryRelosByUserName(userName);
Set<String> roles = new HashSet<>(list);
return roles;
}
Shiro自动登录
- Shiro RememberMe
spring.xml
<!-- 创建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()); // 设置是否记住
subject.login(token);
} catch (AuthenticationException e) {
return e.getMessage() ;
}