Shiro
Realm相当于数据源,SecurityManager从Reaml获取相应的用户进行比较
Shiro的架构
- Subject:任何可以与用户交互的用户
- SecurityManager:所有具体的交互都由SecurityManager控制,它管理所有的Subject,切负责进行认证,授权,会话即缓存管理
- Authenticator:认证器,负责Subject的认证,可以自定义实现。可以使用认证策略来定义什么情况下算用户认证通过
- Authorizer: 授权器,即访问控制器,确定主体是否有权限进行相应的操作。
- Realm:用于获取安全实体,可以有一个或多个。一般在用于中都需要自己实现Realm
- SessionManager:管理Session生命周期的组件。
- SessionDao:代表SessionManager执行Session持久化操作
- CacheManager:缓存控制器,用来管理用户,角色,权限等缓存
- Cryptography:密码模块,加密解密。
初步使用
导入依赖
<dependencies> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.4.1</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-simple</artifactId> <version>1.7.21</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> <version>1.7.21</version> </dependency> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.2</version> </dependency> </dependencies>
编写Shiro配置
shiro.ini
[users]
root = secret, admin
guest = guest, guest
presidentskroob = 12345, president
darkhelmet = ludicrousspeed, darklord, schwartz
lonestarr = vespa, goodguy, schwartz
[roles]
admin = *
schwartz = lightsaber:*
goodguy = winnebago:drive:eagle5
log4j.properties
log4j.rootLogger=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n
# General Apache libraries
log4j.logger.org.apache=WARN
# Spring
log4j.logger.org.springframework=WARN
# Default Shiro logging
log4j.logger.org.apache.shiro=INFO
# Disable verbose logging
log4j.logger.org.apache.shiro.util.ThreadContext=WARN
log4j.logger.org.apache.shiro.cache.ehcache.EhCache=WARN
使用shiro API
package com.deng; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.*; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.session.Session; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.Factory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class Quickstart { private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class); public static void main(String[] args) { Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini"); SecurityManager securityManager = factory.getInstance(); SecurityUtils.setSecurityManager(securityManager); Subject currentUser = SecurityUtils.getSubject(); Session session = currentUser.getSession(); session.setAttribute("someKey", "aValue"); String value = (String) session.getAttribute("someKey"); if (value.equals("aValue")) { log.info("Retrieved the correct value! [" + value + "]"); } if (!currentUser.isAuthenticated()) { UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa"); token.setRememberMe(true); try { currentUser.login(token); } catch (UnknownAccountException uae) { log.info("There is no user with username of " + token.getPrincipal()); } catch (IncorrectCredentialsException ice) { log.info("Password for account " + token.getPrincipal() + " was incorrect!"); } catch (LockedAccountException lae) { log.info("The account for username " + token.getPrincipal() + " is locked. " + "Please contact your administrator to unlock it."); } catch (AuthenticationException ae) { } } log.info("User [" + currentUser.getPrincipal() + "] logged in successfully."); if (currentUser.hasRole("schwartz")) { log.info("May the Schwartz be with you!"); } else { log.info("Hello, mere mortal."); } if (currentUser.isPermitted("lightsaber:wield")) { log.info("You may use a lightsaber ring. Use it wisely."); } else { log.info("Sorry, lightsaber rings are for schwartz masters only."); } if (currentUser.isPermitted("winnebago:drive:eagle5")) { log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'. " + "Here are the keys - have fun!"); } else { log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!"); } currentUser.logout(); System.exit(0); } }
解析上面的代码:
1.通过工厂模式创建SecurityManager实例对象
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini"); SecurityManager securityManager = factory.getInstance(); SecurityUtils.setSecurityManager(securityManager);
2.获取当前的subject
Subject currentUser = SecurityUtils.getSubject();
3.session操作
Session session = currentUser.getSession();//获取session session.setAttribute("someKey", "aValue");//设置session的值 String value = (String) session.getAttribute("someKey");//从session中获取值 if (value.equals("aValue")) {//判断session中是否存在值 log.info("Retrieved the correct value! [" + value + "]"); }
4.用户认证功能
if (!currentUser.isAuthenticated()) {//当前用户是否已经认证 //将用户名和密码封装为UsernamePasswordToken UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa"); //开启记住我功能 token.setRememberMe(true); try { currentUser.login(token);//执行登录 } catch (UnknownAccountException uae) {//如果没有指定用户,抛出该异常 log.info("There is no user with username of " + token.getPrincipal()); } catch (IncorrectCredentialsException ice) {//如果密码不对,抛出该异常 log.info("Password for account " + token.getPrincipal() + " was incorrect!"); } catch (LockedAccountException lae) {//如果用户被锁定抛出该异常 log.info("The account for username " + token.getPrincipal() + " is locked. " + "Please contact your administrator to unlock it."); } catch (AuthenticationException ae) {//认证异常,上面的异常都是其子类 } } //getPrincipal()获取标识主体 log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");
5.角色检查
//检测用户是否拥有某一角色 if (currentUser.hasRole("schwartz")) { log.info("May the Schwartz be with you!"); } else { log.info("Hello, mere mortal."); }
6.权限检查
//测试用户是否具有某一权限,行为 if (currentUser.isPermitted("lightsaber:wield")) { log.info("You may use a lightsaber ring. Use it wisely."); } else { log.info("Sorry, lightsaber rings are for schwartz masters only."); } //测试用户是否具有某一权限,行为,比上面更加具体 if (currentUser.isPermitted("winnebago:drive:eagle5")) { log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'. " + "Here are the keys - have fun!"); } else { log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!"); }
7.注销
currentUser.logout();
SpringBoot集成
导入Shiro和Spring整合的依赖
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.0</version> </dependency>
Realm是Shiro的连接数据,创建Realm类继承AuthorizingRealm类,重写认证和授权逻辑
public class UserRealm extends AuthorizingRealm { //执行授权逻辑 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("执行了授权逻辑"); return null; } //执行认证逻辑 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { System.out.println("执行了认证逻辑"); return null; } }
编写ShiroConfig配置类,对请求进行过滤
@Configuration public class ShiroConfig { //创建ShiroFilterFactoryBean @Bean public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager")DefaultWebSecurityManager securityManager){ ShiroFilterFactoryBean shiroFilterFactoryBean=new ShiroFilterFactoryBean(); //设置安全管理器 shiroFilterFactoryBean.setSecurityManager(securityManager); /*添加内置过滤器,常用过滤器如下: anon:无需认证就可以访问 authc:必须认证才可以访问 user:如果使用了记住我功能就可以直接访问 perms:拥有某个资源权限才可以访问 role:拥有某个角色才可以访问 */ Map<String,String> filterMap=new LinkedHashMap<>(); filterMap.put("/user/add","authc"); filterMap.put("/user/update","authc"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap); //配置跳转页面 shiroFilterFactoryBean.setLoginUrl("/toLogin"); return shiroFilterFactoryBean; } //创建DefaultWebSecurityManager @Bean public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm")UserRealm userRealm){ DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager(); //关联Realm securityManager.setRealm(userRealm); return securityManager; } //创建realm @Bean public UserRealm userRealm(){ return new UserRealm(); } }
登录认证操作
Controller
@Controller public class LoginController { //登录认证 @RequestMapping("/login") public String login(String username, String password, Model model){ //获取Subject Subject subject=SecurityUtils.getSubject(); //封装用户数据 UsernamePasswordToken token=new UsernamePasswordToken(username,password); //执行登录操作 try{ subject.login(token);//登录成功就返回首页 return "index"; }catch (UnknownAccountException e){ model.addAttribute("msg","用户名不存在"); return "login"; }catch (IncorrectCredentialsException e){ model.addAttribute("msg","密码错误"); return "login"; } } }
在登录认证是会执行Realm里面的认证逻辑
编写Realm的认证逻辑
//执行认证逻辑 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { System.out.println("执行了认证逻辑"); String name="root"; String password="123456"; //将token强转为UsernamePasswordToken UsernamePasswordToken token=(UsernamePasswordToken)authenticationToken; //判断用户名,不存在直接返回 if(!token.getUsername().equals(name)){ return null; } //验证密码,使用AuthenticationInfo实现类SimpleAuthenticationInfo,shiro会自动验证密码 return new SimpleAuthenticationInfo("",password,""); }
整合数据库
配置好mybatis查询逻辑
改造Realm
//执行认证逻辑 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { System.out.println("执行了认证逻辑"); //将token强转为UsernamePasswordToken UsernamePasswordToken token=(UsernamePasswordToken)authenticationToken; User user=userService.queryUserByName(token.getUsername()); //判断用户名,不存在直接返回 if(user==null){ return null; } //验证密码,使用AuthenticationInfo实现类SimpleAuthenticationInfo,shiro会自动验证密码 return new SimpleAuthenticationInfo("",user.getPwd(),""); }
修改当前Realm的CredentialsMatcher属性可以设置加密算法
用户授权操作
在配置类中给资源设置权限
filterMap.put("/user/add","perms[user:add]");
设置没有权限时执行逻辑
//配置未授权页面 shiroFilterFactoryBean.setUnauthorizedUrl("/noauth");
此时用户登录后没有访问/user/add的权限
给用户授权,编写Realm
//执行授权逻辑 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("执行了授权逻辑"); SimpleAuthorizationInfo info=new SimpleAuthorizationInfo(); //添加资源的授权字符串 info.addStringPermission("user:add"); return info; }
实际开发中可以在数据库增加一个字段,来获取不同用户的权限
@Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { System.out.println("执行了=>授权逻辑PrincipalCollection"); //给资源进行授权 SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); //添加资源的授权字符串 //info.addStringPermission("user:add"); Subject subject = SecurityUtils.getSubject(); //获得当前对象 User currentUser = (User) subject.getPrincipal(); //拿到User对象 info.addStringPermission(currentUser.getPerms()); //设置权限 return info; }