Shiro在SpringBoot工程的应用
spring和shiro的整合依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.3.2</version>
</dependency>
登录方法
//用户登录 @RequestMapping(value="/loginDemo") public String loginShiro(String username ,String password){ //构造登录令牌 try { /** * 密码加密: * shiro提供的md5加密 * Md5Hash: * 参数一:加密的内容 * 123456 --- aaa * 参数二:盐(加密的混淆字符串)(用户登录的用户名) * 123456+混淆字符串 * 参数三:加密次数 * */ //用户注册的时候密码也应该用 这个 Md5Hash 和登录验证的密码加密规则保持一致 password = new Md5Hash(password,username,3).toString(); UsernamePasswordToken upToken = new UsernamePasswordToken(username,password); //1.获取subject Subject subject = SecurityUtils.getSubject(); //获取session String sid = (String) subject.getSession().getId(); //2.调用subject进行登录 subject.login(upToken); return "登录成功"+sid; }catch (Exception e) { return "用户名或密码错误"; } }
自定义realm
Realm域:Shiro从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么
它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行
验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源
package cn.zhao.shiro.realm; import cn.zhao.shiro.domain.Permission; import cn.zhao.shiro.domain.Role; import cn.zhao.shiro.domain.User; import cn.zhao.shiro.service.UserService; import org.apache.shiro.authc.*; 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.springframework.beans.factory.annotation.Autowired; import java.util.HashSet; import java.util.Set; /** * 自定义的realm */ public class CustomRealm extends AuthorizingRealm { public void setName(String name) { super.setName("customRealm"); } @Autowired private UserService userService; /** * 授权方法 * 操作的时候,判断用户是否具有响应的权限 * 先认证 -- 安全数据 * 再授权 -- 根据安全数据获取用户具有的所有操作权限 * * */ protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { //1.获取已认证的用户数据 User user = (User) principalCollection.getPrimaryPrincipal();//得到唯一的安全数据 //2.根据用户数据获取用户的权限信息(所有角色,所有权限) SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); Set<String> roles = new HashSet<>();//所有角色 Set<String> perms = new HashSet<>();//所有权限 for (Role role : user.getRoles()) { roles.add(role.getName()); for (Permission perm : role.getPermissions()) { perms.add(perm.getCode()); } } info.setStringPermissions(perms); info.setRoles(roles); return info; } /** * 认证方法 * 参数:传递的用户名密码 */ protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { //1.获取登录的用户名密码(token) UsernamePasswordToken upToken = (UsernamePasswordToken) authenticationToken; String username = upToken.getUsername(); String password = new String( upToken.getPassword()); //2.根据用户名查询数据库 User user = userService.findByName(username); //3.判断用户是否存在或者密码是否一致 if(user != null && user.getPassword().equals(password)) { //4.如果一致返回安全数据 //构造方法:安全数据,密码,realm域名 SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user,user.getPassword(),this.getName()); return info; } //5.不一致,返回null 或(抛出异常) return null; } public static void main(String[] args) { System.out.println(new Md5Hash("123456","zhao",3).toString()); } }
Shiro的配置
package cn.zhao.shiro; import cn.zhao.shiro.realm.CustomRealm; import cn.zhao.shiro.session.CustomSessionManager; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.apache.shiro.web.session.mgt.DefaultWebSessionManager; import org.crazycake.shiro.RedisCacheManager; import org.crazycake.shiro.RedisManager; import org.crazycake.shiro.RedisSessionDAO; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.LinkedHashMap; import java.util.Map; @Configuration public class ShiroConfiguration { //1.创建realm @Bean public CustomRealm getRealm() { return new CustomRealm(); } //2.创建安全管理器 @Bean public SecurityManager getSecurityManager(CustomRealm realm) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(realm); //将自定义的会话管理器注册到安全管理器中 securityManager.setSessionManager(sessionManager()); //将自定义的redis缓存管理器注册到安全管理器中 securityManager.setCacheManager(cacheManager()); return securityManager; } //3.配置shiro的过滤器工厂 /** * 再web程序中,shiro进行权限控制全部是通过一组过滤器集合进行控制 * */ @Bean public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) { //1.创建过滤器工厂 ShiroFilterFactoryBean filterFactory = new ShiroFilterFactoryBean(); //2.设置安全管理器 filterFactory.setSecurityManager(securityManager); //3.通用配置(跳转未登录页面,未授权跳转的页面) filterFactory.setLoginUrl("/autherror?code=1");//跳转未登录url地址 filterFactory.setUnauthorizedUrl("/autherror?code=2");//未授权的url //4.设置过滤器集合 /** * 设置所有的过滤器:有顺序map * key = 拦截的url地址 * value = 过滤器类型 * */ Map<String,String> filterMap = new LinkedHashMap<>(); filterMap.put("/test/home","anon");//当前请求地址可以匿名访问 //具有某中权限才能访问 //使用过滤器的形式配置请求地址的依赖权限 filterMap.put("/test/img","perms[user-add]"); //不具备指定的权限,跳转到setUnauthorizedUrl地址 //使用过滤器的形式配置请求地址的依赖角色 //filterMap.put("/test/img","roles[系统管理员]"); filterMap.put("/test/**","authc");//当前请求地址必须认证之后可以访问 // filterMap // 我们的的url 和对应的Filter 关系配置。 // 这里的加载顺序是自上而下,所以看到我们/user/** 写到最后,因为匹配不中最后都让这个匹配中。 filterFactory.setFilterChainDefinitionMap(filterMap); return filterFactory; } //------------- 配置自定义的会话管理将sessionId放在redis中 @Value("${spring.redis.host}") private String host; @Value("${spring.redis.port}") private int port; /** * 1.redis的控制器,操作redis */ public RedisManager redisManager() { RedisManager redisManager = new RedisManager(); redisManager.setHost(host); redisManager.setPort(port); return redisManager; } /** * 2.sessionDao */ public RedisSessionDAO redisSessionDAO() { RedisSessionDAO sessionDAO = new RedisSessionDAO(); sessionDAO.setRedisManager(redisManager()); return sessionDAO; } /** * 3.会话管理器 */ public DefaultWebSessionManager sessionManager() { CustomSessionManager sessionManager = new CustomSessionManager(); sessionManager.setSessionDAO(redisSessionDAO()); return sessionManager; } /** * 4.缓存管理器 */ public RedisCacheManager cacheManager() { RedisCacheManager redisCacheManager = new RedisCacheManager(); redisCacheManager.setRedisManager(redisManager()); return redisCacheManager; } //------------- //开启对shior注解的支持 @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor(); advisor.setSecurityManager(securityManager); return advisor; } }
shiro中的过滤器
Filter 解释
anon 无参,开放权限,可以理解为匿名用户或游客
authc 无参,需要认证
logout 无参,注销,执行后会直接跳转到 shiroFilterFactoryBean.setLoginUrl(); 设置的url
authcBasic 无参,表示 httpBasic 认证
user 无参,表示必须存在用户,当登入操作时不做检查
ssl 无参,表示安全的URL请求,协议为 https
perms[user] 参数可写多个,表示需要某个或某些权限才能通过,多个参数时写 perms[“user,admin”],当有多个参数时必须每个参数都通过才算通过
roles[admin] 参数可写多个,表示是某个或某些角色才能通过,多个参数时写 roles[“admin,user”],当有多个参数时必须每个参数都通过才算通过
rest[user] 根据请求的方法,相当于 perms[user:method],其中 method 为 post,get,delete 等
port[8081] 当请求的URL端口不是8081时,跳转到当前访问主机HOST的8081端口
授权
shiro支持基于过滤器的授权方式也支持注解的授权方式
基于配置的授权
/** * 设置所有的过滤器:有顺序map * key = 拦截的url地址 * value = 过滤器类型 * */ Map<String,String> filterMap = new LinkedHashMap<>(); filterMap.put("/test/home","anon");//当前请求地址可以匿名访问 //具有某中权限才能访问 //使用过滤器的形式配置请求地址的依赖权限 filterMap.put("/test/img","perms[user-add]"); //不具备指定的权限,跳转到setUnauthorizedUrl地址 //使用过滤器的形式配置请求地址的依赖角色 //filterMap.put("/test/img","roles[系统管理员]"); filterMap.put("/test/**","authc");//当前请求地址必须认证之后可以访问 // filterMap // 我们的的url 和对应的Filter 关系配置。 // 这里的加载顺序是自上而下,所以看到我们/user/** 写到最后,因为匹配不中最后都让这个匹配中。 filterFactory.setFilterChainDefinitionMap(filterMap);
基于配置的方式进行授权,一旦操作用户不具有操作权限,目标地址不会执行,回跳到指定的url地址
基于注解的授权
/** * 1.过滤器 ,如果 权限 信息不匹配 setUnauthorizedUrl 地址 * 5.注解 :如果 权限 信息不匹配 会抛出异常 * @return */ @RequiresPermissions("user-find") //shiro 注解标明访问该方法必须有user-find这个权限 @RequestMapping(value="/test/img") public String img(){ return "图片"; }
会话管理
SessionManager(会话管理器):管理所有Subject的session包括创建、维护、删除、失效、验证等工作。
SessionManager是顶层组件,由SecurityManager管理
shiro提供了三个默认实现:
1. DefaultSessionManager:用于JavaSE环境
2. ServletContainerSessionManager:用于Web环境,直接使用servlet容器的会话。
3. DefaultWebSessionManager:用于web环境,自己维护会话(自己维护着会话,直接废弃了Servlet容器的
会话管理)。
在web程序中,通过shiro的Subject.login()方法登录成功后,用户的认证信息实际上是保存在HttpSession中的通
过如下代码验证。
/** 登录成功后,打印所有session内容 * shiro 会话管理器如果不指定默认是 ServletContainerSessionManager * 注意先要登录 不然就是空的 * 打印 * <B>org.apache.shiro.subject.support.DefaultSubjectContext_AUTHENTICATED_SESSION_KEY</B>=true<br>/n * <B>org.apache.shiro.web.session.HttpServletSession.HOST_SESSION_KEY</B>=127.0.0.1<br>/n * <B>org.apache.shiro.subject.support.DefaultSubjectContext_PRINCIPALS_SESSION_KEY</B>=cn.zhao.shiro.domain.User@4a2dd234<br>/n * cn.zhao.shiro.domain.User@4a2dd234 默认认证之后的安全数据( 也就是 user对象)存入到了HttpSession 中 也就是 user对象 */ @RequestMapping(value="/show") public String show(HttpSession session) { // 获取session中所有的键值 Enumeration<?> enumeration = session.getAttributeNames(); // 遍历enumeration中的 while (enumeration.hasMoreElements()) { // 获取session键值 String name = enumeration.nextElement().toString(); // 根据键值取session中的值 Object value = session.getAttribute(name); // 打印结果 System.out.println("<B>" + name + "</B>=" + value + "<br>/n"); } return "查看session成功"; }
在分布式系统或者微服务架构下,都是通过统一的认证中心进行用户认证。如果使用默认会话管理,用户信息只会
保存到一台服务器上。那么其他服务就需要进行会话的同步。

会话管理器可以指定sessionId的生成以及获取方式。
通过sessionDao完成模拟session存入,取出等操作
Shiro结合redis的统一会话管理

shiro与redis依赖
<dependency> <groupId>org.crazycake</groupId> <artifactId>shiro-redis</artifactId> <version>3.0.0</version> </dependency>
springboot redis配置application.yml
redis:
host: 127.0.0.1
port: 6379
自定义session会话管理
package cn.zhao.shiro.session; import org.apache.shiro.web.servlet.ShiroHttpServletRequest; import org.apache.shiro.web.session.mgt.DefaultWebSessionManager; import org.apache.shiro.web.util.WebUtils; import org.springframework.util.StringUtils; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import java.io.Serializable; /** * 自定义的sessionManager 继承 DefaultWebSessionManager */ public class CustomSessionManager extends DefaultWebSessionManager { /** * sessionid 默认在cookie 中 重写 sessionid getSessionId 将sessionId 放在请求头中 * 头信息中具有sessionid * 请求头:Authorization: * * 指定sessionId的获取方式 */ protected Serializable getSessionId(ServletRequest request, ServletResponse response) { //获取请求头Authorization中的数据 String id = WebUtils.toHttp(request).getHeader("Authorization"); if(StringUtils.isEmpty(id)) { //第一次请求 //如果没有携带,生成新的sessionId return super.getSessionId(request,response); }else{ //返回sessionId; // 将sessionId 放在请求头中 //request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, "cookie"); //sessionId 从哪获取到的 request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, "header"); //sessionId 从哪获取到的 request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id); //sessionId 是什么 request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE); //sessionId 要不要验证 return id; } } }
配置Shiro基于redis的会话管理
在Shiro配置类 cn.itcast.shiro.ShiroConfiguration 配置
1. 配置shiro的RedisManager,通过shiro-redis包提供的RedisManager统一对redis操作
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
//配置shiro redisManager
public RedisManager redisManager() {
RedisManager redisManager = new RedisManager();
redisManager.setHost(host);
redisManager.setPort(port);
return redisManager; }
Shiro内部有自己的本地缓存机制,为了更加统一方便管理,全部替换redis实现
//配置Shiro的缓存管理器 //使用redis实现 public RedisCacheManager cacheManager() { RedisCacheManager redisCacheManager = new RedisCacheManager(); redisCacheManager.setRedisManager(redisManager()); return redisCacheManager; }
配置SessionDao,使用shiro-redis实现的基于redis的sessionDao
/** * RedisSessionDAO shiro sessionDao层的实现 通过redis * 使用的是shiro-redis开源插件 */ public RedisSessionDAO redisSessionDAO() { RedisSessionDAO redisSessionDAO = new RedisSessionDAO(); redisSessionDAO.setRedisManager(redisManager()); return redisSessionDAO; }
配置会话管理器,指定sessionDao的依赖关系
/** * 3.会话管理器 */ public DefaultWebSessionManager sessionManager() { CustomSessionManager sessionManager = new CustomSessionManager(); sessionManager.setSessionDAO(redisSessionDAO()); return sessionManager; }
统一交给SecurityManager管理
//配置安全管理器 @Bean public SecurityManager securityManager(CustomRealm realm) { //使用默认的安全管理器 DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(realm); // 自定义session管理 使用redis securityManager.setSessionManager(sessionManager()); // 自定义缓存实现 使用redis securityManager.setCacheManager(cacheManager()); //将自定义的realm交给安全管理器统一调度管理 securityManager.setRealm(realm); return securityManager; }
Shiro全部配置
package cn.zhao.shiro; import cn.zhao.shiro.realm.CustomRealm; import cn.zhao.shiro.session.CustomSessionManager; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.apache.shiro.web.session.mgt.DefaultWebSessionManager; import org.crazycake.shiro.RedisCacheManager; import org.crazycake.shiro.RedisManager; import org.crazycake.shiro.RedisSessionDAO; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.LinkedHashMap; import java.util.Map; @Configuration public class ShiroConfiguration { //1.创建realm @Bean public CustomRealm getRealm() { return new CustomRealm(); } //2.创建安全管理器 @Bean public SecurityManager getSecurityManager(CustomRealm realm) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(realm); //将自定义的会话管理器注册到安全管理器中 securityManager.setSessionManager(sessionManager()); //将自定义的redis缓存管理器注册到安全管理器中 securityManager.setCacheManager(cacheManager()); return securityManager; } //3.配置shiro的过滤器工厂 /** * 再web程序中,shiro进行权限控制全部是通过一组过滤器集合进行控制 * */ @Bean public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) { //1.创建过滤器工厂 ShiroFilterFactoryBean filterFactory = new ShiroFilterFactoryBean(); //2.设置安全管理器 filterFactory.setSecurityManager(securityManager); //3.通用配置(跳转未登录页面,未授权跳转的页面) filterFactory.setLoginUrl("/autherror?code=1");//跳转未登录url地址 filterFactory.setUnauthorizedUrl("/autherror?code=2");//未授权的url //4.设置过滤器集合 /** * 设置所有的过滤器:有顺序map * key = 拦截的url地址 * value = 过滤器类型 * */ Map<String,String> filterMap = new LinkedHashMap<>(); filterMap.put("/test/home","anon");//当前请求地址可以匿名访问 //具有某中权限才能访问 //使用过滤器的形式配置请求地址的依赖权限 filterMap.put("/test/img","perms[user-add]"); //不具备指定的权限,跳转到setUnauthorizedUrl地址 //使用过滤器的形式配置请求地址的依赖角色 //filterMap.put("/test/img","roles[系统管理员]"); filterMap.put("/test/**","authc");//当前请求地址必须认证之后可以访问 // filterMap // 我们的的url 和对应的Filter 关系配置。 // 这里的加载顺序是自上而下,所以看到我们/user/** 写到最后,因为匹配不中最后都让这个匹配中。 filterFactory.setFilterChainDefinitionMap(filterMap); return filterFactory; } //------------- 配置自定义的会话管理将sessionId放在redis中 @Value("${spring.redis.host}") private String host; @Value("${spring.redis.port}") private int port; /** * 1.redis的控制器,操作redis */ public RedisManager redisManager() { RedisManager redisManager = new RedisManager(); redisManager.setHost(host); redisManager.setPort(port); return redisManager; } /** * 2.sessionDao */ public RedisSessionDAO redisSessionDAO() { RedisSessionDAO sessionDAO = new RedisSessionDAO(); sessionDAO.setRedisManager(redisManager()); return sessionDAO; } /** * 3.会话管理器 */ public DefaultWebSessionManager sessionManager() { CustomSessionManager sessionManager = new CustomSessionManager(); sessionManager.setSessionDAO(redisSessionDAO()); return sessionManager; } /** * 4.缓存管理器 */ public RedisCacheManager cacheManager() { RedisCacheManager redisCacheManager = new RedisCacheManager(); redisCacheManager.setRedisManager(redisManager()); return redisCacheManager; } //------------- //开启对shior注解的支持 @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor(); advisor.setSecurityManager(securityManager); return advisor; } }

浙公网安备 33010602011771号