- shiro 核心架构
- Subject(主体):与软件交互的一个特定的实体(用户、第三方服务等)。
- SecurityManager(安全管理器) :Shiro 的核心,用来协调管理组件工作。
- Authenticator(认证管理器):负责执行认证操作
- Authorizer(授权管理器):负责授权检测
- SessionManager(会话管理):负责创建并管理用户 Session 生命周期,提供一个强有力的 Session 体验。
- SessionDAO:代表 SessionManager 执行 Session 持久(CRUD)动作,它允许任何存储的数据挂接到 session 管理基础上。
- CacheManager(缓存管理器):提供创建缓存实例和管理缓存生命周期的功能
- Cryptography(加密管理器):提供了加密方式的设计及管理。
- Realms(领域对象):是shiro和你的应用程序安全数据之间的桥梁。
- shiro 认证
- 系统调用subject的login方法将用户信息提交给SecurityManager
- SecurityManager将认证操作委托给认证器对象Authenticator
- Authenticator将用户输入的身份信息传递给Realm。
- Realm访问数据库获取用户信息然后对信息进行封装并返回。
- Authenticator 对realm返回的信息进行身份认证。
- shiro 授权
- 系统调用subject相关方法将用户信息(例如isPermitted)递交给SecurityManager
- SecurityManager将权限检测操作委托给Authorizer对象,Authorizer将用户信息委托给realm.
- Realm访问数据库获取用户权限信息并封装。
- Authorizer对用户授权信息进行判定。
- 添加依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
- web.xml
<!--
1) targetBeanName 名字由DelegatingFilterProxy对象底层设置并读取。
2) shiroFilterFactory 名字要与ShiroFilterFactoryBean配置的id相同。
-->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>targetBeanName</param-name>
<param-value>shiroFilterFactory</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
- spring-shiro.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans default-lazy-init="true"
xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/data/jpa
http://www.springframework.org/schema/data/jpa/spring-jpa.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- shiro过滤与认证 -->
<bean id="shiroFilterFactory" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="SecurityManager" ref="securityManager" />
<property name="LoginUrl" value="/doLoginUI.do" />
<!-- 设置请求过滤规则 -->
<property name="FilterChainDefinitionMap">
<map>
<!--anon 匿名可访问,authc 必须认证,假如添加了记住我功能以后需要设置为user -->
<entry key="/bower_components/**" value="anon" />
<entry key="/build/**" value="anon" />
<entry key="/dist/**" value="anon" />
<entry key="/plugins/**" value="anon" />
<entry key="/user/doLogin.do" value="anon" />
<entry key="/doLogout.do" value="logout" />
<!--<entry key="/**" value="authc" /> 必须认证 -->
<entry key="/**" value="user" />
</map>
</property>
</bean>
<!-- 安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!-- 认证与授权,在 ShiroUserRealm 类中重写各自方法-->
<property name="Realm" ref="shiroUserRealm" />
<!-- shiro缓存 -->
<property name="CacheManager" ref="cacheManager" />
<!-- remember me -->
<property name="RememberMeManager" ref="rememberMeManager" />
<!-- session -->
<property name="sessionManager" ref="sessionManager"/>
</bean>
<!-- shiro授权 -->
<!-- 授权属性的Advisor配置 -->
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="SecurityManager" ref="securityManager" />
</bean>
<!-- 配置bean对象的生命周期管理 -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"></bean>
<!-- 通过此配置要为目标业务对象创建代理对象 -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
depends-on="lifecycleBeanPostProcessor">
</bean>
<!-- shiro缓存 -->
<bean id="cacheManager" class="org.apache.shiro.cache.MemoryConstrainedCacheManager" />
<!-- remember me -->
<bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
<constructor-arg value="rememberMe" />
<!-- 7天,采用spring el表达式来计算,方便修改 -->
<property name="maxAge" value="#{7 * 24 * 60 * 60}" />
</bean>
<bean id="rememberMeManager"
class="org.apache.shiro.web.mgt.CookieRememberMeManager">
<property name="cookie" ref="rememberMeCookie" />
</bean>
<!-- shiro结合Session会话管理器 start -->
<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
<!-- session的失效时长,单位毫秒 1小时: 3600000, itzixi站点设置以 6小时 为主:21600000 -->
<!-- 设置全局会话超时时间,默认30分钟,即如果30分钟内没有访问会话将过期 1800000 -->
<property name="globalSessionTimeout" value="21600000" />
<!-- 删除失效的session -->
<property name="deleteInvalidSessions" value="true" />
<!-- 是否开启会话验证器,默认是开启的 -->
<property name="sessionValidationSchedulerEnabled" value="true" />
</bean>
</beans>
- service 层新建类 ShiroUserRealm 继承AuthorizingRealm 接口,重写3个方法
@Service
public class ShiroUserRealm extends AuthorizingRealm {
@Autowired
private SysUserDao sysUserDao;
@Autowired
private SysUserRoleDao sysUserRoleDao;
@Autowired
private SysRoleMenuDao sysRoleMenuDao;
@Autowired
private SysMenuDao sysMenuDao;
/** 通过此方法完成授权信息的获取及封装,在service 层的方法使用@RequiresPermissions("sys:user:valid")来允许拥有sys:user:valid 权限的用户调用方法 */
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// 1.获取登录用户信息,例如用户id
SysUser user = (SysUser) principals.getPrimaryPrincipal();
Integer userId = user.getId();
// 2.基于用户id获取用户拥有的角色(sys_user_roles)
List<Integer> roleIds = sysUserRoleDao.findRoleIdsByUserId(userId);
if (roleIds == null || roleIds.size() == 0)
throw new AuthorizationException();
// 3.基于角色id获取菜单id(sys_role_menus)
Integer[] array = {};
List<Integer> menuIds = sysRoleMenuDao.findMenuIdsByRoleIds(roleIds.toArray(array));
if (menuIds == null || menuIds.size() == 0)
throw new AuthorizationException();
// 4.基于菜单id获取权限标识(sys_menus)
List<String> permissions = sysMenuDao.findPermissions(menuIds.toArray(array));
// 5.对权限标识信息进行封装并返回
Set<String> set = new HashSet<>();
for (String per : permissions) {
if (!StringUtils.isEmpty(per)) {
set.add(per);
}
}
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.setStringPermissions(set);
return info;// 返回给授权管理器
}
/**
* 设置凭证匹配器(与用户添加操作使用相同的加密算法)
*/
@Override
public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {
// 构建凭证匹配对象
HashedCredentialsMatcher cMatcher = new HashedCredentialsMatcher();
// 设置加密算法
cMatcher.setHashAlgorithmName("MD5");
// 设置加密次数
cMatcher.setHashIterations(1);
super.setCredentialsMatcher(cMatcher);
}
/**
* 通过此方法完成认证数据的获取及封装,系统 底层会将认证数据传递认证管理器,由认证 管理器完成认证操作。
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 1.获取用户名(用户页面输入)
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
String username = upToken.getUsername();
// 2.基于用户名查询用户信息
SysUser user = sysUserDao.findUserByUserName(username);
// 3.判定用户是否存在
if (user == null)
throw new UnknownAccountException("账户不存在");
// 4.判定用户是否已被禁用。
if (user.getValid() == 0)
throw new LockedAccountException();
// 5.封装用户信息
ByteSource credentialsSalt = ByteSource.Util.bytes(user.getSalt());
// 记住:构建什么对象要看方法的返回值
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, // principal (身份)
user.getPassword(), // hashedCredentials,token 存有前端传来的密码,这里是数据库取的密码
credentialsSalt, // credentialsSalt,每个账号的盐值都不同,所以要从数据库获取账户的盐值
getName());// realName
// 6.返回封装结果
return info;// 返回值会传递给认证管理器(后续
// 认证管理器会通过此信息完成认证操作)
}
}
- 业务Service 层
保存用户,保存密码代码片段
// 将数据写入数据库,保存密码部分
String salt = UUID.randomUUID().toString();
entity.setSalt(salt);
// 加密
SimpleHash sHash = new SimpleHash("MD5", entity.getPassword(), salt, 1);
entity.setPassword(sHash.toHex());
更新密码
@Override
public int updatePassword(String password, String newPassword, String cfgPassword) {
// 1.判定新密码与密码确认是否相同
if (StringUtils.isEmpty(newPassword))
throw new IllegalArgumentException("新密码不能为空");
if (StringUtils.isEmpty(cfgPassword))
throw new IllegalArgumentException("确认密码不能为空");
if (!newPassword.equals(cfgPassword))
throw new IllegalArgumentException("两次输入的密码不相等");
// 2.判定原密码是否正确
if (StringUtils.isEmpty(password))
throw new IllegalArgumentException("原密码不能为空");
// 获取登陆用户
SysUser user = (SysUser) SecurityUtils.getSubject().getPrincipal();//获取的是当前登陆用户
SimpleHash sh = new SimpleHash("MD5", password, user.getSalt(), 1);
if (!user.getPassword().equals(sh.toHex()))
throw new IllegalArgumentException("原密码不正确");
// 3.对新密码进行加密
String salt = UUID.randomUUID().toString();
sh = new SimpleHash("MD5", newPassword, salt, 1);
// 4.将新密码加密以后的结果更新到数据库
int rows = sysUserDao.updatePassword(sh.toHex(), salt, user.getId());
if (rows == 0)
throw new ServiceException("修改失败");
return rows;
}
- Controller 层
@RequestMapping("/doLogin")
@ResponseBody
public ReturnJson doLogin(boolean isRememberMe, String username, String password) {
// 1.获取Subject对象
Subject subject = SecurityUtils.getSubject();
// 2.通过Subject提交用户信息,交给shiro框架进行认证操作
// 2.1对用户进行封装
UsernamePasswordToken token = new UsernamePasswordToken(
username, // 身份信息
password);// 凭证信息
if (isRememberMe) {
token.setRememberMe(true);
}
// 2.2对用户信息进行身份认证
subject.login(token);
// 分析:
// 1)token会传给shiro的SecurityManager
// 2)SecurityManager将token传递给认证管理器
// 3)认证管理器会将token传递给realm
return new ReturnJson("login ok");
}
- 另java 类配置shiro
package com.db.common.config;
import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.cache.MemoryConstrainedCacheManager;
import org.apache.shiro.mgt.RememberMeManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import com.db.sys.service.realm.ShiroUserRealm;
/** @Configuration 描述的bean一般为一个配置类 */
@Configuration
public class SpringShiroConfig {
/**
* @Bean一般用户描述方法,然后将方法的返回值 交给Spring管理,其中@Bean注解中的内容为Bean 对象的key。
* @return
*/
@Bean("securityManager")
public SecurityManager newSecurityManager(ShiroUserRealm realm, CacheManager cacheManager,
RememberMeManager rememberMeManager) {
DefaultWebSecurityManager sm = new DefaultWebSecurityManager();
sm.setRealm(realm);
sm.setCacheManager(cacheManager);
sm.setRememberMeManager(rememberMeManager);
sm.setSessionManager(newSessionManager());
return sm;// 不是java.lang包中的SecurityManager
}
@Bean("shiroFilterFactory")
public ShiroFilterFactoryBean newShiroFilterFactoryBean(SecurityManager securityManager) {
// 1.构建ShiroFilterFactoryBean对象(负责创建ShiroFilter工厂对象)
ShiroFilterFactoryBean fBean = new ShiroFilterFactoryBean();
// 2.设置安全管理器
fBean.setSecurityManager(securityManager);
// 3.设置登录页面对应的url(非认证用户要跳转到此url对应的页面)
fBean.setLoginUrl("/doLoginUI.do");
// 4.设置过滤规则(哪些允许匿名访问,哪些需要认证访问)
Map<String, String> filterMap = new LinkedHashMap<String, String>();
filterMap.put("/bower_components/**", "anon");
filterMap.put("/build/**", "anon");
filterMap.put("/dist/**", "anon");
filterMap.put("/plugins/**", "anon");
filterMap.put("/user/doLogin.do", "anon");
filterMap.put("/doLogout.do", "logout");
filterMap.put("/**", "user");
fBean.setFilterChainDefinitionMap(filterMap);
return fBean;
}
// ==========================
// @Bean注解没有指定名字时,默认bean的名字为方法名
@Bean("lifecycleBeanPostProcessor")
public LifecycleBeanPostProcessor newLifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
@DependsOn("lifecycleBeanPostProcessor")
@Bean
public DefaultAdvisorAutoProxyCreator DefaultAdvisorAutoProxyCreator() {
return new DefaultAdvisorAutoProxyCreator();
}
@Bean
public AuthorizationAttributeSourceAdvisor newAuthorizationAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
// 配置缓存管理器(可以缓存用户的权限信息)
@Bean
public MemoryConstrainedCacheManager newCacheManager() {
return new MemoryConstrainedCacheManager();
}
// 配置记住我
@Bean
public CookieRememberMeManager newCookieManager() {
CookieRememberMeManager cookieManager = new CookieRememberMeManager();
SimpleCookie cookie = new SimpleCookie("rememberMe");
cookie.setMaxAge(24 * 7 * 60 * 60);
cookieManager.setCookie(cookie);
return cookieManager;
}
// 配置session管理器
public DefaultWebSessionManager newSessionManager() {
DefaultWebSessionManager sManager = new DefaultWebSessionManager();
sManager.setGlobalSessionTimeout(21600000);
sManager.setDeleteInvalidSessions(true);
sManager.setSessionValidationSchedulerEnabled(true);
return sManager;
}
}
package com.db.common.config;
import javax.servlet.FilterRegistration.Dynamic;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import org.springframework.web.filter.DelegatingFilterProxy;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
//替换web.xml (参考官网实现)
public class AppWebApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer{
//其中前端控制器的配置是在父类的onStart方法实现的。
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
registerFilter(servletContext);
super.onStartup(servletContext);
}
private void registerFilter(ServletContext servletContext) {
Dynamic d = servletContext.addFilter("shiroFilter",
DelegatingFilterProxy.class);
d.setInitParameter("targetBeanName","shiroFilterFactory");
d.addMappingForUrlPatterns(null,true,"/*");
}
/**
* 配置@Service,@Repository
*/
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] {SpringRespositoryConfig.class,SpringServiceConfig.class};
}
/**加载spring mvc配置*/
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] {SpringWebConfig.class} ;
}
/**定义映射路径(spring mvc要处理哪些url)*/
@Override
protected String[] getServletMappings() {
System.out.println("getServletMappings");
return new String[] {"/"};
}
}