Shiro安全框架的简单介绍,以及与SpringBoot的整合
一、Shiro快速开始 1.1 Shiro介绍
Apache Shiro是一个Java的安全框架
Shiro可以非常容易开发出足够好的应用,其不仅可以用在JavaSE环境,也可以用在JavaEE环境中
Shiro可以完成,认证,授权,加密,会话管理,Web集成,缓存等
1.2 快速入门
下载后,按照官方文档,将sample/quickstart下的maven项目复制到IDEA中,进行测试
由于Maven文件中继承了父maven,依赖没有版本号,所以要手动导入依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <dependency > <groupId > org.apache.shiro</groupId > <artifactId > shiro-core</artifactId > <version > 1.7.0</version > </dependency > <dependency > <groupId > org.slf4j</groupId > <artifactId > jcl-over-slf4j</artifactId > <scope > runtime</scope > <version > 1.7.21</version > </dependency > <dependency > <groupId > org.slf4j</groupId > <artifactId > slf4j-log4j12</artifactId > <scope > runtime</scope > <version > 1.7.21</version > </dependency > <dependency > <groupId > log4j</groupId > <artifactId > log4j</artifactId > <scope > runtime</scope > <version > 1.2.17</version > </dependency >
运行Quickstart,显示以下结果,证明运行成功
二、Shiro分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 public class Quickstart { private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class); public static void main (String[] args) { IniRealm iniRealm = new IniRealm("classpath:shiro.ini" ); DefaultSecurityManager securityManager = new DefaultSecurityManager(); securityManager.setRealm(iniRealm); 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 ); } }
三、SpringBoot整合Shiro 创建SpringBoot项目
3.1 导入相关依赖 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-thymeleaf</artifactId > </dependency > <dependency > <groupId > org.apache.shiro</groupId > <artifactId > shiro-spring-boot-web-starter</artifactId > <version > 1.7.0</version > </dependency >
3.2 创建自定义Realm
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class UserRealm extends AuthorizingRealm { @Override protected AuthorizationInfo doGetAuthorizationInfo (PrincipalCollection principals) { System.out.println("执行 ==> 授权doGetAuthorizationInfo" ); return null ; } @Override protected AuthenticationInfo doGetAuthenticationInfo (AuthenticationToken token) throws AuthenticationException { System.out.println("执行 ==> 认证doGetAuthenticationInfo" ); return null ; } }
3.3 创建Shiro配置类
注入Realm类
注入DefaultWebSecurityManager类,Realm类作为参数传递
注入ShiroFilterFactoryBean类,DefaultWebSecurityManage类作为参数传递
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 @Configuration public class ShiroConfig { @Bean public UserRealm getRealm () { return new UserRealm(); } @Bean public DefaultWebSecurityManager getSecurityManager (UserRealm realm) { return new DefaultWebSecurityManager(realm); } @Bean(name = "shiroFilterFactoryBean") public ShiroFilterFactoryBean getFilterFactoryBean (DefaultWebSecurityManager securityManager) { ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean(); filterFactoryBean.setSecurityManager(securityManager); return filterFactoryBean; } }
3.4 简单的环境配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 index.html <!DOCTYPE html > <html lang ="en" xmlns:th ="http://www.thymeleaf.org" > <head > <meta charset ="UTF-8" > <title > Title</title > </head > <body > <h1 > 首页</h1 > <a th:href ="@{/user/toAdd}" > 增加用户</a > <a th:href ="@{/user/toUpdate}" > 修改用户</a > <a th:href ="@{/user/toDelete}" > 删除用户</a > </body > </html >
1 2 3 4 5 6 7 8 9 10 11 add.html 和 update.html 和 delete.html都类似,改一下h1内容 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > Title</title > </head > <body > <h1 > 增加用户</h1 > </body > </html >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 Controller接口层 @Controller public class RouteController { @RequestMapping({"/", "index.thml"}) public String toIndex () { return "index" ; } @RequestMapping("/user/toAdd") public String toAdd () { return "/user/add" ; } @RequestMapping("/user/toUpdate") public String toUpdate () { return "/user/update" ; } @RequestMapping("/user/toDelete") public String toDelete () { return "/user/delete" ; } }
以上是最基本的Shrio框架的搭建,举例功能下面实现
四、Shiro实现拦截登录 4.1 设置要拦截的请求
“/user/toAdd”:无需认证,可以直接访问
“/user/toUpdate”:需要认证,才能访问
“/user/toDelete”:用户需要”admin”权限才能访问
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @Bean(name = "shiroFilterFactoryBean") public ShiroFilterFactoryBean getFilterFactoryBean (DefaultWebSecurityManager securityManager) { ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean(); filterFactoryBean.setSecurityManager(securityManager); Map<String, String> filterMap = new LinkedHashMap<>(); filterMap.put("/user/toAdd" , "anon" ); filterMap.put("/user/toUpdate" , "authc" ); filterMap.put("/user/toDelete" , "perms[admin]" ); filterFactoryBean.setFilterChainDefinitionMap(filterMap); return filterFactoryBean; }
4.2 设置拦截跳转
Shrio成功拦截请求,但没有进行登录页面跳转,不想SpringSecurity自带登录页面,需要手动设置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 login.html <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > Title</title > </head > <body > <h1 > 登录</h1 > <form action ="" > <p > 用户名: <input type ="text" name ="username" > </p > <p > 密码: <input type ="text" name ="password" > </p > <p > <input type ="submit" > </p > </form > </body > </html >
1 2 3 4 5 Controller跳转Login页面 @RequestMapping("/toLogin") public String toLogin () { return "login" ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 Shiro配置类设置登录页面 @Bean(name = "shiroFilterFactoryBean") public ShiroFilterFactoryBean getFilterFactoryBean (DefaultWebSecurityManager securityManager) { ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean(); filterFactoryBean.setSecurityManager(securityManager); Map<String, String> filterMap = new LinkedHashMap<>(); filterMap.put("/user/toAdd" , "anon" ); filterMap.put("/user/toUpdate" , "authc" ); filterMap.put("/user/toDelete" , "perms[admin]" ); filterFactoryBean.setFilterChainDefinitionMap(filterMap); filterFactoryBean.setLoginUrl("/toLogin" ); return filterFactoryBean; }
五、Shiro实现用户认证 5.1 Controller层登录业务
在login.html 添加aciton=”/login”进行Controller业务跳转
Controller接受登录页面发送的账号、密码,执行登录操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @RequestMapping("/login") public String login (String username, String password, Model model) { Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken(username, password); try { subject.login(token); } catch (UnknownAccountException e) { model.addAttribute("msg" , "用户名错误" ); return "login" ; } catch (IncorrectCredentialsException e){ model.addAttribute("msg" , "密码错误" ); return "login" ; } return "index" ; }
5.2 Realm类实现认证判断
subject.login()方法会去执行Realm中的认证方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Override protected AuthenticationInfo doGetAuthenticationInfo (AuthenticationToken token) throws AuthenticationException { System.out.println("执行 ==> 认证doGetAuthenticationInfo" ); String name = "莱特雷" ; String password = "123" ; UsernamePasswordToken userToken = (UsernamePasswordToken)token; if (!name.equals(userToken.getUsername())){ return null ; } return new SimpleAuthenticationInfo("" ,password,"" ); }
六、Shiro整合Mybatis Shiro整合Mybatis,实现从数据库获取用户、密码,实现数据库认证
6.1 导入相关依赖 1 2 3 4 5 6 7 8 9 10 11 12 <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > </dependency > <dependency > <groupId > org.mybatis.spring.boot</groupId > <artifactId > mybatis-spring-boot-starter</artifactId > <version > 2.1.3</version > </dependency >
6.2 配置文件 1 2 3 4 5 6 7 8 9 10 11 spring: datasource: username: root password: 123 url: jdbc:mysql://localhost:3306/jdbctest?serverTimezone=GMT%2B8 driver-class-name: com.mysql.cj.jdbc.Driver mybatis: mapper-locations: classpath:mapper/*.xml
6.3 创建相应的Bean,Mapper,Service 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 Bean public class Person { private String name; private Integer age; private String password; private String perms; public Person () { } public Person (String name, Integer age, String password) { this .name = name; this .age = age; this .password = password; } public String getName () { return name; } public void setName (String name) { this .name = name; } public Integer getAge () { return age; } public void setAge (Integer age) { this .age = age; } public String getPassword () { return password; } public void setPassword (String password) { this .password = password; } @Override public String toString () { return "Person{" + "name='" + name + '\'' + ", age=" + age + ", password='" + password + '\'' + '}' ; } }
1 2 3 4 5 6 Mapper接口 @Mapper @Repository public interface PersonMapper { public Person queryPersonByName (String name) ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 Mapper.xml中SQL <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace ="com.letere.bean.PersonMapper" > <select id ="queryPersonByName" resultType ="com.letere.bean.Person" > select * from `person` where name = #{name} </select > </mapper >
1 2 3 4 5 6 7 8 9 10 11 Service调用Dao层 @Service public class PersonService implements PersonMapper { @Autowired PersonMapper personMapper; @Override public Person queryPersonByName (String name) { return personMapper.queryPersonByName(name); } }
6.4 修改Relam中的认证方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Override protected AuthenticationInfo doGetAuthenticationInfo (AuthenticationToken token) throws AuthenticationException { System.out.println("执行 ==> 认证doGetAuthenticationInfo" ); UsernamePasswordToken userToken = (UsernamePasswordToken)token; Person person = personService.queryPersonByName(userToken.getUsername()); if (person == null ){ return null ; } return new SimpleAuthenticationInfo(person, person.getPassword(), "" ); }
七、授权方法实现 授予用户特定的权限去访问特定的页面
7.1 添加需要权限访问的页面 在ShiroConfig的配置类中的拦截中,添加需要权限才能访问的页面 并且可以设置权限不够时,自动跳转的页面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @Bean(name = "shiroFilterFactoryBean") public ShiroFilterFactoryBean getFilterFactoryBean (DefaultWebSecurityManager securityManager) { ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean(); filterFactoryBean.setSecurityManager(securityManager); Map<String, String> filterMap = new LinkedHashMap<>(); filterMap.put("/user/toAdd" , "anon" ); filterMap.put("/user/toUpdate" , "authc" ); filterMap.put("/user/toDelete" , "perms[admin]" ); filterFactoryBean.setFilterChainDefinitionMap(filterMap); filterFactoryBean.setLoginUrl("/toLogin" ); filterFactoryBean.setUnauthorizedUrl("/unauthorized" ); return filterFactoryBean; }
7.2 整合数据库实现
在数据库中添加新的字段perms,来表示该用户拥有的权限
1 2 alter table `person` add perms varchar (20 )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Override protected AuthorizationInfo doGetAuthorizationInfo (PrincipalCollection principals) { System.out.println("执行 ==> 授权doGetAuthorizationInfo" ); SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); Person person = (Person)principals.getPrimaryPrincipal(); authorizationInfo.addStringPermission(person.getPerms()); return authorizationInfo; }
八、Shiro整合thymeleaf 整合thymeleaf,实现根据用户的权限,来显示其能够访问的页面
8.1 导入相关依赖 1 2 3 4 5 6 <dependency > <groupId > com.github.theborakompanioni</groupId > <artifactId > thymeleaf-extras-shiro</artifactId > <version > 2.0.0</version > </dependency >
8.2 修改ShiroConfig类 在Shiro配置类中,住一个ShiroDialect类的Bean
1 2 3 4 5 @Bean public ShiroDialect getShiroDialect () { return new ShiroDialect(); }
8.3 修改前端代码
9 修改过滤器(拦截器) 9.1 介绍
shiro对需要验证的请求authc
,进行拦截,并进行判断是否已登录等操作或其他操作
这些操作都在shiro的默认过滤器中处理
我们可以自定义一个过滤器,来替换shiro默认的过滤器,来实现我们自己想要的业务
9.2 实现
(1)创建过滤器 1 2 3 4 5 6 7 8 9 10 11 12 public class MyShiroFilter extends FormAuthenticationFilter { @Override protected boolean onAccessDenied (ServletRequest request, ServletResponse response) throws IOException { return true ; return false ; } }
(2)替换过滤器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Configuration public class ShiroConfig extends ShiroWebFilterConfiguration { @Override @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean () { ShiroFilterFactoryBean shiroFilterFactoryBean = super .shiroFilterFactoryBean(); Map<String, Filter> filters = new HashMap<>(); filters.put("authc" , new MyShiroFilter()); shiroFilterFactoryBean.setFilters(filters); return shiroFilterFactoryBean; } }