目录
一. Shiro 基础解释
Shiro 是 apache 下的权限安全框架,通过该框架可以完成安全认证,例如登入,权限认证等,并且增加了加密认证,并发执行,缓存设计等
过滤器+AOP 实现安全认证权限管理逻辑
- 提供用户表,存储代表当前用户的数据例如用户名,密码等
- 提供权限表,存储权限码
- 提供角色表,一个角色可以持有一个或多个不同权限
- 用户表添加角色字段,存储当前用户的角色
- 安全认证: 用户登录时将用户信息存储到Session中(注意分布式环境下Session一致性问题),后续访问通过拦截器拦截获取Session中当前用户信息与用户表中进行比对,通过则放行,不通过则重定向到指定页面
- 权限认证: 对不同接口设置不同的权限码,用户访问该接口时通过前置通知获取用户角色权限,与接口上的进行比对,成功则放行,失败则在前置通知中拒绝提示用户没有该权限
了解 Shiro 的组织架构
Shiro 的核心三大对象: Subject 代表一个用户(当前用户),通过该对象进行交互, SecurityManager 用户管理器,管理所有用户, Realm获取存储的真实数据,与当前请求的用户进行比对认证授权等
二. SpringBoot 整合 Shiro
1. 在项目中使用 Shiro 需要配置的地方
- 自定义 Realm,编写用户登入认证的方法,与授权方法,当用户登入时,会调用认证方法与授权方法,查询数据库中真实用户数据,与当前发送请求的用户信息进行比对,比对成功认证通过,获取当前用户的真实权限信息存储起来,供用户访问权限接口时获取,与接口上需要的权限信息进行比对,成功则放行
- 自定义 SessionManager (不是必须的,专门针对提供App端,微信小程序端,微信公众号等等不支持Session),重新编写获取sessionId的方法,用户登入后,会将用户信息保存到Session中,并对Session进行持久化等设置,将该Session的id返回给给前端,在后续的访问中需要携带这个sessionId,此处获取sessionId,通过sessionId获取Session,判断是否已登入等,由于此处指名了获取SessionId的方式,那么在登入后后续访问接口时携带sessionId的方式要与此处获取的方式对应
- 自定义Filter(不是必须的,专门针对一个接口多个角色或权限访问问题,自定义角色相关过滤器,权限相关过滤器等),Shiro默认的校验规则是全部满足,自定义修改为只要当前用户持有其中一个即可
- 创建配置类,配置使用自定义Realm,配置缓存,配置SessionDAO持久化,配置密码校验规则,配置Shiro监管的请求接口,与接口上的权限,或角色…
2. 代码示例
引入依赖
除了SpringBoot依赖以外,此处使用了redis作为缓存,redis依赖,Shiro整合redis插件依赖,还有Shiro支持不同模板的依赖模板不同依赖不同
<!--Shir0 权限框架-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<!--Shiro 支持 thymeleaf模板-->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
<!--Shiro 整合 redis 插件-->
<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>3.1.0</version>
</dependency>
请求接口
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.crypto.hash.Md5Hash;
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 java.util.HashMap;
import java.util.Map;
@Controller
@RequestMapping(value = "/shiroController")
public class ShiroController {
//登入接口: 注意点登入成功后获取sessionId返回给前端
//后续请求其它接口需要获取sessionId,将这个sessionId封装到请求头中
//请求头中封装sessionid时,请求头的key要与配置的SessionManager中的一致
@RequestMapping(value = "login", method = RequestMethod.GET)
@ResponseBody
public Map<String, String> login(String userName, String password,boolean remember){
System.out.println("用户请求登录执行---->userName: "+userName+",password: "+password+",remember: "+remember);
Map<String, String> result = new HashMap<>();
//1.通过用户名密码获取UsernamePasswordToken
UsernamePasswordToken token = new UsernamePasswordToken(userName, password);
//2.token设置是否选择"记住我"选项
token.setRememberMe(remember);
//3.获取Subject
Subject subject = SecurityUtils.getSubject();
//4.通过Subject 进行登录,如果登录失败会抛出异常
try {
//登录,会自动调用MyRealm中的认证方法进行认证
subject.login(token);
String sessionId = (String) subject.getSession().getId();
result.put("code", "200");
result.put("session-id",sessionId);
result.put("message","登入成功!");
return result;
}catch (UnknownAccountException uae){
result.put("message","用户名不存在!");
return result;
}catch (IncorrectCredentialsException ice){
result.put("message","密码错误!");
return result;
}catch (LockedAccountException lae){
result.put("message","多次失败账户被锁定!");
return result;
}
}
//退出登入
@RequestMapping(value = "/logOut", method = RequestMethod.GET)
@ResponseBody
public String logOut(){
System.out.println("退出登入");
Subject subject = SecurityUtils.getSubject();
subject.logout();
return "退出登入";
}
//MD5 两次,并且加盐的示例方法
public void md5Test(){
//测试密码
String password = "123456";
//测试盐
String userName = "admin";
Object obj = new Md5Hash(password,userName,2);
System.out.println(obj);
}
@RequestMapping(value = "/indexA", method = RequestMethod.GET)
public String goIndexA() {
System.out.println("不用登入就可以访问的页面");
return "aaa";
}
@RequestMapping(value = "/indexB", method = RequestMethod.GET)
public String goIndexB() {
System.out.println("需要登入才可以访问的页面");
return "bbb";
}
@RequestMapping(value = "/permsAAA", method = RequestMethod.GET)
public String getPermsAAA() {
System.out.println("需要登入才可以访问的页面");
return "bbb";
}
@RequestMapping(value = "/indexError", method = RequestMethod.GET)
@ResponseBody
public String indexError() {
System.out.println("用户没有登入时访问自动跳入的页面");
return "用户没有登入时访问自动跳入的页面";
}
@RequestMapping(value = "/unauthorized", method = RequestMethod.GET)
@ResponseBody
public String unauthorized(){
System.out.println("访问需要权限页面,当前用户没有该权限自动跳入的页面");
return "未授权页面";
}
}
自定义 Realm
当 Subject 调用 login(token) 方法进行登入时,会自动调用该Realm中的认证方法获取库中存储的真实用户数据进行比对认证,调用授权方法,获取该用户的真实权限数据进行存储
import com.test.entity.User;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.ByteSource;
import java.util.*;
//自定义Realm(Realm与数据库进行交互获取存储的用户真实数据,默认提供了SimpleAccountRealm)
//1.继承 AuthorizingRealm
//2.重写 AuthorizingRealm 中的抽象方法:
// doGetAuthorizationInfo(): 授权
// doGetAuthenticationInfo(): 认证
public class MyRealm extends AuthorizingRealm {
//授权(用户登入后自动调用该方法,获取权限相关数据赋值给当前用户)
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//1.创建SimpleAuthorizationInfo
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//2.获取Subject
Subject subject = SecurityUtils.getSubject();
//3.通过Subjcet获取登入时存储的在数据库中查询出的当前用户信息,拿到当前用户权限信息
User currentUser = (User) subject.getPrincipal();
//4.通过SimpleAuthorizationInfo设置用户权限
info.addStringPermissions(currentUser.getPermissionSet());
//6.设置角色
info.setRoles(currentUser.getRole());
//7.当用户走到该步骤说明登入完成,授权完成通过Subject获取Shiro提供的Session
//将用户信息存入Session,供前端页面获取(或者存入域对象Session中等等)
Session session = subject.getSession();
//例如页面中获取到当前用户的权限信息,选择显示该权限下的指定按钮等等
session.setAttribute("userVal",currentUser);
//8.将设置好权限的SimpleAuthorizationInfo 返回
return info;
}
//认证(用户登入时自动调用该方法进行认证)
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationtoken) throws AuthenticationException {
//1.将传递进来的AuthenticationToken转换为UsernamePasswordToken
UsernamePasswordToken token = (UsernamePasswordToken) authenticationtoken;
//2.通过token获取用户登入时传递用户名
String userName = token.getUsername();
//3.通过用户名查询数据库,是否存在该用户
User user = this.userMapperGetUserByUserName(userName);
//3.如果查询为空return null,会自动抛出UnknownAccountException异常
if (null == user) {
return null;
}
//4.获取数据库中查询出的密码,通过SimpleAuthenticationInfo,使用Shiro进行验证
//默认调用 SimpleCredentialsMatcher 中的 doCredentialsMatch()进行密码验证
//并将数据库查询的当前用户信息存入SimpleAuthenticationInfo 对象中,构造器的第一个参数
//(注意点,在Shiro整合redis做缓存时必须传递对象,在缓存时,会自动获取该对象的id作为key存储到redis中)
//供后面授权时通过该对象获取数据库查询的用户信息权限信息
//第三个参数为当前自定义Realm类名
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(), this.getClass().getSimpleName());
//5.设置盐(假设用户注册时以用户名为盐进行注册的)
info.setCredentialsSalt(ByteSource.Util.bytes(user.getUserName()));
//6.返回SimpleAuthenticationInfo
return info;
}
//模拟通过用户名查询用户表获取当前用户(注意点,该方法模拟的参数密码是未加密的,而配置的是MD5*2+盐方式加密过的)
public User userMapperGetUserByUserName(String userName) {
//模拟创建所有用户数据
List<User> userList = new ArrayList<>();
Set<String> permissionSet = new HashSet<>();
permissionSet.add("user:add");
permissionSet.add("user:update");
permissionSet.add("user:delete");
User user1 = new User("admin", "12346", permissionSet);
User user2 = new User("root", "root", permissionSet);
User user3 = new User("root1", "123456", permissionSet);
User user4 = new User("root2", "123456", permissionSet);
User user5 = new User("root3", "123456", permissionSet);
userList.add(user1);
userList.add(user2);
userList.add(user3);
userList.add(user4);
userList.add(user5);
//返回集合中user中userName等于传递进来的用户,如果不存在返回null;
User user = userList.stream().filter(u -> u.getUserName().equals(userName)).findAny().orElse(null);
return user;
}
}
自定义 SessionManager
主要针对例如App,小程序等,不支持Session的,又包装了一层sessionId获取方式,
import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.util.WebUtils;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.Serializable;
//自定义SessionManager继承 DefaultWebSessionManager
//我们在为App接口做Web应用时,想使用Session功能时。因为App端的请求一般不会设置Cookies,
//所以我们需要使用Token(一般存储的HTTP请求头中)来做SessionId。这时我们可以继承
//DefaultWebSessionManager实现自己的SessionManager类,然后重写getSessionId()
//Shiro默认提供的SessionManager:
// DefaultSessionManager 是默认的Session管理器,用于javase
// ServletContainerSessionManager 用户web环境
// DefaultWebSessionManager 用于自定义实现 继承该类
public class MyShiroSessionManager extends DefaultWebSessionManager {
//1.重写获取sessionId方法
@Override
protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
//1.通过Shiro提供的工具类WebUtils将ServletRequest 转换为 HttpServletRequest
HttpServletRequest httpServletRequest = WebUtils.toHttp(request);
//2.通过HttpServletRequest获取请求头中的sessionId
//(注意点:在用户登入后,再次发送请求时,sessionId存储到请求头中的key要与此处的"token"对应)
String sessionId = httpServletRequest.getHeader("token");
//3.判断是否获取成功
if(null != sessionId){
//如果获取SessionId不为空,存储
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, "cookie");
//通过Shiro校验当前获取的sessionId是否有效
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, sessionId);
//标识当前的sessionId有效
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
return sessionId;
}else {
//如果为空通过父类进行处理
return super.getSessionId(request,response);
}
}
}
自定义 Filter
针对一个接口多个角色,或多个权限访问问题,可以查看 DefaultFilter 模拟创建自定义的,此处以自定义Role相关过滤器,修改过滤规则为当前用户持有一个即可放行(默认是满足所有)
//在配置接口访问权限接口时,创建Map集合,接口请求路径为key,对应的权限或角色过滤器为value存入该集合中
//例如: filterMap.put("/接口访问路径1","anon");
// filterMap.put("/接口访问路径2", "authc");
// filterMap.put("/接口访问路径3","role[admin:aaa]");
//然后ShiroFilterFactoryBean调用setFilterChainDefinitionMap()方法,将存有接口与对应过滤器的集合设置进去
//在用户访问接口时,会自动执行该接口的对应过滤器,获取当前用户的信息,判断是否有权限访问,但是会有一个问题
//以"/接口访问路径3"为例,当用户访问该接口时执行默认的role过滤器,获取当前用户的权限,与"role[admin:aaa]"中
//的进行对比,默认是当前用户要同时存在"admin"与"aaa"两个权限时才可以访问,在实际开发中角色分级,假设,当前有一个
//接口可以被管理员角色访问,也可以被"aaa"角色访问,在使用默认规则进行配置,可以将"aaa"角色权限赋值给管理员,
//管理员可以正常访问这个接口了.但是不能把管理员角色赋值给"aaa",就会造成只有"aaa"角色的不可以访问这个用户
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.CollectionUtils;
import org.apache.shiro.web.filter.authz.AuthorizationFilter;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
import java.util.Set;
//自定义过滤器,修改验证规则,只要满足一个即可,在ShiroFilterFactoryBean中配置使用自定义过滤器
//继承AuthorizationFilter类重写过滤规则方法
public class MyRoleFilter extends AuthorizationFilter {
@Override
public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException {
//1.获取Subject,通过Subject可以获取到当前登入用户的信息,与数据库总
Subject subject = this.getSubject(request, response);
//2.mappedValue 就是配置到ShiroFilterFactoryBean中的拦截器Map集合中的value中的数组
//例如配置filterMap.put("/接口访问路径3","role[admin:aaa]")时mapValue中就可以拿到admin与aaa
String[] rolesArray = (String[])((String[])mappedValue);
if (rolesArray != null && rolesArray.length != 0) {
Set<String> roles = CollectionUtils.asSet(rolesArray);
//3.默认是 subject.hasAllRoles(roles) 该方法时比对所有
//也就是当前访问该接口的用户要同时持有roles集合中所有权限才可以访问
//修改为只要有一个即可
return roles.stream().anyMatch(role -> subject.hasRole(role));
} else {
//如果为空,说明没有配置对应的角色,直接放行
return true;
}
}
}
配置类
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
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.DefaultWebSecurityManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import javax.servlet.Filter;
import java.util.LinkedHashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
//Shiro使用redis作为缓存,配置ResidManager
@Bean
public RedisManager getRedisManager(){
RedisManager redisManager = new RedisManager();
//设置连接redis服务器的地址
redisManager.setHost("127.0.0.1");
//设置连接redis服务器的端口号
redisManager.setPort(6379);
return redisManager;
}
//配置RedisCacheManager
@Bean
public RedisCacheManager getCacheManager(@Qualifier("getRedisManager") RedisManager redisManager){
RedisCacheManager cacheManager = new RedisCacheManager();
cacheManager.setRedisManager(redisManager);
//设置登入时自动向redis缓存当前用户数据的过期时间,单位秒,
//过期后重新查数据库,并且要考虑修改用户数据不要忘记级联操作redis
cacheManager.setExpire(30000);
return cacheManager;
}
//配置Session持久化到redis,注意点在持久化Session到redis时,
//Session中保存用户数据所有对象都需要实现序列化接口否则报错Serializable
@Bean
public RedisSessionDAO getResisSessionDAO(@Qualifier("getRedisManager") RedisManager redisManager){
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
redisSessionDAO.setRedisManager(redisManager);
return redisSessionDAO;
}
//1.分布式前后端分离情况下,配置SessionManager管理器,对session的一些设置
//例如设置Session的失效时间,Session的缓存位置,统计Session获取当前在线人数等
//主要为App小程序等不支持Sesion的,想使用Session功能时。因为App端的请求一般不会设置Cookies,
//所以我们需要使用Token(一般存储的HTTP请求头中)来做SessionId。
//这时我们可以继承DefaultWebSessionManager实现自己的SessionManager类,然后重写getSessionId
@Bean
public MyShiroSessionManager sessionManager(@Qualifier("getResisSessionDAO") RedisSessionDAO resisSessionDAO){
MyShiroSessionManager sessionManager = new MyShiroSessionManager();
//设置session会话超时时间,默认30分钟
sessionManager.setGlobalSessionTimeout(20000);
//此处配置持久化到Redis(注意点,将Session持久化到redis,
//Session中存储的是当前用户信息,在进行持久化是首先需要序列化
//所以User对象,角色对象...凡是Session中包含的对象数据都需要
//实现序列化接口否则在持久化时会报错,Shiro提供了默认的持久化,如果不配置则使用默认的)
sessionManager.setSessionDAO(resisSessionDAO);
return sessionManager;
}
//2.配置加密校验规则(在验证时通过该规则对接收的客户端密码进行加密然后比对校验)
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher(){
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
//使用md5加密规则
matcher.setHashAlgorithmName("md5");
//表示md5加密两次
matcher.setHashIterations(2);
return matcher;
}
//3.配置Realm,需要自定义,继续AuthorizingRealm重写认证授权方法
@Bean
public MyRealm createMyRealm(@Qualifier("hashedCredentialsMatcher") HashedCredentialsMatcher hashedCredentialsMatcher){
MyRealm realm = new MyRealm();
realm.setCredentialsMatcher(hashedCredentialsMatcher);
return realm;
}
//4.配置DefaultWebSecurityManager
@Bean
public DefaultWebSecurityManager createSecurityManager(
@Qualifier("createMyRealm") MyRealm realm,
@Qualifier("sessionManager")MyShiroSessionManager sessionManager,
@Qualifier("getCacheManager") RedisCacheManager redisCacheManager
){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//设置自定义Realm
securityManager.setRealm(realm);
//如果有自定义session管理,上面配置了
securityManager.setSessionManager(sessionManager);
//如果有设置自定义缓存实现(上面配置了redis缓存
securityManager.setCacheManager(redisCacheManager);
return securityManager;
}
//5.配置 ShiroFilterFactoryBean 关联 DefaultWebSecurityManager
@Bean
public ShiroFilterFactoryBean createShiroFilterFactoryBean(@Qualifier("createSecurityManager") DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
//1.关联SecurityManager
factoryBean.setSecurityManager(securityManager);
//2.一些默认页面跳转的设置
//设置没有登录时访问自动跳入的页面
factoryBean.setLoginUrl("/shiroController/indexError");
//设置访问需要权限而当前用户没有该权限时自动跳入的页面
factoryBean.setUnauthorizedUrl("/shiroController/unauthorized");
//设置登入成功后默认跳转的页面
factoryBean.setSuccessUrl("/index");
//3.配置使用自定义过滤器(注意Filter是javax.servlet包下的,并且集合类型是LinkedHashMap)
Map<String, Filter> myFilterMa = new LinkedHashMap<>();
//代表当前自定义过滤器的字符串与自定义过滤器进行绑定 MyRoleFilter 自定义过滤器
myFilterMa.put("myRole", new MyRoleFilter());
//将绑定了自定义过滤器的map集合设置到ShiroFilterFactoryBean中
factoryBean.setFilters(myFilterMa);
//使用自定义过滤器,代表自定义过滤器的字符串与对应需要设置权限请求的接口进行绑定
//2.添加Shiro内置过滤器,有多种过滤器
//注意点配置拦截路径在执行时有先后顺序,
//假设第一个配置是拦截所有后续的放行则不会生效
//anon: 无需认证就可以访问
//authc: 必须认证才能访问
//user: 必须拥有"记住我"功能才能使用
//perms: 拥有对某个资源的权限才能访问
//role: 拥有某个角色权限才能访问
//访问路径支持通配符例如"/user/*" 标识对"/user"请求下的所有生效
Map<String, String> filterMap = new LinkedHashMap<>();
filterMap.put("/shiroController/pageA","anon");
filterMap.put("/shiroController/pageB", "authc");
//Shiro默认过滤条件时满足所有,即用户如果访问该接口,用户需要同时持有user与aaa两个权限才可以
filterMap.put("/shiroController/permsAAA","perms[user:aaa]");
/*filterMap.put("", "user");
filterMap.put("", "perms");
filterMap.put("", "role");*/
//配置使用自定义过滤器,由于自定义过滤器的过滤条件为满足一个即可,
//访问该接口路径时,当前访问用户只需要有admin或aaa一个权限即可
filterMap.put("/shiroController/myRoleFilterTest","myRole[admin:aaa]");
factoryBean.setFilterChainDefinitionMap(filterMap);
return factoryBean;
}
//配置开启Spring监管Shiro中的bean的初始化销毁
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){
return new LifecycleBeanPostProcessor();
}
//配置开启支持Shiro注解方式
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("createSecurityManager") DefaultWebSecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
//扫描上下文,将符合条件的Advisor交给Spring
@Bean
//@DependsOn 标识当前方法注入的bean需要依赖LifecycleBeanPostProcessor,
//当LifecycleBeanPostProcessor创建注入成功后再创建注册当前方法创建的bean
@DependsOn("LifecycleBeanPostProcessor")
public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator(){
DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
creator.setUsePrefix(true);
return creator;
}
//如果前端使用thymeleaf作为模板时需要配置ShiroDialect方言
@Bean
public ShiroDialect getShiroDialect(){
return new ShiroDialect();
}
}