Shiro整合SSM 实现认证、授权、会话管理、多Realm、记住我功能
一、环境搭建
1.1 pom.xml引入依赖
- shiro依赖:
<!--shiro start-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-all</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.4.1</version>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache-core</artifactId>
<version>2.6.11</version>
</dependency>
<!--shiro end-->
- ssm+shiro所有依赖:
<properties>
<spring.version>4.3.30.RELEASE</spring.version>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<version>7.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-all</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.4.1</version>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache-core</artifactId>
<version>2.6.11</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-instrument</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-instrument-tomcat</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jms</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-messaging</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-oxm</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc-portlet</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- mybatis核心包 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.40</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.8.7</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.8.7</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.8.7</version>
</dependency>
<!--打印日志 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.5</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.5</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.6</version>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.2</version>
</dependency>
</dependencies>
1.2 SSM其他配置文件整合(省略)
1.3 web.xml配置shiro的代理filter
<!--
配置Shiro的核心代理对象
-->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<!--
参数的意义:
默认targetFilterLifecycle为false,当他为false的时候shiroFilter代理对象默认加入到IOC容器中
并且在IOC容器中遵循IOC的生命周期管理,将其设置为true,让其受tomcat容器生命周期管理
将Filter的生命周期交给Tomcat服务器
-->
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
1.4 applicatioContext.xml中配置
-
开启shiro在controller层的注解功能
-
ShiroFilter配置
-
配置web的安全管理器
在安全管理器中设置多Realm管理器
配置多Realm
记住我
会话管理 -
配置多Realm管理器
设置多Realm管理器的策略 -
配置自定义realm
-
会话管理
<!--开启shiro注解功能-->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"></bean>
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor">
<property name="proxyTargetClass" value="true"></property>
</bean>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"></property>
</bean>
<!--
ShiroFilter配置
-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!--配置安全管理器-->
<property name="securityManager" ref="securityManager"></property>
</bean>
<!--配置web的安全管理器-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!--在安全管理器中设置多Realm管理器,重点: 多Realm策略,authenticator必须配置在realms上面,否则认证不通过-->
<property name="authenticator" ref="modularRealmAuthenticator"></property>
<!--配置多Realm-->
<property name="realms">
<list>
<ref bean="sysUserRealm1"></ref>
</list>
</property>
<!--配置完会话之后将其设置到安全管理器中-->
<!-- 会话管理-->
<!--<property name="sessionManager" ref="defaultWebSessionManager"></property>-->
<!--记住我,此时会话管理的10秒期限将被覆盖了,没有用了-->
<property name="rememberMeManager">
<bean class="org.apache.shiro.web.mgt.CookieRememberMeManager">
<property name="cookie">
<bean class="org.apache.shiro.web.servlet.SimpleCookie">
<!--自定义cookie名称-->
<constructor-arg value="qianfeng"></constructor-arg>
<!--防止前端js使用document.cookie获取cookie-->
<property name="httpOnly" value="true"></property>
<!--过期时间,默认在浏览器关闭是过期 -1 有效期30天-->
<property name="maxAge" value="2592000"></property>
</bean>
</property>
</bean>
</property>
</bean>
<!--配置多Realm管理器-->
<bean id="modularRealmAuthenticator" class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">
<!--设置多Realm管理器的策略-->
<property name="authenticationStrategy">
<!--
FirstSuccessfulStrategy : 当有一个Realm认证成功就为成功,只返回第一个Realm身份验证 成功的认证信息,其他的忽略
AtLeastOneSuccessfulStrategy(默认策略 ):只要有一个Realm验证成功即可,返回所有Realm身份验证成功的认证信息
AllSuccessfulStrategy:所有Realm验证成功才算成功,且返回所有Realm身份验证成功的认证信息,如果有一个失败就失败
-->
<bean class="org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy"></bean>
</property>
</bean>
<!--配置自定义realm1-->
<bean id="sysUserRealm1" class="com.qianfeng.realm.SysUsersRealm1">
<!--设置加密方式-->
<property name="credentialsMatcher" ref="credentialsMatcher"></property>
</bean>
<!--
配置Realm1密码加密
注意:加密方式是 MD5
-->
<bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<property name="hashAlgorithmName" value="MD5"></property>
<property name="hashIterations" value="1024"></property>
</bean>
<!-- 会话管理
<bean id="defaultWebSessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
<!–设置超时时间–>
<property name="globalSessionTimeout" value="10000"></property>
</bean>
-->
1.5 dispatcherServlet-servlet.xml中配置
<!--开启shiro注解-->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"></bean>
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor">
<property name="proxyTargetClass" value="true"></property>
</bean>
1.4 多Realm实现(常用)
- Shiro中提供了一个多Realm的管理类ModularRealmAuthenticator帮助Shiro框架进行多个Realm管理
<!--配置web的安全管理器-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!--在安全管理器中设置多Realm管理器,重点: 多Realm策略,authenticator必须配置在realms上面,否则认证不通过-->
<property name="authenticator" ref="modularRealmAuthenticator"></property>
<!--配置多Realm-->
<property name="realms">
<list>
<ref bean="userRealm01"></ref>
<ref bean="userRealm02"></ref>
</list>
</property>
</bean>
<!--配置多Realm管理器,realm策略管理-->
<bean id="modularRealmAuthenticator" class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">
<!--设置多Realm管理器的策略-->
<property name="authenticationStrategy">
<!--
FirstSuccessfulStrategy : 当有一个Realm认证成功就为成功,只返回第一个Realm身份验证 成功的认证信息,其他的忽略
AtLeastOneSuccessfulStrategy(默认策略 ):只要有一个Realm验证成功即可,返回所有Realm身份验证成功的认证信息
AllSuccessfulStrategy:所有Realm验证成功才算成功,且返回所有Realm身份验证成功的认证信息,如果有一个失败就失败
-->
<bean class="org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy"></bean>
</property>
</bean>
<!--配置Realm01-->
<bean id="userRealm01" class="com.qianfeng.shiro.SysUserRealm">
<!--使用内部bean设置加密方式-->
<property name="credentialsMatcher">
<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<property name="hashAlgorithmName" value="MD5"></property>
<property name="hashIterations" value="1024"></property>
</bean>
</property>
</bean>
<!--配置Realm02-->
<bean id="userRealm02" class="com.qianfeng.shiro.SysUserRealm">
<property name="credentialsMatcher">
<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<property name="hashAlgorithmName" value="SHA-1"></property>
<property name="hashIterations" value="1024"></property>
</bean>
</property>
</bean>
1.6 Shiro的会话管理
- Shiro多采用DefaultWebSessionManager进行会话管理
<!--会话管理-->
<bean id="webSessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
<!--会话超时时间,单位毫秒-->
<property name="globalSessionTimeout" value="10000"></property>
</bean>
<!--配置完会话之后将其设置到安全管理器中-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!--会话管理-->
<property name="sessionManager" ref="webSessionManager"></property>
</bean>
1.7 记住我功能
1.7.1 在配置文件中配置记住我功能(在安全管理器中)
<!--配置web的安全管理器-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!--在安全管理器中设置多Realm管理器,重点: 多Realm策略,authenticator必须配置在realms上面,否则认证不通过-->
<property name="authenticator" ref="modularRealmAuthenticator"></property>
<!--配置多Realm-->
<property name="realms">
<list>
<ref bean="sysUserRealm1"></ref>
</list>
</property>
<!--配置完会话之后将其设置到安全管理器中-->
<!-- 会话管理-->
<!--<property name="sessionManager" ref="defaultWebSessionManager"></property>-->
<!--记住我,此时会话管理的10秒期限将被覆盖了,没有用了-->
<property name="rememberMeManager">
<bean class="org.apache.shiro.web.mgt.CookieRememberMeManager">
<property name="cookie">
<bean class="org.apache.shiro.web.servlet.SimpleCookie">
<!--自定义cookie名称-->
<constructor-arg value="qianfeng"></constructor-arg>
<!--防止前端js使用document.cookie获取cookie-->
<property name="httpOnly" value="true"></property>
<!--过期时间,默认在浏览器关闭是过期 -1 有效期30天-->
<property name="maxAge" value="2592000"></property>
</bean>
</property>
</bean>
</property>
</bean>
1.7.2 控制器中修改登陆的规则(在登陆中调用Shiro记住我API方法)
/**
* 认证(登录)
* @param username
* @param password
* @param rm
* @param model
* @return
*/
@RequestMapping(value = "/login",method = RequestMethod.POST)
public String login(String username, String password, String rm,Model model){
System.out.println("登录...");
//获取当前用户实体
Subject subject = SecurityUtils.getSubject();
//将前端发送过来的用户名和密码封装进token中,调用subject中的login方法将其发送到安全管理器中
UsernamePasswordToken token = new UsernamePasswordToken(username,password);
//记住我
if(rm!=null && rm.equals("1")){
token.setRememberMe(true);//调用Shiro记住我API方法
}
try {
//将用户名和密码发送到安全管理器
subject.login(token);
} catch (UnknownAccountException uae) {
System.out.println("账户不存在....");
model.addAttribute("msg","账户不存在");
return "login";
} catch (IncorrectCredentialsException ice) {
System.out.println("密码错误....");
model.addAttribute("msg","密码错误");
return "login";
}catch (AuthenticationException ae) {
ae.printStackTrace();
System.out.println("其他认证异常...."+ae.getMessage());
model.addAttribute("msg","其他异常...");
return "login";
}
//登陆成功跳转列表页(列表页需要认证才可以访问)
return "home";
}
1.7.4 测试(打开浏览器调试模式)
- 记住我 登录后 会看见cookie
1.8 控制层代码编写
/**
* 认证(登录)
* @param empName
* @param empPassword
* @param rm
* @param model
* @return
*/
@ResponseBody
@RequestMapping(value = "/login",method = RequestMethod.POST)
public Result login(String empName , String empPassword, String rm, Model model){
System.out.println("登录...");
Result result = new Result();
Message message=new Message();
result.setMessage(message);
//获取当前用户实体
Subject subject = SecurityUtils.getSubject();
//将前端发送过来的用户名和密码封装进token中,调用subject中的login方法将其发送到安全管理器中
UsernamePasswordToken token = new UsernamePasswordToken(empName,empPassword);
//记住我
if(rm!=null && rm.equals("1")){
token.setRememberMe(true);//调用Shiro记住我API方法
}
try {
//将用户名和密码发送到安全管理器
subject.login(token);
} catch (UnknownAccountException uae) {
System.out.println("账户不存在....");
throw new OaException(OaEnum.LOGIN1_ERROR);
} catch (IncorrectCredentialsException ice) {
System.out.println("密码错误....");
throw new OaException(OaEnum.LOGIN2_ERROR);
}catch (AuthenticationException ae) {
ae.printStackTrace();
System.out.println("其他认证异常...."+ae.getMessage());
throw new OaException(OaEnum.LOGIN3_ERROR);
}
//登陆成功
return result;
}
1.9 统一异常处理捕获异常
/**
* 捕获未认证、未授权异常
* @param e
* @return
*/
@ResponseBody
@ExceptionHandler(value = {Exception.class})
public Result excetion(Exception e) {
System.out.println("进入异常");
e.printStackTrace();
Result result = new Result();
if (e instanceof UnauthenticatedException) {
System.out.println("未认证异常");
result.setMessage(new Message(901,"未认证异常"));
return result;
} else if (e instanceof UnauthorizedException) {
System.out.println("未授权异常");
result.setMessage(new Message(902,"未授权异常"));
return result;
}
return result;
}
2.0 自定义Realm
package com.qianfeng.realms;
import com.qianfeng.entity.*;
import com.qianfeng.service.*;
import jdk.nashorn.internal.parser.Token;
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.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* 自定义realm,实现从数据库中获取认证和授权数据,返回给安全管理器
*/
public class SysUserRealm extends AuthorizingRealm {
@Autowired
private SysUsersService sysUsersService;
@Autowired
private SysRolesService sysRolesService;
@Autowired
private SysUsersRolesService sysUsersRolesService;
@Autowired
private SysPermissionsService sysPermissionsService;
@Autowired
private SysRolesPermissionsService sysRolesPermissionsService;
/**
* 授权数据获取
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//获取用户名
String username = principals.getPrimaryPrincipal().toString();
//根据用户名查询用户信息
SysUsers user = sysUsersService.getUserByUserName(username);
//根据用户ID查询角色ID列表
List<SysUsersRoles> usersRolesList = sysUsersRolesService.getSysUsersRolesList(user.getUserId());
//将当前用户下的所有角色名称封装进SimpleAuthorizationInfo对象中
HashSet<String> roles = new HashSet<>();
//遍历查询出来的角色列表
for (SysUsersRoles sysUsersRoles : usersRolesList) {
Set<String> ps = new HashSet<>();
//根据角色ID查询角色信息
SysRoles sysRoles = sysRolesService.getSysRolesByRoleId(sysUsersRoles.getRoleId());
//将sysRoles的角色名称添加到SimpleAuthorizationInfo中
roles.add(sysRoles.getRoleName());
//通过角色ID,查询权限ID列表
List<SysRolesPermissions> sysRolesPermissions = sysRolesPermissionsService.getSysRolesPermissionsListByRoleId(sysRoles.getRoleId());
for (SysRolesPermissions sysRolesPermission : sysRolesPermissions) {
//通过权限ID查询权限名称
SysPermissions permissions = sysPermissionsService.getSysPermissionsByPermissionId(sysRolesPermission.getPermissionId());
//将permission的名称加入到SimpleAuthorizationInfo里面
ps.add(permissions.getPermissionName());
}
info.addStringPermissions(ps);
}
info.setRoles(roles);
return info;
}
/**
* 封装认证信息到安全管理器
* @param token
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//获取用户名
String principal = token.getPrincipal().toString();
SysUsers user = sysUsersService.getUserByUserName(principal);
// System.out.println("realm获取user:"+user);
//用户名不存在
if (user==null){
// SimpleAuthenticationInfo info1 = new SimpleAuthenticationInfo();
return null;
}
String salt = user.getSalt();
ByteSource credentialsSalt = ByteSource.Util.bytes(salt);
//封装数据库中的用户名 和 密码
//通过SimpleAuthenticationInfo对象传递给安全管理器
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user.getUsername(),user.getPassword(),credentialsSalt,getName());
return info;
}
}
2.1 数据库SQL准备
- SQL语句:
-- 1.sys_users用户表
CREATE TABLE sys_users (
user_id bigint PRIMARY KEY AUTO_INCREMENT COMMENT '编号',
username VARCHAR (100) UNIQUE COMMENT '用户名',
password VARCHAR(100) COMMENT '密码',
salt VARCHAR(100) COMMENT '盐值'
) charset=utf8 ENGINE=InnoDB COMMENT="用户表";
-- 2.sys_roles角色表
CREATE TABLE sys_roles (
role_id bigint PRIMARY KEY AUTO_INCREMENT COMMENT '角色编号',
role_name VARCHAR(100) COMMENT '角色名称'
) charset=utf8 ENGINE=InnoDB COMMENT="角色表";
-- 3.sys_permissions权限表(或资源表)
CREATE TABLE sys_permissions (
permission_id bigint PRIMARY KEY AUTO_INCREMENT COMMENT '编号',
permission_name VARCHAR(100) COMMENT '权限'
) charset=utf8 ENGINE=InnoDB COMMENT="权限表";
-- 4.sys_users_roles用户-角色关联表
CREATE TABLE sys_users_roles (
ur_id bigint PRIMARY KEY AUTO_INCREMENT COMMENT '编号',
user_id bigint COMMENT '用户编号',
role_id bigint COMMENT '角色编号'
) charset=utf8 ENGINE=InnoDB COMMENT="用户-角色关联表";
-- 5.sys_roles_permissions角色-权限关联表(或角色-资源关联表)
CREATE TABLE sys_roles_permissions (
rp_id bigint PRIMARY KEY AUTO_INCREMENT COMMENT '编号',
role_id bigint COMMENT '角色编号',
permission_id bigint COMMENT '权限编号'
) charset=utf8 ENGINE=InnoDB COMMENT="角色-权限关联表";
2.2 shiro注解解释
自己百度
2.3 备注
- shiro认证是项目完成后再加上去的